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 092e8d9110..49fb1beadf 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java @@ -163,6 +163,20 @@ public class DefaultRenderersFactory implements RenderersFactory { return this; } + /** + * Sets whether to enable {@link MediaCodec#CONFIGURE_FLAG_USE_CRYPTO_ASYNC} on API 34 and above + * when operating the codec in asynchronous mode. + * + *
This method is experimental. Its default value may change, or it may be renamed or removed
+ * in a future release.
+ */
+ @CanIgnoreReturnValue
+ public DefaultRenderersFactory experimentalSetMediaCodecAsyncCryptoFlagEnabled(
+ boolean enableAsyncCryptoFlag) {
+ codecAdapterFactory.experimentalSetAsyncCryptoFlagEnabled(enableAsyncCryptoFlag);
+ 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 745a5b0d5a..cdd391657b 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
@@ -26,12 +26,16 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.PersistableBundle;
import android.view.Surface;
+import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
+import androidx.media3.common.Format;
+import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.TraceUtil;
+import androidx.media3.common.util.Util;
import androidx.media3.decoder.CryptoInfo;
import com.google.common.base.Supplier;
import java.io.IOException;
@@ -54,6 +58,8 @@ import java.nio.ByteBuffer;
private final Supplier This method is experimental. Its default value may change, or it may be renamed or removed
+ * in a future release.
+ */
+ public void experimentalSetAsyncCryptoFlagEnabled(boolean enableAsyncCryptoFlag) {
+ enableSynchronousBufferQueueingWithAsyncCryptoFlag = enableAsyncCryptoFlag;
}
@Override
@@ -85,15 +103,21 @@ import java.nio.ByteBuffer;
try {
TraceUtil.beginSection("createCodec:" + codecName);
codec = MediaCodec.createByCodecName(codecName);
+ int flags = configuration.flags;
+ MediaCodecBufferEnqueuer bufferEnqueuer;
+ if (enableSynchronousBufferQueueingWithAsyncCryptoFlag
+ && useSynchronousBufferQueueingWithAsyncCryptoFlag(configuration.format)) {
+ bufferEnqueuer = new SynchronousMediaCodecBufferEnqueuer(codec);
+ flags |= MediaCodec.CONFIGURE_FLAG_USE_CRYPTO_ASYNC;
+ } else {
+ bufferEnqueuer =
+ new AsynchronousMediaCodecBufferEnqueuer(codec, queueingThreadSupplier.get());
+ }
codecAdapter =
- new AsynchronousMediaCodecAdapter(
- codec, callbackThreadSupplier.get(), queueingThreadSupplier.get());
+ new AsynchronousMediaCodecAdapter(codec, callbackThreadSupplier.get(), bufferEnqueuer);
TraceUtil.endSection();
codecAdapter.initialize(
- configuration.mediaFormat,
- configuration.surface,
- configuration.crypto,
- configuration.flags);
+ configuration.mediaFormat, configuration.surface, configuration.crypto, flags);
return codecAdapter;
} catch (Exception e) {
if (codecAdapter != null) {
@@ -104,6 +128,16 @@ import java.nio.ByteBuffer;
throw e;
}
}
+
+ @ChecksSdkIntAtLeast(api = 34)
+ private static boolean useSynchronousBufferQueueingWithAsyncCryptoFlag(Format format) {
+ if (Util.SDK_INT < 34) {
+ return false;
+ }
+ // TODO: b/316565675 - Remove restriction to video once MediaCodec supports
+ // CONFIGURE_FLAG_USE_CRYPTO_ASYNC for audio too
+ return MimeTypes.isVideo(format.sampleMimeType);
+ }
}
@Documented
@@ -118,15 +152,15 @@ import java.nio.ByteBuffer;
private final MediaCodec codec;
private final AsynchronousMediaCodecCallback asynchronousMediaCodecCallback;
- private final AsynchronousMediaCodecBufferEnqueuer bufferEnqueuer;
+ private final MediaCodecBufferEnqueuer bufferEnqueuer;
private boolean codecReleased;
private @State int state;
private AsynchronousMediaCodecAdapter(
- MediaCodec codec, HandlerThread callbackThread, HandlerThread enqueueingThread) {
+ MediaCodec codec, HandlerThread callbackThread, MediaCodecBufferEnqueuer bufferEnqueuer) {
this.codec = codec;
this.asynchronousMediaCodecCallback = new AsynchronousMediaCodecCallback(callbackThread);
- this.bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, enqueueingThread);
+ this.bufferEnqueuer = bufferEnqueuer;
this.state = STATE_CREATED;
}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java
index f6d33f78bb..2ae384501f 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package androidx.media3.exoplayer.mediacodec;
import static androidx.annotation.VisibleForTesting.NONE;
@@ -39,13 +38,11 @@ import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
- * Performs {@link MediaCodec} input buffer queueing on a background thread.
- *
- * The implementation of this class assumes that its public methods will be called from the same
- * thread.
+ * Performs {@link MediaCodec} input buffer queueing on a background thread. This is required on API
+ * 33 and below because queuing secure buffers blocks until decryption is complete.
*/
@RequiresApi(23)
-class AsynchronousMediaCodecBufferEnqueuer {
+/* package */ class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecBufferEnqueuer {
private static final int MSG_QUEUE_INPUT_BUFFER = 0;
private static final int MSG_QUEUE_SECURE_INPUT_BUFFER = 1;
@@ -83,11 +80,7 @@ class AsynchronousMediaCodecBufferEnqueuer {
pendingRuntimeException = new AtomicReference<>();
}
- /**
- * Starts this instance.
- *
- * Call this method after creating an instance and before queueing input buffers.
- */
+ @Override
public void start() {
if (!started) {
handlerThread.start();
@@ -102,11 +95,7 @@ class AsynchronousMediaCodecBufferEnqueuer {
}
}
- /**
- * Submits an input buffer for decoding.
- *
- * @see android.media.MediaCodec#queueInputBuffer
- */
+ @Override
public void queueInputBuffer(
int index, int offset, int size, long presentationTimeUs, int flags) {
maybeThrowException();
@@ -116,15 +105,7 @@ class AsynchronousMediaCodecBufferEnqueuer {
message.sendToTarget();
}
- /**
- * Submits an input buffer that potentially contains encrypted data for decoding.
- *
- * Note: This method behaves as {@link MediaCodec#queueSecureInputBuffer} with the difference
- * that {@code info} is of type {@link CryptoInfo} and not {@link
- * android.media.MediaCodec.CryptoInfo}.
- *
- * @see android.media.MediaCodec#queueSecureInputBuffer
- */
+ @Override
public void queueSecureInputBuffer(
int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
maybeThrowException();
@@ -136,12 +117,13 @@ class AsynchronousMediaCodecBufferEnqueuer {
message.sendToTarget();
}
+ @Override
public void setParameters(Bundle params) {
maybeThrowException();
castNonNull(handler).obtainMessage(MSG_SET_PARAMETERS, params).sendToTarget();
}
- /** Flushes the instance. */
+ @Override
public void flush() {
if (started) {
try {
@@ -155,7 +137,7 @@ class AsynchronousMediaCodecBufferEnqueuer {
}
}
- /** Shuts down the instance. Make sure to call this method to release its internal resources. */
+ @Override
public void shutdown() {
if (started) {
flush();
@@ -164,12 +146,12 @@ class AsynchronousMediaCodecBufferEnqueuer {
started = false;
}
- /** Blocks the current thread until all input buffers pending queueing are submitted. */
+ @Override
public void waitUntilQueueingComplete() throws InterruptedException {
blockUntilHandlerThreadIsIdle();
}
- /** Throw any exception that occurred on the enqueuer's background queueing thread. */
+ @Override
public void maybeThrowException() {
@Nullable RuntimeException exception = pendingRuntimeException.getAndSet(null);
if (exception != null) {
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 5b595ffa89..dd40266f4f 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
@@ -63,6 +63,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable
private MediaCodec.CodecException mediaCodecException;
+ @GuardedBy("lock")
+ @Nullable
+ private MediaCodec.CryptoException mediaCodecCryptoException;
+
@GuardedBy("lock")
private long pendingFlushCount;
@@ -228,6 +232,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
+ @Override
+ public void onCryptoError(MediaCodec codec, MediaCodec.CryptoException e) {
+ synchronized (lock) {
+ mediaCodecCryptoException = e;
+ }
+ }
+
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
synchronized (lock) {
@@ -287,6 +298,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private void maybeThrowException() {
maybeThrowInternalException();
maybeThrowMediaCodecException();
+ maybeThrowMediaCodecCryptoException();
}
@GuardedBy("lock")
@@ -307,6 +319,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
+ @GuardedBy("lock")
+ private void maybeThrowMediaCodecCryptoException() {
+ if (mediaCodecCryptoException != null) {
+ MediaCodec.CryptoException cryptoException = mediaCodecCryptoException;
+ mediaCodecCryptoException = null;
+ throw cryptoException;
+ }
+ }
+
private void setInternalException(IllegalStateException e) {
synchronized (lock) {
internalException = e;
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 ec46fadd8e..48ffcc87a0 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
@@ -17,6 +17,7 @@ package androidx.media3.exoplayer.mediacodec;
import static java.lang.annotation.ElementType.TYPE_USE;
+import android.media.MediaCodec;
import androidx.annotation.IntDef;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Log;
@@ -54,9 +55,11 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
private static final String TAG = "DMCodecAdapterFactory";
private @Mode int asynchronousMode;
+ private boolean asyncCryptoFlagEnabled;
public DefaultMediaCodecAdapterFactory() {
asynchronousMode = MODE_DEFAULT;
+ asyncCryptoFlagEnabled = true;
}
/**
@@ -83,6 +86,20 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
return this;
}
+ /**
+ * Sets whether to enable {@link MediaCodec#CONFIGURE_FLAG_USE_CRYPTO_ASYNC} on API 34 and above
+ * for {@link AsynchronousMediaCodecAdapter} instances.
+ *
+ * This method is experimental. Its default value may change, or it may be renamed or removed
+ * in a future release.
+ */
+ @CanIgnoreReturnValue
+ public DefaultMediaCodecAdapterFactory experimentalSetAsyncCryptoFlagEnabled(
+ boolean enableAsyncCryptoFlag) {
+ asyncCryptoFlagEnabled = enableAsyncCryptoFlag;
+ return this;
+ }
+
@Override
public MediaCodecAdapter createAdapter(MediaCodecAdapter.Configuration configuration)
throws IOException {
@@ -96,6 +113,7 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
+ Util.getTrackTypeString(trackType));
AsynchronousMediaCodecAdapter.Factory factory =
new AsynchronousMediaCodecAdapter.Factory(trackType);
+ factory.experimentalSetAsyncCryptoFlagEnabled(asyncCryptoFlagEnabled);
return factory.createAdapter(configuration);
}
return new SynchronousMediaCodecAdapter.Factory().createAdapter(configuration);
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecBufferEnqueuer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecBufferEnqueuer.java
new file mode 100644
index 0000000000..4267b741fd
--- /dev/null
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecBufferEnqueuer.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.media3.exoplayer.mediacodec;
+
+import android.media.MediaCodec;
+import android.os.Bundle;
+import androidx.annotation.RequiresApi;
+import androidx.media3.decoder.CryptoInfo;
+
+/**
+ * Interface to queue buffers to a {@link MediaCodec}.
+ *
+ * All methods must be called from the same thread.
+ */
+/* package */ interface MediaCodecBufferEnqueuer {
+
+ /**
+ * Starts this instance.
+ *
+ * Call this method after creating an instance and before queueing input buffers.
+ */
+ void start();
+
+ /**
+ * Submits an input buffer for decoding.
+ *
+ * @see android.media.MediaCodec#queueInputBuffer
+ */
+ void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags);
+
+ /**
+ * Submits an input buffer that potentially contains encrypted data for decoding.
+ *
+ * Note: This method behaves as {@link MediaCodec#queueSecureInputBuffer} with the difference
+ * that {@code info} is of type {@link CryptoInfo} and not {@link MediaCodec.CryptoInfo}.
+ *
+ * @see MediaCodec#queueSecureInputBuffer
+ */
+ void queueSecureInputBuffer(
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags);
+
+ /**
+ * Submits new codec parameters that should be applied from the next queued input buffer.
+ *
+ * @see MediaCodec#setParameters(Bundle)
+ */
+ @RequiresApi(19)
+ void setParameters(Bundle parameters);
+
+ /** Flushes the instance. */
+ void flush();
+
+ /** Shuts down the instance. Make sure to call this method to release its internal resources. */
+ void shutdown();
+
+ /** Blocks the current thread until all input buffers pending queueing are submitted. */
+ void waitUntilQueueingComplete() throws InterruptedException;
+
+ /** Throw any exception that occurred during the enqueueing process. */
+ void maybeThrowException();
+}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/SynchronousMediaCodecBufferEnqueuer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/SynchronousMediaCodecBufferEnqueuer.java
new file mode 100644
index 0000000000..7584403080
--- /dev/null
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/SynchronousMediaCodecBufferEnqueuer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.media3.exoplayer.mediacodec;
+
+import android.media.MediaCodec;
+import android.os.Bundle;
+import androidx.annotation.RequiresApi;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.decoder.CryptoInfo;
+
+@RequiresApi(23)
+@UnstableApi
+/* package */ class SynchronousMediaCodecBufferEnqueuer implements MediaCodecBufferEnqueuer {
+
+ private final MediaCodec codec;
+
+ public SynchronousMediaCodecBufferEnqueuer(MediaCodec codec) {
+ this.codec = codec;
+ }
+
+ @Override
+ public void start() {
+ // Do nothing.
+ }
+
+ @Override
+ public void queueInputBuffer(
+ int index, int offset, int size, long presentationTimeUs, int flags) {
+ codec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
+ }
+
+ @Override
+ public void queueSecureInputBuffer(
+ int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
+ codec.queueSecureInputBuffer(
+ index, offset, info.getFrameworkCryptoInfo(), presentationTimeUs, flags);
+ }
+
+ @Override
+ public void setParameters(Bundle parameters) {
+ codec.setParameters(parameters);
+ }
+
+ @Override
+ public void flush() {
+ // Do nothing.
+ }
+
+ @Override
+ public void shutdown() {
+ // Do nothing.
+ }
+
+ @Override
+ public void waitUntilQueueingComplete() {
+ // Do nothing.
+ }
+
+ @Override
+ public void maybeThrowException() {
+ // Do nothing.
+ }
+}