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 static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertTrue; 22 23 import com.android.compatibility.common.util.DeviceReportLog; 24 import com.android.compatibility.common.util.ResultType; 25 import com.android.compatibility.common.util.ResultUnit; 26 import java.nio.ByteBuffer; 27 28 import org.junit.Assert; 29 30 import android.annotation.IntRange; 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.media.AudioAttributes; 34 import android.media.AudioFormat; 35 import android.media.AudioManager; 36 import android.media.AudioRecord; 37 import android.media.AudioTimestamp; 38 import android.media.AudioTrack; 39 import android.os.Looper; 40 import android.os.PersistableBundle; 41 import android.util.Log; 42 43 import androidx.test.InstrumentationRegistry; 44 45 // Used for statistics and loopers in listener tests. 46 // See AudioRecordTest.java and AudioTrack_ListenerTest.java. 47 public class AudioHelper { 48 49 // asserts key equals expected in the metrics bundle. assertMetricsKeyEquals( PersistableBundle metrics, String key, Object expected)50 public static void assertMetricsKeyEquals( 51 PersistableBundle metrics, String key, Object expected) { 52 Object actual = metrics.get(key); 53 assertEquals("metric " + key + " actual " + actual + " != " + " expected " + expected, 54 expected, actual); 55 } 56 57 // asserts key exists in the metrics bundle. assertMetricsKey(PersistableBundle metrics, String key)58 public static void assertMetricsKey(PersistableBundle metrics, String key) { 59 Object actual = metrics.get(key); 60 assertNotNull("metric " + key + " does not exist", actual); 61 } 62 63 // create sine waves or chirps for data arrays createSoundDataInByteArray(int bufferSamples, final int sampleRate, final double frequency, double sweep)64 public static byte[] createSoundDataInByteArray(int bufferSamples, final int sampleRate, 65 final double frequency, double sweep) { 66 final double rad = 2 * Math.PI * frequency / sampleRate; 67 byte[] vai = new byte[bufferSamples]; 68 sweep = Math.PI * sweep / ((double)sampleRate * vai.length); 69 for (int j = 0; j < vai.length; j++) { 70 int unsigned = (int)(Math.sin(j * (rad + j * sweep)) * Byte.MAX_VALUE) 71 + Byte.MAX_VALUE & 0xFF; 72 vai[j] = (byte) unsigned; 73 } 74 return vai; 75 } 76 createSoundDataInShortArray(int bufferSamples, final int sampleRate, final double frequency, double sweep)77 public static short[] createSoundDataInShortArray(int bufferSamples, final int sampleRate, 78 final double frequency, double sweep) { 79 final double rad = 2 * Math.PI * frequency / sampleRate; 80 short[] vai = new short[bufferSamples]; 81 sweep = Math.PI * sweep / ((double)sampleRate * vai.length); 82 for (int j = 0; j < vai.length; j++) { 83 vai[j] = (short)(Math.sin(j * (rad + j * sweep)) * Short.MAX_VALUE); 84 } 85 return vai; 86 } 87 createSoundDataInFloatArray(int bufferSamples, final int sampleRate, final double frequency, double sweep)88 public static float[] createSoundDataInFloatArray(int bufferSamples, final int sampleRate, 89 final double frequency, double sweep) { 90 final double rad = 2 * Math.PI * frequency / sampleRate; 91 float[] vaf = new float[bufferSamples]; 92 sweep = Math.PI * sweep / ((double)sampleRate * vaf.length); 93 for (int j = 0; j < vaf.length; j++) { 94 vaf[j] = (float)(Math.sin(j * (rad + j * sweep))); 95 } 96 return vaf; 97 } 98 99 /** 100 * Create and fill a short array with complete sine waves so we can 101 * hear buffer underruns more easily. 102 */ createSineWavesShort(int numFrames, int samplesPerFrame, int numCycles, double amplitude)103 public static short[] createSineWavesShort(int numFrames, int samplesPerFrame, 104 int numCycles, double amplitude) { 105 final short[] data = new short[numFrames * samplesPerFrame]; 106 final double rad = numCycles * 2.0 * Math.PI / numFrames; 107 for (int j = 0; j < data.length;) { 108 short sample = (short)(amplitude * Math.sin(j * rad) * Short.MAX_VALUE); 109 for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) { 110 data[j++] = sample; 111 } 112 } 113 return data; 114 } 115 frameSizeFromFormat(AudioFormat format)116 public static int frameSizeFromFormat(AudioFormat format) { 117 return format.getChannelCount() 118 * format.getBytesPerSample(format.getEncoding()); 119 } 120 frameCountFromMsec(int ms, AudioFormat format)121 public static int frameCountFromMsec(int ms, AudioFormat format) { 122 return ms * format.getSampleRate() / 1000; 123 } 124 125 public static class Statistics { add(double value)126 public void add(double value) { 127 final double absValue = Math.abs(value); 128 mSum += value; 129 mSumAbs += absValue; 130 mMaxAbs = Math.max(mMaxAbs, absValue); 131 ++mCount; 132 } 133 getAvg()134 public double getAvg() { 135 if (mCount == 0) { 136 return 0; 137 } 138 return mSum / mCount; 139 } 140 getAvgAbs()141 public double getAvgAbs() { 142 if (mCount == 0) { 143 return 0; 144 } 145 return mSumAbs / mCount; 146 } 147 getMaxAbs()148 public double getMaxAbs() { 149 return mMaxAbs; 150 } 151 152 private int mCount = 0; 153 private double mSum = 0; 154 private double mSumAbs = 0; 155 private double mMaxAbs = 0; 156 } 157 158 // for listener tests 159 // lightweight java.util.concurrent.Future* 160 public static class FutureLatch<T> 161 { 162 private T mValue; 163 private boolean mSet; set(T value)164 public void set(T value) 165 { 166 synchronized (this) { 167 assert !mSet; 168 mValue = value; 169 mSet = true; 170 notify(); 171 } 172 } get()173 public T get() 174 { 175 T value; 176 synchronized (this) { 177 while (!mSet) { 178 try { 179 wait(); 180 } catch (InterruptedException e) { 181 ; 182 } 183 } 184 value = mValue; 185 } 186 return value; 187 } 188 } 189 190 // for listener tests 191 // represents a factory for T 192 public interface MakesSomething<T> 193 { makeSomething()194 T makeSomething(); 195 } 196 197 // for listener tests 198 // used to construct an object in the context of an asynchronous thread with looper 199 public static class MakeSomethingAsynchronouslyAndLoop<T> 200 { 201 private Thread mThread; 202 volatile private Looper mLooper; 203 private final MakesSomething<T> mWhatToMake; 204 MakeSomethingAsynchronouslyAndLoop(MakesSomething<T> whatToMake)205 public MakeSomethingAsynchronouslyAndLoop(MakesSomething<T> whatToMake) 206 { 207 assert whatToMake != null; 208 mWhatToMake = whatToMake; 209 } 210 make()211 public T make() 212 { 213 final FutureLatch<T> futureLatch = new FutureLatch<T>(); 214 mThread = new Thread() 215 { 216 @Override 217 public void run() 218 { 219 Looper.prepare(); 220 mLooper = Looper.myLooper(); 221 T something = mWhatToMake.makeSomething(); 222 futureLatch.set(something); 223 Looper.loop(); 224 } 225 }; 226 mThread.start(); 227 return futureLatch.get(); 228 } join()229 public void join() 230 { 231 mLooper.quit(); 232 try { 233 mThread.join(); 234 } catch (InterruptedException e) { 235 ; 236 } 237 // avoid dangling references 238 mLooper = null; 239 mThread = null; 240 } 241 } 242 outChannelMaskFromInChannelMask(int channelMask)243 public static int outChannelMaskFromInChannelMask(int channelMask) { 244 switch (channelMask) { 245 case AudioFormat.CHANNEL_IN_MONO: 246 return AudioFormat.CHANNEL_OUT_MONO; 247 case AudioFormat.CHANNEL_IN_STEREO: 248 return AudioFormat.CHANNEL_OUT_STEREO; 249 default: 250 return AudioFormat.CHANNEL_INVALID; 251 } 252 } 253 254 public static class TimestampVerifier { 255 256 // CDD 5.6 1ms timestamp accuracy 257 private static final double TEST_MAX_JITTER_MS_ALLOWED = 6.; // a sanity check 258 private static final double TEST_STD_JITTER_MS_ALLOWED = 3.; // flaky tolerance 3x 259 private static final double TEST_STD_JITTER_MS_WARN = 1.; // CDD requirement warning 260 261 // CDD 5.6 100ms track startup latency 262 private static final double TEST_STARTUP_TIME_MS_ALLOWED = 500.; // flaky tolerance 5x 263 private static final double TEST_STARTUP_TIME_MS_WARN = 100.; // CDD requirement warning 264 265 private static final int MILLIS_PER_SECOND = 1000; 266 private static final long NANOS_PER_MILLISECOND = 1000000; 267 private static final long NANOS_PER_SECOND = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND; 268 private static final String REPORT_LOG_NAME = "CtsMediaTestCases"; 269 270 private final String mTag; 271 private final int mSampleRate; 272 273 // Running statistics 274 private int mCount = 0; 275 private long mLastFrames = 0; 276 private long mLastTimeNs = 0; 277 private int mJitterCount = 0; 278 private double mMeanJitterMs = 0.; 279 private double mSecondMomentJitterMs = 0.; 280 private double mMaxAbsJitterMs = 0.; 281 private int mWarmupCount = 0; 282 TimestampVerifier(@ullable String tag, @IntRange(from=4000) int sampleRate)283 public TimestampVerifier(@Nullable String tag, @IntRange(from=4000) int sampleRate) { 284 mTag = tag; // Log accepts null 285 mSampleRate = sampleRate; 286 } 287 getJitterCount()288 public int getJitterCount() { return mJitterCount; } getMeanJitterMs()289 public double getMeanJitterMs() { return mMeanJitterMs; } getStdJitterMs()290 public double getStdJitterMs() { return Math.sqrt(mSecondMomentJitterMs / mJitterCount); } getMaxAbsJitterMs()291 public double getMaxAbsJitterMs() { return mMaxAbsJitterMs; } getStartTimeNs()292 public double getStartTimeNs() { 293 return mLastTimeNs - (mLastFrames * NANOS_PER_SECOND / mSampleRate); 294 } 295 add(@onNull AudioTimestamp ts)296 public void add(@NonNull AudioTimestamp ts) { 297 final long frames = ts.framePosition; 298 final long timeNs = ts.nanoTime; 299 300 assertTrue("timestamps must have causal time", System.nanoTime() >= timeNs); 301 302 if (mCount > 0) { // need delta info from previous iteration (skipping first) 303 final long deltaFrames = frames - mLastFrames; 304 final long deltaTimeNs = timeNs - mLastTimeNs; 305 306 if (deltaFrames == 0 && deltaTimeNs == 0) return; 307 308 final double deltaFramesNs = (double)deltaFrames * NANOS_PER_SECOND / mSampleRate; 309 final double jitterMs = (deltaTimeNs - deltaFramesNs) // actual - expected 310 * (1. / NANOS_PER_MILLISECOND); 311 312 Log.d(mTag, "frames(" + frames 313 + ") timeNs(" + timeNs 314 + ") lastframes(" + mLastFrames 315 + ") lastTimeNs(" + mLastTimeNs 316 + ") deltaFrames(" + deltaFrames 317 + ") deltaTimeNs(" + deltaTimeNs 318 + ") jitterMs(" + jitterMs + ")"); 319 assertTrue("timestamp time should be increasing", deltaTimeNs >= 0); 320 assertTrue("timestamp frames should be increasing", deltaFrames >= 0); 321 322 if (mLastFrames != 0) { 323 if (mWarmupCount++ > 1) { // ensure device is warmed up 324 // Welford's algorithm 325 // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance 326 ++mJitterCount; 327 final double delta = jitterMs - mMeanJitterMs; 328 mMeanJitterMs += delta / mJitterCount; 329 final double delta2 = jitterMs - mMeanJitterMs; 330 mSecondMomentJitterMs += delta * delta2; 331 332 // jitterMs is signed, so max uses abs() here. 333 final double absJitterMs = Math.abs(jitterMs); 334 if (absJitterMs > mMaxAbsJitterMs) { 335 mMaxAbsJitterMs = absJitterMs; 336 } 337 } 338 } 339 } 340 ++mCount; 341 mLastFrames = frames; 342 mLastTimeNs = timeNs; 343 } 344 verifyAndLog(long trackStartTimeNs, @Nullable String logName)345 public void verifyAndLog(long trackStartTimeNs, @Nullable String logName) { 346 // enough timestamps? 347 assertTrue("need at least 2 jitter measurements", mJitterCount >= 2); 348 349 // Compute startup time and std jitter. 350 final int startupTimeMs = 351 (int) ((getStartTimeNs() - trackStartTimeNs) / NANOS_PER_MILLISECOND); 352 final double stdJitterMs = getStdJitterMs(); 353 354 // Check startup time 355 if (startupTimeMs > TEST_STARTUP_TIME_MS_WARN) { 356 Log.w(mTag, "CDD warning: startup time " + startupTimeMs 357 + " > " + TEST_STARTUP_TIME_MS_WARN); 358 } 359 assertTrue("expect startupTimeMs " + startupTimeMs 360 + " < " + TEST_STARTUP_TIME_MS_ALLOWED, 361 startupTimeMs < TEST_STARTUP_TIME_MS_ALLOWED); 362 363 // Check maximum jitter 364 assertTrue("expect maxAbsJitterMs(" + mMaxAbsJitterMs + ") < " 365 + TEST_MAX_JITTER_MS_ALLOWED, 366 mMaxAbsJitterMs < TEST_MAX_JITTER_MS_ALLOWED); 367 368 // Check std jitter 369 if (stdJitterMs > TEST_STD_JITTER_MS_WARN) { 370 Log.w(mTag, "CDD warning: std timestamp jitter " + stdJitterMs 371 + " > " + TEST_STD_JITTER_MS_WARN); 372 } 373 assertTrue("expect stdJitterMs " + stdJitterMs + " < " + TEST_STD_JITTER_MS_ALLOWED, 374 stdJitterMs < TEST_STD_JITTER_MS_ALLOWED); 375 376 Log.d(mTag, "startupTimeMs(" + startupTimeMs 377 + ") meanJitterMs(" + mMeanJitterMs 378 + ") maxAbsJitterMs(" + mMaxAbsJitterMs 379 + ") stdJitterMs(" + stdJitterMs 380 + ")"); 381 382 // Log results if logName is provided 383 if (logName != null) { 384 DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, logName); 385 // ReportLog needs at least one Value and Summary. 386 log.addValue("startup_time_ms", startupTimeMs, 387 ResultType.LOWER_BETTER, ResultUnit.MS); 388 log.addValue("maximum_abs_jitter_ms", mMaxAbsJitterMs, 389 ResultType.LOWER_BETTER, ResultUnit.MS); 390 log.addValue("mean_jitter_ms", mMeanJitterMs, 391 ResultType.LOWER_BETTER, ResultUnit.MS); 392 log.setSummary("std_jitter_ms", stdJitterMs, 393 ResultType.LOWER_BETTER, ResultUnit.MS); 394 log.submit(InstrumentationRegistry.getInstrumentation()); 395 } 396 } 397 } 398 399 /* AudioRecordAudit extends AudioRecord to allow concurrent playback 400 * of read content to an AudioTrack. This is for testing only. 401 * For general applications, it is NOT recommended to extend AudioRecord. 402 * This affects AudioRecord timing. 403 */ 404 public static class AudioRecordAudit extends AudioRecord { AudioRecordAudit(int audioSource, int sampleRate, int channelMask, int format, int bufferSize, boolean isChannelIndex)405 public AudioRecordAudit(int audioSource, int sampleRate, int channelMask, 406 int format, int bufferSize, boolean isChannelIndex) { 407 this(audioSource, sampleRate, channelMask, format, bufferSize, isChannelIndex, 408 AudioManager.STREAM_MUSIC, 500 /*delayMs*/); 409 } 410 AudioRecordAudit(int audioSource, int sampleRate, int channelMask, int format, int bufferSize, boolean isChannelIndex, int auditStreamType, int delayMs)411 public AudioRecordAudit(int audioSource, int sampleRate, int channelMask, 412 int format, int bufferSize, 413 boolean isChannelIndex, int auditStreamType, int delayMs) { 414 // without channel index masks, one could call: 415 // super(audioSource, sampleRate, channelMask, format, bufferSize); 416 super(new AudioAttributes.Builder() 417 .setInternalCapturePreset(audioSource) 418 .build(), 419 (isChannelIndex 420 ? new AudioFormat.Builder().setChannelIndexMask(channelMask) 421 : new AudioFormat.Builder().setChannelMask(channelMask)) 422 .setEncoding(format) 423 .setSampleRate(sampleRate) 424 .build(), 425 bufferSize, 426 AudioManager.AUDIO_SESSION_ID_GENERATE); 427 428 if (delayMs >= 0) { // create an AudioTrack 429 final int channelOutMask = isChannelIndex ? channelMask : 430 outChannelMaskFromInChannelMask(channelMask); 431 final int bufferOutFrames = sampleRate * delayMs / 1000; 432 final int bufferOutSamples = bufferOutFrames 433 * AudioFormat.channelCountFromOutChannelMask(channelOutMask); 434 final int bufferOutSize = bufferOutSamples 435 * AudioFormat.getBytesPerSample(format); 436 437 // Caution: delayMs too large results in buffer sizes that cannot be created. 438 mTrack = new AudioTrack.Builder() 439 .setAudioAttributes(new AudioAttributes.Builder() 440 .setLegacyStreamType(auditStreamType) 441 .build()) 442 .setAudioFormat((isChannelIndex ? 443 new AudioFormat.Builder().setChannelIndexMask(channelOutMask) : 444 new AudioFormat.Builder().setChannelMask(channelOutMask)) 445 .setEncoding(format) 446 .setSampleRate(sampleRate) 447 .build()) 448 .setBufferSizeInBytes(bufferOutSize) 449 .build(); 450 Assert.assertEquals(AudioTrack.STATE_INITIALIZED, mTrack.getState()); 451 mPosition = 0; 452 mFinishAtMs = 0; 453 } 454 } 455 456 @Override read(byte[] audioData, int offsetInBytes, int sizeInBytes)457 public int read(byte[] audioData, int offsetInBytes, int sizeInBytes) { 458 // for byte array access we verify format is 8 bit PCM (typical use) 459 Assert.assertEquals(TAG + ": format mismatch", 460 AudioFormat.ENCODING_PCM_8BIT, getAudioFormat()); 461 int samples = super.read(audioData, offsetInBytes, sizeInBytes); 462 if (mTrack != null) { 463 Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples)); 464 mPosition += samples / mTrack.getChannelCount(); 465 } 466 return samples; 467 } 468 469 @Override read(byte[] audioData, int offsetInBytes, int sizeInBytes, int readMode)470 public int read(byte[] audioData, int offsetInBytes, int sizeInBytes, int readMode) { 471 // for byte array access we verify format is 8 bit PCM (typical use) 472 Assert.assertEquals(TAG + ": format mismatch", 473 AudioFormat.ENCODING_PCM_8BIT, getAudioFormat()); 474 int samples = super.read(audioData, offsetInBytes, sizeInBytes, readMode); 475 if (mTrack != null) { 476 Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples, 477 AudioTrack.WRITE_BLOCKING)); 478 mPosition += samples / mTrack.getChannelCount(); 479 } 480 return samples; 481 } 482 483 @Override read(short[] audioData, int offsetInShorts, int sizeInShorts)484 public int read(short[] audioData, int offsetInShorts, int sizeInShorts) { 485 // for short array access we verify format is 16 bit PCM (typical use) 486 Assert.assertEquals(TAG + ": format mismatch", 487 AudioFormat.ENCODING_PCM_16BIT, getAudioFormat()); 488 int samples = super.read(audioData, offsetInShorts, sizeInShorts); 489 if (mTrack != null) { 490 Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples)); 491 mPosition += samples / mTrack.getChannelCount(); 492 } 493 return samples; 494 } 495 496 @Override read(short[] audioData, int offsetInShorts, int sizeInShorts, int readMode)497 public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readMode) { 498 // for short array access we verify format is 16 bit PCM (typical use) 499 Assert.assertEquals(TAG + ": format mismatch", 500 AudioFormat.ENCODING_PCM_16BIT, getAudioFormat()); 501 int samples = super.read(audioData, offsetInShorts, sizeInShorts, readMode); 502 if (mTrack != null) { 503 Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples, 504 AudioTrack.WRITE_BLOCKING)); 505 mPosition += samples / mTrack.getChannelCount(); 506 } 507 return samples; 508 } 509 510 @Override read(float[] audioData, int offsetInFloats, int sizeInFloats, int readMode)511 public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readMode) { 512 // for float array access we verify format is float PCM (typical use) 513 Assert.assertEquals(TAG + ": format mismatch", 514 AudioFormat.ENCODING_PCM_FLOAT, getAudioFormat()); 515 int samples = super.read(audioData, offsetInFloats, sizeInFloats, readMode); 516 if (mTrack != null) { 517 Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples, 518 AudioTrack.WRITE_BLOCKING)); 519 mPosition += samples / mTrack.getChannelCount(); 520 } 521 return samples; 522 } 523 524 @Override read(ByteBuffer audioBuffer, int sizeInBytes)525 public int read(ByteBuffer audioBuffer, int sizeInBytes) { 526 int bytes = super.read(audioBuffer, sizeInBytes); 527 if (mTrack != null) { 528 // read does not affect position and limit of the audioBuffer. 529 // we make a duplicate to change that for writing to the output AudioTrack 530 // which does check position and limit. 531 ByteBuffer copy = audioBuffer.duplicate(); 532 copy.position(0).limit(bytes); // read places data at the start of the buffer. 533 Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING)); 534 mPosition += bytes / 535 (mTrack.getChannelCount() 536 * AudioFormat.getBytesPerSample(mTrack.getAudioFormat())); 537 } 538 return bytes; 539 } 540 541 @Override read(ByteBuffer audioBuffer, int sizeInBytes, int readMode)542 public int read(ByteBuffer audioBuffer, int sizeInBytes, int readMode) { 543 int bytes = super.read(audioBuffer, sizeInBytes, readMode); 544 if (mTrack != null) { 545 // read does not affect position and limit of the audioBuffer. 546 // we make a duplicate to change that for writing to the output AudioTrack 547 // which does check position and limit. 548 ByteBuffer copy = audioBuffer.duplicate(); 549 copy.position(0).limit(bytes); // read places data at the start of the buffer. 550 Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING)); 551 mPosition += bytes / 552 (mTrack.getChannelCount() 553 * AudioFormat.getBytesPerSample(mTrack.getAudioFormat())); 554 } 555 return bytes; 556 } 557 558 @Override startRecording()559 public void startRecording() { 560 super.startRecording(); 561 if (mTrack != null) { 562 mTrack.play(); 563 } 564 } 565 566 @Override stop()567 public void stop() { 568 super.stop(); 569 if (mTrack != null) { 570 if (mPosition > 0) { // stop may be called multiple times. 571 final int remainingFrames = mPosition - mTrack.getPlaybackHeadPosition(); 572 mFinishAtMs = System.currentTimeMillis() 573 + remainingFrames * 1000 / mTrack.getSampleRate(); 574 mPosition = 0; 575 } 576 mTrack.stop(); // allows remaining data to play out 577 } 578 } 579 580 @Override release()581 public void release() { 582 super.release(); 583 if (mTrack != null) { 584 final long remainingMs = mFinishAtMs - System.currentTimeMillis(); 585 if (remainingMs > 0) { 586 try { 587 Thread.sleep(remainingMs); 588 } catch (InterruptedException e) { 589 ; 590 } 591 } 592 mTrack.release(); 593 mTrack = null; 594 } 595 } 596 597 public AudioTrack mTrack; 598 private final static String TAG = "AudioRecordAudit"; 599 private int mPosition; 600 private long mFinishAtMs; 601 } 602 603 /* AudioRecordAudit extends AudioRecord to allow concurrent playback 604 * of read content to an AudioTrack. This is for testing only. 605 * For general applications, it is NOT recommended to extend AudioRecord. 606 * This affects AudioRecord timing. 607 */ 608 public static class AudioRecordAuditNative extends AudioRecordNative { AudioRecordAuditNative()609 public AudioRecordAuditNative() { 610 super(); 611 // Caution: delayMs too large results in buffer sizes that cannot be created. 612 mTrack = new AudioTrackNative(); 613 } 614 615 @Override open(int numChannels, int sampleRate, boolean useFloat, int numBuffers)616 public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) { 617 if (super.open(numChannels, sampleRate, useFloat, numBuffers)) { 618 if (!mTrack.open(numChannels, sampleRate, useFloat, 2 /* numBuffers */)) { 619 mTrack = null; // remove track 620 } 621 return true; 622 } 623 return false; 624 } 625 626 @Override close()627 public void close() { 628 super.close(); 629 if (mTrack != null) { 630 mTrack.close(); 631 } 632 } 633 634 @Override start()635 public boolean start() { 636 if (super.start()) { 637 if (mTrack != null) { 638 mTrack.start(); 639 } 640 return true; 641 } 642 return false; 643 } 644 645 @Override stop()646 public boolean stop() { 647 if (super.stop()) { 648 if (mTrack != null) { 649 mTrack.stop(); // doesn't allow remaining data to play out 650 } 651 return true; 652 } 653 return false; 654 } 655 656 @Override read(short[] audioData, int offsetInShorts, int sizeInShorts, int readFlags)657 public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readFlags) { 658 int samples = super.read(audioData, offsetInShorts, sizeInShorts, readFlags); 659 if (mTrack != null) { 660 Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples, 661 AudioTrackNative.WRITE_FLAG_BLOCKING)); 662 mPosition += samples / mTrack.getChannelCount(); 663 } 664 return samples; 665 } 666 667 @Override read(float[] audioData, int offsetInFloats, int sizeInFloats, int readFlags)668 public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readFlags) { 669 int samples = super.read(audioData, offsetInFloats, sizeInFloats, readFlags); 670 if (mTrack != null) { 671 Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples, 672 AudioTrackNative.WRITE_FLAG_BLOCKING)); 673 mPosition += samples / mTrack.getChannelCount(); 674 } 675 return samples; 676 } 677 678 public AudioTrackNative mTrack; 679 private final static String TAG = "AudioRecordAuditNative"; 680 private int mPosition; 681 } 682 } 683