diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 39924d8937..0bca120123 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,6 +4,9 @@ * Common Library: * ExoPlayer: + * Fix `MediaCodec.CryptoException` sometimes being reported as an + "unexpected runtime error" when `MediaCodec` is operated in asynchronous + mode (default behaviour on API 31+). * Transformer: * Track Selection: * Extractors: diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java index 0659032fed..fa05f5271c 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java @@ -35,7 +35,6 @@ import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaCodec.CodecException; -import android.media.MediaCodec.CryptoException; import android.media.MediaCrypto; import android.media.MediaCryptoException; import android.media.MediaFormat; @@ -883,6 +882,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { readSourceOmittingSampleData(FLAG_PEEK); } decoderCounters.ensureUpdated(); + } catch (MediaCodec.CryptoException e) { + throw createRendererException( + e, inputFormat, Util.getErrorCodeForMediaDrmErrorCode(e.getErrorCode())); } catch (IllegalStateException e) { if (isMediaCodecException(e)) { onCodecError(e); @@ -1391,22 +1393,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { processEndOfStream(); return false; } - try { - if (codecNeedsEosPropagation) { - // Do nothing. - } else { - codecReceivedEos = true; - codec.queueInputBuffer( - inputIndex, - /* offset= */ 0, - /* size= */ 0, - /* presentationTimeUs= */ 0, - MediaCodec.BUFFER_FLAG_END_OF_STREAM); - resetInputBuffer(); - } - } catch (CryptoException e) { - throw createRendererException( - e, inputFormat, Util.getErrorCodeForMediaDrmErrorCode(e.getErrorCode())); + if (codecNeedsEosPropagation) { + // Do nothing. + } else { + codecReceivedEos = true; + codec.queueInputBuffer( + inputIndex, + /* offset= */ 0, + /* size= */ 0, + /* presentationTimeUs= */ 0, + MediaCodec.BUFFER_FLAG_END_OF_STREAM); + resetInputBuffer(); } return false; } @@ -1463,23 +1460,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { onQueueInputBuffer(buffer); int flags = getCodecBufferFlags(buffer); - try { - if (bufferEncrypted) { - checkNotNull(codec) - .queueSecureInputBuffer( - inputIndex, /* offset= */ 0, buffer.cryptoInfo, presentationTimeUs, flags); - } else { - checkNotNull(codec) - .queueInputBuffer( - inputIndex, - /* offset= */ 0, - checkNotNull(buffer.data).limit(), - presentationTimeUs, - flags); - } - } catch (CryptoException e) { - throw createRendererException( - e, inputFormat, Util.getErrorCodeForMediaDrmErrorCode(e.getErrorCode())); + if (bufferEncrypted) { + checkNotNull(codec) + .queueSecureInputBuffer( + inputIndex, /* offset= */ 0, buffer.cryptoInfo, presentationTimeUs, flags); + } else { + checkNotNull(codec) + .queueInputBuffer( + inputIndex, + /* offset= */ 0, + checkNotNull(buffer.data).limit(), + presentationTimeUs, + flags); } resetInputBuffer(); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecRendererTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecRendererTest.java index e19f5e7edb..14f200776e 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecRendererTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecRendererTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.spy; import android.media.MediaCodec; import android.media.MediaCrypto; +import android.media.MediaDrm; import android.media.MediaFormat; import android.os.Bundle; import android.os.Handler; @@ -525,6 +526,43 @@ public class MediaCodecRendererTest { .contains("ISE from inside MediaCodec"); } + // b/347367307#comment6 + @Test + public void render_wrapsCryptoExceptionFromAnyMediaCodecMethod() throws Exception { + MediaCodecAdapter.Factory throwingMediaCodecAdapterFactory = + new ThrowingMediaCodecAdapter.Factory( + () -> + new MediaCodec.CryptoException( + MediaDrm.ErrorCodes.ERROR_INSUFFICIENT_OUTPUT_PROTECTION, "Test exception")); + 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().isInstanceOf(MediaCodec.CryptoException.class); + assertThat(playbackException).hasCauseThat().hasMessageThat().contains("Test exception"); + } + private FakeSampleStream createFakeSampleStream(Format format, long... sampleTimesUs) { ImmutableList.Builder sampleListBuilder = ImmutableList.builder();