Add experimental method to turn-off async flush
When operating the MediaCodec in asynchronous mode, after a MediaCodec.flush(), we start MediaCodec in the callback thread, which might trigger errors in some platforms. This change adds an experimental flag to move the call to MediaCodec.start() back to the playback thread. PiperOrigin-RevId: 407801013
This commit is contained in:
parent
9ae5083102
commit
81356a222a
@ -193,6 +193,25 @@ public class DefaultRenderersFactory implements RenderersFactory {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable calling {@link MediaCodec#start} immediately after {@link MediaCodec#flush} on the
|
||||||
|
* playback thread, when operating the codec in asynchronous mode. If disabled, {@link
|
||||||
|
* MediaCodec#start} will be called by the callback thread after pending callbacks are handled.
|
||||||
|
*
|
||||||
|
* <p>By default, this feature is disabled.
|
||||||
|
*
|
||||||
|
* <p>This method is experimental, and will be renamed or removed in a future release.
|
||||||
|
*
|
||||||
|
* @param enabled Whether {@link MediaCodec#start} will be called on the playback thread
|
||||||
|
* immediately after {@link MediaCodec#flush}.
|
||||||
|
* @return This factory, for convenience.
|
||||||
|
*/
|
||||||
|
public DefaultRenderersFactory experimentalSetImmediateCodecStartAfterFlushEnabled(
|
||||||
|
boolean enabled) {
|
||||||
|
codecAdapterFactory.experimentalSetImmediateCodecStartAfterFlushEnabled(enabled);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether to enable fallback to lower-priority decoders if decoder initialization fails.
|
* Sets whether to enable fallback to lower-priority decoders if decoder initialization fails.
|
||||||
* This may result in using a decoder that is less efficient or slower than the primary decoder.
|
* This may result in using a decoder that is less efficient or slower than the primary decoder.
|
||||||
|
@ -50,11 +50,7 @@ import java.nio.ByteBuffer;
|
|||||||
private final Supplier<HandlerThread> callbackThreadSupplier;
|
private final Supplier<HandlerThread> callbackThreadSupplier;
|
||||||
private final Supplier<HandlerThread> queueingThreadSupplier;
|
private final Supplier<HandlerThread> queueingThreadSupplier;
|
||||||
private final boolean synchronizeCodecInteractionsWithQueueing;
|
private final boolean synchronizeCodecInteractionsWithQueueing;
|
||||||
|
private final boolean enableImmediateCodecStartAfterFlush;
|
||||||
/** Creates a factory for codecs handling the specified {@link C.TrackType track type}. */
|
|
||||||
public Factory(@C.TrackType int trackType) {
|
|
||||||
this(trackType, /* synchronizeCodecInteractionsWithQueueing= */ false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an factory for {@link AsynchronousMediaCodecAdapter} instances.
|
* Creates an factory for {@link AsynchronousMediaCodecAdapter} instances.
|
||||||
@ -66,23 +62,29 @@ import java.nio.ByteBuffer;
|
|||||||
* interactions will wait until all input buffers pending queueing wil be submitted to the
|
* interactions will wait until all input buffers pending queueing wil be submitted to the
|
||||||
* {@link MediaCodec}.
|
* {@link MediaCodec}.
|
||||||
*/
|
*/
|
||||||
public Factory(@C.TrackType int trackType, boolean synchronizeCodecInteractionsWithQueueing) {
|
public Factory(
|
||||||
|
@C.TrackType int trackType,
|
||||||
|
boolean synchronizeCodecInteractionsWithQueueing,
|
||||||
|
boolean enableImmediateCodecStartAfterFlush) {
|
||||||
this(
|
this(
|
||||||
/* callbackThreadSupplier= */ () ->
|
/* callbackThreadSupplier= */ () ->
|
||||||
new HandlerThread(createCallbackThreadLabel(trackType)),
|
new HandlerThread(createCallbackThreadLabel(trackType)),
|
||||||
/* queueingThreadSupplier= */ () ->
|
/* queueingThreadSupplier= */ () ->
|
||||||
new HandlerThread(createQueueingThreadLabel(trackType)),
|
new HandlerThread(createQueueingThreadLabel(trackType)),
|
||||||
synchronizeCodecInteractionsWithQueueing);
|
synchronizeCodecInteractionsWithQueueing,
|
||||||
|
enableImmediateCodecStartAfterFlush);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
/* package */ Factory(
|
/* package */ Factory(
|
||||||
Supplier<HandlerThread> callbackThreadSupplier,
|
Supplier<HandlerThread> callbackThreadSupplier,
|
||||||
Supplier<HandlerThread> queueingThreadSupplier,
|
Supplier<HandlerThread> queueingThreadSupplier,
|
||||||
boolean synchronizeCodecInteractionsWithQueueing) {
|
boolean synchronizeCodecInteractionsWithQueueing,
|
||||||
|
boolean enableImmediateCodecStartAfterFlush) {
|
||||||
this.callbackThreadSupplier = callbackThreadSupplier;
|
this.callbackThreadSupplier = callbackThreadSupplier;
|
||||||
this.queueingThreadSupplier = queueingThreadSupplier;
|
this.queueingThreadSupplier = queueingThreadSupplier;
|
||||||
this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
|
this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
|
||||||
|
this.enableImmediateCodecStartAfterFlush = enableImmediateCodecStartAfterFlush;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -99,7 +101,8 @@ import java.nio.ByteBuffer;
|
|||||||
codec,
|
codec,
|
||||||
callbackThreadSupplier.get(),
|
callbackThreadSupplier.get(),
|
||||||
queueingThreadSupplier.get(),
|
queueingThreadSupplier.get(),
|
||||||
synchronizeCodecInteractionsWithQueueing);
|
synchronizeCodecInteractionsWithQueueing,
|
||||||
|
enableImmediateCodecStartAfterFlush);
|
||||||
TraceUtil.endSection();
|
TraceUtil.endSection();
|
||||||
codecAdapter.initialize(
|
codecAdapter.initialize(
|
||||||
configuration.mediaFormat,
|
configuration.mediaFormat,
|
||||||
@ -132,6 +135,7 @@ import java.nio.ByteBuffer;
|
|||||||
private final AsynchronousMediaCodecCallback asynchronousMediaCodecCallback;
|
private final AsynchronousMediaCodecCallback asynchronousMediaCodecCallback;
|
||||||
private final AsynchronousMediaCodecBufferEnqueuer bufferEnqueuer;
|
private final AsynchronousMediaCodecBufferEnqueuer bufferEnqueuer;
|
||||||
private final boolean synchronizeCodecInteractionsWithQueueing;
|
private final boolean synchronizeCodecInteractionsWithQueueing;
|
||||||
|
private final boolean enableImmediateCodecStartAfterFlush;
|
||||||
private boolean codecReleased;
|
private boolean codecReleased;
|
||||||
@State private int state;
|
@State private int state;
|
||||||
@Nullable private Surface inputSurface;
|
@Nullable private Surface inputSurface;
|
||||||
@ -140,11 +144,13 @@ import java.nio.ByteBuffer;
|
|||||||
MediaCodec codec,
|
MediaCodec codec,
|
||||||
HandlerThread callbackThread,
|
HandlerThread callbackThread,
|
||||||
HandlerThread enqueueingThread,
|
HandlerThread enqueueingThread,
|
||||||
boolean synchronizeCodecInteractionsWithQueueing) {
|
boolean synchronizeCodecInteractionsWithQueueing,
|
||||||
|
boolean enableImmediateCodecStartAfterFlush) {
|
||||||
this.codec = codec;
|
this.codec = codec;
|
||||||
this.asynchronousMediaCodecCallback = new AsynchronousMediaCodecCallback(callbackThread);
|
this.asynchronousMediaCodecCallback = new AsynchronousMediaCodecCallback(callbackThread);
|
||||||
this.bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, enqueueingThread);
|
this.bufferEnqueuer = new AsynchronousMediaCodecBufferEnqueuer(codec, enqueueingThread);
|
||||||
this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
|
this.synchronizeCodecInteractionsWithQueueing = synchronizeCodecInteractionsWithQueueing;
|
||||||
|
this.enableImmediateCodecStartAfterFlush = enableImmediateCodecStartAfterFlush;
|
||||||
this.state = STATE_CREATED;
|
this.state = STATE_CREATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,13 +237,20 @@ import java.nio.ByteBuffer;
|
|||||||
@Override
|
@Override
|
||||||
public void flush() {
|
public void flush() {
|
||||||
// The order of calls is important:
|
// The order of calls is important:
|
||||||
// First, flush the bufferEnqueuer to stop queueing input buffers.
|
// 1. Flush the bufferEnqueuer to stop queueing input buffers.
|
||||||
// Second, flush the codec to stop producing available input/output buffers.
|
// 2. Flush the codec to stop producing available input/output buffers.
|
||||||
// Third, flush the callback after flushing the codec so that in-flight callbacks are discarded.
|
// 3. Flush the callback after flushing the codec so that in-flight callbacks are discarded.
|
||||||
bufferEnqueuer.flush();
|
bufferEnqueuer.flush();
|
||||||
codec.flush();
|
codec.flush();
|
||||||
// When flushAsync() is completed, start the codec again.
|
if (enableImmediateCodecStartAfterFlush) {
|
||||||
asynchronousMediaCodecCallback.flushAsync(/* onFlushCompleted= */ codec::start);
|
// The asynchronous callback will drop pending callbacks but we can start the codec now.
|
||||||
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
|
codec.start();
|
||||||
|
} else {
|
||||||
|
// Let the asynchronous callback start the codec in the callback thread after pending
|
||||||
|
// callbacks are handled.
|
||||||
|
asynchronousMediaCodecCallback.flush(codec);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -34,8 +34,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
@RequiresApi(23)
|
@RequiresApi(23)
|
||||||
/* package */ final class AsynchronousMediaCodecCallback extends MediaCodec.Callback {
|
/* package */ final class AsynchronousMediaCodecCallback extends MediaCodec.Callback {
|
||||||
private final Object lock;
|
private final Object lock;
|
||||||
|
|
||||||
private final HandlerThread callbackThread;
|
private final HandlerThread callbackThread;
|
||||||
|
|
||||||
private @MonotonicNonNull Handler handler;
|
private @MonotonicNonNull Handler handler;
|
||||||
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
@ -192,14 +192,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* Initiates a flush asynchronously, which will be completed on the callback thread. When the
|
* Initiates a flush asynchronously, which will be completed on the callback thread. When the
|
||||||
* flush is complete, it will trigger {@code onFlushCompleted} from the callback thread.
|
* flush is complete, it will trigger {@code onFlushCompleted} from the callback thread.
|
||||||
*
|
*
|
||||||
* @param onFlushCompleted A {@link Runnable} that will be called when flush is completed. {@code
|
* @param codec A {@link MediaCodec} to {@link MediaCodec#start start} after all pending callbacks
|
||||||
* onFlushCompleted} will be called from the scallback thread, therefore it should execute
|
* are handled, or {@code null} if starting the {@link MediaCodec} is performed elsewhere.
|
||||||
* synchronized and thread-safe code.
|
|
||||||
*/
|
*/
|
||||||
public void flushAsync(Runnable onFlushCompleted) {
|
public void flush(@Nullable MediaCodec codec) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
++pendingFlushCount;
|
++pendingFlushCount;
|
||||||
Util.castNonNull(handler).post(() -> this.onFlushCompleted(onFlushCompleted));
|
Util.castNonNull(handler).post(() -> this.onFlushCompleted(codec));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,34 +238,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFlushCompleted(Runnable onFlushCompleted) {
|
private void onFlushCompleted(@Nullable MediaCodec codec) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
onFlushCompletedSynchronized(onFlushCompleted);
|
if (shutDown) {
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
--pendingFlushCount;
|
||||||
private void onFlushCompletedSynchronized(Runnable onFlushCompleted) {
|
if (pendingFlushCount > 0) {
|
||||||
if (shutDown) {
|
// Another flush() has been called.
|
||||||
return;
|
return;
|
||||||
}
|
} else if (pendingFlushCount < 0) {
|
||||||
|
// This should never happen.
|
||||||
--pendingFlushCount;
|
setInternalException(new IllegalStateException());
|
||||||
if (pendingFlushCount > 0) {
|
return;
|
||||||
// Another flush() has been called.
|
}
|
||||||
return;
|
flushInternal();
|
||||||
} else if (pendingFlushCount < 0) {
|
if (codec != null) {
|
||||||
// This should never happen.
|
try {
|
||||||
setInternalException(new IllegalStateException());
|
codec.start();
|
||||||
return;
|
} catch (IllegalStateException e) {
|
||||||
}
|
setInternalException(e);
|
||||||
flushInternal();
|
} catch (Exception e) {
|
||||||
try {
|
setInternalException(new IllegalStateException(e));
|
||||||
onFlushCompleted.run();
|
}
|
||||||
} catch (IllegalStateException e) {
|
}
|
||||||
setInternalException(e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
setInternalException(new IllegalStateException(e));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,10 +271,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private void flushInternal() {
|
private void flushInternal() {
|
||||||
if (!formats.isEmpty()) {
|
if (!formats.isEmpty()) {
|
||||||
pendingOutputFormat = formats.getLast();
|
pendingOutputFormat = formats.getLast();
|
||||||
} else {
|
|
||||||
// pendingOutputFormat may already be non-null following a previous flush, and remains set in
|
|
||||||
// this case.
|
|
||||||
}
|
}
|
||||||
|
// else, pendingOutputFormat may already be non-null following a previous flush, and remains
|
||||||
|
// set in this case.
|
||||||
|
|
||||||
availableInputBuffers.clear();
|
availableInputBuffers.clear();
|
||||||
availableOutputBuffers.clear();
|
availableOutputBuffers.clear();
|
||||||
bufferInfos.clear();
|
bufferInfos.clear();
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.mediacodec;
|
package androidx.media3.exoplayer.mediacodec;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
@ -48,6 +49,7 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
|
|||||||
|
|
||||||
@Mode private int asynchronousMode;
|
@Mode private int asynchronousMode;
|
||||||
private boolean enableSynchronizeCodecInteractionsWithQueueing;
|
private boolean enableSynchronizeCodecInteractionsWithQueueing;
|
||||||
|
private boolean enableImmediateCodecStartAfterFlush;
|
||||||
|
|
||||||
public DefaultMediaCodecAdapterFactory() {
|
public DefaultMediaCodecAdapterFactory() {
|
||||||
asynchronousMode = MODE_DEFAULT;
|
asynchronousMode = MODE_DEFAULT;
|
||||||
@ -87,6 +89,22 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
|
|||||||
enableSynchronizeCodecInteractionsWithQueueing = enabled;
|
enableSynchronizeCodecInteractionsWithQueueing = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable calling {@link MediaCodec#start} immediately after {@link MediaCodec#flush} on the
|
||||||
|
* playback thread, when operating the codec in asynchronous mode. If disabled, {@link
|
||||||
|
* MediaCodec#start} will be called by the callback thread after pending callbacks are handled.
|
||||||
|
*
|
||||||
|
* <p>By default, this feature is disabled.
|
||||||
|
*
|
||||||
|
* <p>This method is experimental, and will be renamed or removed in a future release.
|
||||||
|
*
|
||||||
|
* @param enabled Whether {@link MediaCodec#start()} will be called on the playback thread
|
||||||
|
* immediately after {@link MediaCodec#flush}.
|
||||||
|
*/
|
||||||
|
public void experimentalSetImmediateCodecStartAfterFlushEnabled(boolean enabled) {
|
||||||
|
enableImmediateCodecStartAfterFlush = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaCodecAdapter createAdapter(MediaCodecAdapter.Configuration configuration)
|
public MediaCodecAdapter createAdapter(MediaCodecAdapter.Configuration configuration)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@ -99,7 +117,9 @@ public final class DefaultMediaCodecAdapterFactory implements MediaCodecAdapter.
|
|||||||
+ Util.getTrackTypeString(trackType));
|
+ Util.getTrackTypeString(trackType));
|
||||||
AsynchronousMediaCodecAdapter.Factory factory =
|
AsynchronousMediaCodecAdapter.Factory factory =
|
||||||
new AsynchronousMediaCodecAdapter.Factory(
|
new AsynchronousMediaCodecAdapter.Factory(
|
||||||
trackType, enableSynchronizeCodecInteractionsWithQueueing);
|
trackType,
|
||||||
|
enableSynchronizeCodecInteractionsWithQueueing,
|
||||||
|
enableImmediateCodecStartAfterFlush);
|
||||||
return factory.createAdapter(configuration);
|
return factory.createAdapter(configuration);
|
||||||
}
|
}
|
||||||
return new SynchronousMediaCodecAdapter.Factory().createAdapter(configuration);
|
return new SynchronousMediaCodecAdapter.Factory().createAdapter(configuration);
|
||||||
|
@ -54,7 +54,8 @@ public class AsynchronousMediaCodecAdapterTest {
|
|||||||
new AsynchronousMediaCodecAdapter.Factory(
|
new AsynchronousMediaCodecAdapter.Factory(
|
||||||
/* callbackThreadSupplier= */ () -> callbackThread,
|
/* callbackThreadSupplier= */ () -> callbackThread,
|
||||||
/* queueingThreadSupplier= */ () -> queueingThread,
|
/* queueingThreadSupplier= */ () -> queueingThread,
|
||||||
/* synchronizeCodecInteractionsWithQueueing= */ false)
|
/* synchronizeCodecInteractionsWithQueueing= */ false,
|
||||||
|
/* enableImmediateCodecStartAfterFlush= */ false)
|
||||||
.createAdapter(configuration);
|
.createAdapter(configuration);
|
||||||
bufferInfo = new MediaCodec.BufferInfo();
|
bufferInfo = new MediaCodec.BufferInfo();
|
||||||
// After starting the MediaCodec, the ShadowMediaCodec offers input buffer 0. We advance the
|
// After starting the MediaCodec, the ShadowMediaCodec offers input buffer 0. We advance the
|
||||||
|
@ -24,6 +24,7 @@ import static org.robolectric.Shadows.shadowOf;
|
|||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@ -81,16 +82,24 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dequeInputBufferIndex_withPendingFlush_returnsTryAgain() {
|
public void dequeInputBufferIndex_withPendingFlush_returnsTryAgain() {
|
||||||
Looper callbackThreadLooper = callbackThread.getLooper();
|
AtomicBoolean beforeFlushCompletes = new AtomicBoolean();
|
||||||
AtomicBoolean flushCompleted = new AtomicBoolean();
|
AtomicBoolean flushCompleted = new AtomicBoolean();
|
||||||
|
Looper callbackThreadLooper = callbackThread.getLooper();
|
||||||
|
Handler callbackHandler = new Handler(callbackThreadLooper);
|
||||||
|
ShadowLooper shadowCallbackLooper = shadowOf(callbackThreadLooper);
|
||||||
// Pause the callback thread so that flush() never completes.
|
// Pause the callback thread so that flush() never completes.
|
||||||
shadowOf(callbackThreadLooper).pause();
|
shadowCallbackLooper.pause();
|
||||||
|
|
||||||
// Send two input buffers to the callback and then flush().
|
// Send two input buffers to the callback and then flush().
|
||||||
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0);
|
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0);
|
||||||
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1);
|
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1);
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
callbackHandler.post(() -> beforeFlushCompletes.set(true));
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
|
callbackHandler.post(() -> flushCompleted.set(true));
|
||||||
|
while (!beforeFlushCompletes.get()) {
|
||||||
|
shadowCallbackLooper.runOneTask();
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(flushCompleted.get()).isFalse();
|
assertThat(flushCompleted.get()).isFalse();
|
||||||
assertThat(asynchronousMediaCodecCallback.dequeueInputBufferIndex())
|
assertThat(asynchronousMediaCodecCallback.dequeueInputBufferIndex())
|
||||||
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
@ -104,8 +113,8 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
// Send two input buffers to the callback and then flush().
|
// Send two input buffers to the callback and then flush().
|
||||||
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0);
|
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0);
|
||||||
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1);
|
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1);
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true));
|
||||||
// Progress the callback thread so that flush() completes.
|
// Progress the callback thread so that flush() completes.
|
||||||
shadowOf(callbackThreadLooper).idle();
|
shadowOf(callbackThreadLooper).idle();
|
||||||
|
|
||||||
@ -123,10 +132,11 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
// another input buffer.
|
// another input buffer.
|
||||||
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0);
|
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0);
|
||||||
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1);
|
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1);
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true));
|
||||||
// Progress the callback thread so that flush() completes.
|
// Progress the callback thread to complete flush.
|
||||||
shadowOf(callbackThreadLooper).idle();
|
shadowOf(callbackThread.getLooper()).idle();
|
||||||
|
// Send another input buffer to the callback
|
||||||
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 2);
|
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 2);
|
||||||
|
|
||||||
assertThat(flushCompleted.get()).isTrue();
|
assertThat(flushCompleted.get()).isTrue();
|
||||||
@ -152,20 +162,6 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
() -> asynchronousMediaCodecCallback.dequeueInputBufferIndex());
|
() -> asynchronousMediaCodecCallback.dequeueInputBufferIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void dequeueInputBufferIndex_afterFlushCompletedWithError_throwsError() throws Exception {
|
|
||||||
MediaCodec.CodecException codecException = createCodecException();
|
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
|
||||||
() -> {
|
|
||||||
throw codecException;
|
|
||||||
});
|
|
||||||
shadowOf(callbackThread.getLooper()).idle();
|
|
||||||
|
|
||||||
assertThrows(
|
|
||||||
MediaCodec.CodecException.class,
|
|
||||||
() -> asynchronousMediaCodecCallback.dequeueInputBufferIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dequeOutputBufferIndex_afterCreation_returnsTryAgain() {
|
public void dequeOutputBufferIndex_afterCreation_returnsTryAgain() {
|
||||||
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
||||||
@ -198,17 +194,24 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dequeOutputBufferIndex_withPendingFlush_returnsTryAgain() {
|
public void dequeOutputBufferIndex_withPendingFlush_returnsTryAgain() {
|
||||||
Looper callbackThreadLooper = callbackThread.getLooper();
|
AtomicBoolean beforeFlushCompletes = new AtomicBoolean();
|
||||||
AtomicBoolean flushCompleted = new AtomicBoolean();
|
AtomicBoolean flushCompleted = new AtomicBoolean();
|
||||||
|
Looper callbackThreadLooper = callbackThread.getLooper();
|
||||||
|
Handler callbackHandler = new Handler(callbackThreadLooper);
|
||||||
|
ShadowLooper shadowCallbackLooper = shadowOf(callbackThreadLooper);
|
||||||
// Pause the callback thread so that flush() never completes.
|
// Pause the callback thread so that flush() never completes.
|
||||||
shadowOf(callbackThreadLooper).pause();
|
shadowCallbackLooper.pause();
|
||||||
|
|
||||||
// Send two output buffers to the callback and then flush().
|
// Send two output buffers to the callback and then flush().
|
||||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo);
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo);
|
||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo);
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo);
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
callbackHandler.post(() -> beforeFlushCompletes.set(true));
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
|
callbackHandler.post(() -> flushCompleted.set(true));
|
||||||
|
while (beforeFlushCompletes.get()) {
|
||||||
|
shadowCallbackLooper.runOneTask();
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(flushCompleted.get()).isFalse();
|
assertThat(flushCompleted.get()).isFalse();
|
||||||
assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo()))
|
assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo()))
|
||||||
@ -224,8 +227,8 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo);
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo);
|
||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo);
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo);
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true));
|
||||||
// Progress the callback looper so that flush() completes.
|
// Progress the callback looper so that flush() completes.
|
||||||
shadowOf(callbackThreadLooper).idle();
|
shadowOf(callbackThreadLooper).idle();
|
||||||
|
|
||||||
@ -245,10 +248,11 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format0"));
|
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format0"));
|
||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo);
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo);
|
||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo);
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo);
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true));
|
||||||
// Progress the callback looper so that flush() completes.
|
// Progress the callback looper so that flush() completes.
|
||||||
shadowOf(callbackThreadLooper).idle();
|
shadowOf(callbackThreadLooper).idle();
|
||||||
|
// Emulate an output buffer is available.
|
||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 2, bufferInfo);
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 2, bufferInfo);
|
||||||
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
|
||||||
@ -271,8 +275,8 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
MediaFormat pendingMediaFormat = new MediaFormat();
|
MediaFormat pendingMediaFormat = new MediaFormat();
|
||||||
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, pendingMediaFormat);
|
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, pendingMediaFormat);
|
||||||
// flush() should not discard the last format.
|
// flush() should not discard the last format.
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true));
|
||||||
// Progress the callback looper so that flush() completes.
|
// Progress the callback looper so that flush() completes.
|
||||||
shadowOf(callbackThreadLooper).idle();
|
shadowOf(callbackThreadLooper).idle();
|
||||||
// Right after flush(), we send an output buffer: the pending output format should be
|
// Right after flush(), we send an output buffer: the pending output format should be
|
||||||
@ -298,8 +302,8 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
MediaFormat pendingMediaFormat = new MediaFormat();
|
MediaFormat pendingMediaFormat = new MediaFormat();
|
||||||
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, pendingMediaFormat);
|
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, pendingMediaFormat);
|
||||||
// flush() should not discard the last format.
|
// flush() should not discard the last format.
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true));
|
||||||
// Progress the callback looper so that flush() completes.
|
// Progress the callback looper so that flush() completes.
|
||||||
shadowOf(callbackThreadLooper).idle();
|
shadowOf(callbackThreadLooper).idle();
|
||||||
// The first callback after flush() is a new MediaFormat, it should overwrite the pending
|
// The first callback after flush() is a new MediaFormat, it should overwrite the pending
|
||||||
@ -335,20 +339,6 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
() -> asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo()));
|
() -> asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void dequeueOutputBufferIndex_afterFlushCompletedWithError_throwsError() throws Exception {
|
|
||||||
MediaCodec.CodecException codecException = createCodecException();
|
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
|
||||||
() -> {
|
|
||||||
throw codecException;
|
|
||||||
});
|
|
||||||
shadowOf(callbackThread.getLooper()).idle();
|
|
||||||
|
|
||||||
assertThrows(
|
|
||||||
MediaCodec.CodecException.class,
|
|
||||||
() -> asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getOutputFormat_onNewInstance_raisesException() {
|
public void getOutputFormat_onNewInstance_raisesException() {
|
||||||
try {
|
try {
|
||||||
@ -377,8 +367,8 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
|
|
||||||
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, format);
|
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, format);
|
||||||
asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo());
|
asynchronousMediaCodecCallback.dequeueOutputBufferIndex(new MediaCodec.BufferInfo());
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true));
|
||||||
// Progress the callback looper so that flush() completes.
|
// Progress the callback looper so that flush() completes.
|
||||||
shadowOf(callbackThreadLooper).idle();
|
shadowOf(callbackThreadLooper).idle();
|
||||||
|
|
||||||
@ -390,7 +380,8 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
public void getOutputFormat_afterFlushWithPendingFormat_returnsPendingFormat() {
|
public void getOutputFormat_afterFlushWithPendingFormat_returnsPendingFormat() {
|
||||||
MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
|
||||||
AtomicBoolean flushCompleted = new AtomicBoolean();
|
AtomicBoolean flushCompleted = new AtomicBoolean();
|
||||||
ShadowLooper shadowCallbackLooper = shadowOf(callbackThread.getLooper());
|
Looper callbackThreadLooper = callbackThread.getLooper();
|
||||||
|
ShadowLooper shadowCallbackLooper = shadowOf(callbackThreadLooper);
|
||||||
shadowCallbackLooper.pause();
|
shadowCallbackLooper.pause();
|
||||||
|
|
||||||
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format0"));
|
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format0"));
|
||||||
@ -399,8 +390,8 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format1"));
|
asynchronousMediaCodecCallback.onOutputFormatChanged(codec, createMediaFormat("format1"));
|
||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(
|
||||||
codec, /* index= */ 1, new MediaCodec.BufferInfo());
|
codec, /* index= */ 1, new MediaCodec.BufferInfo());
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ () -> flushCompleted.set(true));
|
new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true));
|
||||||
// Progress the looper so that flush is completed
|
// Progress the looper so that flush is completed
|
||||||
shadowCallbackLooper.idle();
|
shadowCallbackLooper.idle();
|
||||||
// Enqueue an output buffer to make the pending format available.
|
// Enqueue an output buffer to make the pending format available.
|
||||||
@ -419,7 +410,8 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
public void
|
public void
|
||||||
getOutputFormat_withConsecutiveFlushAndPendingFormatFromFirstFlush_returnsPendingFormat() {
|
getOutputFormat_withConsecutiveFlushAndPendingFormatFromFirstFlush_returnsPendingFormat() {
|
||||||
MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
|
||||||
AtomicInteger flushesCompleted = new AtomicInteger();
|
AtomicInteger flushCompleted = new AtomicInteger();
|
||||||
|
Handler callbackThreadHandler = new Handler(callbackThread.getLooper());
|
||||||
ShadowLooper shadowCallbackLooper = shadowOf(callbackThread.getLooper());
|
ShadowLooper shadowCallbackLooper = shadowOf(callbackThread.getLooper());
|
||||||
shadowCallbackLooper.pause();
|
shadowCallbackLooper.pause();
|
||||||
|
|
||||||
@ -427,17 +419,17 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(
|
||||||
codec, /* index= */ 0, new MediaCodec.BufferInfo());
|
codec, /* index= */ 0, new MediaCodec.BufferInfo());
|
||||||
// Flush and progress the looper so that flush is completed.
|
// Flush and progress the looper so that flush is completed.
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ flushesCompleted::incrementAndGet);
|
callbackThreadHandler.post(flushCompleted::incrementAndGet);
|
||||||
shadowCallbackLooper.idle();
|
shadowCallbackLooper.idle();
|
||||||
// Flush again, the pending format from the first flush should remain as pending.
|
// Flush again, the pending format from the first flush should remain as pending.
|
||||||
asynchronousMediaCodecCallback.flushAsync(
|
asynchronousMediaCodecCallback.flush(/* codec= */ null);
|
||||||
/* onFlushCompleted= */ flushesCompleted::incrementAndGet);
|
callbackThreadHandler.post(flushCompleted::incrementAndGet);
|
||||||
shadowCallbackLooper.idle();
|
shadowCallbackLooper.idle();
|
||||||
asynchronousMediaCodecCallback.onOutputBufferAvailable(
|
asynchronousMediaCodecCallback.onOutputBufferAvailable(
|
||||||
codec, /* index= */ 1, new MediaCodec.BufferInfo());
|
codec, /* index= */ 1, new MediaCodec.BufferInfo());
|
||||||
|
|
||||||
assertThat(flushesCompleted.get()).isEqualTo(2);
|
assertThat(flushCompleted.get()).isEqualTo(2);
|
||||||
assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo))
|
assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo))
|
||||||
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||||
assertThat(asynchronousMediaCodecCallback.getOutputFormat().getString("name"))
|
assertThat(asynchronousMediaCodecCallback.getOutputFormat().getString("name"))
|
||||||
@ -445,19 +437,6 @@ public class AsynchronousMediaCodecCallbackTest {
|
|||||||
assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo)).isEqualTo(1);
|
assertThat(asynchronousMediaCodecCallback.dequeueOutputBufferIndex(outInfo)).isEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void flush_withPendingFlush_onlyLastFlushCompletes() {
|
|
||||||
ShadowLooper callbackLooperShadow = shadowOf(callbackThread.getLooper());
|
|
||||||
callbackLooperShadow.pause();
|
|
||||||
AtomicInteger flushCompleted = new AtomicInteger();
|
|
||||||
|
|
||||||
asynchronousMediaCodecCallback.flushAsync(/* onFlushCompleted= */ () -> flushCompleted.set(1));
|
|
||||||
asynchronousMediaCodecCallback.flushAsync(/* onFlushCompleted= */ () -> flushCompleted.set(2));
|
|
||||||
callbackLooperShadow.idle();
|
|
||||||
|
|
||||||
assertThat(flushCompleted.get()).isEqualTo(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Reflectively create a {@link MediaCodec.CodecException}. */
|
/** Reflectively create a {@link MediaCodec.CodecException}. */
|
||||||
private static MediaCodec.CodecException createCodecException() throws Exception {
|
private static MediaCodec.CodecException createCodecException() throws Exception {
|
||||||
Constructor<MediaCodec.CodecException> constructor =
|
Constructor<MediaCodec.CodecException> constructor =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user