1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.cts; 18 19 import java.nio.ByteBuffer; 20 21 import org.junit.Assert; 22 23 import android.media.AudioAttributes; 24 import android.media.AudioFormat; 25 import android.media.AudioManager; 26 import android.media.AudioRecord; 27 import android.media.AudioTrack; 28 import android.os.Looper; 29 30 // Used for statistics and loopers in listener tests. 31 // See AudioRecordTest.java and AudioTrack_ListenerTest.java. 32 public class AudioHelper { 33 34 // create sine waves or chirps for data arrays 35 public static byte[] createSoundDataInByteArray(int bufferSamples, final int sampleRate, 36 final double frequency, double sweep) { 37 final double rad = 2 * Math.PI * frequency / sampleRate; 38 byte[] vai = new byte[bufferSamples]; 39 sweep = Math.PI * sweep / ((double)sampleRate * vai.length); 40 for (int j = 0; j < vai.length; j++) { 41 int unsigned = (int)(Math.sin(j * (rad + j * sweep)) * Byte.MAX_VALUE) 42 + Byte.MAX_VALUE & 0xFF; 43 vai[j] = (byte) unsigned; 44 } 45 return vai; 46 } 47 48 public static short[] createSoundDataInShortArray(int bufferSamples, final int sampleRate, 49 final double frequency, double sweep) { 50 final double rad = 2 * Math.PI * frequency / sampleRate; 51 short[] vai = new short[bufferSamples]; 52 sweep = Math.PI * sweep / ((double)sampleRate * vai.length); 53 for (int j = 0; j < vai.length; j++) { 54 vai[j] = (short)(Math.sin(j * (rad + j * sweep)) * Short.MAX_VALUE); 55 } 56 return vai; 57 } 58 59 public static float[] createSoundDataInFloatArray(int bufferSamples, final int sampleRate, 60 final double frequency, double sweep) { 61 final double rad = 2 * Math.PI * frequency / sampleRate; 62 float[] vaf = new float[bufferSamples]; 63 sweep = Math.PI * sweep / ((double)sampleRate * vaf.length); 64 for (int j = 0; j < vaf.length; j++) { 65 vaf[j] = (float)(Math.sin(j * (rad + j * sweep))); 66 } 67 return vaf; 68 } 69 70 /** 71 * Create and fill a short array with complete sine waves so we can 72 * hear buffer underruns more easily. 73 */ 74 public static short[] createSineWavesShort(int numFrames, int samplesPerFrame, 75 int numCycles, double amplitude) { 76 final short[] data = new short[numFrames * samplesPerFrame]; 77 final double rad = numCycles * 2.0 * Math.PI / numFrames; 78 for (int j = 0; j < data.length;) { 79 short sample = (short)(amplitude * Math.sin(j * rad) * Short.MAX_VALUE); 80 for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) { 81 data[j++] = sample; 82 } 83 } 84 return data; 85 } 86 87 public static int frameSizeFromFormat(AudioFormat format) { 88 return format.getChannelCount() 89 * format.getBytesPerSample(format.getEncoding()); 90 } 91 92 public static int frameCountFromMsec(int ms, AudioFormat format) { 93 return ms * format.getSampleRate() / 1000; 94 } 95 96 public static class Statistics { 97 public void add(double value) { 98 final double absValue = Math.abs(value); 99 mSum += value; 100 mSumAbs += absValue; 101 mMaxAbs = Math.max(mMaxAbs, absValue); 102 ++mCount; 103 } 104 105 public double getAvg() { 106 if (mCount == 0) { 107 return 0; 108 } 109 return mSum / mCount; 110 } 111 112 public double getAvgAbs() { 113 if (mCount == 0) { 114 return 0; 115 } 116 return mSumAbs / mCount; 117 } 118 119 public double getMaxAbs() { 120 return mMaxAbs; 121 } 122 123 private int mCount = 0; 124 private double mSum = 0; 125 private double mSumAbs = 0; 126 private double mMaxAbs = 0; 127 } 128 129 // for listener tests 130 // lightweight java.util.concurrent.Future* 131 public static class FutureLatch<T> 132 { 133 private T mValue; 134 private boolean mSet; 135 public void set(T value) 136 { 137 synchronized (this) { 138 assert !mSet; 139 mValue = value; 140 mSet = true; 141 notify(); 142 } 143 } 144 public T get() 145 { 146 T value; 147 synchronized (this) { 148 while (!mSet) { 149 try { 150 wait(); 151 } catch (InterruptedException e) { 152 ; 153 } 154 } 155 value = mValue; 156 } 157 return value; 158 } 159 } 160 161 // for listener tests 162 // represents a factory for T 163 public interface MakesSomething<T> 164 { 165 T makeSomething(); 166 } 167 168 // for listener tests 169 // used to construct an object in the context of an asynchronous thread with looper 170 public static class MakeSomethingAsynchronouslyAndLoop<T> 171 { 172 private Thread mThread; 173 volatile private Looper mLooper; 174 private final MakesSomething<T> mWhatToMake; 175 176 public MakeSomethingAsynchronouslyAndLoop(MakesSomething<T> whatToMake) 177 { 178 assert whatToMake != null; 179 mWhatToMake = whatToMake; 180 } 181 182 public T make() 183 { 184 final FutureLatch<T> futureLatch = new FutureLatch<T>(); 185 mThread = new Thread() 186 { 187 @Override 188 public void run() 189 { 190 Looper.prepare(); 191 mLooper = Looper.myLooper(); 192 T something = mWhatToMake.makeSomething(); 193 futureLatch.set(something); 194 Looper.loop(); 195 } 196 }; 197 mThread.start(); 198 return futureLatch.get(); 199 } 200 public void join() 201 { 202 mLooper.quit(); 203 try { 204 mThread.join(); 205 } catch (InterruptedException e) { 206 ; 207 } 208 // avoid dangling references 209 mLooper = null; 210 mThread = null; 211 } 212 } 213 214 public static int outChannelMaskFromInChannelMask(int channelMask) { 215 switch (channelMask) { 216 case AudioFormat.CHANNEL_IN_MONO: 217 return AudioFormat.CHANNEL_OUT_MONO; 218 case AudioFormat.CHANNEL_IN_STEREO: 219 return AudioFormat.CHANNEL_OUT_STEREO; 220 default: 221 return AudioFormat.CHANNEL_INVALID; 222 } 223 } 224 225 /* AudioRecordAudit extends AudioRecord to allow concurrent playback 226 * of read content to an AudioTrack. This is for testing only. 227 * For general applications, it is NOT recommended to extend AudioRecord. 228 * This affects AudioRecord timing. 229 */ 230 public static class AudioRecordAudit extends AudioRecord { 231 public AudioRecordAudit(int audioSource, int sampleRate, int channelMask, 232 int format, int bufferSize, boolean isChannelIndex) { 233 this(audioSource, sampleRate, channelMask, format, bufferSize, isChannelIndex, 234 AudioManager.STREAM_MUSIC, 500 /*delayMs*/); 235 } 236 237 public AudioRecordAudit(int audioSource, int sampleRate, int channelMask, 238 int format, int bufferSize, 239 boolean isChannelIndex, int auditStreamType, int delayMs) { 240 // without channel index masks, one could call: 241 // super(audioSource, sampleRate, channelMask, format, bufferSize); 242 super(new AudioAttributes.Builder() 243 .setInternalCapturePreset(audioSource) 244 .build(), 245 (isChannelIndex 246 ? new AudioFormat.Builder().setChannelIndexMask(channelMask) 247 : new AudioFormat.Builder().setChannelMask(channelMask)) 248 .setEncoding(format) 249 .setSampleRate(sampleRate) 250 .build(), 251 bufferSize, 252 AudioManager.AUDIO_SESSION_ID_GENERATE); 253 254 if (delayMs >= 0) { // create an AudioTrack 255 final int channelOutMask = isChannelIndex ? channelMask : 256 outChannelMaskFromInChannelMask(channelMask); 257 final int bufferOutFrames = sampleRate * delayMs / 1000; 258 final int bufferOutSamples = bufferOutFrames 259 * AudioFormat.channelCountFromOutChannelMask(channelOutMask); 260 final int bufferOutSize = bufferOutSamples 261 * AudioFormat.getBytesPerSample(format); 262 263 // Caution: delayMs too large results in buffer sizes that cannot be created. 264 mTrack = new AudioTrack.Builder() 265 .setAudioAttributes(new AudioAttributes.Builder() 266 .setLegacyStreamType(auditStreamType) 267 .build()) 268 .setAudioFormat((isChannelIndex ? 269 new AudioFormat.Builder().setChannelIndexMask(channelOutMask) : 270 new AudioFormat.Builder().setChannelMask(channelOutMask)) 271 .setEncoding(format) 272 .setSampleRate(sampleRate) 273 .build()) 274 .setBufferSizeInBytes(bufferOutSize) 275 .build(); 276 Assert.assertEquals(AudioTrack.STATE_INITIALIZED, mTrack.getState()); 277 mPosition = 0; 278 mFinishAtMs = 0; 279 } 280 } 281 282 @Override 283 public int read(byte[] audioData, int offsetInBytes, int sizeInBytes) { 284 // for byte array access we verify format is 8 bit PCM (typical use) 285 Assert.assertEquals(TAG + ": format mismatch", 286 AudioFormat.ENCODING_PCM_8BIT, getAudioFormat()); 287 int samples = super.read(audioData, offsetInBytes, sizeInBytes); 288 if (mTrack != null) { 289 Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples)); 290 mPosition += samples / mTrack.getChannelCount(); 291 } 292 return samples; 293 } 294 295 @Override 296 public int read(byte[] audioData, int offsetInBytes, int sizeInBytes, int readMode) { 297 // for byte array access we verify format is 8 bit PCM (typical use) 298 Assert.assertEquals(TAG + ": format mismatch", 299 AudioFormat.ENCODING_PCM_8BIT, getAudioFormat()); 300 int samples = super.read(audioData, offsetInBytes, sizeInBytes, readMode); 301 if (mTrack != null) { 302 Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples, 303 AudioTrack.WRITE_BLOCKING)); 304 mPosition += samples / mTrack.getChannelCount(); 305 } 306 return samples; 307 } 308 309 @Override 310 public int read(short[] audioData, int offsetInShorts, int sizeInShorts) { 311 // for short array access we verify format is 16 bit PCM (typical use) 312 Assert.assertEquals(TAG + ": format mismatch", 313 AudioFormat.ENCODING_PCM_16BIT, getAudioFormat()); 314 int samples = super.read(audioData, offsetInShorts, sizeInShorts); 315 if (mTrack != null) { 316 Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples)); 317 mPosition += samples / mTrack.getChannelCount(); 318 } 319 return samples; 320 } 321 322 @Override 323 public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readMode) { 324 // for short array access we verify format is 16 bit PCM (typical use) 325 Assert.assertEquals(TAG + ": format mismatch", 326 AudioFormat.ENCODING_PCM_16BIT, getAudioFormat()); 327 int samples = super.read(audioData, offsetInShorts, sizeInShorts, readMode); 328 if (mTrack != null) { 329 Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples, 330 AudioTrack.WRITE_BLOCKING)); 331 mPosition += samples / mTrack.getChannelCount(); 332 } 333 return samples; 334 } 335 336 @Override 337 public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readMode) { 338 // for float array access we verify format is float PCM (typical use) 339 Assert.assertEquals(TAG + ": format mismatch", 340 AudioFormat.ENCODING_PCM_FLOAT, getAudioFormat()); 341 int samples = super.read(audioData, offsetInFloats, sizeInFloats, readMode); 342 if (mTrack != null) { 343 Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples, 344 AudioTrack.WRITE_BLOCKING)); 345 mPosition += samples / mTrack.getChannelCount(); 346 } 347 return samples; 348 } 349 350 @Override 351 public int read(ByteBuffer audioBuffer, int sizeInBytes) { 352 int bytes = super.read(audioBuffer, sizeInBytes); 353 if (mTrack != null) { 354 // read does not affect position and limit of the audioBuffer. 355 // we make a duplicate to change that for writing to the output AudioTrack 356 // which does check position and limit. 357 ByteBuffer copy = audioBuffer.duplicate(); 358 copy.position(0).limit(bytes); // read places data at the start of the buffer. 359 Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING)); 360 mPosition += bytes / 361 (mTrack.getChannelCount() 362 * AudioFormat.getBytesPerSample(mTrack.getAudioFormat())); 363 } 364 return bytes; 365 } 366 367 @Override 368 public int read(ByteBuffer audioBuffer, int sizeInBytes, int readMode) { 369 int bytes = super.read(audioBuffer, sizeInBytes, readMode); 370 if (mTrack != null) { 371 // read does not affect position and limit of the audioBuffer. 372 // we make a duplicate to change that for writing to the output AudioTrack 373 // which does check position and limit. 374 ByteBuffer copy = audioBuffer.duplicate(); 375 copy.position(0).limit(bytes); // read places data at the start of the buffer. 376 Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING)); 377 mPosition += bytes / 378 (mTrack.getChannelCount() 379 * AudioFormat.getBytesPerSample(mTrack.getAudioFormat())); 380 } 381 return bytes; 382 } 383 384 @Override 385 public void startRecording() { 386 super.startRecording(); 387 if (mTrack != null) { 388 mTrack.play(); 389 } 390 } 391 392 @Override 393 public void stop() { 394 super.stop(); 395 if (mTrack != null) { 396 if (mPosition > 0) { // stop may be called multiple times. 397 final int remainingFrames = mPosition - mTrack.getPlaybackHeadPosition(); 398 mFinishAtMs = System.currentTimeMillis() 399 + remainingFrames * 1000 / mTrack.getSampleRate(); 400 mPosition = 0; 401 } 402 mTrack.stop(); // allows remaining data to play out 403 } 404 } 405 406 @Override 407 public void release() { 408 super.release(); 409 if (mTrack != null) { 410 final long remainingMs = mFinishAtMs - System.currentTimeMillis(); 411 if (remainingMs > 0) { 412 try { 413 Thread.sleep(remainingMs); 414 } catch (InterruptedException e) { 415 ; 416 } 417 } 418 mTrack.release(); 419 mTrack = null; 420 } 421 } 422 423 public AudioTrack mTrack; 424 private final static String TAG = "AudioRecordAudit"; 425 private int mPosition; 426 private long mFinishAtMs; 427 } 428 429 /* AudioRecordAudit extends AudioRecord to allow concurrent playback 430 * of read content to an AudioTrack. This is for testing only. 431 * For general applications, it is NOT recommended to extend AudioRecord. 432 * This affects AudioRecord timing. 433 */ 434 public static class AudioRecordAuditNative extends AudioRecordNative { 435 public AudioRecordAuditNative() { 436 super(); 437 // Caution: delayMs too large results in buffer sizes that cannot be created. 438 mTrack = new AudioTrackNative(); 439 } 440 441 @Override 442 public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) { 443 if (super.open(numChannels, sampleRate, useFloat, numBuffers)) { 444 if (!mTrack.open(numChannels, sampleRate, useFloat, 2 /* numBuffers */)) { 445 mTrack = null; // remove track 446 } 447 return true; 448 } 449 return false; 450 } 451 452 @Override 453 public void close() { 454 super.close(); 455 if (mTrack != null) { 456 mTrack.close(); 457 } 458 } 459 460 @Override 461 public boolean start() { 462 if (super.start()) { 463 if (mTrack != null) { 464 mTrack.start(); 465 } 466 return true; 467 } 468 return false; 469 } 470 471 @Override 472 public boolean stop() { 473 if (super.stop()) { 474 if (mTrack != null) { 475 mTrack.stop(); // doesn't allow remaining data to play out 476 } 477 return true; 478 } 479 return false; 480 } 481 482 @Override 483 public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readFlags) { 484 int samples = super.read(audioData, offsetInShorts, sizeInShorts, readFlags); 485 if (mTrack != null) { 486 Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples, 487 AudioTrackNative.WRITE_FLAG_BLOCKING)); 488 mPosition += samples / mTrack.getChannelCount(); 489 } 490 return samples; 491 } 492 493 @Override 494 public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readFlags) { 495 int samples = super.read(audioData, offsetInFloats, sizeInFloats, readFlags); 496 if (mTrack != null) { 497 Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples, 498 AudioTrackNative.WRITE_FLAG_BLOCKING)); 499 mPosition += samples / mTrack.getChannelCount(); 500 } 501 return samples; 502 } 503 504 public AudioTrackNative mTrack; 505 private final static String TAG = "AudioRecordAuditNative"; 506 private int mPosition; 507 } 508 } 509