From fa4cc7c65c4db113bea69ec69475b61b3df874b9 Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Wed, 15 Jan 2025 06:12:32 -0800 Subject: [PATCH] Reduce flakiness for MediaCodecAudioRendererTests past SDK 30 PiperOrigin-RevId: 715770321 --- .../audio/MediaCodecAudioRendererTest.java | 110 ++++++++++++------ .../video/MediaCodecVideoRendererTest.java | 22 ++-- 2 files changed, 85 insertions(+), 47 deletions(-) diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/MediaCodecAudioRendererTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/MediaCodecAudioRendererTest.java index 769f9ba26a..d6d819f67f 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/MediaCodecAudioRendererTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/MediaCodecAudioRendererTest.java @@ -33,6 +33,7 @@ import static org.robolectric.Shadows.shadowOf; import android.media.MediaFormat; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.SystemClock; import androidx.annotation.Nullable; @@ -49,6 +50,7 @@ import androidx.media3.exoplayer.RendererConfiguration; import androidx.media3.exoplayer.analytics.PlayerId; import androidx.media3.exoplayer.drm.DrmSessionEventListener; import androidx.media3.exoplayer.drm.DrmSessionManager; +import androidx.media3.exoplayer.mediacodec.DefaultMediaCodecAdapterFactory; import androidx.media3.exoplayer.mediacodec.MediaCodecInfo; import androidx.media3.exoplayer.mediacodec.MediaCodecSelector; import androidx.media3.exoplayer.source.MediaSource; @@ -72,7 +74,6 @@ import org.mockito.junit.MockitoRule; import org.robolectric.annotation.Config; /** Unit tests for {@link MediaCodecAudioRenderer} */ -@Config(sdk = 30) // TODO: b/382017156 - Remove this when the tests pass on API 31+. @RunWith(AndroidJUnit4.class) public class MediaCodecAudioRendererTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @@ -118,6 +119,8 @@ public class MediaCodecAudioRendererTest { private MediaCodecAudioRenderer mediaCodecAudioRenderer; private MediaCodecSelector mediaCodecSelector; + @Nullable private HandlerThread callbackThread; + @Nullable private HandlerThread queueingThread; @Mock private AudioSink audioSink; @Mock private AudioRendererEventListener audioRendererEventListener; @@ -154,10 +157,19 @@ public class MediaCodecAudioRendererTest { /* forceSecure= */ false)); Handler eventHandler = new Handler(Looper.getMainLooper()); - mediaCodecAudioRenderer = new MediaCodecAudioRenderer( ApplicationProvider.getApplicationContext(), + new DefaultMediaCodecAdapterFactory( + ApplicationProvider.getApplicationContext(), + () -> { + callbackThread = new HandlerThread("MCARTest:MediaCodecAsyncAdapter"); + return callbackThread; + }, + () -> { + queueingThread = new HandlerThread("MCARTest:MediaCodecQueueingThread"); + return queueingThread; + }), mediaCodecSelector, /* enableDecoderFallback= */ false, eventHandler, @@ -169,7 +181,6 @@ public class MediaCodecAudioRendererTest { @Test public void render_configuresAudioSink_afterFormatChange() throws Exception { Format changedFormat = AUDIO_AAC.buildUpon().setSampleRate(48_000).setEncoderDelay(400).build(); - FakeSampleStream fakeSampleStream = new FakeSampleStream( new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), @@ -733,11 +744,11 @@ public class MediaCodecAudioRendererTest { /* initialFormat= */ AUDIO_AAC, ImmutableList.of( oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 50000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 100000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 150000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 200000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 250000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 50_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 100_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 150_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 200_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 250_000, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecAudioRenderer.enable( @@ -750,14 +761,16 @@ public class MediaCodecAudioRendererTest { /* startPositionUs= */ 0, /* offsetUs= */ 0, new MediaSource.MediaPeriodId(new Object())); - // Represents audio sink buffers being full when trying to write 150000 us sample. + // Represents audio sink buffers being full when trying to write 150_000 us sample. when(audioSink.handleBuffer( - any(), longThat(presentationTimeUs -> presentationTimeUs == 150000), anyInt())) + any(), longThat(presentationTimeUs -> presentationTimeUs == 150_000), anyInt())) .thenReturn(false); when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT); mediaCodecAudioRenderer.start(); - mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); - mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); + for (int i = 0; i < 10; i++) { + mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); + maybeIdleAsynchronousMediaCodecAdapterThreads(); + } long durationToProgressUs = mediaCodecAudioRenderer.getDurationToProgressUs( @@ -779,11 +792,11 @@ public class MediaCodecAudioRendererTest { /* initialFormat= */ AUDIO_AAC, ImmutableList.of( oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 50000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 100000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 150000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 200000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 250000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 50_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 100_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 150_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 200_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 250_000, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); PlaybackParameters playbackParametersWithDoubleSpeed = new PlaybackParameters(/* speed= */ 2.0f); @@ -798,14 +811,16 @@ public class MediaCodecAudioRendererTest { /* startPositionUs= */ 0, /* offsetUs= */ 0, new MediaSource.MediaPeriodId(new Object())); - // Represents audio sink buffers being full when trying to write 150000 us sample. + // Represents audio sink buffers being full when trying to write 150_000 us sample. when(audioSink.handleBuffer( - any(), longThat(presentationTimeUs -> presentationTimeUs == 150000), anyInt())) + any(), longThat(presentationTimeUs -> presentationTimeUs == 150_000), anyInt())) .thenReturn(false); when(audioSink.getPlaybackParameters()).thenReturn(playbackParametersWithDoubleSpeed); mediaCodecAudioRenderer.start(); - mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); - mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); + for (int i = 0; i < 10; i++) { + mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); + maybeIdleAsynchronousMediaCodecAdapterThreads(); + } long durationToProgressUs = mediaCodecAudioRenderer.getDurationToProgressUs( @@ -822,6 +837,16 @@ public class MediaCodecAudioRendererTest { mediaCodecAudioRenderer = new MediaCodecAudioRenderer( ApplicationProvider.getApplicationContext(), + new DefaultMediaCodecAdapterFactory( + ApplicationProvider.getApplicationContext(), + () -> { + callbackThread = new HandlerThread("MCARTest:MediaCodecAsyncAdapter"); + return callbackThread; + }, + () -> { + queueingThread = new HandlerThread("MCARTest:MediaCodecQueueingThread"); + return queueingThread; + }), mediaCodecSelector, /* enableDecoderFallback= */ false, /* eventHandler= */ new Handler(Looper.getMainLooper()), @@ -837,11 +862,11 @@ public class MediaCodecAudioRendererTest { /* initialFormat= */ AUDIO_AAC, ImmutableList.of( oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 50000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 100000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 150000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 200000, C.BUFFER_FLAG_KEY_FRAME), - oneByteSample(/* timeUs= */ 250000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 50_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 100_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 150_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 200_000, C.BUFFER_FLAG_KEY_FRAME), + oneByteSample(/* timeUs= */ 250_000, C.BUFFER_FLAG_KEY_FRAME), END_OF_STREAM_ITEM)); fakeSampleStream.writeData(/* startPositionUs= */ 0); mediaCodecAudioRenderer.enable( @@ -854,17 +879,19 @@ public class MediaCodecAudioRendererTest { /* startPositionUs= */ 0, /* offsetUs= */ 0, new MediaSource.MediaPeriodId(new Object())); - // Represents audio sink buffers being full when trying to write 150000 us sample. + // Represents audio sink buffers being full when trying to write 150_000 us sample. when(audioSink.handleBuffer( - any(), longThat(presentationTimeUs -> presentationTimeUs == 150000), anyInt())) + any(), longThat(presentationTimeUs -> presentationTimeUs == 150_000), anyInt())) .thenReturn(false); when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT); mediaCodecAudioRenderer.start(); - long rendererPositionElapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; - mediaCodecAudioRenderer.render(/* positionUs= */ 0, rendererPositionElapsedRealtimeUs); - mediaCodecAudioRenderer.render(/* positionUs= */ 0, rendererPositionElapsedRealtimeUs); + for (int i = 0; i < 10; i++) { + mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000); + maybeIdleAsynchronousMediaCodecAdapterThreads(); + } // Simulate playback progressing between render() and getDurationToProgressUs call + long rendererPositionElapsedRealtimeUs = fakeClock.elapsedRealtime() * 1000; fakeClock.advanceTime(/* timeDiffMs= */ 10); long durationToProgressUs = mediaCodecAudioRenderer.getDurationToProgressUs( @@ -916,8 +943,10 @@ public class MediaCodecAudioRendererTest { .thenReturn(false); when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT); mediaCodecAudioRenderer.start(); - mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000); - mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000); + for (int i = 0; i < 10; i++) { + mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); + maybeIdleAsynchronousMediaCodecAdapterThreads(); + } // Simulate a seek through resetPosition which should flush the audio sink. mediaCodecAudioRenderer.stop(); @@ -972,8 +1001,10 @@ public class MediaCodecAudioRendererTest { .thenReturn(false); when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT); mediaCodecAudioRenderer.start(); - mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000); - mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000); + for (int i = 0; i < 10; i++) { + mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); + maybeIdleAsynchronousMediaCodecAdapterThreads(); + } // Simulate a track reselection forcing a disable and causing a flush of the audio sink. mediaCodecAudioRenderer.stop(); @@ -985,6 +1016,15 @@ public class MediaCodecAudioRendererTest { assertThat(durationToProgressUs).isEqualTo(10_000L); } + private void maybeIdleAsynchronousMediaCodecAdapterThreads() { + if (queueingThread != null) { + shadowOf(queueingThread.getLooper()).idle(); + } + if (callbackThread != null) { + shadowOf(callbackThread.getLooper()).idle(); + } + } + private static Format getAudioSinkFormat(Format inputFormat) { return new Format.Builder() .setSampleMimeType(MimeTypes.AUDIO_RAW) diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java index 4691ef1b60..9c06b55938 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/MediaCodecVideoRendererTest.java @@ -91,7 +91,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; @@ -148,13 +147,12 @@ public class MediaCodecVideoRendererTest { /* forceDisableAdaptive= */ false, /* forceSecure= */ false); - private final AtomicReference callbackThread = new AtomicReference<>(); - private final AtomicReference queueingThread = new AtomicReference<>(); - private Looper testMainLooper; private Surface surface; private MediaCodecVideoRenderer mediaCodecVideoRenderer; private MediaCodecSelector mediaCodecSelector; + @Nullable private HandlerThread callbackThread; + @Nullable private HandlerThread queueingThread; @Nullable private Format currentOutputFormat; @Mock private VideoRendererEventListener eventListener; @@ -181,12 +179,12 @@ public class MediaCodecVideoRendererTest { new DefaultMediaCodecAdapterFactory( ApplicationProvider.getApplicationContext(), () -> { - callbackThread.set(new HandlerThread("MCVRTest:MediaCodecAsyncAdapter")); - return callbackThread.get(); + callbackThread = new HandlerThread("MCVRTest:MediaCodecAsyncAdapter"); + return callbackThread; }, () -> { - queueingThread.set(new HandlerThread("MCVRTest:MediaCodecQueueingThread")); - return queueingThread.get(); + queueingThread = new HandlerThread("MCVRTest:MediaCodecQueueingThread"); + return queueingThread; }), mediaCodecSelector, /* allowedJoiningTimeMs= */ 0, @@ -1612,11 +1610,11 @@ public class MediaCodecVideoRendererTest { } private void maybeIdleAsynchronousMediaCodecAdapterThreads() { - if (queueingThread.get() != null) { - shadowOf(queueingThread.get().getLooper()).idle(); + if (queueingThread != null) { + shadowOf(queueingThread.getLooper()).idle(); } - if (callbackThread.get() != null) { - shadowOf(callbackThread.get().getLooper()).idle(); + if (callbackThread != null) { + shadowOf(callbackThread.getLooper()).idle(); } }