Home | History | Annotate | Download | only in cts
      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.
     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.
     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
     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 
     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 
     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      */
    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 
    116     public static int frameSizeFromFormat(AudioFormat format) {
    117         return format.getChannelCount()
    118                 * format.getBytesPerSample(format.getEncoding());
    119     }
    120 
    121     public static int frameCountFromMsec(int ms, AudioFormat format) {
    122         return ms * format.getSampleRate() / 1000;
    123     }
    124 
    125     public static class Statistics {
    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 
    134         public double getAvg() {
    135             if (mCount == 0) {
    136                 return 0;
    137             }
    138             return mSum / mCount;
    139         }
    140 
    141         public double getAvgAbs() {
    142             if (mCount == 0) {
    143                 return 0;
    144             }
    145             return mSumAbs / mCount;
    146         }
    147 
    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;
    164         public void set(T value)
    165         {
    166             synchronized (this) {
    167                 assert !mSet;
    168                 mValue = value;
    169                 mSet = true;
    170                 notify();
    171             }
    172         }
    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     {
    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 
    205         public MakeSomethingAsynchronouslyAndLoop(MakesSomething<T> whatToMake)
    206         {
    207             assert whatToMake != null;
    208             mWhatToMake = whatToMake;
    209         }
    210 
    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         }
    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 
    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 
    283         public TimestampVerifier(@Nullable String tag, @IntRange(from=4000) int sampleRate) {
    284             mTag = tag;  // Log accepts null
    285             mSampleRate = sampleRate;
    286         }
    287 
    288         public int getJitterCount() { return mJitterCount; }
    289         public double getMeanJitterMs() { return mMeanJitterMs; }
    290         public double getStdJitterMs() { return Math.sqrt(mSecondMomentJitterMs / mJitterCount); }
    291         public double getMaxAbsJitterMs() { return mMaxAbsJitterMs; }
    292         public double getStartTimeNs() {
    293             return mLastTimeNs - (mLastFrames * NANOS_PER_SECOND / mSampleRate);
    294         }
    295 
    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 
    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 {
    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 
    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
    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
    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
    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
    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
    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
    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
    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
    559         public void startRecording() {
    560             super.startRecording();
    561             if (mTrack != null) {
    562                 mTrack.play();
    563             }
    564         }
    565 
    566         @Override
    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
    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 {
    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
    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
    627         public void close() {
    628             super.close();
    629             if (mTrack != null) {
    630                 mTrack.close();
    631             }
    632         }
    633 
    634         @Override
    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
    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
    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
    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