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 84ff985495..eb4754c50f 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 @@ -69,59 +69,33 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private long pendingFlushCount; private @State int state; - private final MediaCodecInputBufferEnqueuer bufferEnqueuer; + private final AsynchronousMediaCodecBufferEnqueuer bufferEnqueuer; @GuardedBy("lock") @Nullable private IllegalStateException internalException; /** - * Creates an instance that wraps the specified {@link MediaCodec}. Instances created with this - * constructor will queue input buffers to the {@link MediaCodec} synchronously. + * 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. */ /* package */ AsynchronousMediaCodecAdapter(MediaCodec codec, int trackType) { - this( - codec, - /* enableAsynchronousQueueing= */ false, - trackType, - new HandlerThread(createThreadLabel(trackType))); - } - - /** - * Creates an 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 */ AsynchronousMediaCodecAdapter( - MediaCodec codec, boolean enableAsynchronousQueueing, int trackType) { - this( - codec, - enableAsynchronousQueueing, - trackType, - new HandlerThread(createThreadLabel(trackType))); + this(codec, trackType, new HandlerThread(createThreadLabel(trackType))); } @VisibleForTesting /* package */ AsynchronousMediaCodecAdapter( MediaCodec codec, - boolean enableAsynchronousQueueing, int trackType, HandlerThread handlerThread) { this.lock = new Object(); this.mediaCodecAsyncCallback = new MediaCodecAsyncCallback(); this.codec = codec; this.handlerThread = handlerThread; - this.bufferEnqueuer = - enableAsynchronousQueueing - ? new AsynchronousMediaCodecBufferEnqueuer(codec, trackType) - : new SynchronousMediaCodecBufferEnqueuer(this.codec); + this.bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, trackType); this.state = STATE_CREATED; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java index dd9a086446..6b2ec4e699 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuer.java @@ -37,13 +37,13 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** - * A {@link MediaCodecInputBufferEnqueuer} that defers queueing operations on a background thread. + * Performs {@link MediaCodec} input buffer queueing on a background thread. * *

