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)
This commit is contained in:
christosts 2023-02-10 13:44:34 +00:00 committed by tonihei
parent 3cc93b1f1d
commit 3696076f0f
4 changed files with 34 additions and 2 deletions

View File

@ -191,11 +191,13 @@ import java.nio.ByteBuffer;
@Override @Override
public int dequeueInputBufferIndex() { public int dequeueInputBufferIndex() {
bufferEnqueuer.maybeThrowException();
return asynchronousMediaCodecCallback.dequeueInputBufferIndex(); return asynchronousMediaCodecCallback.dequeueInputBufferIndex();
} }
@Override @Override
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
bufferEnqueuer.maybeThrowException();
return asynchronousMediaCodecCallback.dequeueOutputBufferIndex(bufferInfo); return asynchronousMediaCodecCallback.dequeueOutputBufferIndex(bufferInfo);
} }

View File

@ -162,7 +162,8 @@ class AsynchronousMediaCodecBufferEnqueuer {
blockUntilHandlerThreadIsIdle(); 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); @Nullable RuntimeException exception = pendingRuntimeException.getAndSet(null);
if (exception != null) { if (exception != null) {
throw exception; throw exception;

View File

@ -263,11 +263,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// else, pendingOutputFormat may already be non-null following a previous flush, and remains // else, pendingOutputFormat may already be non-null following a previous flush, and remains
// set in this case. // 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(); availableInputBuffers.clear();
availableOutputBuffers.clear(); availableOutputBuffers.clear();
bufferInfos.clear(); bufferInfos.clear();
formats.clear(); formats.clear();
mediaCodecException = null;
} }
@GuardedBy("lock") @GuardedBy("lock")

View File

@ -80,6 +80,20 @@ public class AsynchronousMediaCodecAdapterTest {
assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex()); 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 @Test
public void dequeueInputBufferIndex_afterShutdown_returnsTryAgainLater() { public void dequeueInputBufferIndex_afterShutdown_returnsTryAgainLater() {
adapter.release(); adapter.release();
@ -123,6 +137,20 @@ public class AsynchronousMediaCodecAdapterTest {
assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo)); 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 @Test
public void dequeueOutputBufferIndex_afterShutdown_returnsTryAgainLater() { public void dequeueOutputBufferIndex_afterShutdown_returnsTryAgainLater() {
int index = adapter.dequeueInputBufferIndex(); int index = adapter.dequeueInputBufferIndex();