1 /*
2  * Copyright (C) 2016 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 android.app.ActivityManager;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.cts.util.CtsAndroidTestCase;
23 import android.media.AudioAttributes;
24 import android.media.AudioFormat;
25 import android.media.AudioManager;
26 import android.media.AudioTimestamp;
27 import android.media.AudioTrack;
28 import android.media.PlaybackParams;
29 import android.util.Log;
30 
31 import java.nio.ByteBuffer;
32 import java.nio.FloatBuffer;
33 import java.nio.ShortBuffer;
34 
35 // Test the Java AudioTrack low latency related features:
36 //
37 // setBufferSizeInFrames()
38 // getBufferCapacityInFrames()
39 // ASSUME getMinBufferSize in frames is significantly lower than getBufferCapacityInFrames.
40 // This gives us room to adjust the sizes.
41 //
42 // getUnderrunCount()
43 // ASSUME normal track will underrun with setBufferSizeInFrames(0).
44 //
45 // AudioAttributes.FLAG_LOW_LATENCY
46 // ASSUME FLAG_LOW_LATENCY reduces output latency by more than 10 msec.
47 // Warns if not. This can happen if there is no Fast Mixer or if a FastTrack
48 // is not available.
49 
50 public class AudioTrackLatencyTest extends CtsAndroidTestCase {
51     private String TAG = "AudioTrackLatencyTest";
52     private final static long NANOS_PER_MILLISECOND = 1000000L;
53     private final static int MILLIS_PER_SECOND = 1000;
54     private final static long NANOS_PER_SECOND = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND;
55 
log(String testName, String message)56     private void log(String testName, String message) {
57         Log.i(TAG, "[" + testName + "] " + message);
58     }
59 
logw(String testName, String message)60     private void logw(String testName, String message) {
61         Log.w(TAG, "[" + testName + "] " + message);
62     }
63 
loge(String testName, String message)64     private void loge(String testName, String message) {
65         Log.e(TAG, "[" + testName + "] " + message);
66     }
67 
testSetBufferSize()68     public void testSetBufferSize() throws Exception {
69         // constants for test
70         final String TEST_NAME = "testSetBufferSize";
71         final int TEST_SR = 44100;
72         final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
73         final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
74         final int TEST_MODE = AudioTrack.MODE_STREAM;
75         final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
76 
77         // -------- initialization --------------
78         int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
79         AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
80                 minBuffSize, TEST_MODE);
81 
82         // -------- test --------------
83         // Initial values
84         int bufferCapacity = track.getBufferCapacityInFrames();
85         int initialBufferSize = track.getBufferSizeInFrames();
86         assertTrue(TEST_NAME, bufferCapacity > 0);
87         assertTrue(TEST_NAME, initialBufferSize > 0);
88         assertTrue(TEST_NAME, initialBufferSize <= bufferCapacity);
89 
90         // set to various values
91         int resultNegative = track.setBufferSizeInFrames(-1);
92         assertEquals(TEST_NAME + ": negative size", AudioTrack.ERROR_BAD_VALUE, resultNegative);
93         assertEquals(TEST_NAME + ": should be unchanged",
94                 initialBufferSize, track.getBufferSizeInFrames());
95 
96         int resultZero = track.setBufferSizeInFrames(0);
97         assertTrue(TEST_NAME + ": should be >0, but got " + resultZero, resultZero > 0);
98         assertTrue(TEST_NAME + ": zero size < original, but got " + resultZero,
99                 resultZero < initialBufferSize);
100         assertEquals(TEST_NAME + ": should match resultZero",
101                 resultZero, track.getBufferSizeInFrames());
102 
103         int resultMax = track.setBufferSizeInFrames(Integer.MAX_VALUE);
104         assertTrue(TEST_NAME + ": set MAX_VALUE, >", resultMax > resultZero);
105         assertTrue(TEST_NAME + ": set MAX_VALUE, <=", resultMax <= bufferCapacity);
106         assertEquals(TEST_NAME + ": should match resultMax",
107                 resultMax, track.getBufferSizeInFrames());
108 
109         int resultMiddle = track.setBufferSizeInFrames(bufferCapacity / 2);
110         assertTrue(TEST_NAME + ": set middle, >", resultMiddle > resultZero);
111         assertTrue(TEST_NAME + ": set middle, <=", resultMiddle < resultMax);
112         assertEquals(TEST_NAME + ": should match resultMiddle",
113                 resultMiddle, track.getBufferSizeInFrames());
114 
115         // -------- tear down --------------
116         track.release();
117     }
118 
119     // Helper class for tests
120     private static class TestSetup {
121         public int sampleRate = 48000;
122         public int samplesPerFrame = 2;
123         public int bytesPerSample = 2;
124         public int config = AudioFormat.CHANNEL_OUT_STEREO;
125         public int format = AudioFormat.ENCODING_PCM_16BIT;
126         public int mode = AudioTrack.MODE_STREAM;
127         public int streamType = AudioManager.STREAM_MUSIC;
128         public int framesPerBuffer = 256;
129         public double amplitude = 0.5;
130 
131         private AudioTrack mTrack;
132         private short[] mData;
133         private int mActualSizeInFrames;
134 
createTrack()135         AudioTrack createTrack() {
136             mData = AudioHelper.createSineWavesShort(framesPerBuffer,
137                     samplesPerFrame, 1, amplitude);
138             int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, config, format);
139             // Create a buffer that is 3/2 times bigger than the minimum.
140             // This gives me room to cut it in half and play without glitching.
141             // This is an arbitrary scaling factor.
142             int bufferSize = (minBufferSize * 3) / 2;
143             mTrack = new AudioTrack(streamType, sampleRate, config, format,
144                     bufferSize, mode);
145 
146             // Calculate and use a smaller buffer size
147             int smallBufferSize = bufferSize / 2; // arbitrary, smaller might underflow
148             int smallBuffSizeInFrames = smallBufferSize / (samplesPerFrame * bytesPerSample);
149             mActualSizeInFrames = mTrack.setBufferSizeInFrames(smallBuffSizeInFrames);
150             return mTrack;
151 
152         }
153 
primeAudioTrack(String testName)154         int primeAudioTrack(String testName) {
155             // Prime the buffer.
156             int samplesWrittenTotal = 0;
157             int samplesWritten;
158             do{
159                 samplesWritten = mTrack.write(mData, 0, mData.length);
160                 if (samplesWritten > 0) {
161                     samplesWrittenTotal += samplesWritten;
162                 }
163             } while (samplesWritten == mData.length);
164             int framesWrittenTotal = samplesWrittenTotal / samplesPerFrame;
165             assertTrue(testName + ": framesWrittenTotal = " + framesWrittenTotal
166                     + ", size = " + mActualSizeInFrames,
167                     framesWrittenTotal >= mActualSizeInFrames);
168             return framesWrittenTotal;
169         }
170 
171         /**
172          * @param seconds
173          */
writeSeconds(double seconds)174         public void writeSeconds(double seconds) throws InterruptedException {
175             long msecEnd = System.currentTimeMillis() + (long)(seconds * 1000);
176             while (System.currentTimeMillis() < msecEnd) {
177                 // Use non-blocking mode in case the track is hung.
178                 int samplesWritten = mTrack.write(mData, 0, mData.length, AudioTrack.WRITE_NON_BLOCKING);
179                 if (samplesWritten < mData.length) {
180                     int samplesRemaining = mData.length - samplesWritten;
181                     int framesRemaining = samplesRemaining / samplesPerFrame;
182                     int millis = (framesRemaining * 1000) / sampleRate;
183                     Thread.sleep(millis);
184                 }
185             }
186         }
187     }
188 
189     // Try to play an AudioTrack when the initial size is less than capacity.
190     // We want to make sure the track starts properly and is not stuck.
testPlaySmallBuffer()191     public void testPlaySmallBuffer() throws Exception {
192         final String TEST_NAME = "testPlaySmallBuffer";
193         TestSetup setup = new TestSetup();
194         AudioTrack track = setup.createTrack();
195 
196         // Prime the buffer.
197         int framesWrittenTotal = setup.primeAudioTrack(TEST_NAME);
198 
199         // Start playing and let it drain.
200         int position1 = track.getPlaybackHeadPosition();
201         assertEquals(TEST_NAME + ": initial position", 0, position1);
202         track.play();
203 
204         // Make sure it starts within a reasonably short time.
205         final long MAX_TIME_TO_START_MSEC =  500; // arbitrary
206         long giveUpAt = System.currentTimeMillis() + MAX_TIME_TO_START_MSEC;
207         int position2 = track.getPlaybackHeadPosition();
208         while ((position1 == position2)
209                 && (System.currentTimeMillis() < giveUpAt)) {
210             Thread.sleep(20); // arbitrary interval
211             position2 = track.getPlaybackHeadPosition();
212         }
213         assertTrue(TEST_NAME + ": did it start?, position after start = " + position2,
214                 position2 > position1);
215 
216         // Make sure it finishes playing the data.
217         // Wait several times longer than it should take to play the data.
218         final int several = 3; // arbitrary
219         Thread.sleep(several * framesWrittenTotal * MILLIS_PER_SECOND / setup.sampleRate);
220         position2 = track.getPlaybackHeadPosition();
221         assertEquals(TEST_NAME + ": did it play all the data?",
222                 framesWrittenTotal, position2);
223 
224         track.release();
225     }
226 
227     // Try to play and pause an AudioTrack when the initial size is less than capacity.
228     // We want to make sure the track starts properly and is not stuck.
testPlayPauseSmallBuffer()229     public void testPlayPauseSmallBuffer() throws Exception {
230         final String TEST_NAME = "testPlayPauseSmallBuffer";
231         TestSetup setup = new TestSetup();
232         AudioTrack track = setup.createTrack();
233 
234         // Prime the buffer.
235         setup.primeAudioTrack(TEST_NAME);
236 
237         // Start playing then pause and play in a loop.
238         int position1 = track.getPlaybackHeadPosition();
239         assertEquals(TEST_NAME + ": initial position", 0, position1);
240         track.play();
241         // try pausing several times to see it if it fails
242         final int several = 4; // arbitrary
243         for (int i = 0; i < several; i++) {
244             // write data in non-blocking mode for a few seconds
245             setup.writeSeconds(2.0); // arbitrary, long enough for audio to get to the device
246             // Did position advance as we were playing? Or was the track stuck?
247             int position2 = track.getPlaybackHeadPosition();
248             int delta = position2 - position1; // safe from wrapping
249             assertTrue(TEST_NAME + ": [" + i + "] did it advance? p1 = " + position1
250                     + ", p2 = " + position2, delta > 0);
251             position1 = position2;
252             // pause for a second
253             track.pause();
254             Thread.sleep(MILLIS_PER_SECOND);
255             track.play();
256         }
257 
258         track.release();
259     }
260 
261     // Create a track with or without FLAG_LOW_LATENCY
createCustomAudioTrack(boolean lowLatency)262     private AudioTrack createCustomAudioTrack(boolean lowLatency) {
263         final String TEST_NAME = "createCustomAudioTrack";
264         final int TEST_SR = 48000;
265         final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
266         final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
267         final int TEST_CONTENT_TYPE = AudioAttributes.CONTENT_TYPE_MUSIC;
268 
269         // Start with buffer twice as large as needed.
270         int bufferSizeBytes = 2 * AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
271         AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder()
272                 .setContentType(TEST_CONTENT_TYPE);
273         if (lowLatency) {
274             attributesBuilder.setFlags(AudioAttributes.FLAG_LOW_LATENCY);
275         }
276         AudioAttributes attributes = attributesBuilder.build();
277 
278         // Do not specify the sample rate so we get the optimal rate.
279         AudioFormat format = new AudioFormat.Builder()
280                 .setEncoding(TEST_FORMAT)
281                 .setChannelMask(TEST_CONF)
282                 .build();
283         AudioTrack track = new AudioTrack.Builder()
284                 .setAudioAttributes(attributes)
285                 .setAudioFormat(format)
286                 .setBufferSizeInBytes(bufferSizeBytes)
287                 .build();
288 
289         assertTrue(track != null);
290         log(TEST_NAME, "Track sample rate = " + track.getSampleRate() + " Hz");
291         return track;
292     }
293 
294 
checkOutputLowLatency(boolean lowLatency)295     private int checkOutputLowLatency(boolean lowLatency) throws Exception {
296         // constants for test
297         final String TEST_NAME = "checkOutputLowLatency";
298         final int TEST_SAMPLES_PER_FRAME = 2;
299         final int TEST_BYTES_PER_SAMPLE = 2;
300         final int TEST_NUM_SECONDS = 4;
301         final int TEST_FRAMES_PER_BUFFER = 128;
302         final double TEST_AMPLITUDE = 0.5;
303 
304         final short[] data = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BUFFER,
305                 TEST_SAMPLES_PER_FRAME, 1, TEST_AMPLITUDE);
306 
307         // -------- initialization --------------
308         AudioTrack track = createCustomAudioTrack(lowLatency);
309         assertTrue(TEST_NAME + " actual SR", track.getSampleRate() > 0);
310 
311         // -------- test --------------
312         // Play some audio for a few seconds.
313         int numSeconds = TEST_NUM_SECONDS;
314         int numBuffers = numSeconds * track.getSampleRate() / TEST_FRAMES_PER_BUFFER;
315         long framesWritten = 0;
316         boolean isPlaying = false;
317         for (int i = 0; i < numBuffers; i++) {
318             track.write(data, 0, data.length);
319             framesWritten += TEST_FRAMES_PER_BUFFER;
320             // prime the buffer a bit before playing
321             if (!isPlaying) {
322                 track.play();
323                 isPlaying = true;
324             }
325         }
326 
327         // Estimate the latency from the timestamp.
328         long timeWritten = System.nanoTime();
329         AudioTimestamp timestamp = new AudioTimestamp();
330         boolean result = track.getTimestamp(timestamp);
331         // FIXME failing LOW_LATENCY case! b/26413951
332         assertTrue(TEST_NAME + " did not get a timestamp, lowLatency = "
333                 + lowLatency, result);
334 
335         // Calculate when the last frame written is going to be rendered.
336         long framesPending = framesWritten - timestamp.framePosition;
337         long timeDelta = framesPending * NANOS_PER_SECOND / track.getSampleRate();
338         long timePresented = timestamp.nanoTime + timeDelta;
339         long latencyNanos = timePresented - timeWritten;
340         int latencyMillis = (int) (latencyNanos / NANOS_PER_MILLISECOND);
341         assertTrue(TEST_NAME + " got latencyMillis <= 0 == "
342                 + latencyMillis, latencyMillis > 0);
343 
344         // -------- cleanup --------------
345         track.release();
346 
347         return latencyMillis;
348     }
349 
350     // Compare output latency with and without FLAG_LOW_LATENCY.
testOutputLowLatency()351     public void testOutputLowLatency() throws Exception {
352         final String TEST_NAME = "testOutputLowLatency";
353 
354         int highLatencyMillis = checkOutputLowLatency(false);
355         log(TEST_NAME, "High latency = " + highLatencyMillis + " msec");
356 
357         int lowLatencyMillis = checkOutputLowLatency(true);
358         log(TEST_NAME, "Low latency = " + lowLatencyMillis + " msec");
359 
360         // We are not guaranteed to get a FAST track. Some platforms
361         // do not even have a FastMixer. So just warn and not fail.
362         if (highLatencyMillis <= (lowLatencyMillis + 10)) {
363             logw(TEST_NAME, "high latency should be much higher, "
364                     + highLatencyMillis
365                     + " vs " + lowLatencyMillis);
366         }
367     }
368 
369     // Verify that no underruns when buffer is >= getMinBufferSize().
370     // Verify that we get underruns with buffer at smallest possible size.
testGetUnderrunCount()371     public void testGetUnderrunCount() throws Exception {
372         // constants for test
373         final String TEST_NAME = "testGetUnderrunCount";
374         final int TEST_SR = 44100;
375         final int TEST_SAMPLES_PER_FRAME = 2;
376         final int TEST_BYTES_PER_SAMPLE = 2;
377         final int TEST_NUM_SECONDS = 2;
378         final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
379         final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
380         final int TEST_MODE = AudioTrack.MODE_STREAM;
381         final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
382         final int TEST_FRAMES_PER_BUFFER = 256;
383         final int TEST_FRAMES_PER_BLIP = TEST_SR / 8;
384         final int TEST_CYCLES_PER_BLIP = 700 * TEST_FRAMES_PER_BLIP / TEST_SR;
385         final double TEST_AMPLITUDE = 0.5;
386 
387         final short[] data = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BUFFER,
388                 TEST_SAMPLES_PER_FRAME, 1, TEST_AMPLITUDE);
389         final short[] blip = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BLIP,
390                 TEST_SAMPLES_PER_FRAME, TEST_CYCLES_PER_BLIP, TEST_AMPLITUDE);
391 
392         // -------- initialization --------------
393         int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
394         // Start with buffer twice as large as needed.
395         AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
396                 minBuffSize * 2, TEST_MODE);
397 
398         // -------- test --------------
399         // Initial values
400         int bufferCapacity = track.getBufferCapacityInFrames();
401         int initialBufferSize = track.getBufferSizeInFrames();
402         int minBuffSizeInFrames = minBuffSize / (TEST_SAMPLES_PER_FRAME * TEST_BYTES_PER_SAMPLE);
403         assertTrue(TEST_NAME, bufferCapacity > 0);
404         assertTrue(TEST_NAME, initialBufferSize > 0);
405         assertTrue(TEST_NAME, initialBufferSize <= bufferCapacity);
406 
407         // Play with initial size.
408         int underrunCount1 = track.getUnderrunCount();
409         assertEquals(TEST_NAME + ": initially no underruns", 0, underrunCount1);
410 
411         // Prime the buffer.
412         while (track.write(data, 0, data.length) == data.length);
413 
414         // Start playing
415         track.play();
416         int numBuffers = TEST_SR / TEST_FRAMES_PER_BUFFER;
417         for (int i = 0; i < numBuffers; i++) {
418             track.write(data, 0, data.length);
419         }
420         int underrunCountBase = track.getUnderrunCount();
421         int numSeconds = TEST_NUM_SECONDS;
422         numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
423         track.write(blip, 0, blip.length);
424         for (int i = 0; i < numBuffers; i++) {
425             track.write(data, 0, data.length);
426         }
427         underrunCount1 = track.getUnderrunCount();
428         assertEquals(TEST_NAME + ": no more underruns after initial",
429                 underrunCountBase, underrunCount1);
430 
431         // Play with getMinBufferSize() size.
432         int resultMin = track.setBufferSizeInFrames(minBuffSizeInFrames);
433         assertTrue(TEST_NAME + ": set minBuff, >", resultMin > 0);
434         assertTrue(TEST_NAME + ": set minBuff, <=", resultMin <= initialBufferSize);
435         track.write(blip, 0, blip.length);
436         for (int i = 0; i < numBuffers; i++) {
437             track.write(data, 0, data.length);
438         }
439         track.write(blip, 0, blip.length);
440         underrunCount1 = track.getUnderrunCount();
441         assertEquals(TEST_NAME + ": no more underruns at min", underrunCountBase, underrunCount1);
442 
443         // Play with ridiculously small size. We want to get underruns so we know that an app
444         // can get to the edge of underrunning.
445         int resultZero = track.setBufferSizeInFrames(0);
446         assertTrue(TEST_NAME + ": should return > 0, got " + resultZero, resultZero > 0);
447         assertTrue(TEST_NAME + ": zero size < original", resultZero < initialBufferSize);
448         numSeconds = TEST_NUM_SECONDS / 2; // cuz test takes longer when underflowing
449         numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
450         // Play for a few seconds or until we get some new underruns.
451         for (int i = 0; (i < numBuffers) && ((underrunCount1 - underrunCountBase) < 10); i++) {
452             track.write(data, 0, data.length);
453             underrunCount1 = track.getUnderrunCount();
454         }
455         assertTrue(TEST_NAME + ": underruns at zero", underrunCount1 > underrunCountBase);
456         int underrunCount2 = underrunCount1;
457         // Play for a few seconds or until we get some new underruns.
458         for (int i = 0; (i < numBuffers) && ((underrunCount2 - underrunCount1) < 10); i++) {
459             track.write(data, 0, data.length);
460             underrunCount2 = track.getUnderrunCount();
461         }
462         assertTrue(TEST_NAME + ": underruns still accumulating", underrunCount2 > underrunCount1);
463 
464         // Restore buffer to good size
465         numSeconds = TEST_NUM_SECONDS;
466         numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
467         int resultMax = track.setBufferSizeInFrames(bufferCapacity);
468         track.write(blip, 0, blip.length);
469         for (int i = 0; i < numBuffers; i++) {
470             track.write(data, 0, data.length);
471         }
472         // Should have stopped by now.
473         underrunCount1 = track.getUnderrunCount();
474         track.write(blip, 0, blip.length);
475         for (int i = 0; i < numBuffers; i++) {
476             track.write(data, 0, data.length);
477         }
478         // Counts should match.
479         underrunCount2 = track.getUnderrunCount();
480         assertEquals(TEST_NAME + ": underruns should stop happening",
481                 underrunCount1, underrunCount2);
482 
483         // -------- tear down --------------
484         track.release();
485     }
486 
487     // Verify that we get underruns if we stop writing to the buffer.
testGetUnderrunCountSleep()488     public void testGetUnderrunCountSleep() throws Exception {
489         // constants for test
490         final String TEST_NAME = "testGetUnderrunCountSleep";
491         final int TEST_SR = 48000;
492         final int TEST_SAMPLES_PER_FRAME = 2;
493         final int TEST_BYTES_PER_SAMPLE = 2;
494         final int TEST_NUM_SECONDS = 2;
495         final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
496         final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
497         final int TEST_MODE = AudioTrack.MODE_STREAM;
498         final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
499         final int TEST_FRAMES_PER_BUFFER = 256;
500         final int TEST_FRAMES_PER_BLIP = TEST_SR / 8;
501         final int TEST_CYCLES_PER_BLIP = 700 * TEST_FRAMES_PER_BLIP / TEST_SR;
502         final double TEST_AMPLITUDE = 0.5;
503 
504         final short[] data = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BUFFER,
505                 TEST_SAMPLES_PER_FRAME, 1, TEST_AMPLITUDE);
506         final short[] blip = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BLIP,
507                 TEST_SAMPLES_PER_FRAME, TEST_CYCLES_PER_BLIP, TEST_AMPLITUDE);
508 
509         // -------- initialization --------------
510         int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
511         // Start with buffer twice as large as needed.
512         AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
513                 minBuffSize * 2, TEST_MODE);
514 
515         // -------- test --------------
516         // Initial values
517         int minBuffSizeInFrames = minBuffSize / (TEST_SAMPLES_PER_FRAME * TEST_BYTES_PER_SAMPLE);
518 
519         int underrunCount1 = track.getUnderrunCount();
520         assertEquals(TEST_NAME + ": initially no underruns", 0, underrunCount1);
521 
522         // Prime the buffer.
523         while (track.write(data, 0, data.length) == data.length);
524 
525         // Start playing
526         track.play();
527         int numBuffers = TEST_SR / TEST_FRAMES_PER_BUFFER;
528         for (int i = 0; i < numBuffers; i++) {
529             track.write(data, 0, data.length);
530         }
531         int underrunCountBase = track.getUnderrunCount();
532         int numSeconds = TEST_NUM_SECONDS;
533         numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
534         track.write(blip, 0, blip.length);
535         for (int i = 0; i < numBuffers; i++) {
536             track.write(data, 0, data.length);
537         }
538         underrunCount1 = track.getUnderrunCount();
539         assertEquals(TEST_NAME + ": no more underruns after initial",
540                 underrunCountBase, underrunCount1);
541 
542         // Sleep and force underruns.
543         track.write(blip, 0, blip.length);
544         for (int i = 0; i < 10; i++) {
545             track.write(data, 0, data.length);
546             Thread.sleep(500);  // ========================= SLEEP! ===========
547         }
548         track.write(blip, 0, blip.length);
549         underrunCount1 = track.getUnderrunCount();
550         assertTrue(TEST_NAME + ": expect underruns after sleep, #ur="
551                 + underrunCount1,
552                 underrunCountBase < underrunCount1);
553 
554         track.write(blip, 0, blip.length);
555         for (int i = 0; i < numBuffers; i++) {
556             track.write(data, 0, data.length);
557         }
558 
559         // Should have stopped by now.
560         underrunCount1 = track.getUnderrunCount();
561         track.write(blip, 0, blip.length);
562         for (int i = 0; i < numBuffers; i++) {
563             track.write(data, 0, data.length);
564         }
565         // Counts should match.
566         int underrunCount2 = track.getUnderrunCount();
567         assertEquals(TEST_NAME + ": underruns should stop happening",
568                 underrunCount1, underrunCount2);
569 
570         // -------- tear down --------------
571         track.release();
572     }
573 
574     static class TrackBufferSizeChecker {
575         private final static String TEST_NAME = "testTrackBufferSize";
576         private final static int TEST_SR = 48000;
577         private final static int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
578         private final static int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
579         private final static int TEST_MODE = AudioTrack.MODE_STREAM;
580         private final static int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
581         private final static int FRAME_SIZE = 2 * 2; // stereo 16-bit PCM
582 
getFrameSize()583         public static int getFrameSize() {
584             return FRAME_SIZE;
585         }
586 
getMinBufferSize()587         public static int getMinBufferSize() {
588             return AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
589         }
590 
createAudioTrack(int bufferSize)591         public static AudioTrack createAudioTrack(int bufferSize) {
592             return new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
593                 bufferSize, TEST_MODE);
594         }
595 
checkBadSize(int bufferSize)596         public static void checkBadSize(int bufferSize) {
597             AudioTrack track = null;
598             try {
599                 track = TrackBufferSizeChecker.createAudioTrack(bufferSize);
600                 assertTrue(TEST_NAME + ": should not have survived size " + bufferSize, false);
601             } catch(IllegalArgumentException e) {
602                 // expected
603             } finally {
604                 if (track != null) {
605                     track.release();
606                 }
607             }
608         }
609 
checkSmallSize(int bufferSize)610         public static void checkSmallSize(int bufferSize) {
611             AudioTrack track = null;
612             try {
613                 track = TrackBufferSizeChecker.createAudioTrack(bufferSize);
614                 assertEquals(TEST_NAME + ": should still be initialized with small size " + bufferSize,
615                             AudioTrack.STATE_INITIALIZED, track.getState());
616             } finally {
617                 if (track != null) {
618                     track.release();
619                 }
620             }
621         }
622     }
623 
624     /**
625      * Test various values for bufferSizeInBytes.
626      *
627      * According to the latest documentation, any positive bufferSize that is a multiple
628      * of the frameSize is legal. Small sizes will be rounded up to the minimum size.
629      *
630      * Negative sizes, zero, or any non-multiple of the frameSize is illegal.
631      *
632      * @throws Exception
633      */
testTrackBufferSize()634     public void testTrackBufferSize() throws Exception {
635         TrackBufferSizeChecker.checkBadSize(0);
636         TrackBufferSizeChecker.checkBadSize(17);
637         TrackBufferSizeChecker.checkBadSize(18);
638         TrackBufferSizeChecker.checkBadSize(-9);
639         int frameSize = TrackBufferSizeChecker.getFrameSize();
640         TrackBufferSizeChecker.checkBadSize(-4 * frameSize);
641         for (int i = 1; i < 8; i++) {
642             TrackBufferSizeChecker.checkSmallSize(i * frameSize);
643             TrackBufferSizeChecker.checkBadSize(3 + (i * frameSize));
644         }
645     }
646 }
647