mirror of
https://github.com/androidx/media.git
synced 2025-05-09 16:40:55 +08:00
Use CRYPTO_ASYNC mode for video from API 34
This allows us to remove the additional thread we create for asynchronous buffer queuing and use a synchronized queueing approach again. This API looks strictly beneficial on all tested devices, but since this code path in MediaCodec has not been used widely, we leave an opt-out flag for now. PiperOrigin-RevId: 591867472
This commit is contained in:
parent
bb7aa2fb19
commit
e5aa69237e
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
|
@ -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<HandlerThread> callbackThreadSupplier;
|
||||
private final Supplier<HandlerThread> queueingThreadSupplier;
|
||||
|
||||
private boolean enableSynchronousBufferQueueingWithAsyncCryptoFlag;
|
||||
|
||||
/**
|
||||
* Creates an factory for {@link AsynchronousMediaCodecAdapter} instances.
|
||||
*
|
||||
@ -74,6 +80,18 @@ import java.nio.ByteBuffer;
|
||||
Supplier<HandlerThread> queueingThreadSupplier) {
|
||||
this.callbackThreadSupplier = callbackThreadSupplier;
|
||||
this.queueingThreadSupplier = queueingThreadSupplier;
|
||||
enableSynchronousBufferQueueingWithAsyncCryptoFlag = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to enable {@link MediaCodec#CONFIGURE_FLAG_USE_CRYPTO_ASYNC} on API 34 and
|
||||
* above.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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) {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
*
|
||||
* <p>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);
|
||||
|
@ -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}.
|
||||
*
|
||||
* <p>All methods must be called from the same thread.
|
||||
*/
|
||||
/* package */ interface MediaCodecBufferEnqueuer {
|
||||
|
||||
/**
|
||||
* Starts this instance.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
@ -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.
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user