diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java index 906c3af9df..e5c50c1f8e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java @@ -29,6 +29,7 @@ import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.decoder.CryptoInfo; +import com.google.common.base.Supplier; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -42,6 +43,69 @@ import java.nio.ByteBuffer; @RequiresApi(23) /* package */ final class AsynchronousMediaCodecAdapter implements MediaCodecAdapter { + /** A factory for {@link AsynchronousMediaCodecAdapter} instances. */ + public static final class Factory implements MediaCodecAdapter.Factory { + private final Supplier callbackThreadSupplier; + private final Supplier 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 callbackThreadSupplier, + Supplier 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 @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_CREATED, STATE_CONFIGURED, STATE_STARTED, STATE_SHUT_DOWN}) @@ -59,32 +123,7 @@ import java.nio.ByteBuffer; private boolean codecReleased; @State private int state; - /** - * 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( + private AsynchronousMediaCodecAdapter( MediaCodec codec, HandlerThread callbackThread, HandlerThread enqueueingThread, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java index 76714f72ea..940675f553 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecAdapter.java @@ -36,6 +36,13 @@ import java.nio.ByteBuffer; */ 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. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 990573bc91..95ec66457d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -1076,13 +1076,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { MediaCodec codec = MediaCodec.createByCodecName(codecName); if (enableAsynchronousBufferQueueing && Util.SDK_INT >= 23) { codecAdapter = - new AsynchronousMediaCodecAdapter( - codec, - getTrackType(), - forceAsyncQueueingSynchronizationWorkaround, - enableSynchronizeCodecInteractionsWithQueueing); + new AsynchronousMediaCodecAdapter.Factory( + getTrackType(), + forceAsyncQueueingSynchronizationWorkaround, + enableSynchronizeCodecInteractionsWithQueueing) + .createAdapter(codec); } else { - codecAdapter = new SynchronousMediaCodecAdapter(codec); + codecAdapter = new SynchronousMediaCodecAdapter.Factory().createAdapter(codec); } TraceUtil.endSection(); TraceUtil.beginSection("configureCodec"); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java index 64e3b6f108..063dcf10a5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecAdapter.java @@ -36,11 +36,19 @@ import java.nio.ByteBuffer; */ /* 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; @Nullable private ByteBuffer[] inputByteBuffers; @Nullable private ByteBuffer[] outputByteBuffers; - public SynchronousMediaCodecAdapter(MediaCodec mediaCodec) { + private SynchronousMediaCodecAdapter(MediaCodec mediaCodec) { this.codec = mediaCodec; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java index 040e8a576b..48ecd2e582 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java @@ -47,12 +47,12 @@ public class AsynchronousMediaCodecAdapterTest { callbackThread = new HandlerThread("TestCallbackThread"); queueingThread = new HandlerThread("TestQueueingThread"); adapter = - new AsynchronousMediaCodecAdapter( - codec, - callbackThread, - queueingThread, - /* forceQueueingSynchronizationWorkaround= */ false, - /* synchronizeCodecInteractionsWithQueueing= */ false); + new AsynchronousMediaCodecAdapter.Factory( + /* callbackThreadSupplier= */ () -> callbackThread, + /* queueingThreadSupplier= */ () -> queueingThread, + /* forceQueueingSynchronizationWorkaround= */ false, + /* synchronizeCodecInteractionsWithQueueing= */ false) + .createAdapter(codec); bufferInfo = new MediaCodec.BufferInfo(); } @@ -64,7 +64,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() { 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 // that the buffer is not propagated to the adapter. shadowOf(callbackThread.getLooper()).pause(); @@ -76,7 +76,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() { adapter.configure( - createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); + createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); adapter.start(); // After start(), the ShadowMediaCodec offers input buffer 0. We advance the looper to make sure // and messages have been propagated to the adapter. @@ -90,7 +90,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_withMediaCodecError_throwsException() throws Exception { 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. shadowOf(callbackThread.getLooper()).pause(); adapter.start(); @@ -104,7 +104,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_afterShutdown_returnsTryAgainLater() { adapter.configure( - createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); + createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); adapter.start(); // 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 @@ -119,7 +119,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() { adapter.configure( - createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); + createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); adapter.start(); // After start(), the ShadowMediaCodec offers an output format change. We progress the looper @@ -136,7 +136,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() { adapter.configure( - createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); + createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); adapter.start(); // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we // progress the adapter's looper. @@ -164,7 +164,7 @@ public class AsynchronousMediaCodecAdapterTest { public void dequeueOutputBufferIndex_withMediaCodecError_throwsException() throws Exception { // Pause the looper so that we interact with the adapter from this thread only. adapter.configure( - createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); + createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); shadowOf(callbackThread.getLooper()).pause(); adapter.start(); @@ -177,7 +177,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_afterShutdown_returnsTryAgainLater() { adapter.configure( - createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); + createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); adapter.start(); // After start(), the ShadowMediaCodec offers input buffer 0, which is available only if we // progress the adapter's looper. @@ -197,7 +197,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void getOutputFormat_withoutFormatReceived_throwsException() { 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 // the format change is not propagated to the adapter. shadowOf(callbackThread.getLooper()).pause(); @@ -209,7 +209,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void getOutputFormat_withMultipleFormats_returnsCorrectFormat() { adapter.configure( - createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); + createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); adapter.start(); // After start(), the ShadowMediaCodec offers an output format, which is available only if we // progress the adapter's looper. @@ -233,7 +233,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void getOutputFormat_afterFlush_returnsPreviousFormat() { adapter.configure( - createMediaFormat("foo"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); + createMediaFormat("format"), /* surface= */ null, /* crypto= */ null, /* flags= */ 0); adapter.start(); // After start(), the ShadowMediaCodec offers an output format, which is available only if we // progress the adapter's looper.