Commit 9e1597a4 by christosts Committed by tonihei

Add experimental method to turn-off async flush

When operating the MediaCodec in asynchronous mode, after a
MediaCodec.flush(), we start MediaCodec in the callback thread,
which might trigger errors in some platforms. This change adds an
experimental flag to move the call to MediaCodec.start() back to the
playback thread.

PiperOrigin-RevId: 407801013
parent 0b48570b
......@@ -192,6 +192,25 @@ public class DefaultRenderersFactory implements RenderersFactory {
}
/**
* 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.
*
* <p>By default, this feature is disabled.
*
* <p>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.
*
......
......@@ -50,11 +50,7 @@ import java.nio.ByteBuffer;
private final Supplier<HandlerThread> callbackThreadSupplier;
private final Supplier<HandlerThread> 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<HandlerThread> callbackThreadSupplier,
Supplier<HandlerThread> 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
......
......@@ -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);
}
}
@GuardedBy("lock")
private void onFlushCompletedSynchronized(Runnable onFlushCompleted) {
if (shutDown) {
return;
}
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();
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.mediacodec;
import android.media.MediaCodec;
import androidx.annotation.IntDef;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
......@@ -46,6 +47,7 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
@Mode private int asynchronousMode;
private boolean enableSynchronizeCodecInteractionsWithQueueing;
private boolean enableImmediateCodecStartAfterFlush;
public DefaultMediaCodecAdapterFactory() {
asynchronousMode = MODE_DEFAULT;
......@@ -85,6 +87,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.
*
* <p>By default, this feature is disabled.
*
* <p>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 {
......@@ -97,7 +115,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);
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment