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