Async queuing on MultiLockAsyncMediaCodecAdapter

Add support for asynchronous input buffer queueing in the
MultiLockAsyncMediaCodecAdapter.

PiperOrigin-RevId: 294397811
This commit is contained in:
christosts 2020-02-11 10:25:30 +00:00 committed by kim-vde
parent 12187c9048
commit bc02643df0
3 changed files with 101 additions and 37 deletions

View File

@ -83,7 +83,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD, OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD,
OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD, OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD,
OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK, OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK,
OPERATION_MODE_ASYNCHRONOUS_QUEUEING OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING,
OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING
}) })
public @interface MediaCodecOperationMode {} public @interface MediaCodecOperationMode {}
@ -91,24 +92,30 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
public static final int OPERATION_MODE_SYNCHRONOUS = 0; public static final int OPERATION_MODE_SYNCHRONOUS = 0;
/** /**
* Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback} * Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback}
* callbacks to the playback Thread. * callbacks to the playback thread.
*/ */
public static final int OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD = 1; public static final int OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD = 1;
/** /**
* Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback} * Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback}
* callbacks to a dedicated Thread. * callbacks to a dedicated thread.
*/ */
public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD = 2; public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD = 2;
/** /**
* Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback} * Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback}
* callbacks to a dedicated Thread. Uses granular locking for input and output buffers. * callbacks to a dedicated thread. Uses granular locking for input and output buffers.
*/ */
public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK = 3; public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK = 3;
/** /**
* Operates the {@link MediaCodec} in asynchronous mode, routes {@link MediaCodec.Callback} * Same as {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD}, and offloads queueing to another
* callbacks to a dedicated Thread, and offloads queueing to another Thread. * thread.
*/ */
public static final int OPERATION_MODE_ASYNCHRONOUS_QUEUEING = 4; public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING = 4;
/**
* Same as {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK}, and offloads queueing
* to another thread.
*/
public static final int
OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING = 5;
/** Thrown when a failure occurs instantiating a decoder. */ /** Thrown when a failure occurs instantiating a decoder. */
public static class DecoderInitializationException extends Exception { public static class DecoderInitializationException extends Exception {
@ -493,21 +500,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* *
* @param mode The mode of the MediaCodec. The supported modes are: * @param mode The mode of the MediaCodec. The supported modes are:
* <ul> * <ul>
* <li>{@link MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}: The {@link MediaCodec} will * <li>{@link #OPERATION_MODE_SYNCHRONOUS}: The {@link MediaCodec} will operate in
* operate in synchronous mode. * synchronous mode.
* <li>{@link MediaCodecRenderer#OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD}: The {@link * <li>{@link #OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD}: The {@link MediaCodec} will
* MediaCodec} will operate in asynchronous mode and {@link MediaCodec.Callback} * operate in asynchronous mode and {@link MediaCodec.Callback} callbacks will be routed
* callbacks will be routed to the Playback Thread. This mode requires API level &ge; * to the playback thread. This mode requires API level &ge; 21; if the API level is
* 21; if the API level is &le; 20, the operation mode will be set to {@link * &le; 20, the operation mode will be set to {@link
* MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}. * MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}.
* <li>{@link MediaCodecRenderer#OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD}: The {@link * <li>{@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD}: The {@link MediaCodec} will
* MediaCodec} will operate in asynchronous mode and {@link MediaCodec.Callback} * operate in asynchronous mode and {@link MediaCodec.Callback} callbacks will be routed
* callbacks will be routed to a dedicated Thread. This mode requires API level &ge; 23; * to a dedicated thread. This mode requires API level &ge; 23; if the API level is &le;
* if the API level is &le; 22, the operation mode will be set to {@link * 22, the operation mode will be set to {@link #OPERATION_MODE_SYNCHRONOUS}.
* MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}. * <li>{@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK}: Same as {@link
* <li>{@link MediaCodecRenderer#OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK}: * #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD} and, in addition, input buffers will
* Same as {@link MediaCodecRenderer#OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD} but * submitted to the {@link MediaCodec} in a separate thread.
* it will internally use a finer grained locking mechanism for increased performance. * <li>{@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING}: Same as
* {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD} and, in addition, input buffers
* will be submitted to the {@link MediaCodec} in a separate thread.
* <li>{@link
* #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING}: Same
* as {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK} and, in addition,
* input buffers will be submitted to the {@link MediaCodec} in a separate thread.
* </ul> * </ul>
* By default, the operation mode is set to {@link * By default, the operation mode is set to {@link
* MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}. * MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}.
@ -1027,11 +1040,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK
&& Util.SDK_INT >= 23) { && Util.SDK_INT >= 23) {
codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType()); codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType());
} else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_QUEUEING } else if (mediaCodecOperationMode
== OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING
&& Util.SDK_INT >= 23) { && Util.SDK_INT >= 23) {
codecAdapter = codecAdapter =
new DedicatedThreadAsyncMediaCodecAdapter( new DedicatedThreadAsyncMediaCodecAdapter(
codec, /* enableAsynchronousQueueing= */ true, getTrackType()); codec, /* enableAsynchronousQueueing= */ true, getTrackType());
} else if (mediaCodecOperationMode
== OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING
&& Util.SDK_INT >= 23) {
codecAdapter =
new MultiLockAsyncMediaCodecAdapter(
codec, /* enableAsynchronousQueueing= */ true, getTrackType());
} else { } else {
codecAdapter = new SynchronousMediaCodecAdapter(codec); codecAdapter = new SynchronousMediaCodecAdapter(codec);
} }

View File

@ -35,7 +35,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in asynchronous mode * A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in asynchronous mode
* and routes {@link MediaCodec.Callback} callbacks on a dedicated Thread that is managed * and routes {@link MediaCodec.Callback} callbacks on a dedicated thread that is managed
* internally. * internally.
* *
* <p>The main difference of this class compared to the {@link * <p>The main difference of this class compared to the {@link
@ -43,8 +43,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* locking. The {@link DedicatedThreadAsyncMediaCodecAdapter} uses a single lock to synchronize * locking. The {@link DedicatedThreadAsyncMediaCodecAdapter} uses a single lock to synchronize
* access, whereas this class uses a different lock to access the available input and available * access, whereas this class uses a different lock to access the available input and available
* output buffer indexes returned from the {@link MediaCodec}. This class assumes that the {@link * output buffer indexes returned from the {@link MediaCodec}. This class assumes that the {@link
* MediaCodecAdapter} methods will be accessed by the Playback Thread and the {@link * MediaCodecAdapter} methods will be accessed by the playback thread and the {@link
* MediaCodec.Callback} methods will be accessed by the internal Thread. This class is * MediaCodec.Callback} methods will be accessed by the internal thread. This class is
* <strong>NOT</strong> generally thread-safe in the sense that its public methods cannot be called * <strong>NOT</strong> generally thread-safe in the sense that its public methods cannot be called
* by any thread. * by any thread.
*/ */
@ -86,20 +86,54 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable @Nullable
private IllegalStateException codecException; private IllegalStateException codecException;
@GuardedBy("objectStateLock")
private @State int state;
private final HandlerThread handlerThread; private final HandlerThread handlerThread;
private @MonotonicNonNull Handler handler; private @MonotonicNonNull Handler handler;
private Runnable codecStartRunnable; private Runnable codecStartRunnable;
private final MediaCodecInputBufferEnqueuer bufferEnqueuer;
/** Creates a new instance that wraps the specified {@link MediaCodec}. */ @GuardedBy("objectStateLock")
@State
private int state;
/**
* Creates a new instance that wraps the specified {@link MediaCodec}. An instance created with
* this constructor will queue input buffers synchronously.
*
* @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.
*/
/* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, int trackType) { /* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, int trackType) {
this(codec, new HandlerThread(createThreadLabel(trackType))); this(
codec,
/* enableAsynchronousQueueing= */ false,
trackType,
new HandlerThread(createThreadLabel(trackType)));
}
/**
* Creates a new instance that wraps the specified {@link MediaCodec}.
*
* @param codec The {@link MediaCodec} to wrap.
* @param enableAsynchronousQueueing Whether input buffers will be queued asynchronously.
* @param trackType One of {@link C#TRACK_TYPE_AUDIO} or {@link C#TRACK_TYPE_VIDEO}. Used for
* labelling the internal thread accordingly.
*/
/* package */ MultiLockAsyncMediaCodecAdapter(
MediaCodec codec, boolean enableAsynchronousQueueing, int trackType) {
this(
codec,
enableAsynchronousQueueing,
trackType,
new HandlerThread(createThreadLabel(trackType)));
} }
@VisibleForTesting @VisibleForTesting
/* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, HandlerThread handlerThread) { /* package */ MultiLockAsyncMediaCodecAdapter(
MediaCodec codec,
boolean enableAsynchronousQueueing,
int trackType,
HandlerThread handlerThread) {
this.codec = codec; this.codec = codec;
inputBufferLock = new Object(); inputBufferLock = new Object();
outputBufferLock = new Object(); outputBufferLock = new Object();
@ -109,9 +143,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
bufferInfos = new ArrayDeque<>(); bufferInfos = new ArrayDeque<>();
formats = new ArrayDeque<>(); formats = new ArrayDeque<>();
codecException = null; codecException = null;
state = STATE_CREATED;
this.handlerThread = handlerThread; this.handlerThread = handlerThread;
codecStartRunnable = codec::start; codecStartRunnable = codec::start;
if (enableAsynchronousQueueing) {
bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, trackType);
} else {
bufferEnqueuer = new SynchronousMediaCodecBufferEnqueuer(codec);
}
state = STATE_CREATED;
} }
@Override @Override
@ -120,6 +159,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
handlerThread.start(); handlerThread.start();
handler = new Handler(handlerThread.getLooper()); handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler); codec.setCallback(this, handler);
bufferEnqueuer.start();
codecStartRunnable.run(); codecStartRunnable.run();
state = STATE_STARTED; state = STATE_STARTED;
} }
@ -165,7 +205,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
int index, int offset, int size, long presentationTimeUs, int flags) { int index, int offset, int size, long presentationTimeUs, int flags) {
// This method does not need to be synchronized because it is not interacting with // This method does not need to be synchronized because it is not interacting with
// MediaCodec.Callback and dequeueing buffers operations. // MediaCodec.Callback and dequeueing buffers operations.
codec.queueInputBuffer(index, offset, size, presentationTimeUs, flags); bufferEnqueuer.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
} }
@Override @Override
@ -173,13 +213,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) { int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
// This method does not need to be synchronized because it is not interacting with // This method does not need to be synchronized because it is not interacting with
// MediaCodec.Callback and dequeueing buffers operations. // MediaCodec.Callback and dequeueing buffers operations.
codec.queueSecureInputBuffer( bufferEnqueuer.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
index, offset, info.getFrameworkCryptoInfo(), presentationTimeUs, flags);
} }
@Override @Override
public void flush() { public void flush() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
bufferEnqueuer.flush();
codec.flush(); codec.flush();
pendingFlush++; pendingFlush++;
Util.castNonNull(handler).post(this::onFlushComplete); Util.castNonNull(handler).post(this::onFlushComplete);
@ -190,6 +230,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public void shutdown() { public void shutdown() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
if (state == STATE_STARTED) { if (state == STATE_STARTED) {
bufferEnqueuer.shutdown();
handlerThread.quit(); handlerThread.quit();
} }
state = STATE_SHUT_DOWN; state = STATE_SHUT_DOWN;
@ -246,7 +287,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
} }
// Called by the internal Thread. // Called by the internal thread.
@Override @Override
public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) { public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {

View File

@ -27,6 +27,7 @@ import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -49,7 +50,9 @@ public class MultiLockAsyncMediaCodecAdapterTest {
public void setUp() throws IOException { public void setUp() throws IOException {
codec = MediaCodec.createByCodecName("h264"); codec = MediaCodec.createByCodecName("h264");
handlerThread = new TestHandlerThread("TestHandlerThread"); handlerThread = new TestHandlerThread("TestHandlerThread");
adapter = new MultiLockAsyncMediaCodecAdapter(codec, handlerThread); adapter =
new MultiLockAsyncMediaCodecAdapter(
codec, /* enableAsynchronousQueueing= */ false, C.TRACK_TYPE_VIDEO, handlerThread);
adapter.setCodecStartRunnable(() -> {}); adapter.setCodecStartRunnable(() -> {});
bufferInfo = new MediaCodec.BufferInfo(); bufferInfo = new MediaCodec.BufferInfo();
} }