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