Async queuing on MultiLockAsyncMediaCodecAdapter
Add support for asynchronous input buffer queueing in the MultiLockAsyncMediaCodecAdapter. PiperOrigin-RevId: 294397811
This commit is contained in:
parent
12187c9048
commit
bc02643df0
@ -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 ≥
|
* to the playback thread. This mode requires API level ≥ 21; if the API level is
|
||||||
* 21; if the API level is ≤ 20, the operation mode will be set to {@link
|
* ≤ 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 ≥ 23;
|
* to a dedicated thread. This mode requires API level ≥ 23; if the API level is ≤
|
||||||
* if the API level is ≤ 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);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user