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 createSoundDataInByteArray(int bufferSamples, final int sampleRate, final double frequency, double sweep)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 createSoundDataInShortArray(int bufferSamples, final int sampleRate, final double frequency, double sweep)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 createSoundDataInFloatArray(int bufferSamples, final int sampleRate, final double frequency, double sweep)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 */ createSineWavesShort(int numFrames, int samplesPerFrame, int numCycles, double amplitude)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 frameSizeFromFormat(AudioFormat format)87 public static int frameSizeFromFormat(AudioFormat format) { 88 return format.getChannelCount() 89 * format.getBytesPerSample(format.getEncoding()); 90 } 91 frameCountFromMsec(int ms, AudioFormat format)92 public static int frameCountFromMsec(int ms, AudioFormat format) { 93 return ms * format.getSampleRate() / 1000; 94 } 95 96 public static class Statistics { add(double value)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 getAvg()105 public double getAvg() { 106 if (mCount == 0) { 107 return 0; 108 } 109 return mSum / mCount; 110 } 111 getAvgAbs()112 public double getAvgAbs() { 113 if (mCount == 0) { 114 return 0; 115 } 116 return mSumAbs / mCount; 117 } 118 getMaxAbs()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; set(T value)135 public void set(T value) 136 { 137 synchronized (this) { 138 assert !mSet; 139 mValue = value; 140 mSet = true; 141 notify(); 142 } 143 } get()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 { makeSomething()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 MakeSomethingAsynchronouslyAndLoop(MakesSomething<T> whatToMake)176 public MakeSomethingAsynchronouslyAndLoop(MakesSomething<T> whatToMake) 177 { 178 assert whatToMake != null; 179 mWhatToMake = whatToMake; 180 } 181 make()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 } join()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 outChannelMaskFromInChannelMask(int channelMask)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 { AudioRecordAudit(int audioSource, int sampleRate, int channelMask, int format, int bufferSize, boolean isChannelIndex)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 AudioRecordAudit(int audioSource, int sampleRate, int channelMask, int format, int bufferSize, boolean isChannelIndex, int auditStreamType, int delayMs)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 read(byte[] audioData, int offsetInBytes, int sizeInBytes)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 read(byte[] audioData, int offsetInBytes, int sizeInBytes, int readMode)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 read(short[] audioData, int offsetInShorts, int sizeInShorts)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 read(short[] audioData, int offsetInShorts, int sizeInShorts, int readMode)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 read(float[] audioData, int offsetInFloats, int sizeInFloats, int readMode)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 read(ByteBuffer audioBuffer, int sizeInBytes)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 read(ByteBuffer audioBuffer, int sizeInBytes, int readMode)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 startRecording()385 public void startRecording() { 386 super.startRecording(); 387 if (mTrack != null) { 388 mTrack.play(); 389 } 390 } 391 392 @Override stop()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 release()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 { AudioRecordAuditNative()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 open(int numChannels, int sampleRate, boolean useFloat, int numBuffers)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 close()453 public void close() { 454 super.close(); 455 if (mTrack != null) { 456 mTrack.close(); 457 } 458 } 459 460 @Override start()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 stop()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 read(short[] audioData, int offsetInShorts, int sizeInShorts, int readFlags)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 read(float[] audioData, int offsetInFloats, int sizeInFloats, int readFlags)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