Reduce flakiness for MediaCodecAudioRendererTests past SDK 30

PiperOrigin-RevId: 715770321
This commit is contained in:
michaelkatz 2025-01-15 06:12:32 -08:00 committed by Copybara-Service
parent 62341f31f9
commit fa4cc7c65c
2 changed files with 85 additions and 47 deletions

View File

@ -33,6 +33,7 @@ import static org.robolectric.Shadows.shadowOf;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -49,6 +50,7 @@ import androidx.media3.exoplayer.RendererConfiguration;
import androidx.media3.exoplayer.analytics.PlayerId; import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.drm.DrmSessionEventListener; import androidx.media3.exoplayer.drm.DrmSessionEventListener;
import androidx.media3.exoplayer.drm.DrmSessionManager; import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.mediacodec.DefaultMediaCodecAdapterFactory;
import androidx.media3.exoplayer.mediacodec.MediaCodecInfo; import androidx.media3.exoplayer.mediacodec.MediaCodecInfo;
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector; import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
@ -72,7 +74,6 @@ import org.mockito.junit.MockitoRule;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
/** Unit tests for {@link MediaCodecAudioRenderer} */ /** Unit tests for {@link MediaCodecAudioRenderer} */
@Config(sdk = 30) // TODO: b/382017156 - Remove this when the tests pass on API 31+.
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class MediaCodecAudioRendererTest { public class MediaCodecAudioRendererTest {
@Rule public final MockitoRule mockito = MockitoJUnit.rule(); @Rule public final MockitoRule mockito = MockitoJUnit.rule();
@ -118,6 +119,8 @@ public class MediaCodecAudioRendererTest {
private MediaCodecAudioRenderer mediaCodecAudioRenderer; private MediaCodecAudioRenderer mediaCodecAudioRenderer;
private MediaCodecSelector mediaCodecSelector; private MediaCodecSelector mediaCodecSelector;
@Nullable private HandlerThread callbackThread;
@Nullable private HandlerThread queueingThread;
@Mock private AudioSink audioSink; @Mock private AudioSink audioSink;
@Mock private AudioRendererEventListener audioRendererEventListener; @Mock private AudioRendererEventListener audioRendererEventListener;
@ -154,10 +157,19 @@ public class MediaCodecAudioRendererTest {
/* forceSecure= */ false)); /* forceSecure= */ false));
Handler eventHandler = new Handler(Looper.getMainLooper()); Handler eventHandler = new Handler(Looper.getMainLooper());
mediaCodecAudioRenderer = mediaCodecAudioRenderer =
new MediaCodecAudioRenderer( new MediaCodecAudioRenderer(
ApplicationProvider.getApplicationContext(), ApplicationProvider.getApplicationContext(),
new DefaultMediaCodecAdapterFactory(
ApplicationProvider.getApplicationContext(),
() -> {
callbackThread = new HandlerThread("MCARTest:MediaCodecAsyncAdapter");
return callbackThread;
},
() -> {
queueingThread = new HandlerThread("MCARTest:MediaCodecQueueingThread");
return queueingThread;
}),
mediaCodecSelector, mediaCodecSelector,
/* enableDecoderFallback= */ false, /* enableDecoderFallback= */ false,
eventHandler, eventHandler,
@ -169,7 +181,6 @@ public class MediaCodecAudioRendererTest {
@Test @Test
public void render_configuresAudioSink_afterFormatChange() throws Exception { public void render_configuresAudioSink_afterFormatChange() throws Exception {
Format changedFormat = AUDIO_AAC.buildUpon().setSampleRate(48_000).setEncoderDelay(400).build(); Format changedFormat = AUDIO_AAC.buildUpon().setSampleRate(48_000).setEncoderDelay(400).build();
FakeSampleStream fakeSampleStream = FakeSampleStream fakeSampleStream =
new FakeSampleStream( new FakeSampleStream(
new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024),
@ -733,11 +744,11 @@ public class MediaCodecAudioRendererTest {
/* initialFormat= */ AUDIO_AAC, /* initialFormat= */ AUDIO_AAC,
ImmutableList.of( ImmutableList.of(
oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 50000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 50_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 100000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 100_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 150000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 150_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 200000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 200_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 250000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 250_000, C.BUFFER_FLAG_KEY_FRAME),
END_OF_STREAM_ITEM)); END_OF_STREAM_ITEM));
fakeSampleStream.writeData(/* startPositionUs= */ 0); fakeSampleStream.writeData(/* startPositionUs= */ 0);
mediaCodecAudioRenderer.enable( mediaCodecAudioRenderer.enable(
@ -750,14 +761,16 @@ public class MediaCodecAudioRendererTest {
/* startPositionUs= */ 0, /* startPositionUs= */ 0,
/* offsetUs= */ 0, /* offsetUs= */ 0,
new MediaSource.MediaPeriodId(new Object())); 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( when(audioSink.handleBuffer(
any(), longThat(presentationTimeUs -> presentationTimeUs == 150000), anyInt())) any(), longThat(presentationTimeUs -> presentationTimeUs == 150_000), anyInt()))
.thenReturn(false); .thenReturn(false);
when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT); when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT);
mediaCodecAudioRenderer.start(); mediaCodecAudioRenderer.start();
mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); for (int i = 0; i < 10; i++) {
mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
maybeIdleAsynchronousMediaCodecAdapterThreads();
}
long durationToProgressUs = long durationToProgressUs =
mediaCodecAudioRenderer.getDurationToProgressUs( mediaCodecAudioRenderer.getDurationToProgressUs(
@ -779,11 +792,11 @@ public class MediaCodecAudioRendererTest {
/* initialFormat= */ AUDIO_AAC, /* initialFormat= */ AUDIO_AAC,
ImmutableList.of( ImmutableList.of(
oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 50000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 50_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 100000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 100_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 150000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 150_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 200000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 200_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 250000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 250_000, C.BUFFER_FLAG_KEY_FRAME),
END_OF_STREAM_ITEM)); END_OF_STREAM_ITEM));
PlaybackParameters playbackParametersWithDoubleSpeed = PlaybackParameters playbackParametersWithDoubleSpeed =
new PlaybackParameters(/* speed= */ 2.0f); new PlaybackParameters(/* speed= */ 2.0f);
@ -798,14 +811,16 @@ public class MediaCodecAudioRendererTest {
/* startPositionUs= */ 0, /* startPositionUs= */ 0,
/* offsetUs= */ 0, /* offsetUs= */ 0,
new MediaSource.MediaPeriodId(new Object())); 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( when(audioSink.handleBuffer(
any(), longThat(presentationTimeUs -> presentationTimeUs == 150000), anyInt())) any(), longThat(presentationTimeUs -> presentationTimeUs == 150_000), anyInt()))
.thenReturn(false); .thenReturn(false);
when(audioSink.getPlaybackParameters()).thenReturn(playbackParametersWithDoubleSpeed); when(audioSink.getPlaybackParameters()).thenReturn(playbackParametersWithDoubleSpeed);
mediaCodecAudioRenderer.start(); mediaCodecAudioRenderer.start();
mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); for (int i = 0; i < 10; i++) {
mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
maybeIdleAsynchronousMediaCodecAdapterThreads();
}
long durationToProgressUs = long durationToProgressUs =
mediaCodecAudioRenderer.getDurationToProgressUs( mediaCodecAudioRenderer.getDurationToProgressUs(
@ -822,6 +837,16 @@ public class MediaCodecAudioRendererTest {
mediaCodecAudioRenderer = mediaCodecAudioRenderer =
new MediaCodecAudioRenderer( new MediaCodecAudioRenderer(
ApplicationProvider.getApplicationContext(), ApplicationProvider.getApplicationContext(),
new DefaultMediaCodecAdapterFactory(
ApplicationProvider.getApplicationContext(),
() -> {
callbackThread = new HandlerThread("MCARTest:MediaCodecAsyncAdapter");
return callbackThread;
},
() -> {
queueingThread = new HandlerThread("MCARTest:MediaCodecQueueingThread");
return queueingThread;
}),
mediaCodecSelector, mediaCodecSelector,
/* enableDecoderFallback= */ false, /* enableDecoderFallback= */ false,
/* eventHandler= */ new Handler(Looper.getMainLooper()), /* eventHandler= */ new Handler(Looper.getMainLooper()),
@ -837,11 +862,11 @@ public class MediaCodecAudioRendererTest {
/* initialFormat= */ AUDIO_AAC, /* initialFormat= */ AUDIO_AAC,
ImmutableList.of( ImmutableList.of(
oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 50000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 50_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 100000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 100_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 150000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 150_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 200000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 200_000, C.BUFFER_FLAG_KEY_FRAME),
oneByteSample(/* timeUs= */ 250000, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 250_000, C.BUFFER_FLAG_KEY_FRAME),
END_OF_STREAM_ITEM)); END_OF_STREAM_ITEM));
fakeSampleStream.writeData(/* startPositionUs= */ 0); fakeSampleStream.writeData(/* startPositionUs= */ 0);
mediaCodecAudioRenderer.enable( mediaCodecAudioRenderer.enable(
@ -854,17 +879,19 @@ public class MediaCodecAudioRendererTest {
/* startPositionUs= */ 0, /* startPositionUs= */ 0,
/* offsetUs= */ 0, /* offsetUs= */ 0,
new MediaSource.MediaPeriodId(new Object())); 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( when(audioSink.handleBuffer(
any(), longThat(presentationTimeUs -> presentationTimeUs == 150000), anyInt())) any(), longThat(presentationTimeUs -> presentationTimeUs == 150_000), anyInt()))
.thenReturn(false); .thenReturn(false);
when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT); when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT);
mediaCodecAudioRenderer.start(); mediaCodecAudioRenderer.start();
long rendererPositionElapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; for (int i = 0; i < 10; i++) {
mediaCodecAudioRenderer.render(/* positionUs= */ 0, rendererPositionElapsedRealtimeUs); mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000);
mediaCodecAudioRenderer.render(/* positionUs= */ 0, rendererPositionElapsedRealtimeUs); maybeIdleAsynchronousMediaCodecAdapterThreads();
}
// Simulate playback progressing between render() and getDurationToProgressUs call // Simulate playback progressing between render() and getDurationToProgressUs call
long rendererPositionElapsedRealtimeUs = fakeClock.elapsedRealtime() * 1000;
fakeClock.advanceTime(/* timeDiffMs= */ 10); fakeClock.advanceTime(/* timeDiffMs= */ 10);
long durationToProgressUs = long durationToProgressUs =
mediaCodecAudioRenderer.getDurationToProgressUs( mediaCodecAudioRenderer.getDurationToProgressUs(
@ -916,8 +943,10 @@ public class MediaCodecAudioRendererTest {
.thenReturn(false); .thenReturn(false);
when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT); when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT);
mediaCodecAudioRenderer.start(); mediaCodecAudioRenderer.start();
mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000); for (int i = 0; i < 10; i++) {
mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000); mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
maybeIdleAsynchronousMediaCodecAdapterThreads();
}
// Simulate a seek through resetPosition which should flush the audio sink. // Simulate a seek through resetPosition which should flush the audio sink.
mediaCodecAudioRenderer.stop(); mediaCodecAudioRenderer.stop();
@ -972,8 +1001,10 @@ public class MediaCodecAudioRendererTest {
.thenReturn(false); .thenReturn(false);
when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT); when(audioSink.getPlaybackParameters()).thenReturn(PlaybackParameters.DEFAULT);
mediaCodecAudioRenderer.start(); mediaCodecAudioRenderer.start();
mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000); for (int i = 0; i < 10; i++) {
mediaCodecAudioRenderer.render(/* positionUs= */ 0, fakeClock.elapsedRealtime() * 1000); mediaCodecAudioRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
maybeIdleAsynchronousMediaCodecAdapterThreads();
}
// Simulate a track reselection forcing a disable and causing a flush of the audio sink. // Simulate a track reselection forcing a disable and causing a flush of the audio sink.
mediaCodecAudioRenderer.stop(); mediaCodecAudioRenderer.stop();
@ -985,6 +1016,15 @@ public class MediaCodecAudioRendererTest {
assertThat(durationToProgressUs).isEqualTo(10_000L); 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) { private static Format getAudioSinkFormat(Format inputFormat) {
return new Format.Builder() return new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_RAW) .setSampleMimeType(MimeTypes.AUDIO_RAW)

View File

@ -91,7 +91,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -148,13 +147,12 @@ public class MediaCodecVideoRendererTest {
/* forceDisableAdaptive= */ false, /* forceDisableAdaptive= */ false,
/* forceSecure= */ false); /* forceSecure= */ false);
private final AtomicReference<HandlerThread> callbackThread = new AtomicReference<>();
private final AtomicReference<HandlerThread> queueingThread = new AtomicReference<>();
private Looper testMainLooper; private Looper testMainLooper;
private Surface surface; private Surface surface;
private MediaCodecVideoRenderer mediaCodecVideoRenderer; private MediaCodecVideoRenderer mediaCodecVideoRenderer;
private MediaCodecSelector mediaCodecSelector; private MediaCodecSelector mediaCodecSelector;
@Nullable private HandlerThread callbackThread;
@Nullable private HandlerThread queueingThread;
@Nullable private Format currentOutputFormat; @Nullable private Format currentOutputFormat;
@Mock private VideoRendererEventListener eventListener; @Mock private VideoRendererEventListener eventListener;
@ -181,12 +179,12 @@ public class MediaCodecVideoRendererTest {
new DefaultMediaCodecAdapterFactory( new DefaultMediaCodecAdapterFactory(
ApplicationProvider.getApplicationContext(), ApplicationProvider.getApplicationContext(),
() -> { () -> {
callbackThread.set(new HandlerThread("MCVRTest:MediaCodecAsyncAdapter")); callbackThread = new HandlerThread("MCVRTest:MediaCodecAsyncAdapter");
return callbackThread.get(); return callbackThread;
}, },
() -> { () -> {
queueingThread.set(new HandlerThread("MCVRTest:MediaCodecQueueingThread")); queueingThread = new HandlerThread("MCVRTest:MediaCodecQueueingThread");
return queueingThread.get(); return queueingThread;
}), }),
mediaCodecSelector, mediaCodecSelector,
/* allowedJoiningTimeMs= */ 0, /* allowedJoiningTimeMs= */ 0,
@ -1612,11 +1610,11 @@ public class MediaCodecVideoRendererTest {
} }
private void maybeIdleAsynchronousMediaCodecAdapterThreads() { private void maybeIdleAsynchronousMediaCodecAdapterThreads() {
if (queueingThread.get() != null) { if (queueingThread != null) {
shadowOf(queueingThread.get().getLooper()).idle(); shadowOf(queueingThread.getLooper()).idle();
} }
if (callbackThread.get() != null) { if (callbackThread != null) {
shadowOf(callbackThread.get().getLooper()).idle(); shadowOf(callbackThread.getLooper()).idle();
} }
} }