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
deleted file mode 100644
index 040ef340ed..0000000000
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapter.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.media.MediaFormat;
-import android.os.Handler;
-import android.os.Looper;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.VisibleForTesting;
-import com.google.android.exoplayer2.decoder.CryptoInfo;
-import com.google.android.exoplayer2.util.Assertions;
-
-/**
- * A {@link MediaCodecAdapter} that operates the {@link MediaCodec} in asynchronous mode.
- *
- *
The AsynchronousMediaCodecAdapter routes callbacks to the current thread's {@link Looper}
- * obtained via {@link Looper#myLooper()}
- */
-@RequiresApi(21)
-/* package */ final class AsynchronousMediaCodecAdapter implements MediaCodecAdapter {
- private final MediaCodecAsyncCallback mediaCodecAsyncCallback;
- private final Handler handler;
- private final MediaCodec codec;
- @Nullable private IllegalStateException internalException;
- private boolean flushing;
- private Runnable codecStartRunnable;
-
- /**
- * Create a new {@code AsynchronousMediaCodecAdapter}.
- *
- * @param codec The {@link MediaCodec} to wrap.
- */
- public AsynchronousMediaCodecAdapter(MediaCodec codec) {
- this(codec, Assertions.checkNotNull(Looper.myLooper()));
- }
-
- @VisibleForTesting
- /* package */ AsynchronousMediaCodecAdapter(MediaCodec codec, Looper looper) {
- mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
- handler = new Handler(looper);
- this.codec = codec;
- this.codec.setCallback(mediaCodecAsyncCallback);
- codecStartRunnable = codec::start;
- }
-
- @Override
- public void start() {
- codecStartRunnable.run();
- }
-
- @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 int dequeueInputBufferIndex() {
- if (flushing) {
- return MediaCodec.INFO_TRY_AGAIN_LATER;
- } else {
- maybeThrowException();
- return mediaCodecAsyncCallback.dequeueInputBufferIndex();
- }
- }
-
- @Override
- public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
- if (flushing) {
- return MediaCodec.INFO_TRY_AGAIN_LATER;
- } else {
- maybeThrowException();
- return mediaCodecAsyncCallback.dequeueOutputBufferIndex(bufferInfo);
- }
- }
-
- @Override
- public MediaFormat getOutputFormat() {
- return mediaCodecAsyncCallback.getOutputFormat();
- }
-
- @Override
- public void flush() {
- clearPendingFlushState();
- flushing = true;
- codec.flush();
- handler.post(this::onCompleteFlush);
- }
-
- @Override
- public void shutdown() {
- clearPendingFlushState();
- }
-
- @VisibleForTesting
- /* package */ MediaCodec.Callback getMediaCodecCallback() {
- return mediaCodecAsyncCallback;
- }
-
- private void onCompleteFlush() {
- flushing = false;
- mediaCodecAsyncCallback.flush();
- try {
- codecStartRunnable.run();
- } catch (IllegalStateException e) {
- // Catch IllegalStateException directly so that we don't have to wrap it.
- internalException = e;
- } catch (Exception e) {
- internalException = new IllegalStateException(e);
- }
- }
-
- @VisibleForTesting
- /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
- this.codecStartRunnable = codecStartRunnable;
- }
-
- private void maybeThrowException() throws IllegalStateException {
- maybeThrowInternalException();
- mediaCodecAsyncCallback.maybeThrowMediaCodecException();
- }
-
- private void maybeThrowInternalException() {
- if (internalException != null) {
- IllegalStateException e = internalException;
- internalException = null;
- throw e;
- }
- }
-
- /** Clear state related to pending flush events. */
- private void clearPendingFlushState() {
- handler.removeCallbacksAndMessages(null);
- internalException = null;
- }
-}
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 b85fbe3a71..27f0621cfc 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
@@ -73,11 +73,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*
*
* - {@link #OPERATION_MODE_SYNCHRONOUS}
- *
- {@link #OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD}
*
- {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD}
- *
- {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK}
*
- {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING}
- *
- {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING}
*
*/
@Documented
@@ -85,42 +82,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@IntDef({
OPERATION_MODE_SYNCHRONOUS,
- OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD,
OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD,
- OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK,
OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING,
- OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK_ASYNCHRONOUS_QUEUEING
})
public @interface MediaCodecOperationMode {}
+ // TODO: Refactor these constants once internal evaluation completed.
+ // Do not assign values 1, 3 and 5 to a new operation mode until the evaluation is completed,
+ // otherwise existing clients may operate one of the dropped modes.
+ // [Internal ref: b/132684114]
/** Operates the {@link MediaCodec} in synchronous mode. */
public static final int OPERATION_MODE_SYNCHRONOUS = 0;
- /**
- * Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback}
- * callbacks to the playback thread.
- */
- public static final int OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD = 1;
/**
* Operates the {@link MediaCodec} in asynchronous mode and routes {@link MediaCodec.Callback}
* callbacks to a dedicated thread.
*/
public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD = 2;
- /**
- * 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.
- */
- public static final int OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK = 3;
/**
* Same as {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD}, and offloads queueing to another
* thread.
*/
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. */
public static class DecoderInitializationException extends Exception {
@@ -488,25 +470,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*
* - {@link #OPERATION_MODE_SYNCHRONOUS}: The {@link MediaCodec} will operate in
* synchronous mode.
- *
- {@link #OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD}: The {@link MediaCodec} will
- * operate in asynchronous mode and {@link MediaCodec.Callback} callbacks will be routed
- * to the playback thread. This mode requires API level ≥ 21; if the API level is
- * ≤ 20, the operation mode will be set to {@link
- * MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}.
*
- {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD}: The {@link MediaCodec} will
* operate in asynchronous mode and {@link MediaCodec.Callback} callbacks will be routed
* to a dedicated thread. This mode requires API level ≥ 23; if the API level is ≤
* 22, the operation mode will be set to {@link #OPERATION_MODE_SYNCHRONOUS}.
- *
- {@link #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK}: Same as {@link
- * #OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD} and, in addition, input buffers will
- * submitted to the {@link MediaCodec} in a separate thread.
*
- {@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.
- *
- {@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.
*
* By default, the operation mode is set to {@link
* MediaCodecRenderer#OPERATION_MODE_SYNCHRONOUS}.
@@ -1103,27 +1073,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecInitializingTimestamp = SystemClock.elapsedRealtime();
TraceUtil.beginSection("createCodec:" + codecName);
codec = MediaCodec.createByCodecName(codecName);
- if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_PLAYBACK_THREAD
- && Util.SDK_INT >= 21) {
- codecAdapter = new AsynchronousMediaCodecAdapter(codec);
- } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD
+ if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD
&& Util.SDK_INT >= 23) {
codecAdapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, getTrackType());
- } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK
- && Util.SDK_INT >= 23) {
- codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType());
} else if (mediaCodecOperationMode
== OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING
&& Util.SDK_INT >= 23) {
codecAdapter =
new DedicatedThreadAsyncMediaCodecAdapter(
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 {
codecAdapter = new SynchronousMediaCodecAdapter(codec);
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapter.java
deleted file mode 100644
index d51f985ed7..0000000000
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapter.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.media.MediaFormat;
-import android.os.Handler;
-import android.os.HandlerThread;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.VisibleForTesting;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.decoder.CryptoInfo;
-import com.google.android.exoplayer2.util.IntArrayQueue;
-import com.google.android.exoplayer2.util.Util;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayDeque;
-import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
-
-/**
- * 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
- * internally.
- *
- * The main difference of this class compared to the {@link
- * DedicatedThreadAsyncMediaCodecAdapter} is that its internal implementation applies finer-grained
- * 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
- * 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
- * MediaCodec.Callback} methods will be accessed by the internal thread. This class is
- * NOT generally thread-safe in the sense that its public methods cannot be called
- * by any thread.
- */
-@RequiresApi(23)
-/* package */ final class MultiLockAsyncMediaCodecAdapter extends MediaCodec.Callback
- implements MediaCodecAdapter {
-
- @Documented
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({STATE_CREATED, STATE_STARTED, STATE_SHUT_DOWN})
- private @interface State {}
-
- private static final int STATE_CREATED = 0;
- private static final int STATE_STARTED = 1;
- private static final int STATE_SHUT_DOWN = 2;
-
- private final MediaCodec codec;
- private final Object inputBufferLock;
- private final Object outputBufferLock;
- private final Object objectStateLock;
-
- @GuardedBy("inputBufferLock")
- private final IntArrayQueue availableInputBuffers;
-
- @GuardedBy("outputBufferLock")
- private final IntArrayQueue availableOutputBuffers;
-
- @GuardedBy("outputBufferLock")
- private final ArrayDeque bufferInfos;
-
- @GuardedBy("outputBufferLock")
- private final ArrayDeque formats;
-
- @GuardedBy("objectStateLock")
- private @MonotonicNonNull MediaFormat currentFormat;
-
- @GuardedBy("objectStateLock")
- private long pendingFlush;
-
- @GuardedBy("objectStateLock")
- @Nullable
- private IllegalStateException codecException;
-
- private final HandlerThread handlerThread;
- private @MonotonicNonNull Handler handler;
- private Runnable codecStartRunnable;
- private final MediaCodecInputBufferEnqueuer bufferEnqueuer;
-
- @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) {
- 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
- /* package */ MultiLockAsyncMediaCodecAdapter(
- MediaCodec codec,
- boolean enableAsynchronousQueueing,
- int trackType,
- HandlerThread handlerThread) {
- this.codec = codec;
- inputBufferLock = new Object();
- outputBufferLock = new Object();
- objectStateLock = new Object();
- availableInputBuffers = new IntArrayQueue();
- availableOutputBuffers = new IntArrayQueue();
- bufferInfos = new ArrayDeque<>();
- formats = new ArrayDeque<>();
- codecException = null;
- this.handlerThread = handlerThread;
- codecStartRunnable = codec::start;
- if (enableAsynchronousQueueing) {
- bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, trackType);
- } else {
- bufferEnqueuer = new SynchronousMediaCodecBufferEnqueuer(codec);
- }
- state = STATE_CREATED;
- }
-
- @Override
- public void start() {
- synchronized (objectStateLock) {
- handlerThread.start();
- handler = new Handler(handlerThread.getLooper());
- codec.setCallback(this, handler);
- bufferEnqueuer.start();
- codecStartRunnable.run();
- state = STATE_STARTED;
- }
- }
-
- @Override
- public int dequeueInputBufferIndex() {
- synchronized (objectStateLock) {
- if (isFlushing()) {
- return MediaCodec.INFO_TRY_AGAIN_LATER;
- } else {
- maybeThrowException();
- return dequeueAvailableInputBufferIndex();
- }
- }
- }
-
- @Override
- public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
- synchronized (objectStateLock) {
- if (isFlushing()) {
- return MediaCodec.INFO_TRY_AGAIN_LATER;
- } else {
- maybeThrowException();
- return dequeueAvailableOutputBufferIndex(bufferInfo);
- }
- }
- }
-
- @Override
- public MediaFormat getOutputFormat() {
- synchronized (objectStateLock) {
- if (currentFormat == null) {
- throw new IllegalStateException();
- }
-
- return currentFormat;
- }
- }
-
- @Override
- public void queueInputBuffer(
- int index, int offset, int size, long presentationTimeUs, int flags) {
- // This method does not need to be synchronized because it is not interacting with
- // MediaCodec.Callback and dequeueing buffers operations.
- bufferEnqueuer.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
- }
-
- @Override
- public void queueSecureInputBuffer(
- int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) {
- // This method does not need to be synchronized because it is not interacting with
- // MediaCodec.Callback and dequeueing buffers operations.
- bufferEnqueuer.queueSecureInputBuffer(index, offset, info, presentationTimeUs, flags);
- }
-
- @Override
- public void flush() {
- synchronized (objectStateLock) {
- bufferEnqueuer.flush();
- codec.flush();
- pendingFlush++;
- Util.castNonNull(handler).post(this::onFlushComplete);
- }
- }
-
- @Override
- public void shutdown() {
- synchronized (objectStateLock) {
- if (state == STATE_STARTED) {
- bufferEnqueuer.shutdown();
- handlerThread.quit();
- }
- state = STATE_SHUT_DOWN;
- }
- }
-
- @VisibleForTesting
- /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
- this.codecStartRunnable = codecStartRunnable;
- }
-
- private int dequeueAvailableInputBufferIndex() {
- synchronized (inputBufferLock) {
- return availableInputBuffers.isEmpty()
- ? MediaCodec.INFO_TRY_AGAIN_LATER
- : availableInputBuffers.remove();
- }
- }
-
- @GuardedBy("objectStateLock")
- private int dequeueAvailableOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
- int bufferIndex;
- synchronized (outputBufferLock) {
- if (availableOutputBuffers.isEmpty()) {
- bufferIndex = MediaCodec.INFO_TRY_AGAIN_LATER;
- } else {
- bufferIndex = availableOutputBuffers.remove();
- if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- currentFormat = formats.remove();
- } else if (bufferIndex >= 0) {
- MediaCodec.BufferInfo outBufferInfo = bufferInfos.remove();
- bufferInfo.set(
- outBufferInfo.offset,
- outBufferInfo.size,
- outBufferInfo.presentationTimeUs,
- outBufferInfo.flags);
- }
- }
- }
- return bufferIndex;
- }
-
- @GuardedBy("objectStateLock")
- private boolean isFlushing() {
- return pendingFlush > 0;
- }
-
- @GuardedBy("objectStateLock")
- private void maybeThrowException() {
- @Nullable IllegalStateException exception = codecException;
- if (exception != null) {
- codecException = null;
- throw exception;
- }
- }
-
- // Called by the internal thread.
-
- @Override
- public void onInputBufferAvailable(MediaCodec codec, int index) {
- synchronized (inputBufferLock) {
- availableInputBuffers.add(index);
- }
- }
-
- @Override
- public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
- synchronized (outputBufferLock) {
- availableOutputBuffers.add(index);
- bufferInfos.add(info);
- }
- }
-
- @Override
- public void onError(MediaCodec codec, MediaCodec.CodecException e) {
- onMediaCodecError(e);
- }
-
- @Override
- public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
- synchronized (outputBufferLock) {
- availableOutputBuffers.add(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
- formats.add(format);
- }
- }
-
- @VisibleForTesting
- /* package */ void onMediaCodecError(IllegalStateException e) {
- synchronized (objectStateLock) {
- codecException = e;
- }
- }
-
- private void onFlushComplete() {
- synchronized (objectStateLock) {
- if (state == STATE_SHUT_DOWN) {
- return;
- }
-
- --pendingFlush;
- if (pendingFlush > 0) {
- // Another flush() has been called.
- return;
- } else if (pendingFlush < 0) {
- // This should never happen.
- codecException = new IllegalStateException();
- return;
- }
-
- clearAvailableInput();
- clearAvailableOutput();
- codecException = null;
- try {
- codecStartRunnable.run();
- } catch (IllegalStateException e) {
- codecException = e;
- } catch (Exception e) {
- codecException = new IllegalStateException(e);
- }
- }
- }
-
- private void clearAvailableInput() {
- synchronized (inputBufferLock) {
- availableInputBuffers.clear();
- }
- }
-
- private void clearAvailableOutput() {
- synchronized (outputBufferLock) {
- availableOutputBuffers.clear();
- bufferInfos.clear();
- formats.clear();
- }
- }
-
- private static String createThreadLabel(int trackType) {
- StringBuilder labelBuilder = new StringBuilder("ExoPlayer:MediaCodecAsyncAdapter:");
- if (trackType == C.TRACK_TYPE_AUDIO) {
- labelBuilder.append("Audio");
- } else if (trackType == C.TRACK_TYPE_VIDEO) {
- labelBuilder.append("Video");
- } else {
- labelBuilder.append("Unknown(").append(trackType).append(")");
- }
- return labelBuilder.toString();
- }
-}
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
deleted file mode 100644
index c36bf74c9c..0000000000
--- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/AsynchronousMediaCodecAdapterTest.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright (C) 2019 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 static com.google.android.exoplayer2.testutil.TestUtil.assertBufferInfosEqual;
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-import static org.robolectric.Shadows.shadowOf;
-import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
-
-import android.media.MediaCodec;
-import android.media.MediaFormat;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.annotation.LooperMode;
-
-/** Unit tests for {@link AsynchronousMediaCodecAdapter}. */
-@LooperMode(LEGACY)
-@RunWith(AndroidJUnit4.class)
-public class AsynchronousMediaCodecAdapterTest {
- private AsynchronousMediaCodecAdapter adapter;
- private MediaCodec codec;
- private HandlerThread handlerThread;
- private Looper looper;
- private MediaCodec.BufferInfo bufferInfo;
-
- @Before
- public void setUp() throws IOException {
- handlerThread = new HandlerThread("TestHandler");
- handlerThread.start();
- looper = handlerThread.getLooper();
- codec = MediaCodec.createByCodecName("h264");
- adapter = new AsynchronousMediaCodecAdapter(codec, looper);
- adapter.setCodecStartRunnable(() -> {});
- bufferInfo = new MediaCodec.BufferInfo();
- }
-
- @After
- public void tearDown() {
- adapter.shutdown();
- handlerThread.quit();
- }
-
- @Test
- public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
- adapter.start();
-
- assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- }
-
- @Test
- public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() {
- adapter.start();
- adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
-
- assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0);
- }
-
- @Test
- public void dequeueInputBufferIndex_whileFlushing_returnsTryAgainLater() {
- adapter.start();
-
- adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
- // A callback that is pending.
- new Handler(looper)
- .post(() -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1));
- adapter.flush();
-
- assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- }
-
- @Test
- public void dequeueInputBufferIndex_afterFlushCompletes_returnsNextInputBuffer() {
- adapter.start();
- Handler handler = new Handler(looper);
- handler.post(
- () -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0));
- adapter.flush(); // enqueues a flush event on the looper
- handler.post(
- () -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1));
-
- // Wait until all tasks have been handled.
- shadowOf(looper).idle();
- assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(1);
- }
-
- @Test
- public void dequeueInputBufferIndex_afterFlushCompletesWithError_throwsException() {
- AtomicInteger calls = new AtomicInteger(0);
- adapter.setCodecStartRunnable(
- () -> {
- if (calls.incrementAndGet() == 2) {
- throw new IllegalStateException();
- }
- });
- adapter.start();
- adapter.flush();
-
- // Wait until all tasks have been handled.
- shadowOf(looper).idle();
- assertThrows(
- IllegalStateException.class,
- () -> {
- adapter.dequeueInputBufferIndex();
- });
- }
-
- @Test
- public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() {
- adapter.start();
-
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
- .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- }
-
- @Test
- public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() {
- adapter.start();
- MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
- outBufferInfo.presentationTimeUs = 10;
- adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, outBufferInfo);
-
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)).isEqualTo(0);
- assertBufferInfosEqual(bufferInfo, outBufferInfo);
- }
-
- @Test
- public void dequeueOutputBufferIndex_whileFlushing_returnsTryAgainLater() {
- adapter.start();
- adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, bufferInfo);
- adapter.flush();
- adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, bufferInfo);
-
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
- .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- }
-
- @Test
- public void dequeueOutputBufferIndex_afterFlushCompletes_returnsNextOutputBuffer() {
- adapter.start();
- Handler handler = new Handler(looper);
- MediaCodec.BufferInfo info0 = new MediaCodec.BufferInfo();
- handler.post(
- () -> adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, info0));
- adapter.flush(); // enqueues a flush event on the looper
- MediaCodec.BufferInfo info1 = new MediaCodec.BufferInfo();
- info1.presentationTimeUs = 1;
- handler.post(
- () -> adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, info1));
-
- // Wait until all tasks have been handled.
- shadowOf(looper).idle();
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)).isEqualTo(1);
- assertBufferInfosEqual(info1, bufferInfo);
- }
-
- @Test
- public void dequeueOutputBufferIndex_afterFlushCompletesWithError_throwsException() {
- AtomicInteger calls = new AtomicInteger(0);
- adapter.setCodecStartRunnable(
- () -> {
- if (calls.incrementAndGet() == 2) {
- throw new RuntimeException("codec#start() exception");
- }
- });
- adapter.start();
- adapter.flush();
-
- // Wait until all tasks have been handled.
- shadowOf(looper).idle();
- assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
- }
-
- @Test
- public void dequeueOutputBufferIndex_withPendingOutputFormat_returnsPendingOutputFormat() {
- MediaFormat pendingOutputFormat = new MediaFormat();
- MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
- MediaCodec.Callback mediaCodecCallback = adapter.getMediaCodecCallback();
- Handler handler = new Handler(looper);
- adapter.start();
-
- // Enqueue callbacks
- handler.post(() -> mediaCodecCallback.onOutputFormatChanged(codec, new MediaFormat()));
- handler.post(
- () ->
- mediaCodecCallback.onOutputBufferAvailable(
- codec, /* index= */ 0, new MediaCodec.BufferInfo()));
- handler.post(() -> mediaCodecCallback.onOutputFormatChanged(codec, pendingOutputFormat));
- handler.post(
- () ->
- mediaCodecCallback.onOutputBufferAvailable(
- codec, /* index= */ 1, new MediaCodec.BufferInfo()));
- adapter.flush();
- // After flush is complete, MediaCodec sends on output buffer.
- handler.post(
- () ->
- mediaCodecCallback.onOutputBufferAvailable(
- codec, /* index= */ 2, new MediaCodec.BufferInfo()));
- shadowOf(looper).idle();
-
- assertThat(adapter.dequeueOutputBufferIndex(outBufferInfo))
- .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
- assertThat(adapter.getOutputFormat()).isEqualTo(pendingOutputFormat);
- assertThat(adapter.dequeueOutputBufferIndex(outBufferInfo)).isEqualTo(2);
- }
-
- @Test
- public void dequeueOutputBufferIndex_withPendingAndNewOutputFormat_returnsNewOutputFormat() {
- MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
- MediaCodec.Callback mediaCodecCallback = adapter.getMediaCodecCallback();
- Handler handler = new Handler(looper);
- adapter.start();
-
- // Enqueue callbacks
- handler.post(() -> mediaCodecCallback.onOutputFormatChanged(codec, new MediaFormat()));
- handler.post(
- () ->
- mediaCodecCallback.onOutputBufferAvailable(
- codec, /* index= */ 0, new MediaCodec.BufferInfo()));
- adapter.flush();
- // After flush is complete, MediaCodec sends an output format change, it should overwrite
- // the pending format.
- MediaFormat newMediaFormat = new MediaFormat();
- handler.post(() -> mediaCodecCallback.onOutputFormatChanged(codec, newMediaFormat));
- shadowOf(looper).idle();
-
- assertThat(adapter.dequeueOutputBufferIndex(outBufferInfo))
- .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
- assertThat(adapter.getOutputFormat()).isEqualTo(newMediaFormat);
- }
-
- @Test
- public void getOutputFormat_withMultipleFormats_returnsFormatsInCorrectOrder() {
- adapter.start();
- MediaFormat[] formats = new MediaFormat[10];
- MediaCodec.Callback mediaCodecCallback = adapter.getMediaCodecCallback();
- for (int i = 0; i < formats.length; i++) {
- formats[i] = new MediaFormat();
- mediaCodecCallback.onOutputFormatChanged(codec, formats[i]);
- }
-
- for (MediaFormat format : formats) {
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
- .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
- assertThat(adapter.getOutputFormat()).isEqualTo(format);
- // Call it again to ensure same format is returned
- assertThat(adapter.getOutputFormat()).isEqualTo(format);
- }
- // Obtain next output buffer
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
- .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- // Format should remain as is
- assertThat(adapter.getOutputFormat()).isEqualTo(formats[formats.length - 1]);
- }
-
- @Test
- public void getOutputFormat_afterFlush_returnsPreviousFormat() {
- adapter.start();
- MediaFormat format = new MediaFormat();
- adapter.getMediaCodecCallback().onOutputFormatChanged(codec, format);
- adapter.dequeueOutputBufferIndex(bufferInfo);
- adapter.flush();
-
- // Wait until all tasks have been handled.
- shadowOf(looper).idle();
- assertThat(adapter.getOutputFormat()).isEqualTo(format);
- }
-
- @Test
- public void shutdown_withPendingFlush_cancelsFlush() {
- AtomicInteger onCodecStartCalled = new AtomicInteger(0);
- adapter.setCodecStartRunnable(() -> onCodecStartCalled.incrementAndGet());
- adapter.start();
- adapter.flush();
- adapter.shutdown();
-
- // Wait until all tasks have been handled.
- shadowOf(looper).idle();
- assertThat(onCodecStartCalled.get()).isEqualTo(1);
- }
-}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapterTest.java
deleted file mode 100644
index cfe9cf2900..0000000000
--- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MultiLockAsyncMediaCodecAdapterTest.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2019 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 static com.google.android.exoplayer2.testutil.TestUtil.assertBufferInfosEqual;
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-import static org.robolectric.Shadows.shadowOf;
-import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
-
-import android.media.MediaCodec;
-import android.media.MediaFormat;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.C;
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.Shadows;
-import org.robolectric.annotation.LooperMode;
-import org.robolectric.shadows.ShadowLooper;
-
-/** Unit tests for {@link MultiLockAsyncMediaCodecAdapter}. */
-@LooperMode(LEGACY)
-@RunWith(AndroidJUnit4.class)
-public class MultiLockAsyncMediaCodecAdapterTest {
- private MultiLockAsyncMediaCodecAdapter adapter;
- private MediaCodec codec;
- private MediaCodec.BufferInfo bufferInfo;
- private TestHandlerThread handlerThread;
-
- @Before
- public void setUp() throws IOException {
- codec = MediaCodec.createByCodecName("h264");
- handlerThread = new TestHandlerThread("TestHandlerThread");
- adapter =
- new MultiLockAsyncMediaCodecAdapter(
- codec, /* enableAsynchronousQueueing= */ false, C.TRACK_TYPE_VIDEO, handlerThread);
- adapter.setCodecStartRunnable(() -> {});
- bufferInfo = new MediaCodec.BufferInfo();
- }
-
- @After
- public void tearDown() {
- adapter.shutdown();
-
- assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0);
- }
-
- @Test
- public void startAndShutdown_works() {
- adapter.start();
- adapter.shutdown();
- }
-
- @Test
- public void dequeueInputBufferIndex_withAfterFlushFailed_throwsException()
- throws InterruptedException {
- AtomicInteger codecStartCalls = new AtomicInteger(0);
- adapter.setCodecStartRunnable(
- () -> {
- if (codecStartCalls.incrementAndGet() == 2) {
- throw new IllegalStateException("codec#start() exception");
- }
- });
- adapter.start();
- adapter.flush();
-
- Shadows.shadowOf(handlerThread.getLooper()).idle();
- assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex());
- }
-
- @Test
- public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
- adapter.start();
-
- assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- }
-
- @Test
- public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() {
- adapter.start();
- adapter.onInputBufferAvailable(codec, 0);
-
- assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0);
- }
-
- @Test
- public void dequeueInputBufferIndex_withPendingFlush_returnsTryAgainLater() {
- adapter.start();
- adapter.onInputBufferAvailable(codec, 0);
- adapter.flush();
-
- assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- }
-
- @Test
- public void dequeueInputBufferIndex_withFlushCompletedAndInputBuffer_returnsInputBuffer()
- throws InterruptedException {
- adapter.start();
- Looper looper = handlerThread.getLooper();
- Handler handler = new Handler(looper);
- // Enqueue 10 callbacks from codec
- for (int i = 0; i < 10; i++) {
- int bufferIndex = i;
- handler.post(() -> adapter.onInputBufferAvailable(codec, bufferIndex));
- }
- adapter.flush(); // Enqueues a flush event after the onInputBufferAvailable callbacks
- // Enqueue another onInputBufferAvailable after the flush event
- handler.post(() -> adapter.onInputBufferAvailable(codec, 10));
-
- // Wait until all tasks have been handled
- Shadows.shadowOf(handlerThread.getLooper()).idle();
- assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(10);
- }
-
- @Test
- public void dequeueInputBufferIndex_withMediaCodecError_throwsException() {
- adapter.start();
- adapter.onMediaCodecError(new IllegalStateException("error from codec"));
-
- assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex());
- }
-
-
- @Test
- public void dequeueOutputBufferIndex_withInternalException_throwsException()
- throws InterruptedException {
- AtomicInteger codecStartCalls = new AtomicInteger(0);
- adapter.setCodecStartRunnable(
- () -> {
- if (codecStartCalls.incrementAndGet() == 2) {
- throw new RuntimeException("codec#start() exception");
- }
- });
- adapter.start();
- adapter.flush();
-
- Shadows.shadowOf(handlerThread.getLooper()).idle();
- assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
- }
-
- @Test
- public void dequeueOutputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
- adapter.start();
-
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
- .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- }
-
- @Test
- public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() {
- adapter.start();
- MediaCodec.BufferInfo enqueuedBufferInfo = new MediaCodec.BufferInfo();
- adapter.onOutputBufferAvailable(codec, 0, enqueuedBufferInfo);
-
- assertThat(adapter.dequeueOutputBufferIndex((bufferInfo))).isEqualTo(0);
- assertBufferInfosEqual(enqueuedBufferInfo, bufferInfo);
- }
-
- @Test
- public void dequeueOutputBufferIndex_withPendingFlush_returnsTryAgainLater() {
- adapter.start();
- adapter.dequeueOutputBufferIndex(bufferInfo);
- adapter.flush();
-
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
- .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- }
-
- @Test
- public void dequeueOutputBufferIndex_withFlushCompletedAndOutputBuffer_returnsOutputBuffer()
- throws InterruptedException {
- adapter.start();
- Looper looper = handlerThread.getLooper();
- Handler handler = new Handler(looper);
- // Enqueue 10 callbacks from codec
- for (int i = 0; i < 10; i++) {
- int bufferIndex = i;
- MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
- outBufferInfo.presentationTimeUs = i;
- handler.post(() -> adapter.onOutputBufferAvailable(codec, bufferIndex, outBufferInfo));
- }
- adapter.flush(); // Enqueues a flush event after the onOutputBufferAvailable callbacks
- // Enqueue another onOutputBufferAvailable after the flush event
- MediaCodec.BufferInfo lastBufferInfo = new MediaCodec.BufferInfo();
- lastBufferInfo.presentationTimeUs = 10;
- handler.post(() -> adapter.onOutputBufferAvailable(codec, 10, lastBufferInfo));
-
- // Wait until all tasks have been handled
- Shadows.shadowOf(handlerThread.getLooper()).idle();
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)).isEqualTo(10);
- assertBufferInfosEqual(lastBufferInfo, bufferInfo);
- }
-
- @Test
- public void dequeueOutputBufferIndex_withMediaCodecError_throwsException() {
- adapter.start();
- adapter.onMediaCodecError(new IllegalStateException("error from codec"));
-
- assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
- }
-
- @Test
- public void getOutputFormat_withoutFormatReceived_throwsException() {
- adapter.start();
-
- assertThrows(IllegalStateException.class, () -> adapter.getOutputFormat());
- }
-
- @Test
- public void getOutputFormat_withMultipleFormats_returnsCorrectFormat() {
- adapter.start();
- MediaFormat[] formats = new MediaFormat[10];
- for (int i = 0; i < formats.length; i++) {
- formats[i] = new MediaFormat();
- adapter.onOutputFormatChanged(codec, formats[i]);
- }
-
- for (int i = 0; i < 10; i++) {
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
- .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
- assertThat(adapter.getOutputFormat()).isEqualTo(formats[i]);
- // A subsequent call to getOutputFormat() should return the previously fetched format
- assertThat(adapter.getOutputFormat()).isEqualTo(formats[i]);
- }
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
- .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
- }
-
- @Test
- public void getOutputFormat_afterFlush_returnsPreviousFormat() {
- MediaFormat format = new MediaFormat();
- adapter.start();
- adapter.onOutputFormatChanged(codec, format);
-
- assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
- .isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
- assertThat(adapter.getOutputFormat()).isEqualTo(format);
-
- adapter.flush();
- Shadows.shadowOf(handlerThread.getLooper()).idle();
- assertThat(adapter.getOutputFormat()).isEqualTo(format);
- }
-
- @Test
- public void flush_multipleTimes_onlyLastFlushExecutes() {
- AtomicInteger codecStartCalls = new AtomicInteger(0);
- adapter.setCodecStartRunnable(() -> codecStartCalls.incrementAndGet());
- adapter.start();
- Looper looper = handlerThread.getLooper();
- Handler handler = new Handler(looper);
- handler.post(() -> adapter.onInputBufferAvailable(codec, 0));
- adapter.flush(); // Enqueues a flush event
- handler.post(() -> adapter.onInputBufferAvailable(codec, 2));
- AtomicInteger milestoneCount = new AtomicInteger(0);
- handler.post(() -> milestoneCount.incrementAndGet());
- adapter.flush(); // Enqueues a second flush event
- handler.post(() -> adapter.onInputBufferAvailable(codec, 3));
-
- // Progress the looper until the milestoneCount is increased:
- // adapter.start() called codec.start() but first flush event should have been a no-op
- ShadowLooper shadowLooper = shadowOf(looper);
- while (milestoneCount.get() < 1) {
- shadowLooper.runOneTask();
- }
- assertThat(codecStartCalls.get()).isEqualTo(1);
-
- shadowLooper.idle();
- assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(3);
- assertThat(codecStartCalls.get()).isEqualTo(2);
- }
-
- @Test
- public void flush_andImmediatelyShutdown_flushIsNoOp() {
- AtomicInteger codecStartCalls = new AtomicInteger(0);
- adapter.setCodecStartRunnable(() -> codecStartCalls.incrementAndGet());
- adapter.start();
- // Grab reference to Looper before shutting down the adapter otherwise handlerThread.getLooper()
- // might return null.
- Looper looper = handlerThread.getLooper();
- adapter.flush();
- adapter.shutdown();
-
- Shadows.shadowOf(looper).idle();
- // Only adapter.start() called codec#start()
- assertThat(codecStartCalls.get()).isEqualTo(1);
- }
-
- private static class TestHandlerThread extends HandlerThread {
-
- private static final AtomicLong INSTANCES_STARTED = new AtomicLong(0);
-
- public TestHandlerThread(String name) {
- super(name);
- }
-
- @Override
- public synchronized void start() {
- super.start();
- INSTANCES_STARTED.incrementAndGet();
- }
-
- @Override
- public boolean quit() {
- boolean quit = super.quit();
- INSTANCES_STARTED.decrementAndGet();
- return quit;
- }
- }
-}