From 51d90a40ba88bb63ef1824b3c752d26e251b9b02 Mon Sep 17 00:00:00 2001 From: christosts Date: Tue, 5 Jan 2021 19:01:03 +0000 Subject: [PATCH] Keep pending output format after multiple flushes The AsynchronousMediaCodecCallback has logic to retain a pending output format in case flush() is called. This commit fixes a case where calling flush() again while an output format is pending would nullify the pending output format. A unit test is added in AsynchronousMediaCodecCallback but not the AsynchronousMediaCodecAdapter. That is because the adapter operates directly on top of MediaCodec, but Robolectric's ShadowMediaCodec produces an output format on every MediaCodec.start(). This is unrealistic when operating MediaCodec in asynchronous mode where we need to call MediaCodec.start() after every MediaCodec.flush(). PiperOrigin-RevId: 350176659 --- .../AsynchronousMediaCodecCallback.java | 7 +- .../AsynchronousMediaCodecCallbackTest.java | 65 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallback.java index f05d752061..1302549ac8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallback.java @@ -274,7 +274,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** Flushes all available input and output buffers and any error that was previously set. */ @GuardedBy("lock") private void flushInternal() { - pendingOutputFormat = formats.isEmpty() ? null : formats.getLast(); + if (!formats.isEmpty()) { + pendingOutputFormat = formats.getLast(); + } else { + // pendingOutputFormat may already be non-null following a previous flush, and remains set in + // this case. + } availableInputBuffers.clear(); availableOutputBuffers.clear(); bufferInfos.clear(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallbackTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallbackTest.java index 6ca468d739..3765da56b1 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallbackTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecCallbackTest.java @@ -376,6 +376,65 @@ public class AsynchronousMediaCodecCallbackTest { assertThat(asynchronousMediaCodecCallback.getOutputFormat()).isEqualTo(format); } + @Test + public void getOutputFormat_afterFlushWithPendingFormat_returnsPendingFormat() { + MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); + AtomicBoolean flushCompleted = new AtomicBoolean(); + ShadowLooper shadowCallbackLooper = shadowOf(callbackThread.getLooper()); + shadowCallbackLooper.pause(); + + asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format0")); + asynchronousMediaCodecCallback.onOutputBufferAvailable( + codec, /* index= */ 0, new MediaCodec.BufferInfo()); + asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format1")); + asynchronousMediaCodecCallback.onOutputBufferAvailable( + codec, /* index= */ 1, new MediaCodec.BufferInfo()); + asynchronousMediaCodecCallback.flushAsync( + /* onFlushCompleted= */ () -> flushCompleted.set(true)); + // Progress the looper so that flush is completed + shadowCallbackLooper.idle(); + // Enqueue an output buffer to make the pending format available. + asynchronousMediaCodecCallback.onOutputBufferAvailable( + codec, /* index= */ 2, new MediaCodec.BufferInfo()); + + assertThat(flushCompleted.get()).isTrue(); + assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo)) + .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); + assertThat(asynchronousMediaCodecCallback.getOutputFormat().getString("name")) + .isEqualTo("format1"); + assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo)).isEqualTo(2); + } + + @Test + public void + getOutputFormat_withConsecutiveFlushAndPendingFormatFromFirstFlush_returnsPendingFormat() { + MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); + AtomicInteger flushesCompleted = new AtomicInteger(); + ShadowLooper shadowCallbackLooper = shadowOf(callbackThread.getLooper()); + shadowCallbackLooper.pause(); + + asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format0")); + asynchronousMediaCodecCallback.onOutputBufferAvailable( + codec, /* index= */ 0, new MediaCodec.BufferInfo()); + // Flush and progress the looper so that flush is completed. + asynchronousMediaCodecCallback.flushAsync( + /* onFlushCompleted= */ flushesCompleted::incrementAndGet); + shadowCallbackLooper.idle(); + // Flush again, the pending format from the first flush should remain as pending. + asynchronousMediaCodecCallback.flushAsync( + /* onFlushCompleted= */ flushesCompleted::incrementAndGet); + shadowCallbackLooper.idle(); + asynchronousMediaCodecCallback.onOutputBufferAvailable( + codec, /* index= */ 1, new MediaCodec.BufferInfo()); + + assertThat(flushesCompleted.get()).isEqualTo(2); + assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo)) + .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); + assertThat(asynchronousMediaCodecCallback.getOutputFormat().getString("name")) + .isEqualTo("format0"); + assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo)).isEqualTo(1); + } + @Test public void flush_withPendingFlush_onlyLastFlushCompletes() { ShadowLooper callbackLooperShadow = shadowOf(callbackThread.getLooper()); @@ -398,6 +457,12 @@ public class AsynchronousMediaCodecCallbackTest { /* errorCode= */ 0, /* actionCode= */ 0, /* detailMessage= */ "error from codec"); } + private static MediaFormat createMediaFormat(String name) { + MediaFormat format = new MediaFormat(); + format.setString("name", name); + return format; + } + private static class TestHandlerThread extends HandlerThread { private boolean quit;