Add start() method in MediaCodecAdapter

PiperOrigin-RevId: 288476415
This commit is contained in:
christosts 2020-01-07 13:05:14 +00:00 committed by Oliver Woodman
parent c5535e825e
commit fb42f818ec
9 changed files with 135 additions and 357 deletions

View File

@ -38,7 +38,7 @@ import com.google.android.exoplayer2.util.Assertions;
private final MediaCodec codec; private final MediaCodec codec;
@Nullable private IllegalStateException internalException; @Nullable private IllegalStateException internalException;
private boolean flushing; private boolean flushing;
private Runnable onCodecStart; private Runnable codecStartRunnable;
/** /**
* Create a new {@code AsynchronousMediaCodecAdapter}. * Create a new {@code AsynchronousMediaCodecAdapter}.
@ -55,7 +55,12 @@ import com.google.android.exoplayer2.util.Assertions;
handler = new Handler(looper); handler = new Handler(looper);
this.codec = codec; this.codec = codec;
this.codec.setCallback(mediaCodecAsyncCallback); this.codec.setCallback(mediaCodecAsyncCallback);
onCodecStart = () -> codec.start(); codecStartRunnable = codec::start;
}
@Override
public void start() {
codecStartRunnable.run();
} }
@Override @Override
@ -105,7 +110,7 @@ import com.google.android.exoplayer2.util.Assertions;
flushing = false; flushing = false;
mediaCodecAsyncCallback.flush(); mediaCodecAsyncCallback.flush();
try { try {
onCodecStart.run(); codecStartRunnable.run();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
// Catch IllegalStateException directly so that we don't have to wrap it. // Catch IllegalStateException directly so that we don't have to wrap it.
internalException = e; internalException = e;
@ -115,8 +120,8 @@ import com.google.android.exoplayer2.util.Assertions;
} }
@VisibleForTesting @VisibleForTesting
/* package */ void setOnCodecStart(Runnable onCodecStart) { /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.onCodecStart = onCodecStart; this.codecStartRunnable = codecStartRunnable;
} }
private void maybeThrowException() throws IllegalStateException { private void maybeThrowException() throws IllegalStateException {

View File

@ -26,7 +26,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -54,7 +53,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@MonotonicNonNull private Handler handler; @MonotonicNonNull private Handler handler;
private long pendingFlushCount; private long pendingFlushCount;
private @State int state; private @State int state;
private Runnable onCodecStart; private Runnable codecStartRunnable;
@Nullable private IllegalStateException internalException; @Nullable private IllegalStateException internalException;
/** /**
@ -77,31 +76,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.codec = codec; this.codec = codec;
this.handlerThread = handlerThread; this.handlerThread = handlerThread;
state = STATE_CREATED; state = STATE_CREATED;
onCodecStart = codec::start; codecStartRunnable = codec::start;
} }
/** @Override
* Starts the operation of the instance.
*
* <p>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.
*/
public synchronized void start() { public synchronized void start() {
Assertions.checkState(state == STATE_CREATED);
handlerThread.start(); handlerThread.start();
handler = new Handler(handlerThread.getLooper()); handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler); codec.setCallback(this, handler);
codecStartRunnable.run();
state = STATE_STARTED; state = STATE_STARTED;
} }
@Override @Override
public synchronized int dequeueInputBufferIndex() { public synchronized int dequeueInputBufferIndex() {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) { if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
@ -112,8 +100,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public synchronized int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { public synchronized int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) { if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
@ -124,15 +110,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public synchronized MediaFormat getOutputFormat() { public synchronized MediaFormat getOutputFormat() {
Assertions.checkState(state == STATE_STARTED);
return mediaCodecAsyncCallback.getOutputFormat(); return mediaCodecAsyncCallback.getOutputFormat();
} }
@Override @Override
public synchronized void flush() { public synchronized void flush() {
Assertions.checkState(state == STATE_STARTED);
codec.flush(); codec.flush();
++pendingFlushCount; ++pendingFlushCount;
Util.castNonNull(handler).post(this::onFlushCompleted); Util.castNonNull(handler).post(this::onFlushCompleted);
@ -177,8 +159,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@VisibleForTesting @VisibleForTesting
/* package */ void setOnCodecStart(Runnable onCodecStart) { /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.onCodecStart = onCodecStart; this.codecStartRunnable = codecStartRunnable;
} }
private synchronized void onFlushCompleted() { private synchronized void onFlushCompleted() {
@ -199,7 +181,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
mediaCodecAsyncCallback.flush(); mediaCodecAsyncCallback.flush();
try { try {
onCodecStart.run(); codecStartRunnable.run();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
internalException = e; internalException = e;
} catch (Exception e) { } catch (Exception e) {

View File

@ -31,6 +31,13 @@ import android.media.MediaFormat;
*/ */
/* package */ interface MediaCodecAdapter { /* 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 * Returns the next available input buffer index from the underlying {@link MediaCodec} or {@link
* MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists. * MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists.

View File

@ -995,11 +995,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD
&& Util.SDK_INT >= 23) { && Util.SDK_INT >= 23) {
codecAdapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, getTrackType()); codecAdapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, getTrackType());
((DedicatedThreadAsyncMediaCodecAdapter) codecAdapter).start();
} else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK
&& Util.SDK_INT >= 23) { && Util.SDK_INT >= 23) {
codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType()); codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType());
((MultiLockAsyncMediaCodecAdapter) codecAdapter).start();
} else { } else {
codecAdapter = new SynchronousMediaCodecAdapter(codec); codecAdapter = new SynchronousMediaCodecAdapter(codec);
} }
@ -1009,7 +1007,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate); configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate);
TraceUtil.endSection(); TraceUtil.endSection();
TraceUtil.beginSection("startCodec"); TraceUtil.beginSection("startCodec");
codec.start(); codecAdapter.start();
TraceUtil.endSection(); TraceUtil.endSection();
codecInitializedTimestamp = SystemClock.elapsedRealtime(); codecInitializedTimestamp = SystemClock.elapsedRealtime();
getCodecBuffers(codec); getCodecBuffers(codec);

View File

@ -27,7 +27,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; 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.IntArrayQueue;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -94,7 +93,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final HandlerThread handlerThread; private final HandlerThread handlerThread;
@MonotonicNonNull private Handler handler; @MonotonicNonNull private Handler handler;
private Runnable onCodecStart; private Runnable codecStartRunnable;
/** Creates a new instance that wraps the specified {@link MediaCodec}. */ /** Creates a new instance that wraps the specified {@link MediaCodec}. */
/* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, int trackType) { /* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, int trackType) {
@ -114,25 +113,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
codecException = null; codecException = null;
state = STATE_CREATED; state = STATE_CREATED;
this.handlerThread = handlerThread; this.handlerThread = handlerThread;
onCodecStart = codec::start; codecStartRunnable = codec::start;
} }
/** @Override
* Starts the operation of this instance.
*
* <p>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.
*/
public void start() { public void start() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_CREATED);
handlerThread.start(); handlerThread.start();
handler = new Handler(handlerThread.getLooper()); handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler); codec.setCallback(this, handler);
codecStartRunnable.run();
state = STATE_STARTED; state = STATE_STARTED;
} }
} }
@ -140,8 +130,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public int dequeueInputBufferIndex() { public int dequeueInputBufferIndex() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) { if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
@ -154,8 +142,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) { if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
@ -168,8 +154,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public MediaFormat getOutputFormat() { public MediaFormat getOutputFormat() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
if (currentFormat == null) { if (currentFormat == null) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
@ -181,8 +165,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void flush() { public void flush() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
codec.flush(); codec.flush();
pendingFlush++; pendingFlush++;
Util.castNonNull(handler).post(this::onFlushComplete); Util.castNonNull(handler).post(this::onFlushComplete);
@ -200,8 +182,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@VisibleForTesting @VisibleForTesting
/* package */ void setOnCodecStart(Runnable onCodecStart) { /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.onCodecStart = onCodecStart; this.codecStartRunnable = codecStartRunnable;
} }
private int dequeueAvailableInputBufferIndex() { private int dequeueAvailableInputBufferIndex() {
@ -307,7 +289,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
clearAvailableOutput(); clearAvailableOutput();
codecException = null; codecException = null;
try { try {
onCodecStart.run(); codecStartRunnable.run();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
codecException = e; codecException = e;
} catch (Exception e) { } catch (Exception e) {

View File

@ -23,12 +23,18 @@ import android.media.MediaFormat;
* A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode. * A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode.
*/ */
/* package */ final class SynchronousMediaCodecAdapter implements MediaCodecAdapter { /* package */ final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
private final MediaCodec codec; private final MediaCodec codec;
public SynchronousMediaCodecAdapter(MediaCodec mediaCodec) { public SynchronousMediaCodecAdapter(MediaCodec mediaCodec) {
this.codec = mediaCodec; this.codec = mediaCodec;
} }
@Override
public void start() {
codec.start();
}
@Override @Override
public int dequeueInputBufferIndex() { public int dequeueInputBufferIndex() {
return codec.dequeueInputBuffer(0); return codec.dequeueInputBuffer(0);

View File

@ -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.areEqual;
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted;
import static com.google.common.truth.Truth.assertThat; 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.MediaCodec;
import android.media.MediaFormat; import android.media.MediaFormat;
@ -29,7 +29,7 @@ import android.os.Looper;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -45,27 +45,32 @@ public class AsynchronousMediaCodecAdapterTest {
private MediaCodec.BufferInfo bufferInfo; private MediaCodec.BufferInfo bufferInfo;
@Before @Before
public void setup() throws IOException { public void setUp() throws IOException {
handlerThread = new HandlerThread("TestHandlerThread"); handlerThread = new HandlerThread("TestHandlerThread");
handlerThread.start(); handlerThread.start();
looper = handlerThread.getLooper(); looper = handlerThread.getLooper();
codec = MediaCodec.createByCodecName("h264"); codec = MediaCodec.createByCodecName("h264");
adapter = new AsynchronousMediaCodecAdapter(codec, looper); adapter = new AsynchronousMediaCodecAdapter(codec, looper);
adapter.setCodecStartRunnable(() -> {});
bufferInfo = new MediaCodec.BufferInfo(); bufferInfo = new MediaCodec.BufferInfo();
} }
@After @After
public void tearDown() { public void tearDown() {
adapter.shutdown();
handlerThread.quit(); handlerThread.quit();
} }
@Test @Test
public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() { public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
adapter.start();
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
} }
@Test @Test
public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() { public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() {
adapter.start();
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0); adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0);
@ -73,6 +78,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_whileFlushing_returnsTryAgainLater() { public void dequeueInputBufferIndex_whileFlushing_returnsTryAgainLater() {
adapter.start();
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0); adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
adapter.flush(); adapter.flush();
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1); adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1);
@ -83,9 +89,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_afterFlushCompletes_returnsNextInputBuffer() public void dequeueInputBufferIndex_afterFlushCompletes_returnsNextInputBuffer()
throws InterruptedException { throws InterruptedException {
// Disable calling codec.start() after flush() completes to avoid receiving buffers from the adapter.start();
// shadow codec impl
adapter.setOnCodecStart(() -> {});
Handler handler = new Handler(looper); Handler handler = new Handler(looper);
handler.post( handler.post(
() -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0)); () -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0));
@ -100,28 +104,35 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_afterFlushCompletesWithError_throwsException() public void dequeueInputBufferIndex_afterFlushCompletesWithError_throwsException()
throws InterruptedException { 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(); adapter.flush();
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
try { assertThrows(
IllegalStateException.class,
() -> {
adapter.dequeueInputBufferIndex(); adapter.dequeueInputBufferIndex();
fail(); });
} catch (IllegalStateException expected) {
}
} }
@Test @Test
public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() { public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() {
adapter.start();
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
} }
@Test @Test
public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() { public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() {
adapter.start();
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
outBufferInfo.presentationTimeUs = 10; outBufferInfo.presentationTimeUs = 10;
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, outBufferInfo); adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, outBufferInfo);
@ -132,6 +143,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueOutputBufferIndex_whileFlushing_returnsTryAgainLater() { public void dequeueOutputBufferIndex_whileFlushing_returnsTryAgainLater() {
adapter.start();
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, bufferInfo); adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, bufferInfo);
adapter.flush(); adapter.flush();
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, bufferInfo); adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, bufferInfo);
@ -143,9 +155,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueOutputBufferIndex_afterFlushCompletes_returnsNextOutputBuffer() public void dequeueOutputBufferIndex_afterFlushCompletes_returnsNextOutputBuffer()
throws InterruptedException { throws InterruptedException {
// Disable calling codec.start() after flush() completes to avoid receiving buffers from the adapter.start();
// shadow codec impl
adapter.setOnCodecStart(() -> {});
Handler handler = new Handler(looper); Handler handler = new Handler(looper);
MediaCodec.BufferInfo info0 = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo info0 = new MediaCodec.BufferInfo();
handler.post( handler.post(
@ -164,31 +174,23 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueOutputBufferIndex_afterFlushCompletesWithError_throwsException() public void dequeueOutputBufferIndex_afterFlushCompletesWithError_throwsException()
throws InterruptedException { throws InterruptedException {
adapter.setOnCodecStart( AtomicInteger calls = new AtomicInteger(0);
adapter.setCodecStartRunnable(
() -> { () -> {
if (calls.incrementAndGet() == 2) {
throw new RuntimeException("codec#start() exception"); throw new RuntimeException("codec#start() exception");
}
}); });
adapter.start();
adapter.flush(); adapter.flush();
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
try { assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
adapter.dequeueOutputBufferIndex(bufferInfo);
fail();
} catch (IllegalStateException expected) {
}
}
@Test
public void getOutputFormat_withoutFormat_throwsException() {
try {
adapter.getOutputFormat();
fail();
} catch (IllegalStateException expected) {
}
} }
@Test @Test
public void getOutputFormat_withMultipleFormats_returnsFormatsInCorrectOrder() { public void getOutputFormat_withMultipleFormats_returnsFormatsInCorrectOrder() {
adapter.start();
MediaFormat[] formats = new MediaFormat[10]; MediaFormat[] formats = new MediaFormat[10];
MediaCodec.Callback mediaCodecCallback = adapter.getMediaCodecCallback(); MediaCodec.Callback mediaCodecCallback = adapter.getMediaCodecCallback();
for (int i = 0; i < formats.length; i++) { for (int i = 0; i < formats.length; i++) {
@ -212,6 +214,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void getOutputFormat_afterFlush_returnsPreviousFormat() throws InterruptedException { public void getOutputFormat_afterFlush_returnsPreviousFormat() throws InterruptedException {
adapter.start();
MediaFormat format = new MediaFormat(); MediaFormat format = new MediaFormat();
adapter.getMediaCodecCallback().onOutputFormatChanged(codec, format); adapter.getMediaCodecCallback().onOutputFormatChanged(codec, format);
adapter.dequeueOutputBufferIndex(bufferInfo); adapter.dequeueOutputBufferIndex(bufferInfo);
@ -223,13 +226,13 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void shutdown_withPendingFlush_cancelsFlush() throws InterruptedException { public void shutdown_withPendingFlush_cancelsFlush() throws InterruptedException {
AtomicBoolean onCodecStartCalled = new AtomicBoolean(false); AtomicInteger onCodecStartCalled = new AtomicInteger(0);
Runnable onCodecStart = () -> onCodecStartCalled.set(true); adapter.setCodecStartRunnable(() -> onCodecStartCalled.incrementAndGet());
adapter.setOnCodecStart(onCodecStart); adapter.start();
adapter.flush(); adapter.flush();
adapter.shutdown(); adapter.shutdown();
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
assertThat(onCodecStartCalled.get()).isFalse(); assertThat(onCodecStartCalled.get()).isEqualTo(1);
} }
} }

View File

@ -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.areEqual;
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted;
import static com.google.common.truth.Truth.assertThat; 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 static org.robolectric.Shadows.shadowOf;
import android.media.MediaCodec; import android.media.MediaCodec;
@ -47,16 +47,18 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
private MediaCodec.BufferInfo bufferInfo = null; private MediaCodec.BufferInfo bufferInfo = null;
@Before @Before
public void setup() throws IOException { public void setUp() throws IOException {
codec = MediaCodec.createByCodecName("h264"); codec = MediaCodec.createByCodecName("h264");
handlerThread = new TestHandlerThread("TestHandlerThread"); handlerThread = new TestHandlerThread("TestHandlerThread");
adapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, handlerThread); adapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, handlerThread);
adapter.setCodecStartRunnable(() -> {});
bufferInfo = new MediaCodec.BufferInfo(); bufferInfo = new MediaCodec.BufferInfo();
} }
@After @After
public void tearDown() { public void tearDown() {
adapter.shutdown(); adapter.shutdown();
assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0); assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0);
} }
@ -66,42 +68,15 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
adapter.shutdown(); 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 @Test
public void dequeueInputBufferIndex_withAfterFlushFailed_throwsException() public void dequeueInputBufferIndex_withAfterFlushFailed_throwsException()
throws InterruptedException { throws InterruptedException {
adapter.setOnCodecStart( AtomicInteger codecStartCalls = new AtomicInteger(0);
adapter.setCodecStartRunnable(
() -> { () -> {
if (codecStartCalls.incrementAndGet() == 2) {
throw new IllegalStateException("codec#start() exception"); throw new IllegalStateException("codec#start() exception");
}
}); });
adapter.start(); adapter.start();
adapter.flush(); adapter.flush();
@ -110,11 +85,8 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
waitUntilAllEventsAreExecuted( waitUntilAllEventsAreExecuted(
handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS)) handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS))
.isTrue(); .isTrue();
try {
adapter.dequeueInputBufferIndex(); assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex());
fail();
} catch (IllegalStateException expected) {
}
} }
@Test @Test
@ -144,9 +116,6 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_withFlushCompletedAndInputBuffer_returnsInputBuffer() public void dequeueInputBufferIndex_withFlushCompletedAndInputBuffer_returnsInputBuffer()
throws InterruptedException { throws InterruptedException {
// Disable calling codec.start() after flush to avoid receiving buffers from the
// shadow codec impl
adapter.setOnCodecStart(() -> {});
adapter.start(); adapter.start();
Looper looper = handlerThread.getLooper(); Looper looper = handlerThread.getLooper();
Handler handler = new Handler(looper); Handler handler = new Handler(looper);
@ -169,39 +138,18 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
adapter.start(); adapter.start();
adapter.onMediaCodecError(new IllegalStateException("error from codec")); adapter.onMediaCodecError(new IllegalStateException("error from codec"));
try { assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex());
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) {
}
} }
@Test @Test
public void dequeueOutputBufferIndex_withInternalException_throwsException() public void dequeueOutputBufferIndex_withInternalException_throwsException()
throws InterruptedException { throws InterruptedException {
adapter.setOnCodecStart( AtomicInteger codecStartCalls = new AtomicInteger(0);
adapter.setCodecStartRunnable(
() -> { () -> {
if (codecStartCalls.incrementAndGet() == 2) {
throw new RuntimeException("codec#start() exception"); throw new RuntimeException("codec#start() exception");
}
}); });
adapter.start(); adapter.start();
adapter.flush(); adapter.flush();
@ -210,11 +158,7 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
waitUntilAllEventsAreExecuted( waitUntilAllEventsAreExecuted(
handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS)) handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS))
.isTrue(); .isTrue();
try { assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
adapter.dequeueOutputBufferIndex(bufferInfo);
fail();
} catch (IllegalStateException expected) {
}
} }
@Test @Test
@ -275,42 +219,14 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
adapter.start(); adapter.start();
adapter.onMediaCodecError(new IllegalStateException("error from codec")); adapter.onMediaCodecError(new IllegalStateException("error from codec"));
try { assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
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) {
}
} }
@Test @Test
public void getOutputFormat_withoutFormatReceived_throwsException() { public void getOutputFormat_withoutFormatReceived_throwsException() {
adapter.start(); adapter.start();
try { assertThrows(IllegalStateException.class, () -> adapter.getOutputFormat());
adapter.getOutputFormat();
fail();
} catch (IllegalStateException expected) {
}
} }
@Test @Test
@ -351,28 +267,10 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
assertThat(adapter.getOutputFormat()).isEqualTo(format); 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 @Test
public void flush_multipleTimes_onlyLastFlushExecutes() throws InterruptedException { public void flush_multipleTimes_onlyLastFlushExecutes() throws InterruptedException {
AtomicInteger onCodecStartCount = new AtomicInteger(0); AtomicInteger codecStartCalls = new AtomicInteger(0);
adapter.setOnCodecStart(() -> onCodecStartCount.incrementAndGet()); adapter.setCodecStartRunnable(() -> codecStartCalls.incrementAndGet());
adapter.start(); adapter.start();
Looper looper = handlerThread.getLooper(); Looper looper = handlerThread.getLooper();
Handler handler = new Handler(looper); Handler handler = new Handler(looper);
@ -384,23 +282,23 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
adapter.flush(); // Enqueues a second flush event adapter.flush(); // Enqueues a second flush event
handler.post(() -> adapter.onInputBufferAvailable(codec, 3)); handler.post(() -> adapter.onInputBufferAvailable(codec, 3));
// Progress the looper until the milestoneCount is increased - first flush event // Progress the looper until the milestoneCount is increased.
// should have been a no-op // adapter.start() will call codec.start(). First flush event should not call codec.start().
ShadowLooper shadowLooper = shadowOf(looper); ShadowLooper shadowLooper = shadowOf(looper);
while (milestoneCount.get() < 1) { while (milestoneCount.get() < 1) {
shadowLooper.runOneTask(); shadowLooper.runOneTask();
} }
assertThat(onCodecStartCount.get()).isEqualTo(0); assertThat(codecStartCalls.get()).isEqualTo(1);
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(3); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(3);
assertThat(onCodecStartCount.get()).isEqualTo(1); assertThat(codecStartCalls.get()).isEqualTo(2);
} }
@Test @Test
public void flush_andImmediatelyShutdown_flushIsNoOp() throws InterruptedException { public void flush_andImmediatelyShutdown_flushIsNoOp() throws InterruptedException {
AtomicInteger onCodecStartCount = new AtomicInteger(0); AtomicInteger onCodecStartCount = new AtomicInteger(0);
adapter.setOnCodecStart(() -> onCodecStartCount.incrementAndGet()); adapter.setCodecStartRunnable(() -> onCodecStartCount.incrementAndGet());
adapter.start(); adapter.start();
// Obtain looper when adapter is started // Obtain looper when adapter is started
Looper looper = handlerThread.getLooper(); Looper looper = handlerThread.getLooper();
@ -408,8 +306,8 @@ public class DedicatedThreadAsyncMediaCodecAdapterTest {
adapter.shutdown(); adapter.shutdown();
assertThat(waitUntilAllEventsAreExecuted(looper, 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, 5, TimeUnit.SECONDS)).isTrue();
// only shutdown flushes the MediaCodecAsync handler // Only adapter.start() calls onCodecStart.
assertThat(onCodecStartCount.get()).isEqualTo(0); assertThat(onCodecStartCount.get()).isEqualTo(1);
} }
private static class TestHandlerThread extends HandlerThread { private static class TestHandlerThread extends HandlerThread {

View File

@ -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.areEqual;
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted;
import static com.google.common.truth.Truth.assertThat; 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 static org.robolectric.Shadows.shadowOf;
import android.media.MediaCodec; import android.media.MediaCodec;
@ -44,20 +44,21 @@ public class MultiLockAsyncMediaCodecAdapterTest {
private MultiLockAsyncMediaCodecAdapter adapter; private MultiLockAsyncMediaCodecAdapter adapter;
private MediaCodec codec; private MediaCodec codec;
private MediaCodec.BufferInfo bufferInfo = null; private MediaCodec.BufferInfo bufferInfo = null;
private MediaCodecAsyncCallback mediaCodecAsyncCallbackSpy;
private TestHandlerThread handlerThread; private TestHandlerThread handlerThread;
@Before @Before
public void setup() throws IOException { public void setUp() throws IOException {
codec = MediaCodec.createByCodecName("h264"); codec = MediaCodec.createByCodecName("h264");
handlerThread = new TestHandlerThread("TestHandlerThread"); handlerThread = new TestHandlerThread("TestHandlerThread");
adapter = new MultiLockAsyncMediaCodecAdapter(codec, handlerThread); adapter = new MultiLockAsyncMediaCodecAdapter(codec, handlerThread);
adapter.setCodecStartRunnable(() -> {});
bufferInfo = new MediaCodec.BufferInfo(); bufferInfo = new MediaCodec.BufferInfo();
} }
@After @After
public void tearDown() { public void tearDown() {
adapter.shutdown(); adapter.shutdown();
assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0); assertThat(TestHandlerThread.INSTANCES_STARTED.get()).isEqualTo(0);
} }
@ -67,42 +68,15 @@ public class MultiLockAsyncMediaCodecAdapterTest {
adapter.shutdown(); 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 @Test
public void dequeueInputBufferIndex_withAfterFlushFailed_throwsException() public void dequeueInputBufferIndex_withAfterFlushFailed_throwsException()
throws InterruptedException { throws InterruptedException {
adapter.setOnCodecStart( AtomicInteger codecStartCalls = new AtomicInteger(0);
adapter.setCodecStartRunnable(
() -> { () -> {
if (codecStartCalls.incrementAndGet() == 2) {
throw new IllegalStateException("codec#start() exception"); throw new IllegalStateException("codec#start() exception");
}
}); });
adapter.start(); adapter.start();
adapter.flush(); adapter.flush();
@ -111,11 +85,7 @@ public class MultiLockAsyncMediaCodecAdapterTest {
waitUntilAllEventsAreExecuted( waitUntilAllEventsAreExecuted(
handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS)) handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS))
.isTrue(); .isTrue();
try { assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex());
adapter.dequeueInputBufferIndex();
fail();
} catch (IllegalStateException expected) {
}
} }
@Test @Test
@ -145,9 +115,6 @@ public class MultiLockAsyncMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_withFlushCompletedAndInputBuffer_returnsInputBuffer() public void dequeueInputBufferIndex_withFlushCompletedAndInputBuffer_returnsInputBuffer()
throws InterruptedException { throws InterruptedException {
// Disable calling codec.start() after flush to avoid receiving buffers from the
// shadow codec impl
adapter.setOnCodecStart(() -> {});
adapter.start(); adapter.start();
Looper looper = handlerThread.getLooper(); Looper looper = handlerThread.getLooper();
Handler handler = new Handler(looper); Handler handler = new Handler(looper);
@ -170,39 +137,19 @@ public class MultiLockAsyncMediaCodecAdapterTest {
adapter.start(); adapter.start();
adapter.onMediaCodecError(new IllegalStateException("error from codec")); adapter.onMediaCodecError(new IllegalStateException("error from codec"));
try { assertThrows(IllegalStateException.class, () -> adapter.dequeueInputBufferIndex());
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) {
}
}
@Test @Test
public void dequeueOutputBufferIndex_withInternalException_throwsException() public void dequeueOutputBufferIndex_withInternalException_throwsException()
throws InterruptedException { throws InterruptedException {
adapter.setOnCodecStart( AtomicInteger codecStartCalls = new AtomicInteger(0);
adapter.setCodecStartRunnable(
() -> { () -> {
if (codecStartCalls.incrementAndGet() == 2) {
throw new RuntimeException("codec#start() exception"); throw new RuntimeException("codec#start() exception");
}
}); });
adapter.start(); adapter.start();
adapter.flush(); adapter.flush();
@ -211,11 +158,7 @@ public class MultiLockAsyncMediaCodecAdapterTest {
waitUntilAllEventsAreExecuted( waitUntilAllEventsAreExecuted(
handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS)) handlerThread.getLooper(), /* time= */ 5, TimeUnit.SECONDS))
.isTrue(); .isTrue();
try { assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
adapter.dequeueOutputBufferIndex(bufferInfo);
fail();
} catch (IllegalStateException expected) {
}
} }
@Test @Test
@ -276,42 +219,14 @@ public class MultiLockAsyncMediaCodecAdapterTest {
adapter.start(); adapter.start();
adapter.onMediaCodecError(new IllegalStateException("error from codec")); adapter.onMediaCodecError(new IllegalStateException("error from codec"));
try { assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
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) {
}
} }
@Test @Test
public void getOutputFormat_withoutFormatReceived_throwsException() { public void getOutputFormat_withoutFormatReceived_throwsException() {
adapter.start(); adapter.start();
try { assertThrows(IllegalStateException.class, () -> adapter.getOutputFormat());
adapter.getOutputFormat();
fail();
} catch (IllegalStateException expected) {
}
} }
@Test @Test
@ -352,28 +267,10 @@ public class MultiLockAsyncMediaCodecAdapterTest {
assertThat(adapter.getOutputFormat()).isEqualTo(format); 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 @Test
public void flush_multipleTimes_onlyLastFlushExecutes() throws InterruptedException { public void flush_multipleTimes_onlyLastFlushExecutes() throws InterruptedException {
AtomicInteger onCodecStartCount = new AtomicInteger(0); AtomicInteger codecStartCalls = new AtomicInteger(0);
adapter.setOnCodecStart(() -> onCodecStartCount.incrementAndGet()); adapter.setCodecStartRunnable(() -> codecStartCalls.incrementAndGet());
adapter.start(); adapter.start();
Looper looper = handlerThread.getLooper(); Looper looper = handlerThread.getLooper();
Handler handler = new Handler(looper); Handler handler = new Handler(looper);
@ -385,23 +282,23 @@ public class MultiLockAsyncMediaCodecAdapterTest {
adapter.flush(); // Enqueues a second flush event adapter.flush(); // Enqueues a second flush event
handler.post(() -> adapter.onInputBufferAvailable(codec, 3)); handler.post(() -> adapter.onInputBufferAvailable(codec, 3));
// Progress the looper until the milestoneCount is increased - first flush event // Progress the looper until the milestoneCount is increased:
// should have been a no-op // adapter.start() called codec.start() but first flush event should have been a no-op
ShadowLooper shadowLooper = shadowOf(looper); ShadowLooper shadowLooper = shadowOf(looper);
while (milestoneCount.get() < 1) { while (milestoneCount.get() < 1) {
shadowLooper.runOneTask(); shadowLooper.runOneTask();
} }
assertThat(onCodecStartCount.get()).isEqualTo(0); assertThat(codecStartCalls.get()).isEqualTo(1);
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(3); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(3);
assertThat(onCodecStartCount.get()).isEqualTo(1); assertThat(codecStartCalls.get()).isEqualTo(2);
} }
@Test @Test
public void flush_andImmediatelyShutdown_flushIsNoOp() throws InterruptedException { public void flush_andImmediatelyShutdown_flushIsNoOp() throws InterruptedException {
AtomicInteger onCodecStartCount = new AtomicInteger(0); AtomicInteger codecStartCalls = new AtomicInteger(0);
adapter.setOnCodecStart(() -> onCodecStartCount.incrementAndGet()); adapter.setCodecStartRunnable(() -> codecStartCalls.incrementAndGet());
adapter.start(); adapter.start();
// Obtain looper when adapter is started. // Obtain looper when adapter is started.
Looper looper = handlerThread.getLooper(); Looper looper = handlerThread.getLooper();
@ -409,8 +306,8 @@ public class MultiLockAsyncMediaCodecAdapterTest {
adapter.shutdown(); adapter.shutdown();
assertThat(waitUntilAllEventsAreExecuted(looper, 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, 5, TimeUnit.SECONDS)).isTrue();
// Only shutdown flushes the MediaCodecAsync handler. // Only adapter.start() called codec#start()
assertThat(onCodecStartCount.get()).isEqualTo(0); assertThat(codecStartCalls.get()).isEqualTo(1);
} }
private static class TestHandlerThread extends HandlerThread { private static class TestHandlerThread extends HandlerThread {