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 b5eb8efee3..18c7b1c201 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 @@ -38,7 +38,7 @@ import com.google.android.exoplayer2.util.Assertions; private final MediaCodec codec; @Nullable private IllegalStateException internalException; private boolean flushing; - private Runnable onCodecStart; + private Runnable codecStartRunnable; /** * Create a new {@code AsynchronousMediaCodecAdapter}. @@ -55,7 +55,12 @@ import com.google.android.exoplayer2.util.Assertions; handler = new Handler(looper); this.codec = codec; this.codec.setCallback(mediaCodecAsyncCallback); - onCodecStart = () -> codec.start(); + codecStartRunnable = codec::start; + } + + @Override + public void start() { + codecStartRunnable.run(); } @Override @@ -105,7 +110,7 @@ import com.google.android.exoplayer2.util.Assertions; flushing = false; mediaCodecAsyncCallback.flush(); try { - onCodecStart.run(); + codecStartRunnable.run(); } catch (IllegalStateException e) { // Catch IllegalStateException directly so that we don't have to wrap it. internalException = e; @@ -115,8 +120,8 @@ import com.google.android.exoplayer2.util.Assertions; } @VisibleForTesting - /* package */ void setOnCodecStart(Runnable onCodecStart) { - this.onCodecStart = onCodecStart; + /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) { + this.codecStartRunnable = codecStartRunnable; } private void maybeThrowException() throws IllegalStateException { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapter.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapter.java index bad21f91f8..b623811453 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapter.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapter.java @@ -26,7 +26,6 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -54,7 +53,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @MonotonicNonNull private Handler handler; private long pendingFlushCount; private @State int state; - private Runnable onCodecStart; + private Runnable codecStartRunnable; @Nullable private IllegalStateException internalException; /** @@ -77,31 +76,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; this.codec = codec; this.handlerThread = handlerThread; state = STATE_CREATED; - onCodecStart = codec::start; + codecStartRunnable = codec::start; } - /** - * Starts the operation of the instance. - * - *

After a call to this method, make sure to call {@link #shutdown()} to terminate the internal - * Thread. You can only call this method once during the lifetime of this instance; calling this - * method again will throw an {@link IllegalStateException}. - * - * @throws IllegalStateException If this method has been called already. - */ + @Override public synchronized void start() { - Assertions.checkState(state == STATE_CREATED); - handlerThread.start(); handler = new Handler(handlerThread.getLooper()); codec.setCallback(this, handler); + codecStartRunnable.run(); state = STATE_STARTED; } @Override public synchronized int dequeueInputBufferIndex() { - Assertions.checkState(state == STATE_STARTED); - if (isFlushing()) { return MediaCodec.INFO_TRY_AGAIN_LATER; } else { @@ -112,8 +100,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public synchronized int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { - Assertions.checkState(state == STATE_STARTED); - if (isFlushing()) { return MediaCodec.INFO_TRY_AGAIN_LATER; } else { @@ -124,15 +110,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public synchronized MediaFormat getOutputFormat() { - Assertions.checkState(state == STATE_STARTED); - return mediaCodecAsyncCallback.getOutputFormat(); } @Override public synchronized void flush() { - Assertions.checkState(state == STATE_STARTED); - codec.flush(); ++pendingFlushCount; Util.castNonNull(handler).post(this::onFlushCompleted); @@ -177,8 +159,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } @VisibleForTesting - /* package */ void setOnCodecStart(Runnable onCodecStart) { - this.onCodecStart = onCodecStart; + /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) { + this.codecStartRunnable = codecStartRunnable; } private synchronized void onFlushCompleted() { @@ -199,7 +181,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; mediaCodecAsyncCallback.flush(); try { - onCodecStart.run(); + codecStartRunnable.run(); } catch (IllegalStateException e) { internalException = e; } catch (Exception e) { 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 c984443041..2f347de0ae 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 @@ -31,6 +31,13 @@ import android.media.MediaFormat; */ /* package */ interface MediaCodecAdapter { + /** + * Starts this instance. + * + * @see MediaCodec#start(). + */ + void start(); + /** * Returns the next available input buffer index from the underlying {@link MediaCodec} or {@link * MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists. 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 dbfeed4063..89a0cb5ae1 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 @@ -995,11 +995,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD && Util.SDK_INT >= 23) { codecAdapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, getTrackType()); - ((DedicatedThreadAsyncMediaCodecAdapter) codecAdapter).start(); } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK && Util.SDK_INT >= 23) { codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType()); - ((MultiLockAsyncMediaCodecAdapter) codecAdapter).start(); } else { codecAdapter = new SynchronousMediaCodecAdapter(codec); } @@ -1009,7 +1007,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate); TraceUtil.endSection(); TraceUtil.beginSection("startCodec"); - codec.start(); + codecAdapter.start(); TraceUtil.endSection(); codecInitializedTimestamp = SystemClock.elapsedRealtime(); getCodecBuffers(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 index 56f503c71a..48d4ac9a55 100644 --- 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 @@ -27,7 +27,6 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.IntArrayQueue; import com.google.android.exoplayer2.util.Util; import java.util.ArrayDeque; @@ -94,7 +93,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final HandlerThread handlerThread; @MonotonicNonNull private Handler handler; - private Runnable onCodecStart; + private Runnable codecStartRunnable; /** Creates a new instance that wraps the specified {@link MediaCodec}. */ /* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, int trackType) { @@ -114,25 +113,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; codecException = null; state = STATE_CREATED; this.handlerThread = handlerThread; - onCodecStart = codec::start; + codecStartRunnable = codec::start; } - /** - * Starts the operation of this instance. - * - *

After a call to this method, make sure to call {@link #shutdown()} to terminate the internal - * Thread. You can only call this method once during the lifetime of an instance; calling this - * method again will throw an {@link IllegalStateException}. - * - * @throws IllegalStateException If this method has been called already. - */ + @Override public void start() { synchronized (objectStateLock) { - Assertions.checkState(state == STATE_CREATED); - handlerThread.start(); handler = new Handler(handlerThread.getLooper()); codec.setCallback(this, handler); + codecStartRunnable.run(); state = STATE_STARTED; } } @@ -140,8 +130,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public int dequeueInputBufferIndex() { synchronized (objectStateLock) { - Assertions.checkState(state == STATE_STARTED); - if (isFlushing()) { return MediaCodec.INFO_TRY_AGAIN_LATER; } else { @@ -154,8 +142,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { synchronized (objectStateLock) { - Assertions.checkState(state == STATE_STARTED); - if (isFlushing()) { return MediaCodec.INFO_TRY_AGAIN_LATER; } else { @@ -168,8 +154,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public MediaFormat getOutputFormat() { synchronized (objectStateLock) { - Assertions.checkState(state == STATE_STARTED); - if (currentFormat == null) { throw new IllegalStateException(); } @@ -181,8 +165,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public void flush() { synchronized (objectStateLock) { - Assertions.checkState(state == STATE_STARTED); - codec.flush(); pendingFlush++; Util.castNonNull(handler).post(this::onFlushComplete); @@ -200,8 +182,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } @VisibleForTesting - /* package */ void setOnCodecStart(Runnable onCodecStart) { - this.onCodecStart = onCodecStart; + /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) { + this.codecStartRunnable = codecStartRunnable; } private int dequeueAvailableInputBufferIndex() { @@ -307,7 +289,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; clearAvailableOutput(); codecException = null; try { - onCodecStart.run(); + codecStartRunnable.run(); } catch (IllegalStateException e) { codecException = e; } catch (Exception e) { 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 7dd7ef8f20..ee9ab857cc 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 @@ -23,12 +23,18 @@ import android.media.MediaFormat; * A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode. */ /* package */ final class SynchronousMediaCodecAdapter implements MediaCodecAdapter { + private final MediaCodec codec; public SynchronousMediaCodecAdapter(MediaCodec mediaCodec) { this.codec = mediaCodec; } + @Override + public void start() { + codec.start(); + } + @Override public int dequeueInputBufferIndex() { return codec.dequeueInputBuffer(0); 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 d2bb0fcc5b..34ed88d2d0 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 @@ -19,7 +19,7 @@ package com.google.android.exoplayer2.mediacodec; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.areEqual; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import android.media.MediaCodec; import android.media.MediaFormat; @@ -29,7 +29,7 @@ import android.os.Looper; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.IOException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -45,27 +45,32 @@ public class AsynchronousMediaCodecAdapterTest { private MediaCodec.BufferInfo bufferInfo; @Before - public void setup() throws IOException { + public void setUp() throws IOException { handlerThread = new HandlerThread("TestHandlerThread"); 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); @@ -73,6 +78,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_whileFlushing_returnsTryAgainLater() { + adapter.start(); adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0); adapter.flush(); adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1); @@ -83,9 +89,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_afterFlushCompletes_returnsNextInputBuffer() throws InterruptedException { - // Disable calling codec.start() after flush() completes to avoid receiving buffers from the - // shadow codec impl - adapter.setOnCodecStart(() -> {}); + adapter.start(); Handler handler = new Handler(looper); handler.post( () -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0)); @@ -100,28 +104,35 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_afterFlushCompletesWithError_throwsException() throws InterruptedException { - adapter.setOnCodecStart( + AtomicInteger calls = new AtomicInteger(0); + adapter.setCodecStartRunnable( () -> { - throw new IllegalStateException("codec#start() exception"); + if (calls.incrementAndGet() == 2) { + throw new IllegalStateException(); + } }); + adapter.start(); adapter.flush(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); - try { - adapter.dequeueInputBufferIndex(); - fail(); - } catch (IllegalStateException expected) { - } + 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); @@ -132,6 +143,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_whileFlushing_returnsTryAgainLater() { + adapter.start(); adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, bufferInfo); adapter.flush(); adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, bufferInfo); @@ -143,9 +155,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_afterFlushCompletes_returnsNextOutputBuffer() throws InterruptedException { - // Disable calling codec.start() after flush() completes to avoid receiving buffers from the - // shadow codec impl - adapter.setOnCodecStart(() -> {}); + adapter.start(); Handler handler = new Handler(looper); MediaCodec.BufferInfo info0 = new MediaCodec.BufferInfo(); handler.post( @@ -164,31 +174,23 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void dequeueOutputBufferIndex_afterFlushCompletesWithError_throwsException() throws InterruptedException { - adapter.setOnCodecStart( + AtomicInteger calls = new AtomicInteger(0); + adapter.setCodecStartRunnable( () -> { - throw new RuntimeException("codec#start() exception"); + if (calls.incrementAndGet() == 2) { + throw new RuntimeException("codec#start() exception"); + } }); + adapter.start(); adapter.flush(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); - try { - adapter.dequeueOutputBufferIndex(bufferInfo); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void getOutputFormat_withoutFormat_throwsException() { - try { - adapter.getOutputFormat(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo)); } @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++) { @@ -212,6 +214,7 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void getOutputFormat_afterFlush_returnsPreviousFormat() throws InterruptedException { + adapter.start(); MediaFormat format = new MediaFormat(); adapter.getMediaCodecCallback().onOutputFormatChanged(codec, format); adapter.dequeueOutputBufferIndex(bufferInfo); @@ -223,13 +226,13 @@ public class AsynchronousMediaCodecAdapterTest { @Test public void shutdown_withPendingFlush_cancelsFlush() throws InterruptedException { - AtomicBoolean onCodecStartCalled = new AtomicBoolean(false); - Runnable onCodecStart = () -> onCodecStartCalled.set(true); - adapter.setOnCodecStart(onCodecStart); + AtomicInteger onCodecStartCalled = new AtomicInteger(0); + adapter.setCodecStartRunnable(() -> onCodecStartCalled.incrementAndGet()); + adapter.start(); adapter.flush(); adapter.shutdown(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); - assertThat(onCodecStartCalled.get()).isFalse(); + assertThat(onCodecStartCalled.get()).isEqualTo(1); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapterTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapterTest.java index 2cfb577579..f974144dd6 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapterTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/DedicatedThreadAsyncMediaCodecAdapterTest.java @@ -19,7 +19,7 @@ package com.google.android.exoplayer2.mediacodec; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.areEqual; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import static org.robolectric.Shadows.shadowOf; import android.media.MediaCodec; @@ -47,16 +47,18 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { private MediaCodec.BufferInfo bufferInfo = null; @Before - public void setup() throws IOException { + public void setUp() throws IOException { codec = MediaCodec.createByCodecName("h264"); handlerThread = new TestHandlerThread("TestHandlerThread"); adapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, handlerThread); + adapter.setCodecStartRunnable(() -> {}); bufferInfo = new MediaCodec.BufferInfo(); } @After public void tearDown() { adapter.shutdown(); + assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0); } @@ -66,42 +68,15 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { adapter.shutdown(); } - @Test - public void start_calledTwice_throwsException() { - adapter.start(); - try { - adapter.start(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void dequeueInputBufferIndex_withoutStart_throwsException() { - try { - adapter.dequeueInputBufferIndex(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void dequeueInputBufferIndex_afterShutdown_throwsException() { - adapter.start(); - adapter.shutdown(); - try { - adapter.dequeueInputBufferIndex(); - fail(); - } catch (IllegalStateException expected) { - } - } - @Test public void dequeueInputBufferIndex_withAfterFlushFailed_throwsException() throws InterruptedException { - adapter.setOnCodecStart( + AtomicInteger codecStartCalls = new AtomicInteger(0); + adapter.setCodecStartRunnable( () -> { - throw new IllegalStateException("codec#start() exception"); + if (codecStartCalls.incrementAndGet() == 2) { + throw new IllegalStateException("codec#start() exception"); + } }); adapter.start(); adapter.flush(); @@ -110,11 +85,8 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { waitUntilAllEventsAreExecuted( handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS)) .isTrue(); - try { - adapter.dequeueInputBufferIndex(); - fail(); - } catch (IllegalStateException expected) { - } + + assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex()); } @Test @@ -144,9 +116,6 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_withFlushCompletedAndInputBuffer_returnsInputBuffer() throws InterruptedException { - // Disable calling codec.start() after flush to avoid receiving buffers from the - // shadow codec impl - adapter.setOnCodecStart(() -> {}); adapter.start(); Looper looper = handlerThread.getLooper(); Handler handler = new Handler(looper); @@ -169,39 +138,18 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { adapter.start(); adapter.onMediaCodecError(new IllegalStateException("error from codec")); - try { - adapter.dequeueInputBufferIndex(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void dequeueOutputBufferIndex_withoutStart_throwsException() { - try { - adapter.dequeueOutputBufferIndex(bufferInfo); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void dequeueOutputBufferIndex_afterShutdown_throwsException() { - adapter.start(); - adapter.shutdown(); - try { - adapter.dequeueOutputBufferIndex(bufferInfo); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex()); } @Test public void dequeueOutputBufferIndex_withInternalException_throwsException() throws InterruptedException { - adapter.setOnCodecStart( + AtomicInteger codecStartCalls = new AtomicInteger(0); + adapter.setCodecStartRunnable( () -> { - throw new RuntimeException("codec#start() exception"); + if (codecStartCalls.incrementAndGet() == 2) { + throw new RuntimeException("codec#start() exception"); + } }); adapter.start(); adapter.flush(); @@ -210,11 +158,7 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { waitUntilAllEventsAreExecuted( handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS)) .isTrue(); - try { - adapter.dequeueOutputBufferIndex(bufferInfo); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo)); } @Test @@ -275,42 +219,14 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { adapter.start(); adapter.onMediaCodecError(new IllegalStateException("error from codec")); - try { - adapter.dequeueOutputBufferIndex(bufferInfo); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void getOutputFormat_withoutStart_throwsException() { - try { - adapter.getOutputFormat(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void getOutputFormat_afterShutdown_throwsException() { - adapter.start(); - adapter.shutdown(); - try { - adapter.getOutputFormat(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo)); } @Test public void getOutputFormat_withoutFormatReceived_throwsException() { adapter.start(); - try { - adapter.getOutputFormat(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.getOutputFormat()); } @Test @@ -351,28 +267,10 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { assertThat(adapter.getOutputFormat()).isEqualTo(format); } - @Test - public void flush_withoutStarted_throwsException() { - try { - adapter.flush(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void flush_afterShutdown_throwsException() { - adapter.start(); - adapter.shutdown(); - try { - adapter.flush(); - } catch (IllegalStateException expected) { - } - } - @Test public void flush_multipleTimes_onlyLastFlushExecutes() throws InterruptedException { - AtomicInteger onCodecStartCount = new AtomicInteger(0); - adapter.setOnCodecStart(() -> onCodecStartCount.incrementAndGet()); + AtomicInteger codecStartCalls = new AtomicInteger(0); + adapter.setCodecStartRunnable(() -> codecStartCalls.incrementAndGet()); adapter.start(); Looper looper = handlerThread.getLooper(); Handler handler = new Handler(looper); @@ -384,23 +282,23 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { adapter.flush(); // Enqueues a second flush event handler.post(() -> adapter.onInputBufferAvailable(codec, 3)); - // Progress the looper until the milestoneCount is increased - first flush event - // should have been a no-op + // Progress the looper until the milestoneCount is increased. + // adapter.start() will call codec.start(). First flush event should not call codec.start(). ShadowLooper shadowLooper = shadowOf(looper); while (milestoneCount.get() < 1) { shadowLooper.runOneTask(); } - assertThat(onCodecStartCount.get()).isEqualTo(0); + assertThat(codecStartCalls.get()).isEqualTo(1); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(3); - assertThat(onCodecStartCount.get()).isEqualTo(1); + assertThat(codecStartCalls.get()).isEqualTo(2); } @Test public void flush_andImmediatelyShutdown_flushIsNoOp() throws InterruptedException { AtomicInteger onCodecStartCount = new AtomicInteger(0); - adapter.setOnCodecStart(() -> onCodecStartCount.incrementAndGet()); + adapter.setCodecStartRunnable(() -> onCodecStartCount.incrementAndGet()); adapter.start(); // Obtain looper when adapter is started Looper looper = handlerThread.getLooper(); @@ -408,8 +306,8 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest { adapter.shutdown(); assertThat(waitUntilAllEventsAreExecuted(looper, 5, TimeUnit.SECONDS)).isTrue(); - // only shutdown flushes the MediaCodecAsync handler - assertThat(onCodecStartCount.get()).isEqualTo(0); + // Only adapter.start() calls onCodecStart. + assertThat(onCodecStartCount.get()).isEqualTo(1); } private static class TestHandlerThread extends HandlerThread { 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 index b984d28914..c31b86db39 100644 --- 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 @@ -19,7 +19,7 @@ package com.google.android.exoplayer2.mediacodec; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.areEqual; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import static org.robolectric.Shadows.shadowOf; import android.media.MediaCodec; @@ -44,20 +44,21 @@ public class MultiLockAsyncMediaCodecAdapterTest { private MultiLockAsyncMediaCodecAdapter adapter; private MediaCodec codec; private MediaCodec.BufferInfo bufferInfo = null; - private MediaCodecAsyncCallback mediaCodecAsyncCallbackSpy; private TestHandlerThread handlerThread; @Before - public void setup() throws IOException { + public void setUp() throws IOException { codec = MediaCodec.createByCodecName("h264"); handlerThread = new TestHandlerThread("TestHandlerThread"); adapter = new MultiLockAsyncMediaCodecAdapter(codec, handlerThread); + adapter.setCodecStartRunnable(() -> {}); bufferInfo = new MediaCodec.BufferInfo(); } @After public void tearDown() { adapter.shutdown(); + assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0); } @@ -67,42 +68,15 @@ public class MultiLockAsyncMediaCodecAdapterTest { adapter.shutdown(); } - @Test - public void start_calledTwice_throwsException() { - adapter.start(); - try { - adapter.start(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void dequeueInputBufferIndex_withoutStart_throwsException() { - try { - adapter.dequeueInputBufferIndex(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void dequeueInputBufferIndex_afterShutdown_throwsException() { - adapter.start(); - adapter.shutdown(); - try { - adapter.dequeueInputBufferIndex(); - fail(); - } catch (IllegalStateException expected) { - } - } - @Test public void dequeueInputBufferIndex_withAfterFlushFailed_throwsException() throws InterruptedException { - adapter.setOnCodecStart( + AtomicInteger codecStartCalls = new AtomicInteger(0); + adapter.setCodecStartRunnable( () -> { - throw new IllegalStateException("codec#start() exception"); + if (codecStartCalls.incrementAndGet() == 2) { + throw new IllegalStateException("codec#start() exception"); + } }); adapter.start(); adapter.flush(); @@ -111,11 +85,7 @@ public class MultiLockAsyncMediaCodecAdapterTest { waitUntilAllEventsAreExecuted( handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS)) .isTrue(); - try { - adapter.dequeueInputBufferIndex(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex()); } @Test @@ -145,9 +115,6 @@ public class MultiLockAsyncMediaCodecAdapterTest { @Test public void dequeueInputBufferIndex_withFlushCompletedAndInputBuffer_returnsInputBuffer() throws InterruptedException { - // Disable calling codec.start() after flush to avoid receiving buffers from the - // shadow codec impl - adapter.setOnCodecStart(() -> {}); adapter.start(); Looper looper = handlerThread.getLooper(); Handler handler = new Handler(looper); @@ -170,39 +137,19 @@ public class MultiLockAsyncMediaCodecAdapterTest { adapter.start(); adapter.onMediaCodecError(new IllegalStateException("error from codec")); - try { - adapter.dequeueInputBufferIndex(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex()); } - @Test - public void dequeueOutputBufferIndex_withoutStart_throwsException() { - try { - adapter.dequeueOutputBufferIndex(bufferInfo); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void dequeueOutputBufferIndex_afterShutdown_throwsException() { - adapter.start(); - adapter.shutdown(); - try { - adapter.dequeueOutputBufferIndex(bufferInfo); - fail(); - } catch (IllegalStateException expected) { - } - } @Test public void dequeueOutputBufferIndex_withInternalException_throwsException() throws InterruptedException { - adapter.setOnCodecStart( + AtomicInteger codecStartCalls = new AtomicInteger(0); + adapter.setCodecStartRunnable( () -> { - throw new RuntimeException("codec#start() exception"); + if (codecStartCalls.incrementAndGet() == 2) { + throw new RuntimeException("codec#start() exception"); + } }); adapter.start(); adapter.flush(); @@ -211,11 +158,7 @@ public class MultiLockAsyncMediaCodecAdapterTest { waitUntilAllEventsAreExecuted( handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS)) .isTrue(); - try { - adapter.dequeueOutputBufferIndex(bufferInfo); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo)); } @Test @@ -276,42 +219,14 @@ public class MultiLockAsyncMediaCodecAdapterTest { adapter.start(); adapter.onMediaCodecError(new IllegalStateException("error from codec")); - try { - adapter.dequeueOutputBufferIndex(bufferInfo); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void getOutputFormat_withoutStart_throwsException() { - try { - adapter.getOutputFormat(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void getOutputFormat_afterShutdown_throwsException() { - adapter.start(); - adapter.shutdown(); - try { - adapter.getOutputFormat(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo)); } @Test public void getOutputFormat_withoutFormatReceived_throwsException() { adapter.start(); - try { - adapter.getOutputFormat(); - fail(); - } catch (IllegalStateException expected) { - } + assertThrows(IllegalStateException.class, () -> adapter.getOutputFormat()); } @Test @@ -352,28 +267,10 @@ public class MultiLockAsyncMediaCodecAdapterTest { assertThat(adapter.getOutputFormat()).isEqualTo(format); } - @Test - public void flush_withoutStarted_throwsException() { - try { - adapter.flush(); - } catch (IllegalStateException expected) { - } - } - - @Test - public void flush_afterShutdown_throwsException() { - adapter.start(); - adapter.shutdown(); - try { - adapter.flush(); - } catch (IllegalStateException expected) { - } - } - @Test public void flush_multipleTimes_onlyLastFlushExecutes() throws InterruptedException { - AtomicInteger onCodecStartCount = new AtomicInteger(0); - adapter.setOnCodecStart(() -> onCodecStartCount.incrementAndGet()); + AtomicInteger codecStartCalls = new AtomicInteger(0); + adapter.setCodecStartRunnable(() -> codecStartCalls.incrementAndGet()); adapter.start(); Looper looper = handlerThread.getLooper(); Handler handler = new Handler(looper); @@ -385,23 +282,23 @@ public class MultiLockAsyncMediaCodecAdapterTest { adapter.flush(); // Enqueues a second flush event handler.post(() -> adapter.onInputBufferAvailable(codec, 3)); - // Progress the looper until the milestoneCount is increased - first flush event - // should have been a no-op + // 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(onCodecStartCount.get()).isEqualTo(0); + assertThat(codecStartCalls.get()).isEqualTo(1); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(3); - assertThat(onCodecStartCount.get()).isEqualTo(1); + assertThat(codecStartCalls.get()).isEqualTo(2); } @Test public void flush_andImmediatelyShutdown_flushIsNoOp() throws InterruptedException { - AtomicInteger onCodecStartCount = new AtomicInteger(0); - adapter.setOnCodecStart(() -> onCodecStartCount.incrementAndGet()); + AtomicInteger codecStartCalls = new AtomicInteger(0); + adapter.setCodecStartRunnable(() -> codecStartCalls.incrementAndGet()); adapter.start(); // Obtain looper when adapter is started. Looper looper = handlerThread.getLooper(); @@ -409,8 +306,8 @@ public class MultiLockAsyncMediaCodecAdapterTest { adapter.shutdown(); assertThat(waitUntilAllEventsAreExecuted(looper, 5, TimeUnit.SECONDS)).isTrue(); - // Only shutdown flushes the MediaCodecAsync handler. - assertThat(onCodecStartCount.get()).isEqualTo(0); + // Only adapter.start() called codec#start() + assertThat(codecStartCalls.get()).isEqualTo(1); } private static class TestHandlerThread extends HandlerThread {