mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
Add a test for MediaCodecRenderer handling of IllegalStateException
This is a regression test for the bug introduced inbb9ff30c3awhich was manually spotted and fixed in0d2bf49d6a. Reverting the fix causes this test to fail. This test is a bit hacky because we have to munge the stack trace of the `IllegalStateException` to make it look like it was thrown from inside `MediaCodec`. We deliberately do this 'badly' (e.g. using `fakeMethod`) to avoid a future reader being confused by a fake-but-plausible stack trace. PiperOrigin-RevId: 652820878
This commit is contained in:
parent
d4c6e39dfb
commit
99679645fc
1 changed files with 185 additions and 1 deletions
|
|
@ -18,6 +18,8 @@ package androidx.media3.exoplayer.mediacodec;
|
|||
import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION;
|
||||
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
|
||||
import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
|
|
@ -26,14 +28,20 @@ import static org.mockito.ArgumentMatchers.eq;
|
|||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.decoder.CryptoInfo;
|
||||
import androidx.media3.exoplayer.DecoderReuseEvaluation;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.RendererCapabilities;
|
||||
|
|
@ -46,7 +54,9 @@ import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
|||
import androidx.media3.test.utils.FakeSampleStream;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
|
@ -462,6 +472,59 @@ public class MediaCodecRendererTest {
|
|||
inOrder, renderer, /* presentationTimeUs= */ 500, /* isDecodeOnly= */ false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void render_wrapsIllegalStateExceptionFromMediaCodecInExoPlaybackException()
|
||||
throws Exception {
|
||||
MediaCodecAdapter.Factory throwingMediaCodecAdapterFactory =
|
||||
new ThrowingMediaCodecAdapter.Factory(
|
||||
() -> {
|
||||
IllegalStateException ise = new IllegalStateException("ISE from inside MediaCodec");
|
||||
StackTraceElement[] stackTrace = ise.getStackTrace();
|
||||
stackTrace[0] =
|
||||
new StackTraceElement(
|
||||
"android.media.MediaCodec",
|
||||
"fakeMethod",
|
||||
stackTrace[0].getFileName(),
|
||||
stackTrace[0].getLineNumber());
|
||||
ise.setStackTrace(stackTrace);
|
||||
return ise;
|
||||
});
|
||||
TestRenderer renderer = new TestRenderer(throwingMediaCodecAdapterFactory);
|
||||
renderer.init(/* index= */ 0, PlayerId.UNSET, Clock.DEFAULT);
|
||||
Format format =
|
||||
new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).setAverageBitrate(1000).build();
|
||||
FakeSampleStream fakeSampleStream =
|
||||
createFakeSampleStream(format, /* sampleTimesUs...= */ 0, 100, 200, 300, 400, 500);
|
||||
MediaSource.MediaPeriodId mediaPeriodId = new MediaSource.MediaPeriodId(new Object());
|
||||
renderer.enable(
|
||||
RendererConfiguration.DEFAULT,
|
||||
new Format[] {format},
|
||||
fakeSampleStream,
|
||||
/* positionUs= */ 0,
|
||||
/* joining= */ false,
|
||||
/* mayRenderStartOfStream= */ true,
|
||||
/* startPositionUs= */ 400,
|
||||
/* offsetUs= */ 0,
|
||||
mediaPeriodId);
|
||||
renderer.start();
|
||||
|
||||
ExoPlaybackException playbackException =
|
||||
assertThrows(
|
||||
ExoPlaybackException.class,
|
||||
() -> renderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime()));
|
||||
|
||||
assertThat(playbackException.type).isEqualTo(ExoPlaybackException.TYPE_RENDERER);
|
||||
assertThat(playbackException)
|
||||
.hasCauseThat()
|
||||
.hasCauseThat()
|
||||
.isInstanceOf(IllegalStateException.class);
|
||||
assertThat(playbackException)
|
||||
.hasCauseThat()
|
||||
.hasCauseThat()
|
||||
.hasMessageThat()
|
||||
.contains("ISE from inside MediaCodec");
|
||||
}
|
||||
|
||||
private FakeSampleStream createFakeSampleStream(Format format, long... sampleTimesUs) {
|
||||
ImmutableList.Builder<FakeSampleStream.FakeSampleStreamItem> sampleListBuilder =
|
||||
ImmutableList.builder();
|
||||
|
|
@ -484,9 +547,13 @@ public class MediaCodecRendererTest {
|
|||
private static class TestRenderer extends MediaCodecRenderer {
|
||||
|
||||
public TestRenderer() {
|
||||
this(MediaCodecAdapter.Factory.getDefault(ApplicationProvider.getApplicationContext()));
|
||||
}
|
||||
|
||||
public TestRenderer(MediaCodecAdapter.Factory mediaCodecAdapterFactory) {
|
||||
super(
|
||||
C.TRACK_TYPE_AUDIO,
|
||||
MediaCodecAdapter.Factory.getDefault(ApplicationProvider.getApplicationContext()),
|
||||
mediaCodecAdapterFactory,
|
||||
/* mediaCodecSelector= */ (mimeType, requiresSecureDecoder, requiresTunnelingDecoder) ->
|
||||
Collections.singletonList(
|
||||
MediaCodecInfo.newInstance(
|
||||
|
|
@ -570,6 +637,123 @@ public class MediaCodecRendererTest {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link MediaCodecAdapter} that throws a pre-specified exception from every decoding-related
|
||||
* interaction.
|
||||
*/
|
||||
private static class ThrowingMediaCodecAdapter implements MediaCodecAdapter {
|
||||
|
||||
public static class Factory implements MediaCodecAdapter.Factory {
|
||||
|
||||
private final Supplier<RuntimeException> exceptionSupplier;
|
||||
|
||||
public Factory(Supplier<RuntimeException> exceptionSupplier) {
|
||||
this.exceptionSupplier = exceptionSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException {
|
||||
return new ThrowingMediaCodecAdapter(exceptionSupplier);
|
||||
}
|
||||
}
|
||||
|
||||
private final Supplier<RuntimeException> exceptionSupplier;
|
||||
|
||||
private ThrowingMediaCodecAdapter(Supplier<RuntimeException> exceptionSupplier) {
|
||||
this.exceptionSupplier = exceptionSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int dequeueInputBufferIndex() {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaFormat getOutputFormat() {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ByteBuffer getInputBuffer(int index) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ByteBuffer getOutputBuffer(int index) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueInputBuffer(
|
||||
int index, int offset, int size, long presentationTimeUs, int flags) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueSecureInputBuffer(
|
||||
int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseOutputBuffer(int index, boolean render) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseOutputBuffer(int index, long renderTimeStampNs) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {}
|
||||
|
||||
@Override
|
||||
public void setOnFrameRenderedListener(OnFrameRenderedListener listener, Handler handler) {}
|
||||
|
||||
@Override
|
||||
public boolean registerOnBufferAvailableListener(OnBufferAvailableListener listener) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutputSurface(Surface surface) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameters(Bundle params) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoScalingMode(@C.VideoScalingMode int scalingMode) {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsReconfiguration() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistableBundle getMetrics() {
|
||||
return new PersistableBundle();
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyProcessOutputBufferDecodeOnly(
|
||||
InOrder inOrder, MediaCodecRenderer renderer, long presentationTimeUs, boolean isDecodeOnly)
|
||||
throws Exception {
|
||||
|
|
|
|||
Loading…
Reference in a new issue