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 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<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
@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,

View File

@ -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.
*

View File

@ -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");

View File

@ -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;
}

View File

@ -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.