Move MCR CryptoException handling to top-level render() method

Handling for `MediaCodec.CryptoException` was originally added only
around calls to `MediaCodec.queueSecureInputBuffer` and
`queueInputBuffer` (because these are the only methods that can throw
this exception). When asynchronous interaction with `MediaCodec` was
added in <unknown commit>, exceptions from `MediaCodec` started being stored
and bubbled out of **later** interactions with `MediaCodecAdapter`. This
means that `MediaCodecRenderer` can now see `CryptoException` thrown
from a different method, like
`MediaCodecAdapter.dequeueInputBufferIndex()`, and this ends up missing
the `catch (CryptoException)` code in `MediaCodecRenderer`. This results
in an "unexpected runtime error" stack trace like [A].

This change fixes the stack trace to:
1. Make it a "renderer exception" instead of "unexpected runtime error"
2. Include the correct DRM error code -> `@PlaybackException.ErrorCode`
   mapping.

You can see the corrected stack trace below [B].

-----

[A] (synthesized from manually throwing a `CryptoException` from
`AsynchronousMediaCodecBufferEnqueuer#doQueueSecureInputBuffer`)

```
playerFailed [eventTime=11.56, mediaPos=10.35, window=0, period=0, errorCode=ERROR_CODE_UNSPECIFIED
  androidx.media3.exoplayer.ExoPlaybackException: Unexpected runtime error
      at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:729)
      at android.os.Handler.dispatchMessage(Handler.java:103)
      at android.os.Looper.loopOnce(Looper.java:232)
      at android.os.Looper.loop(Looper.java:317)
      at android.os.HandlerThread.run(HandlerThread.java:85)
  Caused by: android.media.MediaCodec$CryptoException: Test error message
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doQueueSecureInputBuffer(AsynchronousMediaCodecBufferEnqueuer.java:232)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doHandleMessage(AsynchronousMediaCodecBufferEnqueuer.java:196)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.access$000(AsynchronousMediaCodecBufferEnqueuer.java:47)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer$1.handleMessage(AsynchronousMediaCodecBufferEnqueuer.java:93)
      at android.os.Handler.dispatchMessage(Handler.java:107)
      at android.os.Looper.loopOnce(Looper.java:232) 
      at android.os.Looper.loop(Looper.java:317) 
      at android.os.HandlerThread.run(HandlerThread.java:85) 
```

[B]

```
Playback error
  androidx.media3.exoplayer.ExoPlaybackException: MediaCodecAudioRenderer error, index=1, format=Format(0, null, null, audio/mp4a-latm, mp4a.40.2, 134359, en, [-1, -1, -1.0, null], [2, 44100]), format_supported=YES
      at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:649)
      at android.os.Handler.dispatchMessage(Handler.java:103)
      at android.os.Looper.loopOnce(Looper.java:232)
      at android.os.Looper.loop(Looper.java:317)
      at android.os.HandlerThread.run(HandlerThread.java:85)
  Caused by: android.media.MediaCodec$CryptoException: Test error message
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doQueueSecureInputBuffer(AsynchronousMediaCodecBufferEnqueuer.java:232)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doHandleMessage(AsynchronousMediaCodecBufferEnqueuer.java:196)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.access$000(AsynchronousMediaCodecBufferEnqueuer.java:47)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer$1.handleMessage(AsynchronousMediaCodecBufferEnqueuer.java:93)
      at android.os.Handler.dispatchMessage(Handler.java:107)
      at android.os.Looper.loopOnce(Looper.java:232) 
      at android.os.Looper.loop(Looper.java:317) 
      at android.os.HandlerThread.run(HandlerThread.java:85) 
```

PiperOrigin-RevId: 670951229
This commit is contained in:
ibaker 2024-09-04 06:41:52 -07:00 committed by Copybara-Service
parent e27c7d5d45
commit 0933f561b7
3 changed files with 67 additions and 34 deletions

View File

@ -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:

View File

@ -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,7 +1393,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
processEndOfStream();
return false;
}
try {
if (codecNeedsEosPropagation) {
// Do nothing.
} else {
@ -1404,10 +1405,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
resetInputBuffer();
}
} catch (CryptoException e) {
throw createRendererException(
e, inputFormat, Util.getErrorCodeForMediaDrmErrorCode(e.getErrorCode()));
}
return false;
}
@ -1463,7 +1460,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
onQueueInputBuffer(buffer);
int flags = getCodecBufferFlags(buffer);
try {
if (bufferEncrypted) {
checkNotNull(codec)
.queueSecureInputBuffer(
@ -1477,10 +1473,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
presentationTimeUs,
flags);
}
} catch (CryptoException e) {
throw createRendererException(
e, inputFormat, Util.getErrorCodeForMediaDrmErrorCode(e.getErrorCode()));
}
resetInputBuffer();
codecReceivedBuffers = true;

View File

@ -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<FakeSampleStream.FakeSampleStreamItem> sampleListBuilder =
ImmutableList.builder();