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