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
This commit is contained in:
christosts 2021-01-05 19:01:03 +00:00 committed by Ian Baker
parent a4fbc2c98d
commit 51d90a40ba
2 changed files with 71 additions and 1 deletions

View file

@ -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();

View file

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