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