diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java index a7211da19b..96d5e72191 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java @@ -193,6 +193,25 @@ public class DefaultRenderersFactory implements RenderersFactory { return this; } + /** + * Enable calling {@link MediaCodec#start} immediately after {@link MediaCodec#flush} on the + * playback thread, when operating the codec in asynchronous mode. If disabled, {@link + * MediaCodec#start} will be called by the callback thread after pending callbacks are handled. + * + *

By default, this feature is disabled. + * + *

This method is experimental, and will be renamed or removed in a future release. + * + * @param enabled Whether {@link MediaCodec#start} will be called on the playback thread + * immediately after {@link MediaCodec#flush}. + * @return This factory, for convenience. + */ + public DefaultRenderersFactory experimentalSetImmediateCodecStartAfterFlushEnabled( + boolean enabled) { + codecAdapterFactory.experimentalSetImmediateCodecStartAfterFlushEnabled(enabled); + return this; + } + /** * Sets whether to enable fallback to lower-priority decoders if decoder initialization fails. * This may result in using a decoder that is less efficient or slower than the primary decoder. 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 66e76a7d43..c54fef3591 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 @@ -50,11 +50,7 @@ import java.nio.ByteBuffer; private final Supplier callbackThreadSupplier; private final Supplier queueingThreadSupplier; private final boolean synchronizeCodecInteractionsWithQueueing; - - /** Creates a factory for codecs handling the specified {@link C.TrackType track type}. */ - public Factory(@C.TrackType int trackType) { - this(trackType, /* synchronizeCodecInteractionsWithQueueing= */ false); - } + private final boolean enableImmediateCodecStartAfterFlush; /** * Creates an factory for {@link AsynchronousMediaCodecAdapter} instances. @@ -66,23 +62,29 @@ import java.nio.ByteBuffer; * interactions will wait until all input buffers pending queueing wil be submitted to the * {@link MediaCodec}. */ - public Factory(@C.TrackType int trackType, boolean synchronizeCodecInteractionsWithQueueing) { + public Factory( + @C.TrackType int trackType, + boolean synchronizeCodecInteractionsWithQueueing, + boolean enableImmediateCodecStartAfterFlush) { this( /* callbackThreadSupplier= */ () -> new HandlerThread(createCallbackThreadLabel(trackType)), /* queueingThreadSupplier= */ () -> new HandlerThread(createQueueingThreadLabel(trackType)), - synchronizeCodecInteractionsWithQueueing); + synchronizeCodecInteractionsWithQueueing, + enableImmediateCodecStartAfterFlush); } @VisibleForTesting /* package */ Factory( Supplier callbackThreadSupplier, Supplier queueingThreadSupplier, - boolean synchronizeCodecInteractionsWithQueueing) { + boolean synchronizeCodecInteractionsWithQueueing, + boolean enableImmediateCodecStartAfterFlush) { this.callbackThreadSupplier = callbackThreadSupplier; this.queueingThreadSupplier = queueingThreadSupplier; this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing; + this.enableImmediateCodecStartAfterFlush = enableImmediateCodecStartAfterFlush; } @Override @@ -99,7 +101,8 @@ import java.nio.ByteBuffer; codec, callbackThreadSupplier.get(), queueingThreadSupplier.get(), - synchronizeCodecInteractionsWithQueueing); + synchronizeCodecInteractionsWithQueueing, + enableImmediateCodecStartAfterFlush); TraceUtil.endSection(); codecAdapter.initialize( configuration.mediaFormat, @@ -132,6 +135,7 @@ import java.nio.ByteBuffer; private final AsynchronousMediaCodecCallback asynchronousMediaCodecCallback; private final AsynchronousMediaCodecBufferEnqueuer bufferEnqueuer; private final boolean synchronizeCodecInteractionsWithQueueing; + private final boolean enableImmediateCodecStartAfterFlush; private boolean codecReleased; @State private int state; @Nullable private Surface inputSurface; @@ -140,11 +144,13 @@ import java.nio.ByteBuffer; MediaCodec codec, HandlerThread callbackThread, HandlerThread enqueueingThread, - boolean synchronizeCodecInteractionsWithQueueing) { + boolean synchronizeCodecInteractionsWithQueueing, + boolean enableImmediateCodecStartAfterFlush) { this.codec = codec; this.asynchronousMediaCodecCallback = new AsynchronousMediaCodecCallback(callbackThread); this.bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, enqueueingThread); this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing; + this.enableImmediateCodecStartAfterFlush = enableImmediateCodecStartAfterFlush; this.state = STATE_CREATED; } @@ -231,13 +237,20 @@ import java.nio.ByteBuffer; @Override public void flush() { // The order of calls is important: - // First, flush the bufferEnqueuer to stop queueing input buffers. - // Second, flush the codec to stop producing available input/output buffers. - // Third, flush the callback after flushing the codec so that in-flight callbacks are discarded. + // 1. Flush the bufferEnqueuer to stop queueing input buffers. + // 2. Flush the codec to stop producing available input/output buffers. + // 3. Flush the callback after flushing the codec so that in-flight callbacks are discarded. bufferEnqueuer.flush(); codec.flush(); - // When flushAsync() is completed, start the codec again. - asynchronousMediaCodecCallback.flushAsync(/* onFlushCompleted= */ codec::start); + if (enableImmediateCodecStartAfterFlush) { + // The asynchronous callback will drop pending callbacks but we can start the codec now. + asynchronousMediaCodecCallback.flush(/* codec= */ null); + codec.start(); + } else { + // Let the asynchronous callback start the codec in the callback thread after pending + // callbacks are handled. + asynchronousMediaCodecCallback.flush(codec); + } } @Override 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 98c7604f76..a3c5d44d6d 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 @@ -34,8 +34,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @RequiresApi(23) /* package */ final class AsynchronousMediaCodecCallback extends MediaCodec.Callback { private final Object lock; - private final HandlerThread callbackThread; + private @MonotonicNonNull Handler handler; @GuardedBy("lock") @@ -192,14 +192,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * Initiates a flush asynchronously, which will be completed on the callback thread. When the * flush is complete, it will trigger {@code onFlushCompleted} from the callback thread. * - * @param onFlushCompleted A {@link Runnable} that will be called when flush is completed. {@code - * onFlushCompleted} will be called from the scallback thread, therefore it should execute - * synchronized and thread-safe code. + * @param codec A {@link MediaCodec} to {@link MediaCodec#start start} after all pending callbacks + * are handled, or {@code null} if starting the {@link MediaCodec} is performed elsewhere. */ - public void flushAsync(Runnable onFlushCompleted) { + public void flush(@Nullable MediaCodec codec) { synchronized (lock) { ++pendingFlushCount; - Util.castNonNull(handler).post(() -> this.onFlushCompleted(onFlushCompleted)); + Util.castNonNull(handler).post(() -> this.onFlushCompleted(codec)); } } @@ -239,34 +238,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } } - private void onFlushCompleted(Runnable onFlushCompleted) { + private void onFlushCompleted(@Nullable MediaCodec codec) { synchronized (lock) { - onFlushCompletedSynchronized(onFlushCompleted); - } - } + if (shutDown) { + return; + } - @GuardedBy("lock") - private void onFlushCompletedSynchronized(Runnable onFlushCompleted) { - if (shutDown) { - return; - } - - --pendingFlushCount; - if (pendingFlushCount > 0) { - // Another flush() has been called. - return; - } else if (pendingFlushCount < 0) { - // This should never happen. - setInternalException(new IllegalStateException()); - return; - } - flushInternal(); - try { - onFlushCompleted.run(); - } catch (IllegalStateException e) { - setInternalException(e); - } catch (Exception e) { - setInternalException(new IllegalStateException(e)); + --pendingFlushCount; + if (pendingFlushCount > 0) { + // Another flush() has been called. + return; + } else if (pendingFlushCount < 0) { + // This should never happen. + setInternalException(new IllegalStateException()); + return; + } + flushInternal(); + if (codec != null) { + try { + codec.start(); + } catch (IllegalStateException e) { + setInternalException(e); + } catch (Exception e) { + setInternalException(new IllegalStateException(e)); + } + } } } @@ -275,10 +271,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private void flushInternal() { if (!formats.isEmpty()) { pendingOutputFormat = formats.getLast(); - } else { - // pendingOutputFormat may already be non-null following a previous flush, and remains set in - // this case. } + // 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/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/DefaultMediaCodecAdapterFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/DefaultMediaCodecAdapterFactory.java index 3af8bfe2de..911d5ccab2 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/DefaultMediaCodecAdapterFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/DefaultMediaCodecAdapterFactory.java @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer.mediacodec; +import android.media.MediaCodec; import androidx.annotation.IntDef; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Log; @@ -48,6 +49,7 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter. @Mode private int asynchronousMode; private boolean enableSynchronizeCodecInteractionsWithQueueing; + private boolean enableImmediateCodecStartAfterFlush; public DefaultMediaCodecAdapterFactory() { asynchronousMode = MODE_DEFAULT; @@ -87,6 +89,22 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter. enableSynchronizeCodecInteractionsWithQueueing = enabled; } + /** + * Enable calling {@link MediaCodec#start} immediately after {@link MediaCodec#flush} on the + * playback thread, when operating the codec in asynchronous mode. If disabled, {@link + * MediaCodec#start} will be called by the callback thread after pending callbacks are handled. + * + *

By default, this feature is disabled. + * + *

This method is experimental, and will be renamed or removed in a future release. + * + * @param enabled Whether {@link MediaCodec#start()} will be called on the playback thread + * immediately after {@link MediaCodec#flush}. + */ + public void experimentalSetImmediateCodecStartAfterFlushEnabled(boolean enabled) { + enableImmediateCodecStartAfterFlush = enabled; + } + @Override public MediaCodecAdapter createAdapter(MediaCodecAdapter.Configuration configuration) throws IOException { @@ -99,7 +117,9 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter. + Util.getTrackTypeString(trackType)); AsynchronousMediaCodecAdapter.Factory factory = new AsynchronousMediaCodecAdapter.Factory( - trackType, enableSynchronizeCodecInteractionsWithQueueing); + trackType, + enableSynchronizeCodecInteractionsWithQueueing, + enableImmediateCodecStartAfterFlush); return factory.createAdapter(configuration); } return new SynchronousMediaCodecAdapter.Factory().createAdapter(configuration); 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..0401f95e1d 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 @@ -54,7 +54,8 @@ public class AsynchronousMediaCodecAdapterTest { new AsynchronousMediaCodecAdapter.Factory( /* callbackThreadSupplier= */ () -> callbackThread, /* queueingThreadSupplier= */ () -> queueingThread, - /* synchronizeCodecInteractionsWithQueueing= */ false) + /* synchronizeCodecInteractionsWithQueueing= */ false, + /* enableImmediateCodecStartAfterFlush= */ false) .createAdapter(configuration); bufferInfo = new MediaCodec.BufferInfo(); // After starting the MediaCodec, the ShadowMediaCodec offers input buffer 0. We advance the diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallbackTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallbackTest.java index 4d2f887324..f059e2a24e 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallbackTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecCallbackTest.java @@ -24,6 +24,7 @@ import static org.robolectric.Shadows.shadowOf; import android.media.MediaCodec; import android.media.MediaFormat; +import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -81,16 +82,24 @@ public class AsynchronousMediaCodecCallbackTest { @Test public void dequeInputBufferIndex_withPendingFlush_returnsTryAgain() { - Looper callbackThreadLooper = callbackThread.getLooper(); + AtomicBoolean beforeFlushCompletes = new AtomicBoolean(); AtomicBoolean flushCompleted = new AtomicBoolean(); + Looper callbackThreadLooper = callbackThread.getLooper(); + Handler callbackHandler = new Handler(callbackThreadLooper); + ShadowLooper shadowCallbackLooper = shadowOf(callbackThreadLooper); // Pause the callback thread so that flush() never completes. - shadowOf(callbackThreadLooper).pause(); + shadowCallbackLooper.pause(); // Send two input buffers to the callback and then flush(). asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0); asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1); - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); + callbackHandler.post(() -> beforeFlushCompletes.set(true)); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + callbackHandler.post(() -> flushCompleted.set(true)); + while (!beforeFlushCompletes.get()) { + shadowCallbackLooper.runOneTask(); + } + assertThat(flushCompleted.get()).isFalse(); assertThat(asynchronousMediaCodecCallback.dequeueInputBufferIndex()) .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); @@ -104,8 +113,8 @@ public class AsynchronousMediaCodecCallbackTest { // Send two input buffers to the callback and then flush(). asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0); asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1); - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true)); // Progress the callback thread so that flush() completes. shadowOf(callbackThreadLooper).idle(); @@ -123,10 +132,11 @@ public class AsynchronousMediaCodecCallbackTest { // another input buffer. asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0); asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1); - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); - // Progress the callback thread so that flush() completes. - shadowOf(callbackThreadLooper).idle(); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true)); + // Progress the callback thread to complete flush. + shadowOf(callbackThread.getLooper()).idle(); + // Send another input buffer to the callback asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 2); assertThat(flushCompleted.get()).isTrue(); @@ -152,20 +162,6 @@ public class AsynchronousMediaCodecCallbackTest { () -> asynchronousMediaCodecCallback.dequeueInputBufferIndex()); } - @Test - public void dequeueInputBufferIndex_afterFlushCompletedWithError_throwsError() throws Exception { - MediaCodec.CodecException codecException = createCodecException(); - asynchronousMediaCodecCallback.flushAsync( - () -> { - throw codecException; - }); - shadowOf(callbackThread.getLooper()).idle(); - - assertThrows( - MediaCodec.CodecException.class, - () -> asynchronousMediaCodecCallback.dequeueInputBufferIndex()); - } - @Test public void dequeOutputBufferIndex_afterCreation_returnsTryAgain() { MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo(); @@ -198,17 +194,24 @@ public class AsynchronousMediaCodecCallbackTest { @Test public void dequeOutputBufferIndex_withPendingFlush_returnsTryAgain() { - Looper callbackThreadLooper = callbackThread.getLooper(); + AtomicBoolean beforeFlushCompletes = new AtomicBoolean(); AtomicBoolean flushCompleted = new AtomicBoolean(); + Looper callbackThreadLooper = callbackThread.getLooper(); + Handler callbackHandler = new Handler(callbackThreadLooper); + ShadowLooper shadowCallbackLooper = shadowOf(callbackThreadLooper); // Pause the callback thread so that flush() never completes. - shadowOf(callbackThreadLooper).pause(); + shadowCallbackLooper.pause(); // Send two output buffers to the callback and then flush(). MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo); asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo); - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); + callbackHandler.post(() -> beforeFlushCompletes.set(true)); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + callbackHandler.post(() -> flushCompleted.set(true)); + while (beforeFlushCompletes.get()) { + shadowCallbackLooper.runOneTask(); + } assertThat(flushCompleted.get()).isFalse(); assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo())) @@ -224,8 +227,8 @@ public class AsynchronousMediaCodecCallbackTest { MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo); asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo); - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true)); // Progress the callback looper so that flush() completes. shadowOf(callbackThreadLooper).idle(); @@ -245,10 +248,11 @@ public class AsynchronousMediaCodecCallbackTest { asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format0")); asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo); asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo); - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true)); // Progress the callback looper so that flush() completes. shadowOf(callbackThreadLooper).idle(); + // Emulate an output buffer is available. asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 2, bufferInfo); MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo(); @@ -271,8 +275,8 @@ public class AsynchronousMediaCodecCallbackTest { MediaFormat pendingMediaFormat = new MediaFormat(); asynchronousMediaCodecCallback.onOutputFormatChanged(codec, pendingMediaFormat); // flush() should not discard the last format. - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true)); // Progress the callback looper so that flush() completes. shadowOf(callbackThreadLooper).idle(); // Right after flush(), we send an output buffer: the pending output format should be @@ -298,8 +302,8 @@ public class AsynchronousMediaCodecCallbackTest { MediaFormat pendingMediaFormat = new MediaFormat(); asynchronousMediaCodecCallback.onOutputFormatChanged(codec, pendingMediaFormat); // flush() should not discard the last format. - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true)); // Progress the callback looper so that flush() completes. shadowOf(callbackThreadLooper).idle(); // The first callback after flush() is a new MediaFormat, it should overwrite the pending @@ -335,20 +339,6 @@ public class AsynchronousMediaCodecCallbackTest { () -> asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo())); } - @Test - public void dequeueOutputBufferIndex_afterFlushCompletedWithError_throwsError() throws Exception { - MediaCodec.CodecException codecException = createCodecException(); - asynchronousMediaCodecCallback.flushAsync( - () -> { - throw codecException; - }); - shadowOf(callbackThread.getLooper()).idle(); - - assertThrows( - MediaCodec.CodecException.class, - () -> asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo())); - } - @Test public void getOutputFormat_onNewInstance_raisesException() { try { @@ -377,8 +367,8 @@ public class AsynchronousMediaCodecCallbackTest { asynchronousMediaCodecCallback.onOutputFormatChanged(codec, format); asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo()); - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true)); // Progress the callback looper so that flush() completes. shadowOf(callbackThreadLooper).idle(); @@ -390,7 +380,8 @@ public class AsynchronousMediaCodecCallbackTest { public void getOutputFormat_afterFlushWithPendingFormat_returnsPendingFormat() { MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); AtomicBoolean flushCompleted = new AtomicBoolean(); - ShadowLooper shadowCallbackLooper = shadowOf(callbackThread.getLooper()); + Looper callbackThreadLooper = callbackThread.getLooper(); + ShadowLooper shadowCallbackLooper = shadowOf(callbackThreadLooper); shadowCallbackLooper.pause(); asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format0")); @@ -399,8 +390,8 @@ public class AsynchronousMediaCodecCallbackTest { asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format1")); asynchronousMediaCodecCallback.onOutputBufferAvailable( codec, /* index= */ 1, new MediaCodec.BufferInfo()); - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ () -> flushCompleted.set(true)); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true)); // Progress the looper so that flush is completed shadowCallbackLooper.idle(); // Enqueue an output buffer to make the pending format available. @@ -419,7 +410,8 @@ public class AsynchronousMediaCodecCallbackTest { public void getOutputFormat_withConsecutiveFlushAndPendingFormatFromFirstFlush_returnsPendingFormat() { MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo(); - AtomicInteger flushesCompleted = new AtomicInteger(); + AtomicInteger flushCompleted = new AtomicInteger(); + Handler callbackThreadHandler = new Handler(callbackThread.getLooper()); ShadowLooper shadowCallbackLooper = shadowOf(callbackThread.getLooper()); shadowCallbackLooper.pause(); @@ -427,17 +419,17 @@ public class AsynchronousMediaCodecCallbackTest { asynchronousMediaCodecCallback.onOutputBufferAvailable( codec, /* index= */ 0, new MediaCodec.BufferInfo()); // Flush and progress the looper so that flush is completed. - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ flushesCompleted::incrementAndGet); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + callbackThreadHandler.post(flushCompleted::incrementAndGet); shadowCallbackLooper.idle(); // Flush again, the pending format from the first flush should remain as pending. - asynchronousMediaCodecCallback.flushAsync( - /* onFlushCompleted= */ flushesCompleted::incrementAndGet); + asynchronousMediaCodecCallback.flush(/* codec= */ null); + callbackThreadHandler.post(flushCompleted::incrementAndGet); shadowCallbackLooper.idle(); asynchronousMediaCodecCallback.onOutputBufferAvailable( codec, /* index= */ 1, new MediaCodec.BufferInfo()); - assertThat(flushesCompleted.get()).isEqualTo(2); + assertThat(flushCompleted.get()).isEqualTo(2); assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo)) .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); assertThat(asynchronousMediaCodecCallback.getOutputFormat().getString("name")) @@ -445,19 +437,6 @@ public class AsynchronousMediaCodecCallbackTest { assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo)).isEqualTo(1); } - @Test - public void flush_withPendingFlush_onlyLastFlushCompletes() { - ShadowLooper callbackLooperShadow = shadowOf(callbackThread.getLooper()); - callbackLooperShadow.pause(); - AtomicInteger flushCompleted = new AtomicInteger(); - - asynchronousMediaCodecCallback.flushAsync(/* onFlushCompleted= */ () -> flushCompleted.set(1)); - asynchronousMediaCodecCallback.flushAsync(/* onFlushCompleted= */ () -> flushCompleted.set(2)); - callbackLooperShadow.idle(); - - assertThat(flushCompleted.get()).isEqualTo(2); - } - /** Reflectively create a {@link MediaCodec.CodecException}. */ private static MediaCodec.CodecException createCodecException() throws Exception { Constructor constructor =