Fix and refactor logic to remove all frames in ExternalTextureManager
The existing logic was not working sometimes because: 1. The repeated scheduling in releaseAllFramesFromMediaCodec was starving the thread on which the SurfaceTexture frameAvailableListener was called. 2. The case where a pending frame arrives on the surface after flush finishes executing was not handled. The consequence of both problems is that availableFrameCount ended up being > pendingFrames.size(). PiperOrigin-RevId: 668916256
This commit is contained in:
parent
e30161656e
commit
37561c829f
@ -29,7 +29,6 @@ import static java.lang.Math.abs;
|
|||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
import android.opengl.GLES31;
|
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
@ -39,7 +38,6 @@ import androidx.media3.common.GlTextureInfo;
|
|||||||
import androidx.media3.common.VideoFrameProcessingException;
|
import androidx.media3.common.VideoFrameProcessingException;
|
||||||
import androidx.media3.common.util.GlUtil;
|
import androidx.media3.common.util.GlUtil;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.SystemClock;
|
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
@ -81,9 +79,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
// LINT.IfChange(SURFACE_TEXTURE_TIMEOUT_MS)
|
// LINT.IfChange(SURFACE_TEXTURE_TIMEOUT_MS)
|
||||||
private static final long SURFACE_TEXTURE_TIMEOUT_MS = isRunningOnEmulator() ? 20_000 : 500;
|
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 final GlObjectsProvider glObjectsProvider;
|
||||||
private @MonotonicNonNull ExternalShaderProgram externalShaderProgram;
|
private @MonotonicNonNull ExternalShaderProgram externalShaderProgram;
|
||||||
private final int externalTexId;
|
private final int externalTexId;
|
||||||
@ -106,9 +101,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
@Nullable private Future<?> forceSignalEndOfStreamFuture;
|
@Nullable private Future<?> forceSignalEndOfStreamFuture;
|
||||||
private boolean shouldRejectIncomingFrames;
|
private boolean shouldRejectIncomingFrames;
|
||||||
// The first time trying to remove all frames from MediaCodec, used to escape repeated waiting for
|
@Nullable private CountDownLatch releaseAllFramesLatch;
|
||||||
// a frame to arrive on the SurfaceTexture.
|
|
||||||
private long firstTryToRemoveAllFramesTimeMs;
|
|
||||||
|
|
||||||
@Nullable private volatile RuntimeException releaseAllFramesException;
|
@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);
|
DebugTraceUtil.logEvent(COMPONENT_VFP, EVENT_SURFACE_TEXTURE_INPUT, C.TIME_UNSET);
|
||||||
if (shouldRejectIncomingFrames) {
|
if (shouldRejectIncomingFrames) {
|
||||||
surfaceTexture.updateTexImage();
|
surfaceTexture.updateTexImage();
|
||||||
|
pendingFrames.poll();
|
||||||
|
if (releaseAllFramesLatch != null && pendingFrames.isEmpty()) {
|
||||||
|
releaseAllFramesLatch.countDown();
|
||||||
|
}
|
||||||
Log.w(
|
Log.w(
|
||||||
TAG,
|
TAG,
|
||||||
"Dropping frame received on SurfaceTexture after forcing EOS: "
|
"Dropping frame received on SurfaceTexture: "
|
||||||
+ surfaceTexture.getTimestamp() / 1000);
|
+ surfaceTexture.getTimestamp() / 1000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -171,22 +168,36 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
maybeQueueFrameToExternalShaderProgram();
|
maybeQueueFrameToExternalShaderProgram();
|
||||||
}));
|
}));
|
||||||
surface = new Surface(surfaceTexture);
|
surface = new Surface(surfaceTexture);
|
||||||
firstTryToRemoveAllFramesTimeMs = C.TIME_UNSET;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseAllRegisteredFrames() {
|
public void releaseAllRegisteredFrames() {
|
||||||
// Blocks the calling thread until all the registered frames are received and released.
|
// Blocks the calling thread until all the registered frames are received and released.
|
||||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
CountDownLatch releaseAllFramesLatch = new CountDownLatch(1);
|
||||||
videoFrameProcessingTaskExecutor.submit(() -> releaseAllFramesFromMediaCodec(countDownLatch));
|
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 {
|
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) {
|
} catch (InterruptedException e) {
|
||||||
// Not re-thrown to not crash frame processing. Frame process can likely continue even when
|
// Not re-thrown to not crash frame processing. Frame process can likely continue even when
|
||||||
// not all rendered frames arrive.
|
// not all rendered frames arrive.
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
Log.w(TAG, "Interrupted when waiting for MediaCodec frames to arrive.");
|
Log.w(TAG, "Interrupted when waiting for MediaCodec frames to arrive.");
|
||||||
}
|
}
|
||||||
|
this.releaseAllFramesLatch = null;
|
||||||
if (releaseAllFramesException != null) {
|
if (releaseAllFramesException != null) {
|
||||||
throw releaseAllFramesException;
|
throw releaseAllFramesException;
|
||||||
}
|
}
|
||||||
@ -334,7 +345,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void forceSignalEndOfStream() {
|
private void forceSignalEndOfStream() {
|
||||||
// Reset because there could be further input streams after the current one ends.
|
|
||||||
Log.w(
|
Log.w(
|
||||||
TAG,
|
TAG,
|
||||||
Util.formatInvariant(
|
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.
|
// Reset because there could be further input streams after the current one ends.
|
||||||
currentInputStreamEnded = false;
|
currentInputStreamEnded = false;
|
||||||
currentFrame = null;
|
currentFrame = null;
|
||||||
shouldRejectIncomingFrames = true;
|
|
||||||
|
|
||||||
// Frames could be made available while waiting for OpenGL to finish processing. That is,
|
// 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,
|
// 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();
|
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() {
|
private void removeAllSurfaceTextureFrames() {
|
||||||
|
shouldRejectIncomingFrames = true;
|
||||||
while (availableFrameCount > 0) {
|
while (availableFrameCount > 0) {
|
||||||
availableFrameCount--;
|
availableFrameCount--;
|
||||||
surfaceTexture.updateTexImage();
|
surfaceTexture.updateTexImage();
|
||||||
pendingFrames.remove();
|
pendingFrames.remove();
|
||||||
}
|
}
|
||||||
|
if (releaseAllFramesLatch != null && pendingFrames.isEmpty()) {
|
||||||
|
releaseAllFramesLatch.countDown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeQueueFrameToExternalShaderProgram() {
|
private void maybeQueueFrameToExternalShaderProgram() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user