Stop suppressing exceptions in MediaCodec.Callback during flush

Previously `AsynchronousMediaCodecCallback.mediaCodecException` was
cleared when flushing completed. This behaviour was changed in
aeff51c507
so now the exception is not cleared.

The result after that commit was that we would **only** suppress/ignore
the expression if a flush was currently pending, and we would throw it
both before and after the flush. This doesn't really make sense, so this
commit changes the behaviour to also throw the exception during the
flush.

This commit also corrects the assertion in
`flush_withPendingError_resetsError` and deflakes it so that it
consistently passes. The previous version of this test, although the
assertion was incorrect, would often pass because the
`dequeueInputBuffer` call would happen while the `flush` was still
pending, so the exception was suppressed.

#minor-release

PiperOrigin-RevId: 540237228
This commit is contained in:
ibaker 2023-06-14 13:07:39 +01:00 committed by Ian Baker
parent b69b33206e
commit 248d1d99ec
2 changed files with 88 additions and 9 deletions

View File

@ -126,10 +126,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
*/ */
public int dequeueInputBufferIndex() { public int dequeueInputBufferIndex() {
synchronized (lock) { synchronized (lock) {
maybeThrowException();
if (isFlushingOrShutdown()) { if (isFlushingOrShutdown()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
maybeThrowException();
return availableInputBuffers.isEmpty() return availableInputBuffers.isEmpty()
? MediaCodec.INFO_TRY_AGAIN_LATER ? MediaCodec.INFO_TRY_AGAIN_LATER
: availableInputBuffers.remove(); : availableInputBuffers.remove();
@ -145,10 +145,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
*/ */
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
synchronized (lock) { synchronized (lock) {
maybeThrowException();
if (isFlushingOrShutdown()) { if (isFlushingOrShutdown()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
maybeThrowException();
if (availableOutputBuffers.isEmpty()) { if (availableOutputBuffers.isEmpty()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {

View File

@ -30,6 +30,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.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After; import org.junit.After;
@ -105,6 +106,36 @@ public class AsynchronousMediaCodecCallbackTest {
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
} }
@Test
public void dequeInputBufferIndex_withPendingFlushAndError_throwsError() throws Exception {
AtomicBoolean beforeFlushCompletes = 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.
shadowCallbackLooper.pause();
// Send two input buffers to the callback, then an error, and then flush().
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 0);
asynchronousMediaCodecCallback.onInputBufferAvailable(codec, 1);
MediaCodec.CodecException expectedException = createCodecException();
asynchronousMediaCodecCallback.onError(codec, expectedException);
callbackHandler.post(() -> beforeFlushCompletes.set(true));
asynchronousMediaCodecCallback.flush();
callbackHandler.post(() -> flushCompleted.set(true));
while (!beforeFlushCompletes.get()) {
shadowCallbackLooper.runOneTask();
}
assertThat(flushCompleted.get()).isFalse();
MediaCodec.CodecException actualException =
assertThrows(
MediaCodec.CodecException.class,
() -> asynchronousMediaCodecCallback.dequeueInputBufferIndex());
assertThat(actualException).isSameInstanceAs(expectedException);
}
@Test @Test
public void dequeInputBufferIndex_afterFlush_returnsTryAgain() { public void dequeInputBufferIndex_afterFlush_returnsTryAgain() {
Looper callbackThreadLooper = callbackThread.getLooper(); Looper callbackThreadLooper = callbackThread.getLooper();
@ -218,6 +249,39 @@ public class AsynchronousMediaCodecCallbackTest {
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
} }
@Test
public void dequeOutputBufferIndex_withPendingFlushAndError_throwsError() throws Exception {
AtomicBoolean beforeFlushCompletes = 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.
shadowCallbackLooper.pause();
// Send two output buffers to the callback, then an error, and then flush().
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 0, bufferInfo);
asynchronousMediaCodecCallback.onOutputBufferAvailable(codec, 1, bufferInfo);
MediaCodec.CodecException expectedException = createCodecException();
asynchronousMediaCodecCallback.onError(codec, expectedException);
callbackHandler.post(() -> beforeFlushCompletes.set(true));
asynchronousMediaCodecCallback.flush();
callbackHandler.post(() -> flushCompleted.set(true));
while (beforeFlushCompletes.get()) {
shadowCallbackLooper.runOneTask();
}
assertThat(flushCompleted.get()).isFalse();
MediaCodec.CodecException actualException =
assertThrows(
MediaCodec.CodecException.class,
() ->
asynchronousMediaCodecCallback.dequeueOutputBufferIndex(
new MediaCodec.BufferInfo()));
assertThat(actualException).isSameInstanceAs(expectedException);
}
@Test @Test
public void dequeOutputBufferIndex_afterFlush_returnsTryAgain() { public void dequeOutputBufferIndex_afterFlush_returnsTryAgain() {
Looper callbackThreadLooper = callbackThread.getLooper(); Looper callbackThreadLooper = callbackThread.getLooper();
@ -438,13 +502,24 @@ public class AsynchronousMediaCodecCallbackTest {
} }
@Test @Test
public void flush_withPendingError_resetsError() throws Exception { public void flush_withPendingError_doesntResetError() throws Exception {
asynchronousMediaCodecCallback.onError(codec, createCodecException()); AtomicBoolean flushCompleted = new AtomicBoolean();
// Calling flush should clear any pending error. Looper callbackThreadLooper = callbackThread.getLooper();
asynchronousMediaCodecCallback.flush(); ShadowLooper shadowCallbackLooper = shadowOf(callbackThreadLooper);
assertThat(asynchronousMediaCodecCallback.dequeueInputBufferIndex()) MediaCodec.CodecException expectedException = createCodecException();
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); asynchronousMediaCodecCallback.onError(codec, expectedException);
// Flush and progress the looper so that flush is completed.
asynchronousMediaCodecCallback.flush();
new Handler(callbackThreadLooper).post(() -> flushCompleted.set(true));
shadowCallbackLooper.idle();
assertThat(flushCompleted.get()).isTrue();
MediaCodec.CodecException actualException =
assertThrows(
MediaCodec.CodecException.class,
() -> asynchronousMediaCodecCallback.dequeueInputBufferIndex());
assertThat(actualException).isSameInstanceAs(expectedException);
} }
@Test @Test
@ -456,7 +531,11 @@ public class AsynchronousMediaCodecCallbackTest {
} }
/** 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 NoSuchMethodException,
InvocationTargetException,
IllegalAccessException,
InstantiationException {
Constructor<MediaCodec.CodecException> constructor = Constructor<MediaCodec.CodecException> constructor =
MediaCodec.CodecException.class.getDeclaredConstructor( MediaCodec.CodecException.class.getDeclaredConstructor(
Integer.TYPE, Integer.TYPE, String.class); Integer.TYPE, Integer.TYPE, String.class);