The implementation of this class assumes that its public methods will be called from the same * thread. */ @RequiresApi(23) -class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnqueuer { +class AsynchronousMediaCodecBufferEnqueuer { private static final int MSG_QUEUE_INPUT_BUFFER = 0; private static final int MSG_QUEUE_SECURE_INPUT_BUFFER = 1; @@ -85,7 +85,11 @@ class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnque needsSynchronizationWorkaround = needsSynchronizationWorkaround(); } - @Override + /** + * Starts this instance. + * + *

Call this method after creating an instance and before queueing input buffers. + */ public void start() { if (!started) { handlerThread.start(); @@ -100,7 +104,11 @@ class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnque } } - @Override + /** + * Submits an input buffer for decoding. + * + * @see android.media.MediaCodec#queueInputBuffer + */ public void queueInputBuffer( int index, int offset, int size, long presentationTimeUs, int flags) { maybeThrowException(); @@ -111,7 +119,15 @@ class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnque message.sendToTarget(); } - @Override + /** + * Submits an input buffer that potentially contains encrypted data for decoding. + * + *

Note: This method behaves as {@link MediaCodec#queueSecureInputBuffer} with the difference + * that {@code info} is of type {@link CryptoInfo} and not {@link + * android.media.MediaCodec.CryptoInfo}. + * + * @see android.media.MediaCodec#queueSecureInputBuffer + */ public void queueSecureInputBuffer( int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) { maybeThrowException(); @@ -123,7 +139,7 @@ class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnque message.sendToTarget(); } - @Override + /** Flushes the instance. */ public void flush() { if (started) { try { @@ -137,7 +153,7 @@ class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnque } } - @Override + /** Shut down the instance. Make sure to call this method to release its internal resources. */ public void shutdown() { if (started) { flush(); @@ -146,36 +162,8 @@ class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnque started = false; } - private void doHandleMessage(Message msg) { - MessageParams params = null; - switch (msg.what) { - case MSG_QUEUE_INPUT_BUFFER: - params = (MessageParams) msg.obj; - doQueueInputBuffer( - params.index, params.offset, params.size, params.presentationTimeUs, params.flags); - break; - case MSG_QUEUE_SECURE_INPUT_BUFFER: - params = (MessageParams) msg.obj; - doQueueSecureInputBuffer( - params.index, - params.offset, - params.cryptoInfo, - params.presentationTimeUs, - params.flags); - break; - case MSG_FLUSH: - conditionVariable.open(); - break; - default: - setPendingRuntimeException(new IllegalStateException(String.valueOf(msg.what))); - } - if (params != null) { - recycleMessageParams(params); - } - } - private void maybeThrowException() { - RuntimeException exception = pendingRuntimeException.getAndSet(null); + @Nullable RuntimeException exception = pendingRuntimeException.getAndSet(null); if (exception != null) { throw exception; } @@ -202,6 +190,34 @@ class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnque pendingRuntimeException.set(exception); } + private void doHandleMessage(Message msg) { + @Nullable MessageParams params = null; + switch (msg.what) { + case MSG_QUEUE_INPUT_BUFFER: + params = (MessageParams) msg.obj; + doQueueInputBuffer( + params.index, params.offset, params.size, params.presentationTimeUs, params.flags); + break; + case MSG_QUEUE_SECURE_INPUT_BUFFER: + params = (MessageParams) msg.obj; + doQueueSecureInputBuffer( + params.index, + params.offset, + params.cryptoInfo, + params.presentationTimeUs, + params.flags); + break; + case MSG_FLUSH: + conditionVariable.open(); + break; + default: + setPendingRuntimeException(new IllegalStateException(String.valueOf(msg.what))); + } + if (params != null) { + recycleMessageParams(params); + } + } + private void doQueueInputBuffer( int index, int offset, int size, long presentationTimeUs, int flag) { try { @@ -226,13 +242,6 @@ class AsynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnque } } - @VisibleForTesting - /* package */ static int getInstancePoolSize() { - synchronized (MESSAGE_PARAMS_INSTANCE_POOL) { - return MESSAGE_PARAMS_INSTANCE_POOL.size(); - } - } - private static MessageParams getMessageParams() { synchronized (MESSAGE_PARAMS_INSTANCE_POOL) { if (MESSAGE_PARAMS_INSTANCE_POOL.isEmpty()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInputBufferEnqueuer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInputBufferEnqueuer.java deleted file mode 100644 index 34a1ccc6ba..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInputBufferEnqueuer.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.exoplayer2.mediacodec; - -import android.media.MediaCodec; -import com.google.android.exoplayer2.decoder.CryptoInfo; - -/** Abstracts operations to enqueue input buffer on a {@link android.media.MediaCodec}. */ -interface MediaCodecInputBufferEnqueuer { - - /** - * Starts this instance. - * - *

Call this method after creating an instance. - */ - void start(); - - /** - * Submits an input buffer for decoding. - * - * @see android.media.MediaCodec#queueInputBuffer - */ - void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags); - - /** - * Submits an input buffer that potentially contains encrypted data for decoding. - * - *

Note: This method behaves as {@link MediaCodec#queueSecureInputBuffer} with the difference - * that {@code info} is of type {@link CryptoInfo} and not {@link - * android.media.MediaCodec.CryptoInfo}. - * - * @see android.media.MediaCodec#queueSecureInputBuffer - */ - void queueSecureInputBuffer( - int index, int offset, CryptoInfo info, long presentationTimeUs, int flags); - - /** Flushes the instance. */ - void flush(); - - /** Shut down the instance. Make sure to call this method to release its internal resources. */ - void shutdown(); -} 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 c01d43872e..4d5164f551 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 @@ -1056,13 +1056,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { TraceUtil.beginSection("createCodec:" + codecName); codec = MediaCodec.createByCodecName(codecName); if (enableAsynchronousBufferQueueing && Util.SDK_INT >= 23) { - codecAdapter = - new AsynchronousMediaCodecAdapter( - codec, /* enableAsynchronousQueueing= */ true, getTrackType()); + codecAdapter = new AsynchronousMediaCodecAdapter(codec, getTrackType()); } else { codecAdapter = new SynchronousMediaCodecAdapter(codec); } - TraceUtil.endSection(); TraceUtil.beginSection("configureCodec"); configureCodec(codecInfo, codecAdapter, inputFormat, crypto, codecOperatingRate); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecBufferEnqueuer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecBufferEnqueuer.java deleted file mode 100644 index f16748f8fc..0000000000 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/SynchronousMediaCodecBufferEnqueuer.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.exoplayer2.mediacodec; - -import android.media.MediaCodec; -import com.google.android.exoplayer2.decoder.CryptoInfo; - -/** - * A {@link MediaCodecInputBufferEnqueuer} that forwards queueing methods directly to {@link - * MediaCodec}. - */ -class SynchronousMediaCodecBufferEnqueuer implements MediaCodecInputBufferEnqueuer { - private final MediaCodec codec; - - /** - * Creates an instance that queues input buffers on the specified {@link MediaCodec}. - * - * @param codec The {@link MediaCodec} to submit input buffers to. - */ - SynchronousMediaCodecBufferEnqueuer(MediaCodec codec) { - this.codec = codec; - } - - @Override - public void start() {} - - @Override - public void queueInputBuffer( - int index, int offset, int size, long presentationTimeUs, int flags) { - codec.queueInputBuffer(index, offset, size, presentationTimeUs, flags); - } - - @Override - public void queueSecureInputBuffer( - int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) { - codec.queueSecureInputBuffer( - index, offset, info.getFrameworkCryptoInfo(), presentationTimeUs, flags); - } - - @Override - public void flush() {} - - @Override - public void shutdown() {} -} 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 dc32ce65a1..0c023d3841 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 @@ -48,7 +48,6 @@ public class AsynchronousMediaCodecAdapterTest { adapter = new AsynchronousMediaCodecAdapter( codec, - /* enableAsynchronousQueueing= */ false, /* trackType= */ C.TRACK_TYPE_VIDEO, handlerThread); bufferInfo = new MediaCodec.BufferInfo(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java index 37d31569c3..e27c428a94 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecBufferEnqueuerTest.java @@ -19,6 +19,7 @@ package com.google.android.exoplayer2.mediacodec; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.doAnswer; +import static org.robolectric.Shadows.shadowOf; import android.media.MediaCodec; import android.media.MediaFormat; @@ -28,6 +29,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.decoder.CryptoInfo; import com.google.android.exoplayer2.util.ConditionVariable; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicLong; import org.junit.After; import org.junit.Before; @@ -66,6 +68,33 @@ public class AsynchronousMediaCodecBufferEnqueuerTest { assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0); } + @Test + public void queueInputBuffer_queuesInputBufferOnMediaCodec() { + enqueuer.start(); + int inputBufferIndex = codec.dequeueInputBuffer(0); + assertThat(inputBufferIndex).isAtLeast(0); + byte[] inputData = new byte[] {0, 1, 2, 3}; + codec.getInputBuffer(inputBufferIndex).put(inputData); + + enqueuer.queueInputBuffer( + inputBufferIndex, + /* offset= */ 0, + /* size= */ 4, + /* presentationTimeUs= */ 0, + /* flags= */ 0); + shadowOf(handlerThread.getLooper()).idle(); + + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + assertThat(codec.dequeueOutputBuffer(bufferInfo, 0)) + .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); + assertThat(codec.dequeueOutputBuffer(bufferInfo, 0)).isEqualTo(inputBufferIndex); + ByteBuffer outputBuffer = codec.getOutputBuffer(inputBufferIndex); + assertThat(outputBuffer.limit()).isEqualTo(4); + byte[] outputData = new byte[4]; + outputBuffer.get(outputData); + assertThat(outputData).isEqualTo(inputData); + } + @Test public void queueInputBuffer_withPendingCryptoExceptionSet_throwsCryptoException() { enqueuer.setPendingRuntimeException( @@ -111,7 +140,7 @@ public class AsynchronousMediaCodecBufferEnqueuerTest { enqueuer.queueSecureInputBuffer( /* index= */ 0, /* offset= */ 0, - /* info= */ info, + info, /* presentationTimeUs= */ 0, /* flags= */ 0)); }