From 3696076f0f08f8bcc9c32c0fa322ed48c42e8179 Mon Sep 17 00:00:00 2001 From: christosts Date: Fri, 10 Feb 2023 13:44:34 +0000 Subject: [PATCH] AsynchronousMediaCodecAdapter: surface queueing errors sooner The AsynchronousMediaCodecAdapter's queuing thread stores any exceptions raised by MediaCodec and re-throws them on the next call to queueInputBuffer()/queueSecureInputBuffer(). However, if MediaCodec raises and error while queueing, it goes into a failed state and does not announce available input buffers. If there is no input available input buffer, the MediaCodecRenderer will never call queueInputBuffer()/queueSecureInputBuffer(), hence playback is stalled. This change surfaces the queueing error through the adapter's dequeueing methods. PiperOrigin-RevId: 508637346 (cherry picked from commit 706431059cadf1b503ea8f95fd482d41f48e1a1c) --- .../AsynchronousMediaCodecAdapter.java | 2 ++ .../AsynchronousMediaCodecBufferEnqueuer.java | 3 +- .../AsynchronousMediaCodecCallback.java | 3 +- .../AsynchronousMediaCodecAdapterTest.java | 28 +++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecAdapter.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecAdapter.java index 91aa86ca06..9377aaace6 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecAdapter.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecAdapter.java @@ -191,11 +191,13 @@ import java.nio.ByteBuffer; @Override public int dequeueInputBufferIndex() { + bufferEnqueuer.maybeThrowException(); return asynchronousMediaCodecCallback.dequeueInputBufferIndex(); } @Override public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { + bufferEnqueuer.maybeThrowException(); return asynchronousMediaCodecCallback.dequeueOutputBufferIndex(bufferInfo); } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java index 10f0a24e15..bba85feff9 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java @@ -162,7 +162,8 @@ class AsynchronousMediaCodecBufferEnqueuer { blockUntilHandlerThreadIsIdle(); } - private void maybeThrowException() { + /** Throw any exception that occurred on the enqueuer's background queueing thread. */ + public void maybeThrowException() { @Nullable RuntimeException exception = pendingRuntimeException.getAndSet(null); if (exception != null) { throw exception; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallback.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallback.java index 67c6649d27..e950d3ac73 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallback.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallback.java @@ -263,11 +263,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; // else, pendingOutputFormat may already be non-null following a previous flush, and remains // set in this case. + // mediaCodecException is not reset to null. If the codec has raised an error, then it remains + // in FAILED_STATE even after flushing. availableInputBuffers.clear(); availableOutputBuffers.clear(); bufferInfos.clear(); formats.clear(); - mediaCodecException = null; } @GuardedBy("lock") diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecAdapterTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecAdapterTest.java index 98707a3226..ee7026a669 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecAdapterTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecAdapterTest.java @@ -80,6 +80,20 @@ public class AsynchronousMediaCodecAdapterTest { assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex()); } + @Test + public void dequeueInputBufferIndex_withPendingQueueingError_throwsException() { + // Force MediaCodec to throw an error by attempting to queue input buffer -1. + adapter.queueInputBuffer( + /* index= */ -1, + /* offset= */ 0, + /* size= */ 0, + /* presentationTimeUs= */ 0, + /* flags= */ 0); + shadowOf(queueingThread.getLooper()).idle(); + + assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex()); + } + @Test public void dequeueInputBufferIndex_afterShutdown_returnsTryAgainLater() { adapter.release(); @@ -123,6 +137,20 @@ public class AsynchronousMediaCodecAdapterTest { assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo)); } + @Test + public void dequeueOutputBufferIndex_withPendingQueueingError_throwsException() { + // Force MediaCodec to throw an error by attempting to queue input buffer -1. + adapter.queueInputBuffer( + /* index= */ -1, + /* offset= */ 0, + /* size= */ 0, + /* presentationTimeUs= */ 0, + /* flags= */ 0); + shadowOf(queueingThread.getLooper()).idle(); + + assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo)); + } + @Test public void dequeueOutputBufferIndex_afterShutdown_returnsTryAgainLater() { int index = adapter.dequeueInputBufferIndex();