• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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