3131import java .nio .ByteBuffer ;
3232import java .nio .ByteOrder ;
3333import java .util .ArrayList ;
34+ import java .util .concurrent .atomic .AtomicBoolean ;
3435import java .util .concurrent .atomic .AtomicInteger ;
36+ import java .util .concurrent .atomic .AtomicReference ;
3537import org .junit .Test ;
3638import org .junit .runner .RunWith ;
3739
@@ -43,43 +45,47 @@ public class DefaultAudioSinkTest {
4345 @ SdkSuppress (minSdkVersion = 24 ) // TODO: b/399130330 - Debug why this fails on API 23.
4446 public void audioTrackExceedsSharedMemory_retriesUntilOngoingReleasesAreDone () throws Exception {
4547 Context context = ApplicationProvider .getApplicationContext ();
46- getInstrumentation ()
47- .runOnMainSync (
48- () -> {
49- // Create audio sinks in parallel until we exceed the device's shared audio memory.
50- ArrayList <DefaultAudioSink > audioSinks = new ArrayList <>();
51- while (true ) {
52- DefaultAudioSink audioSink = new DefaultAudioSink .Builder (context ).build ();
53- audioSinks .add (audioSink );
54- try {
55- configureAudioSinkAndFeedData (audioSink );
56- } catch (Exception e ) {
57- // Expected to happen once we reached the shared audio memory limit of the device.
58- break ;
59- }
60- }
61-
62- // Trigger release of one sink and immediately try the failed sink again. This should
63- // now succeed even if the sink is released asynchronously.
64- audioSinks .get (0 ).flush ();
65- audioSinks .get (0 ).release ();
66- try {
67- configureAudioSinkAndFeedData (getLast (audioSinks ));
68- } catch (Exception e ) {
69- throw new IllegalStateException (e );
70- }
48+ // Create audio sinks in parallel until we exceed the device's shared audio memory.
49+ ArrayList <DefaultAudioSink > audioSinks = new ArrayList <>();
50+ while (true ) {
51+ runOnMainSync (
52+ () -> {
53+ DefaultAudioSink audioSink = new DefaultAudioSink .Builder (context ).build ();
54+ audioSinks .add (audioSink );
55+ });
56+ try {
57+ configureAudioSinkAndFeedData (getLast (audioSinks ));
58+ } catch (Exception e ) {
59+ // Expected to happen once we reached the shared audio memory limit of the device.
60+ break ;
61+ }
62+ }
63+ // Trigger release of one sink and immediately try the failed sink again. This should
64+ // now succeed even if the sink is released asynchronously.
65+ runOnMainSync (
66+ () -> {
67+ audioSinks .get (0 ).flush ();
68+ audioSinks .get (0 ).release ();
69+ });
70+ try {
71+ configureAudioSinkAndFeedData (getLast (audioSinks ));
72+ } catch (Exception e ) {
73+ throw new IllegalStateException (e );
74+ }
7175
72- // Clean-up
73- for (int i = 1 ; i < audioSinks .size (); i ++) {
74- audioSinks .get (i ).flush ();
75- audioSinks .get (i ).release ();
76- }
77- });
76+ // Clean-up
77+ runOnMainSync (
78+ () -> {
79+ for (int i = 1 ; i < audioSinks .size (); i ++) {
80+ audioSinks .get (i ).flush ();
81+ audioSinks .get (i ).release ();
82+ }
83+ });
7884 }
7985
8086 @ Test
8187 @ SdkSuppress (minSdkVersion = 24 ) // The test depends on AudioTrack#getUnderrunCount() (API 24+).
82- public void audioTrackUnderruns_callsOnUnderrun () throws InterruptedException {
88+ public void audioTrackUnderruns_callsOnUnderrun () throws Exception {
8389 AtomicInteger underrunCount = new AtomicInteger ();
8490 DefaultAudioSink sink =
8591 new DefaultAudioSink .Builder (ApplicationProvider .getApplicationContext ()).build ();
@@ -113,54 +119,79 @@ public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}
113119 sampleCountToDurationUs (/* sampleCount= */ 25 , /* sampleRate= */ 44_100 );
114120 ByteBuffer smallBuffer = ByteBuffer .allocateDirect (50 ).order (ByteOrder .nativeOrder ());
115121
122+ runOnMainSync (
123+ () -> {
124+ try {
125+ // Set buffer size of ~1.1ms. The tiny size helps cause an underrun.
126+ sink .configure (format , /* specifiedBufferSize= */ 100 , /* outputChannels= */ null );
127+
128+ // Prime AudioTrack with buffer larger than start threshold. Otherwise, AudioTrack
129+ // won't start playing.
130+ sink .handleBuffer (
131+ bigBuffer , /* presentationTimeUs= */ 0 , /* encodedAccessUnitCount= */ 1 );
132+ sink .play ();
133+ // Sleep until AudioTrack starts running out of queued samples.
134+ Thread .sleep (usToMs (bigBufferDurationUs ));
135+ for (int i = 0 ; i < 5 ; i ++) {
136+ smallBuffer .rewind ();
137+ // Queue small buffer so that sink buffer is never filled up.
138+ sink .handleBuffer (
139+ smallBuffer ,
140+ /* presentationTimeUs= */ bigBufferDurationUs + smallBufferDurationUs * i ,
141+ /* encodedAccessUnitCount= */ 1 );
142+ // Add additional latency so loop can never fill up sink buffer quickly enough.
143+ Thread .sleep (20 );
144+ }
145+ } catch (Exception e ) {
146+ throw new RuntimeException (e );
147+ }
148+ });
149+
150+ assertThat (underrunCount .get ()).isGreaterThan (0 );
151+ }
152+
153+ private void configureAudioSinkAndFeedData (DefaultAudioSink audioSink ) throws Exception {
154+ ByteBuffer buffer = ByteBuffer .allocateDirect (8000 ).order (ByteOrder .nativeOrder ());
155+ runOnMainSync (
156+ () -> {
157+ Format format =
158+ new Format .Builder ()
159+ .setSampleMimeType (MimeTypes .AUDIO_RAW )
160+ .setPcmEncoding (C .ENCODING_PCM_16BIT )
161+ .setChannelCount (2 )
162+ .setSampleRate (44_100 )
163+ .build ();
164+ audioSink .configure (
165+ format , /* specifiedBufferSize= */ 2_000_000 , /* outputChannels= */ null );
166+ audioSink .play ();
167+ });
168+ AtomicBoolean handledBuffer = new AtomicBoolean ();
169+ while (!handledBuffer .get ()) {
170+ runOnMainSync (
171+ () ->
172+ handledBuffer .set (
173+ audioSink .handleBuffer (
174+ buffer , /* presentationTimeUs= */ 0 , /* encodedAccessUnitCount= */ 1 )));
175+ }
176+ }
177+
178+ private static void runOnMainSync (ThrowingRunnable runnable ) throws Exception {
179+ AtomicReference <Exception > exceptionOnMain = new AtomicReference <>();
116180 getInstrumentation ()
117181 .runOnMainSync (
118182 () -> {
119183 try {
120- // Set buffer size of ~1.1ms. The tiny size helps cause an underrun.
121- sink .configure (format , /* specifiedBufferSize= */ 100 , /* outputChannels= */ null );
122-
123- // Prime AudioTrack with buffer larger than start threshold. Otherwise, AudioTrack
124- // won't start playing.
125- sink .handleBuffer (
126- bigBuffer , /* presentationTimeUs= */ 0 , /* encodedAccessUnitCount= */ 1 );
127- sink .play ();
128- // Sleep until AudioTrack starts running out of queued samples.
129- Thread .sleep (usToMs (bigBufferDurationUs ));
130- for (int i = 0 ; i < 5 ; i ++) {
131- smallBuffer .rewind ();
132- // Queue small buffer so that sink buffer is never filled up.
133- sink .handleBuffer (
134- smallBuffer ,
135- /* presentationTimeUs= */ bigBufferDurationUs + smallBufferDurationUs * i ,
136- /* encodedAccessUnitCount= */ 1 );
137- // Add additional latency so loop can never fill up sink buffer quickly enough.
138- Thread .sleep (20 );
139- }
184+ runnable .run ();
140185 } catch (Exception e ) {
141- throw new RuntimeException (e );
186+ exceptionOnMain . set (e );
142187 }
143188 });
144-
145- assertThat (underrunCount .get ()).isGreaterThan (0 );
189+ if (exceptionOnMain .get () != null ) {
190+ throw exceptionOnMain .get ();
191+ }
146192 }
147193
148- private void configureAudioSinkAndFeedData (DefaultAudioSink audioSink ) throws Exception {
149- Format format =
150- new Format .Builder ()
151- .setSampleMimeType (MimeTypes .AUDIO_RAW )
152- .setPcmEncoding (C .ENCODING_PCM_16BIT )
153- .setChannelCount (2 )
154- .setSampleRate (44_100 )
155- .build ();
156- audioSink .configure (format , /* specifiedBufferSize= */ 2_000_000 , /* outputChannels= */ null );
157- audioSink .play ();
158- ByteBuffer buffer = ByteBuffer .allocateDirect (8000 ).order (ByteOrder .nativeOrder ());
159- boolean handledBuffer = false ;
160- while (!handledBuffer ) {
161- handledBuffer =
162- audioSink .handleBuffer (
163- buffer , /* presentationTimeUs= */ 0 , /* encodedAccessUnitCount= */ 1 );
164- }
194+ private interface ThrowingRunnable {
195+ void run () throws Exception ;
165196 }
166197}
0 commit comments