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