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