diff --git a/libraries/common/src/main/java/androidx/media3/common/FrameProcessor.java b/libraries/common/src/main/java/androidx/media3/common/FrameProcessor.java index 877e06975d..0ecd1cb8e4 100644 --- a/libraries/common/src/main/java/androidx/media3/common/FrameProcessor.java +++ b/libraries/common/src/main/java/androidx/media3/common/FrameProcessor.java @@ -103,6 +103,12 @@ public interface FrameProcessor { void onFrameProcessingEnded(); } + /** + * Indicates the frame should be released immediately after {@link #releaseOutputFrame(long)} is + * invoked. + */ + long RELEASE_OUTPUT_FRAME_IMMEDIATELY = -1; + /** Returns the input {@link Surface}, where {@link FrameProcessor} consumes input frames from. */ Surface getInputSurface(); @@ -166,8 +172,9 @@ public interface FrameProcessor { * {@linkplain Listener#onOutputFrameAvailable(long) available}. * * @param releaseTimeNs The release time to use for the frame, in nanoseconds. Use {@link - * C#TIME_UNSET} to drop the frame. If {@code releaseTimeNs} is after {@link - * System#nanoTime()} at the time of the release, the frame is also dropped. + * C#TIME_UNSET} to drop the frame, or {@link #RELEASE_OUTPUT_FRAME_IMMEDIATELY} to release + * the frame immediately. If {@code releaseTimeNs} is after {@link System#nanoTime()} at the + * time of the release, the frame is also dropped. */ void releaseOutputFrame(long releaseTimeNs); diff --git a/libraries/effect/src/androidTest/java/androidx/media3/effect/GlEffectsFrameProcessorFrameReleaseTest.java b/libraries/effect/src/androidTest/java/androidx/media3/effect/GlEffectsFrameProcessorFrameReleaseTest.java index c94f6c97eb..4f672b82fc 100644 --- a/libraries/effect/src/androidTest/java/androidx/media3/effect/GlEffectsFrameProcessorFrameReleaseTest.java +++ b/libraries/effect/src/androidTest/java/androidx/media3/effect/GlEffectsFrameProcessorFrameReleaseTest.java @@ -135,6 +135,29 @@ public final class GlEffectsFrameProcessorFrameReleaseTest { assertThat(outputReleaseTimesNs).containsExactly(releaseTimesNs); } + @Test + public void controlledFrameRelease_withOneFrameRequestImmediateRelease_releasesFrame() + throws Exception { + long originalPresentationTimeUs = 1234; + long releaseTimesNs = FrameProcessor.RELEASE_OUTPUT_FRAME_IMMEDIATELY; + AtomicLong actualPresentationTimeUs = new AtomicLong(); + setupGlEffectsFrameProcessorWithBlankFrameProducer( + /* inputPresentationTimesUs= */ new long[] {originalPresentationTimeUs}, + /* onFrameAvailableListener= */ presentationTimeUs -> { + actualPresentationTimeUs.set(presentationTimeUs); + checkNotNull(glEffectsFrameProcessor).releaseOutputFrame(releaseTimesNs); + }, + /* releaseFramesAutomatically= */ false); + + checkNotNull(produceBlankFramesTask).run(); + Thread.sleep(FRAME_PROCESSING_WAIT_MS); + + assertThat(frameProcessingException.get()).isNull(); + assertThat(actualPresentationTimeUs.get()).isEqualTo(originalPresentationTimeUs); + // The actual release time is determined by the FrameProcessor when releasing the frame. + assertThat(outputReleaseTimesNs).hasSize(1); + } + @Test public void controlledFrameRelease_withLateFrame_dropsFrame() throws Exception { long originalPresentationTimeUs = 1234; diff --git a/libraries/effect/src/main/java/androidx/media3/effect/FinalMatrixTextureProcessorWrapper.java b/libraries/effect/src/main/java/androidx/media3/effect/FinalMatrixTextureProcessorWrapper.java index 60bdaa7fc8..193d5794c4 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/FinalMatrixTextureProcessorWrapper.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/FinalMatrixTextureProcessorWrapper.java @@ -170,12 +170,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public void releaseOutputFrame(long releaseTimeNs) { checkState(!releaseFramesAutomatically); + boolean dropLateFrame = true; + if (releaseTimeNs == FrameProcessor.RELEASE_OUTPUT_FRAME_IMMEDIATELY) { + dropLateFrame = false; + releaseTimeNs = System.nanoTime(); + } + Pair oldestAvailableFrame = availableFrames.remove(); renderFrameToSurfaces( /* inputTexture= */ oldestAvailableFrame.first, /* presentationTimeUs= */ oldestAvailableFrame.second, releaseTimeNs, - /* dropLateFrame= */ true); + dropLateFrame); } @Override