From fdfca880195da2ca5e5d0ef002feb8144009384c Mon Sep 17 00:00:00 2001 From: hschlueter Date: Wed, 15 Jun 2022 13:27:35 +0000 Subject: [PATCH] Replace FrameProcessorChain#isEnded with listener method. PiperOrigin-RevId: 455114693 --- .../FrameProcessorChainPixelTest.java | 17 ++++++-- .../transformer/FrameProcessorChain.java | 40 ++++++++++++++----- .../VideoTranscodingSamplePipeline.java | 26 +++++++----- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/FrameProcessorChainPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/FrameProcessorChainPixelTest.java index 7cec1d5a6a..0820049cb9 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/FrameProcessorChainPixelTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/FrameProcessorChainPixelTest.java @@ -89,9 +89,10 @@ public final class FrameProcessorChainPixelTest { private final AtomicReference frameProcessingException = new AtomicReference<>(); + private @MonotonicNonNull MediaFormat mediaFormat; private @MonotonicNonNull FrameProcessorChain frameProcessorChain; private volatile @MonotonicNonNull ImageReader outputImageReader; - private @MonotonicNonNull MediaFormat mediaFormat; + private volatile boolean frameProcessingEnded; @After public void release() { @@ -354,7 +355,17 @@ public final class FrameProcessorChainPixelTest { checkNotNull( FrameProcessorChain.create( context, - /* listener= */ this.frameProcessingException::set, + new FrameProcessorChain.Listener() { + @Override + public void onFrameProcessingError(FrameProcessingException exception) { + frameProcessingException.set(exception); + } + + @Override + public void onFrameProcessingEnded() { + frameProcessingEnded = true; + } + }, pixelWidthHeightRatio, inputWidth, inputHeight, @@ -421,7 +432,7 @@ public final class FrameProcessorChainPixelTest { private Bitmap processFirstFrameAndEnd() throws InterruptedException { checkNotNull(frameProcessorChain).signalEndOfInputStream(); Thread.sleep(FRAME_PROCESSING_WAIT_MS); - assertThat(frameProcessorChain.isEnded()).isTrue(); + assertThat(frameProcessingEnded).isTrue(); assertThat(frameProcessingException.get()).isNull(); Image frameProcessorChainOutputImage = checkNotNull(outputImageReader).acquireLatestImage(); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessorChain.java b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessorChain.java index 63421d6d65..2167da9744 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessorChain.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameProcessorChain.java @@ -71,8 +71,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; *

This listener is only called from the {@link FrameProcessorChain}'s background thread. */ public interface Listener { - /** Called when an exception occurs during asynchronous frame processing. */ + /** + * Called when an exception occurs during asynchronous frame processing. + * + *

If an error occurred, consuming and producing further frames will not work as expected and + * the {@link FrameProcessorChain} should be released. + */ void onFrameProcessingError(FrameProcessingException exception); + + /** Called after the frame processor has produced its final output frame. */ + void onFrameProcessingEnded(); } /** @@ -454,22 +462,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return pendingFrameCount.get(); } - /** Informs the {@code FrameProcessorChain} that no further input frames should be accepted. */ + /** + * Informs the {@code FrameProcessorChain} that no further input frames should be accepted. + * + * @throws IllegalStateException If called more than once. + */ public void signalEndOfInputStream() { + checkState(!inputStreamEnded); inputStreamEnded = true; - } - - /** Returns whether all frames have been processed. */ - public boolean isEnded() { - return inputStreamEnded && getPendingFrameCount() == 0; + futures.add(singleThreadExecutorService.submit(this::signalEndOfOutputStream)); } /** * Releases all resources. * - *

If the frame processor chain is released before it has {@linkplain #isEnded() ended}, it - * will attempt to cancel processing any input frames that have already become available. Input - * frames that become available after release are ignored. + *

If the frame processor chain is released before it has {@linkplain + * Listener#onFrameProcessingEnded() ended}, it will attempt to cancel processing any input frames + * that have already become available. Input frames that become available after release are + * ignored. * *

This method blocks until all OpenGL resources are released or releasing times out. */ @@ -563,6 +573,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } } + /** Calls {@link Listener#onFrameProcessingEnded()} once no more frames are pending. */ + @WorkerThread + private void signalEndOfOutputStream() { + if (getPendingFrameCount() == 0) { + listener.onFrameProcessingEnded(); + } else { + futures.add(singleThreadExecutorService.submit(this::signalEndOfOutputStream)); + } + } + /** * Releases the {@link SingleFrameGlTextureProcessor SingleFrameGlTextureProcessors} and destroys * the OpenGL context. diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoTranscodingSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoTranscodingSamplePipeline.java index 32d5c628aa..6c05da6c51 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoTranscodingSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoTranscodingSamplePipeline.java @@ -51,8 +51,6 @@ import org.checkerframework.dataflow.qual.Pure; private final EncoderWrapper encoderWrapper; private final DecoderInputBuffer encoderOutputBuffer; - private boolean signaledEndOfStreamToEncoder; - public VideoTranscodingSamplePipeline( Context context, Format inputFormat, @@ -110,10 +108,23 @@ import org.checkerframework.dataflow.qual.Pure; frameProcessorChain = FrameProcessorChain.create( context, - /* listener= */ exception -> + new FrameProcessorChain.Listener() { + @Override + public void onFrameProcessingError(FrameProcessingException exception) { asyncErrorListener.onTransformationException( TransformationException.createForFrameProcessorChain( - exception, TransformationException.ERROR_CODE_GL_PROCESSING_FAILED)), + exception, TransformationException.ERROR_CODE_GL_PROCESSING_FAILED)); + } + + @Override + public void onFrameProcessingEnded() { + try { + encoderWrapper.signalEndOfInputStream(); + } catch (TransformationException exception) { + asyncErrorListener.onTransformationException(exception); + } + } + }, inputFormat.pixelWidthHeightRatio, /* inputWidth= */ decodedWidth, /* inputHeight= */ decodedHeight, @@ -157,13 +168,6 @@ import org.checkerframework.dataflow.qual.Pure; @Override public boolean processData() throws TransformationException { - if (frameProcessorChain.isEnded()) { - if (!signaledEndOfStreamToEncoder) { - encoderWrapper.signalEndOfInputStream(); - signaledEndOfStreamToEncoder = true; - } - return false; - } if (decoder.isEnded()) { return false; }