diff --git a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index 5d130442b3..ad58917160 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -92,6 +92,7 @@ public class DefaultRenderersFactory implements RenderersFactory { private boolean enableDecoderFallback; private MediaCodecSelector mediaCodecSelector; private boolean enableAsyncQueueing; + private boolean enableSynchronizeCodecInteractionsWithQueueing; private boolean enableFloatOutput; private boolean enableAudioTrackPlaybackParams; private boolean enableOffload; @@ -155,11 +156,26 @@ public class DefaultRenderersFactory implements RenderersFactory { * @param enabled Whether asynchronous queueing is enabled. * @return This factory, for convenience. */ - public DefaultRenderersFactory experimentalEnableAsynchronousBufferQueueing(boolean enabled) { + public DefaultRenderersFactory experimentalSetAsynchronousBufferQueueingEnabled(boolean enabled) { enableAsyncQueueing = enabled; return this; } + /** + * Enable synchronizing codec interactions with asynchronous buffer queueing. + * + *
This method is experimental, and will be renamed or removed in a future release.
+ *
+ * @param enabled Whether codec interactions will be synchronized with asynchronous buffer
+ * queueing.
+ * @return This factory, for convenience.
+ */
+ public DefaultRenderersFactory experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(
+ boolean enabled) {
+ enableSynchronizeCodecInteractionsWithQueueing = 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.
@@ -336,7 +352,9 @@ public class DefaultRenderersFactory implements RenderersFactory {
eventHandler,
eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
- videoRenderer.experimentalEnableAsynchronousBufferQueueing(enableAsyncQueueing);
+ videoRenderer.experimentalSetAsynchronousBufferQueueingEnabled(enableAsyncQueueing);
+ videoRenderer.experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(
+ enableSynchronizeCodecInteractionsWithQueueing);
out.add(videoRenderer);
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
@@ -461,7 +479,9 @@ public class DefaultRenderersFactory implements RenderersFactory {
eventHandler,
eventListener,
audioSink);
- audioRenderer.experimentalEnableAsynchronousBufferQueueing(enableAsyncQueueing);
+ audioRenderer.experimentalSetAsynchronousBufferQueueingEnabled(enableAsyncQueueing);
+ audioRenderer.experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(
+ enableSynchronizeCodecInteractionsWithQueueing);
out.add(audioRenderer);
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
index 2deb5fa2bf..54ad57cafe 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
@@ -56,6 +56,7 @@ import java.nio.ByteBuffer;
private final MediaCodec codec;
private final AsynchronousMediaCodecCallback asynchronousMediaCodecCallback;
private final AsynchronousMediaCodecBufferEnqueuer bufferEnqueuer;
+ private final boolean synchronizeCodecInteractionsWithQueueing;
private boolean codecReleased;
@State private int state;
@@ -65,20 +66,30 @@ import java.nio.ByteBuffer;
* @param codec The {@link MediaCodec} to wrap.
* @param trackType One of {@link C#TRACK_TYPE_AUDIO} or {@link C#TRACK_TYPE_VIDEO}. Used for
* labelling the internal thread accordingly.
+ * @param synchronizeCodecInteractionsWithQueueing Whether the adapter should synchronize {@link
+ * MediaCodec} interactions with asynchronous buffer queueing. When {@code true}, codec
+ * interactions will wait until all input buffers pending queueing wil be submitted to the
+ * {@link MediaCodec}.
*/
- /* package */ AsynchronousMediaCodecAdapter(MediaCodec codec, int trackType) {
+ /* package */ AsynchronousMediaCodecAdapter(
+ MediaCodec codec, int trackType, boolean synchronizeCodecInteractionsWithQueueing) {
this(
codec,
new HandlerThread(createCallbackThreadLabel(trackType)),
- new HandlerThread(createQueueingThreadLabel(trackType)));
+ new HandlerThread(createQueueingThreadLabel(trackType)),
+ synchronizeCodecInteractionsWithQueueing);
}
@VisibleForTesting
/* package */ AsynchronousMediaCodecAdapter(
- MediaCodec codec, HandlerThread callbackThread, HandlerThread enqueueingThread) {
+ MediaCodec codec,
+ HandlerThread callbackThread,
+ HandlerThread enqueueingThread,
+ boolean synchronizeCodecInteractionsWithQueueing) {
this.codec = codec;
this.asynchronousMediaCodecCallback = new AsynchronousMediaCodecCallback(callbackThread);
this.bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, enqueueingThread);
+ this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
this.state = STATE_CREATED;
}
@@ -181,6 +192,7 @@ import java.nio.ByteBuffer;
@Override
public void setOnFrameRenderedListener(OnFrameRenderedListener listener, Handler handler) {
+ maybeBlockOnQueueing();
codec.setOnFrameRenderedListener(
(codec, presentationTimeUs, nanoTime) ->
listener.onFrameRendered(
@@ -190,16 +202,19 @@ import java.nio.ByteBuffer;
@Override
public void setOutputSurface(Surface surface) {
+ maybeBlockOnQueueing();
codec.setOutputSurface(surface);
}
@Override
public void setParameters(Bundle params) {
+ maybeBlockOnQueueing();
codec.setParameters(params);
}
@Override
public void setVideoScalingMode(@VideoScalingMode int scalingMode) {
+ maybeBlockOnQueueing();
codec.setVideoScalingMode(scalingMode);
}
@@ -213,6 +228,19 @@ import java.nio.ByteBuffer;
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, format);
}
+ private void maybeBlockOnQueueing() {
+ if (synchronizeCodecInteractionsWithQueueing) {
+ try {
+ bufferEnqueuer.waitUntilQueueingComplete();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ // The playback thread should not be interrupted. Raising this as an
+ // IllegalStateException.
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
private static String createCallbackThreadLabel(int trackType) {
return createThreadLabel(trackType, /* prefix= */ "ExoPlayer:MediaCodecAsyncAdapter:");
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java
index 10d59d347c..79bb981955 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java
@@ -17,6 +17,7 @@
package com.google.android.exoplayer2.mediacodec;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
+import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.media.MediaCodec;
import android.os.Handler;
@@ -46,7 +47,7 @@ class AsynchronousMediaCodecBufferEnqueuer {
private static final int MSG_QUEUE_INPUT_BUFFER = 0;
private static final int MSG_QUEUE_SECURE_INPUT_BUFFER = 1;
- private static final int MSG_FLUSH = 2;
+ private static final int MSG_OPEN_CV = 2;
@GuardedBy("MESSAGE_PARAMS_INSTANCE_POOL")
private static final ArrayDeque This method is experimental, and will be renamed or removed in a future release. It should
* only be called before the renderer is used.
*/
- public void experimentalEnableAsynchronousBufferQueueing(boolean enabled) {
+ public void experimentalSetAsynchronousBufferQueueingEnabled(boolean enabled) {
enableAsynchronousBufferQueueing = enabled;
}
+ /**
+ * Enable synchronizing codec interactions with asynchronous buffer queueing.
+ *
+ * When enabled, codec interactions will wait until all input buffers pending for asynchronous
+ * queueing are submitted to the {@link MediaCodec} first. This method is effective only if {@link
+ * #experimentalSetAsynchronousBufferQueueingEnabled asynchronous buffer queueing} is enabled.
+ *
+ * This method is experimental, and will be renamed or removed in a future release. It should
+ * only be called before the renderer is used.
+ */
+ public void experimentalSetSynchronizeCodecInteractionsWithQueueingEnabled(boolean enabled) {
+ enableSynchronizeCodecInteractionsWithQueueing = enabled;
+ }
+
@Override
@AdaptiveSupport
public final int supportsMixedMimeTypeAdaptation() {
@@ -1051,7 +1066,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
TraceUtil.beginSection("createCodec:" + codecName);
MediaCodec codec = MediaCodec.createByCodecName(codecName);
if (enableAsynchronousBufferQueueing && Util.SDK_INT >= 23) {
- codecAdapter = new AsynchronousMediaCodecAdapter(codec, getTrackType());
+ codecAdapter =
+ new AsynchronousMediaCodecAdapter(
+ codec, getTrackType(), enableSynchronizeCodecInteractionsWithQueueing);
} else {
codecAdapter = new SynchronousMediaCodecAdapter(codec);
}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
index 60e9c8b77f..8874d5ec7c 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
@@ -46,7 +46,12 @@ public class AsynchronousMediaCodecAdapterTest {
codec = MediaCodec.createByCodecName("h264");
callbackThread = new HandlerThread("TestCallbackThread");
queueingThread = new HandlerThread("TestQueueingThread");
- adapter = new AsynchronousMediaCodecAdapter(codec, callbackThread, queueingThread);
+ adapter =
+ new AsynchronousMediaCodecAdapter(
+ codec,
+ callbackThread,
+ queueingThread,
+ /* synchronizeCodecInteractionsWithQueueing= */ false);
bufferInfo = new MediaCodec.BufferInfo();
}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
index 9e2c715b31..f3a08df819 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java
@@ -219,6 +219,28 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
assertThrows(IllegalStateException.class, () -> enqueuer.shutdown());
}
+ private static CryptoInfo createCryptoInfo() {
+ CryptoInfo info = new CryptoInfo();
+ int numSubSamples = 5;
+ int[] numBytesOfClearData = new int[] {0, 1, 2, 3};
+ int[] numBytesOfEncryptedData = new int[] {4, 5, 6, 7};
+ byte[] key = new byte[] {0, 1, 2, 3};
+ byte[] iv = new byte[] {4, 5, 6, 7};
+ @C.CryptoMode int mode = C.CRYPTO_MODE_AES_CBC;
+ int encryptedBlocks = 16;
+ int clearBlocks = 8;
+ info.set(
+ numSubSamples,
+ numBytesOfClearData,
+ numBytesOfEncryptedData,
+ key,
+ iv,
+ mode,
+ encryptedBlocks,
+ clearBlocks);
+ return info;
+ }
+
private static class TestHandlerThread extends HandlerThread {
private boolean started;
private boolean quit;
@@ -247,26 +269,4 @@ public class AsynchronousMediaCodecBufferEnqueuerTest {
return super.quit();
}
}
-
- private static CryptoInfo createCryptoInfo() {
- CryptoInfo info = new CryptoInfo();
- int numSubSamples = 5;
- int[] numBytesOfClearData = new int[] {0, 1, 2, 3};
- int[] numBytesOfEncryptedData = new int[] {4, 5, 6, 7};
- byte[] key = new byte[] {0, 1, 2, 3};
- byte[] iv = new byte[] {4, 5, 6, 7};
- @C.CryptoMode int mode = C.CRYPTO_MODE_AES_CBC;
- int encryptedBlocks = 16;
- int clearBlocks = 8;
- info.set(
- numSubSamples,
- numBytesOfClearData,
- numBytesOfEncryptedData,
- key,
- iv,
- mode,
- encryptedBlocks,
- clearBlocks);
- return info;
- }
}