Add Factory to MediaCodecAdapter

In a later change, MediaCoderAdapter.Factory will be injectable to
MediaCodecRenderer.

PiperOrigin-RevId: 346525171
This commit is contained in:
christosts 2020-12-09 12:47:19 +00:00 committed by Ian Baker
parent 9a00ba1d38
commit e7f5912677
5 changed files with 104 additions and 50 deletions

View File

@ -29,6 +29,7 @@ import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.decoder.CryptoInfo; import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.common.base.Supplier;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -42,6 +43,69 @@ import java.nio.ByteBuffer;
@RequiresApi(23) @RequiresApi(23)
/* package */ final class AsynchronousMediaCodecAdapter implements MediaCodecAdapter { /* package */ final class AsynchronousMediaCodecAdapter implements MediaCodecAdapter {
/** A factory for {@link AsynchronousMediaCodecAdapter} instances. */
public static final class Factory implements MediaCodecAdapter.Factory {
private final Supplier<HandlerThread> callbackThreadSupplier;
private final Supplier<HandlerThread> queueingThreadSupplier;
private final boolean forceQueueingSynchronizationWorkaround;
private final boolean synchronizeCodecInteractionsWithQueueing;
/** Creates a factory for the specified {@code trackType}. */
public Factory(int trackType) {
this(
trackType,
/* forceQueueingSynchronizationWorkaround= */ false,
/* synchronizeCodecInteractionsWithQueueing= */ false);
}
/**
* Creates an factory for {@link AsynchronousMediaCodecAdapter} instances.
*
* @param trackType One of {@link C#TRACK_TYPE_AUDIO} or {@link C#TRACK_TYPE_VIDEO}. Used for
* labelling the internal thread accordingly.
* @param forceQueueingSynchronizationWorkaround Whether the queueing synchronization workaround
* will be enabled by default or only for the predefined devices.
* @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}.
*/
public Factory(
int trackType,
boolean forceQueueingSynchronizationWorkaround,
boolean synchronizeCodecInteractionsWithQueueing) {
this(
/* callbackThreadSupplier= */ () ->
new HandlerThread(createCallbackThreadLabel(trackType)),
/* queueingThreadSupplier= */ () ->
new HandlerThread(createQueueingThreadLabel(trackType)),
forceQueueingSynchronizationWorkaround,
synchronizeCodecInteractionsWithQueueing);
}
@VisibleForTesting
/* package */ Factory(
Supplier<HandlerThread> callbackThreadSupplier,
Supplier<HandlerThread> queueingThreadSupplier,
boolean forceQueueingSynchronizationWorkaround,
boolean synchronizeCodecInteractionsWithQueueing) {
this.callbackThreadSupplier = callbackThreadSupplier;
this.queueingThreadSupplier = queueingThreadSupplier;
this.forceQueueingSynchronizationWorkaround = forceQueueingSynchronizationWorkaround;
this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
}
@Override
public AsynchronousMediaCodecAdapter createAdapter(MediaCodec codec) {
return new AsynchronousMediaCodecAdapter(
codec,
callbackThreadSupplier.get(),
queueingThreadSupplier.get(),
forceQueueingSynchronizationWorkaround,
synchronizeCodecInteractionsWithQueueing);
}
}
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_CREATED, STATE_CONFIGURED, STATE_STARTED, STATE_SHUT_DOWN}) @IntDef({STATE_CREATED, STATE_CONFIGURED, STATE_STARTED, STATE_SHUT_DOWN})
@ -59,32 +123,7 @@ import java.nio.ByteBuffer;
private boolean codecReleased; private boolean codecReleased;
@State private int state; @State private int state;
/** private AsynchronousMediaCodecAdapter(
* Creates an instance that wraps the specified {@link MediaCodec}.
*
* @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,
boolean forceQueueingSynchronizationWorkaround,
boolean synchronizeCodecInteractionsWithQueueing) {
this(
codec,
new HandlerThread(createCallbackThreadLabel(trackType)),
new HandlerThread(createQueueingThreadLabel(trackType)),
forceQueueingSynchronizationWorkaround,
synchronizeCodecInteractionsWithQueueing);
}
@VisibleForTesting
/* package */ AsynchronousMediaCodecAdapter(
MediaCodec codec, MediaCodec codec,
HandlerThread callbackThread, HandlerThread callbackThread,
HandlerThread enqueueingThread, HandlerThread enqueueingThread,

View File

@ -36,6 +36,13 @@ import java.nio.ByteBuffer;
*/ */
public interface MediaCodecAdapter { public interface MediaCodecAdapter {
/** A factory for {@link MediaCodecAdapter} instances. */
interface Factory {
/** Creates an instance wrapping the provided {@link MediaCodec} instance. */
MediaCodecAdapter createAdapter(MediaCodec codec);
}
/** /**
* Listener to be called when an output frame has rendered on the output surface. * Listener to be called when an output frame has rendered on the output surface.
* *

View File

@ -1076,13 +1076,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
MediaCodec codec = MediaCodec.createByCodecName(codecName); MediaCodec codec = MediaCodec.createByCodecName(codecName);
if (enableAsynchronousBufferQueueing && Util.SDK_INT >= 23) { if (enableAsynchronousBufferQueueing && Util.SDK_INT >= 23) {
codecAdapter = codecAdapter =
new AsynchronousMediaCodecAdapter( new AsynchronousMediaCodecAdapter.Factory(
codec,
getTrackType(), getTrackType(),
forceAsyncQueueingSynchronizationWorkaround, forceAsyncQueueingSynchronizationWorkaround,
enableSynchronizeCodecInteractionsWithQueueing); enableSynchronizeCodecInteractionsWithQueueing)
.createAdapter(codec);
} else { } else {
codecAdapter = new SynchronousMediaCodecAdapter(codec); codecAdapter = new SynchronousMediaCodecAdapter.Factory().createAdapter(codec);
} }
TraceUtil.endSection(); TraceUtil.endSection();
TraceUtil.beginSection("configureCodec"); TraceUtil.beginSection("configureCodec");

View File

@ -36,11 +36,19 @@ import java.nio.ByteBuffer;
*/ */
/* package */ final class SynchronousMediaCodecAdapter implements MediaCodecAdapter { /* package */ final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
/** A factory for {@link SynchronousMediaCodecAdapter} instances. */
public static final class Factory implements MediaCodecAdapter.Factory {
@Override
public MediaCodecAdapter createAdapter(MediaCodec codec) {
return new SynchronousMediaCodecAdapter(codec);
}
}
private final MediaCodec codec; private final MediaCodec codec;
@Nullable private ByteBuffer[] inputByteBuffers; @Nullable private ByteBuffer[] inputByteBuffers;
@Nullable private ByteBuffer[] outputByteBuffers; @Nullable private ByteBuffer[] outputByteBuffers;
public SynchronousMediaCodecAdapter(MediaCodec mediaCodec) { private SynchronousMediaCodecAdapter(MediaCodec mediaCodec) {
this.codec = mediaCodec; this.codec = mediaCodec;
} }

View File

@ -47,12 +47,12 @@ public class AsynchronousMediaCodecAdapterTest {
callbackThread = new HandlerThread("TestCallbackThread"); callbackThread = new HandlerThread("TestCallbackThread");
queueingThread = new HandlerThread("TestQueueingThread"); queueingThread = new HandlerThread("TestQueueingThread");
adapter = adapter =
new AsynchronousMediaCodecAdapter( new AsynchronousMediaCodecAdapter.Factory(
codec, /* callbackThreadSupplier= */ () -> callbackThread,
callbackThread, /* queueingThreadSupplier= */ () -> queueingThread,
queueingThread,
/* forceQueueingSynchronizationWorkaround= */ false, /* forceQueueingSynchronizationWorkaround= */ false,
/* synchronizeCodecInteractionsWithQueueing= */ false); /* synchronizeCodecInteractionsWithQueueing= */ false)
.createAdapter(codec);
bufferInfo = new MediaCodec.BufferInfo(); bufferInfo = new MediaCodec.BufferInfo();
} }
@ -64,7 +64,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() { public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
// After adapter.start(), the ShadowMediaCodec offers one input buffer. We pause the looper so // After adapter.start(), the ShadowMediaCodec offers one input buffer. We pause the looper so
// that the buffer is not propagated to the adapter. // that the buffer is not propagated to the adapter.
shadowOf(callbackThread.getLooper()).pause(); shadowOf(callbackThread.getLooper()).pause();
@ -76,7 +76,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() { public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
adapter.start(); adapter.start();
// After start(), the ShadowMediaCodec offers input buffer 0. We advance the looper to make sure // After start(), the ShadowMediaCodec offers input buffer 0. We advance the looper to make sure
// and messages have been propagated to the adapter. // and messages have been propagated to the adapter.
@ -90,7 +90,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_withMediaCodecError_throwsException() throws Exception { public void dequeueInputBufferIndex_withMediaCodecError_throwsException() throws Exception {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
// Pause the looper so that we interact with the adapter from this thread only. // Pause the looper so that we interact with the adapter from this thread only.
shadowOf(callbackThread.getLooper()).pause(); shadowOf(callbackThread.getLooper()).pause();
adapter.start(); adapter.start();
@ -104,7 +104,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_afterShutdown_returnsTryAgainLater() { public void dequeueInputBufferIndex_afterShutdown_returnsTryAgainLater() {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
adapter.start(); adapter.start();
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
// progress the adapter's looper. We progress the looper so that we call shutdown() on a // progress the adapter's looper. We progress the looper so that we call shutdown() on a
@ -119,7 +119,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() { public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
adapter.start(); adapter.start();
// After start(), the ShadowMediaCodec offers an output format change. We progress the looper // After start(), the ShadowMediaCodec offers an output format change. We progress the looper
@ -136,7 +136,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() { public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
adapter.start(); adapter.start();
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
// progress the adapter's looper. // progress the adapter's looper.
@ -164,7 +164,7 @@ public class AsynchronousMediaCodecAdapterTest {
public void dequeueOutputBufferIndex_withMediaCodecError_throwsException() throws Exception { public void dequeueOutputBufferIndex_withMediaCodecError_throwsException() throws Exception {
// Pause the looper so that we interact with the adapter from this thread only. // Pause the looper so that we interact with the adapter from this thread only.
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
shadowOf(callbackThread.getLooper()).pause(); shadowOf(callbackThread.getLooper()).pause();
adapter.start(); adapter.start();
@ -177,7 +177,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueOutputBufferIndex_afterShutdown_returnsTryAgainLater() { public void dequeueOutputBufferIndex_afterShutdown_returnsTryAgainLater() {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
adapter.start(); adapter.start();
// After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we
// progress the adapter's looper. // progress the adapter's looper.
@ -197,7 +197,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void getOutputFormat_withoutFormatReceived_throwsException() { public void getOutputFormat_withoutFormatReceived_throwsException() {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
// After start() the ShadowMediaCodec offers an output format change. Pause the looper so that // After start() the ShadowMediaCodec offers an output format change. Pause the looper so that
// the format change is not propagated to the adapter. // the format change is not propagated to the adapter.
shadowOf(callbackThread.getLooper()).pause(); shadowOf(callbackThread.getLooper()).pause();
@ -209,7 +209,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void getOutputFormat_withMultipleFormats_returnsCorrectFormat() { public void getOutputFormat_withMultipleFormats_returnsCorrectFormat() {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
adapter.start(); adapter.start();
// After start(), the ShadowMediaCodec offers an output format, which is available only if we // After start(), the ShadowMediaCodec offers an output format, which is available only if we
// progress the adapter's looper. // progress the adapter's looper.
@ -233,7 +233,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void getOutputFormat_afterFlush_returnsPreviousFormat() { public void getOutputFormat_afterFlush_returnsPreviousFormat() {
adapter.configure( adapter.configure(
createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0);
adapter.start(); adapter.start();
// After start(), the ShadowMediaCodec offers an output format, which is available only if we // After start(), the ShadowMediaCodec offers an output format, which is available only if we
// progress the adapter's looper. // progress the adapter's looper.