diff --git a/libraries/effect/src/main/java/androidx/media3/effect/ExternalTextureManager.java b/libraries/effect/src/main/java/androidx/media3/effect/ExternalTextureManager.java index 3564a117e8..d84db63f54 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/ExternalTextureManager.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/ExternalTextureManager.java @@ -29,7 +29,6 @@ import static java.lang.Math.abs; import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.graphics.SurfaceTexture; -import android.opengl.GLES31; import android.view.Surface; import androidx.annotation.Nullable; import androidx.media3.common.C; @@ -39,7 +38,6 @@ import androidx.media3.common.GlTextureInfo; import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.Log; -import androidx.media3.common.util.SystemClock; import androidx.media3.common.util.Util; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -81,9 +79,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; // LINT.IfChange(SURFACE_TEXTURE_TIMEOUT_MS) private static final long SURFACE_TEXTURE_TIMEOUT_MS = isRunningOnEmulator() ? 20_000 : 500; - // Wait delay between checking whether a registered frame arrives on the SurfaceTexture. - private static final long SURFACE_TEXTURE_WAIT_DELAY_MS = 10; - private final GlObjectsProvider glObjectsProvider; private @MonotonicNonNull ExternalShaderProgram externalShaderProgram; private final int externalTexId; @@ -106,9 +101,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Nullable private Future forceSignalEndOfStreamFuture; private boolean shouldRejectIncomingFrames; - // The first time trying to remove all frames from MediaCodec, used to escape repeated waiting for - // a frame to arrive on the SurfaceTexture. - private long firstTryToRemoveAllFramesTimeMs; + @Nullable private CountDownLatch releaseAllFramesLatch; @Nullable private volatile RuntimeException releaseAllFramesException; @@ -157,9 +150,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; DebugTraceUtil.logEvent(COMPONENT_VFP, EVENT_SURFACE_TEXTURE_INPUT, C.TIME_UNSET); if (shouldRejectIncomingFrames) { surfaceTexture.updateTexImage(); + pendingFrames.poll(); + if (releaseAllFramesLatch != null && pendingFrames.isEmpty()) { + releaseAllFramesLatch.countDown(); + } Log.w( TAG, - "Dropping frame received on SurfaceTexture after forcing EOS: " + "Dropping frame received on SurfaceTexture: " + surfaceTexture.getTimestamp() / 1000); return; } @@ -171,22 +168,36 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; maybeQueueFrameToExternalShaderProgram(); })); surface = new Surface(surfaceTexture); - firstTryToRemoveAllFramesTimeMs = C.TIME_UNSET; } @Override public void releaseAllRegisteredFrames() { // Blocks the calling thread until all the registered frames are received and released. - CountDownLatch countDownLatch = new CountDownLatch(1); - videoFrameProcessingTaskExecutor.submit(() -> releaseAllFramesFromMediaCodec(countDownLatch)); + CountDownLatch releaseAllFramesLatch = new CountDownLatch(1); + this.releaseAllFramesLatch = releaseAllFramesLatch; + videoFrameProcessingTaskExecutor.submit( + () -> { + try { + removeAllSurfaceTextureFrames(); + } catch (RuntimeException e) { + releaseAllFramesException = e; + Log.e(TAG, "Failed to remove texture frames", e); + if (this.releaseAllFramesLatch != null) { + this.releaseAllFramesLatch.countDown(); + } + } + }); try { - countDownLatch.await(); + if (!releaseAllFramesLatch.await(SURFACE_TEXTURE_TIMEOUT_MS, MILLISECONDS)) { + Log.w(TAG, "Timeout reached while waiting for latch to be unblocked."); + } } catch (InterruptedException e) { // Not re-thrown to not crash frame processing. Frame process can likely continue even when // not all rendered frames arrive. Thread.currentThread().interrupt(); Log.w(TAG, "Interrupted when waiting for MediaCodec frames to arrive."); } + this.releaseAllFramesLatch = null; if (releaseAllFramesException != null) { throw releaseAllFramesException; } @@ -334,7 +345,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } private void forceSignalEndOfStream() { - // Reset because there could be further input streams after the current one ends. Log.w( TAG, Util.formatInvariant( @@ -343,7 +353,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; // Reset because there could be further input streams after the current one ends. currentInputStreamEnded = false; currentFrame = null; - shouldRejectIncomingFrames = true; // Frames could be made available while waiting for OpenGL to finish processing. That is, // time out is triggered while waiting for the downstream shader programs to process a frame, @@ -354,43 +363,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; signalEndOfCurrentInputStream(); } - private void releaseAllFramesFromMediaCodec(CountDownLatch latch) { - try { - removeAllSurfaceTextureFrames(); - } catch (RuntimeException e) { - releaseAllFramesException = e; - latch.countDown(); - } - - if (pendingFrames.isEmpty() - // Assumes a frame that is registered would not take longer than SURFACE_TEXTURE_TIMEOUT_MS - // to arrive, otherwise unblock the waiting thread. - || (firstTryToRemoveAllFramesTimeMs != C.TIME_UNSET - && SystemClock.DEFAULT.currentTimeMillis() - firstTryToRemoveAllFramesTimeMs - >= SURFACE_TEXTURE_TIMEOUT_MS)) { - firstTryToRemoveAllFramesTimeMs = C.TIME_UNSET; - latch.countDown(); - return; - } - - if (firstTryToRemoveAllFramesTimeMs == C.TIME_UNSET) { - firstTryToRemoveAllFramesTimeMs = SystemClock.DEFAULT.currentTimeMillis(); - } - Future unusedFuture = - scheduledExecutorService.schedule( - () -> - videoFrameProcessingTaskExecutor.submit( - () -> releaseAllFramesFromMediaCodec(latch)), - SURFACE_TEXTURE_WAIT_DELAY_MS, - MILLISECONDS); - } - private void removeAllSurfaceTextureFrames() { + shouldRejectIncomingFrames = true; while (availableFrameCount > 0) { availableFrameCount--; surfaceTexture.updateTexImage(); pendingFrames.remove(); } + if (releaseAllFramesLatch != null && pendingFrames.isEmpty()) { + releaseAllFramesLatch.countDown(); + } } private void maybeQueueFrameToExternalShaderProgram() {