Ensure EGLSurface is released on GL thread

Add VideoFrameProcessingTaskExecutor.invoke() method that blocks until
Task has executed on GL thread.
Use that for FinalShaderProgramWrapper.setOutputSurfaceInfo

PiperOrigin-RevId: 655119768
This commit is contained in:
dancho 2024-07-23 04:56:46 -07:00 committed by Copybara-Service
parent 3c5c81fc3e
commit 9c075b692e
2 changed files with 59 additions and 3 deletions

View File

@ -327,7 +327,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
/** See {@link DefaultVideoFrameProcessor#setOutputSurfaceInfo} */
public synchronized void setOutputSurfaceInfo(@Nullable SurfaceInfo outputSurfaceInfo) {
public void setOutputSurfaceInfo(@Nullable SurfaceInfo outputSurfaceInfo) {
try {
videoFrameProcessingTaskExecutor.invoke(
() -> setOutputSurfaceInfoInternal(outputSurfaceInfo));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
videoFrameProcessorListenerExecutor.execute(
() -> videoFrameProcessorListener.onError(VideoFrameProcessingException.from(e)));
}
}
/** Must be called on the GL thread. */
private synchronized void setOutputSurfaceInfoInternal(@Nullable SurfaceInfo outputSurfaceInfo) {
if (textureOutputListener != null) {
return;
}

View File

@ -24,9 +24,11 @@ import androidx.media3.common.util.GlUtil;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeoutException;
/**
* Wrapper around a single thread {@link ExecutorService} for executing {@link Task} instances.
@ -61,10 +63,11 @@ import java.util.concurrent.RejectedExecutionException;
void onError(VideoFrameProcessingException exception);
}
private static final long RELEASE_WAIT_TIME_MS = 500;
private static final long EXECUTOR_SERVICE_TIMEOUT_MS = 500;
private final boolean shouldShutdownExecutorService;
private final ExecutorService singleThreadExecutorService;
private final Future<Thread> threadFuture; // Used to identify the GL thread.
private final ErrorListener errorListener;
private final Object lock;
@ -80,6 +83,7 @@ import java.util.concurrent.RejectedExecutionException;
boolean shouldShutdownExecutorService,
ErrorListener errorListener) {
this.singleThreadExecutorService = singleThreadExecutorService;
threadFuture = singleThreadExecutorService.submit(Thread::currentThread);
this.shouldShutdownExecutorService = shouldShutdownExecutorService;
this.errorListener = errorListener;
lock = new Object();
@ -106,6 +110,45 @@ import java.util.concurrent.RejectedExecutionException;
}
}
/** Blocks the caller until the given {@link Task} has completed. */
public void invoke(Task task) throws InterruptedException {
// If running on the executor service thread, run synchronously.
// Calling future.get() on the single executor thread would deadlock.
Thread videoFrameProcessingThread;
try {
videoFrameProcessingThread = threadFuture.get(EXECUTOR_SERVICE_TIMEOUT_MS, MILLISECONDS);
} catch (InterruptedException e) {
throw e;
} catch (Exception e) {
handleException(e);
return;
}
if (Thread.currentThread() == videoFrameProcessingThread) {
try {
task.run();
} catch (Exception e) {
handleException(e);
}
return;
}
// Not running on the executor service thread. Block until task.run() returns.
try {
Future<?> taskFuture =
singleThreadExecutorService.submit(
() -> {
try {
task.run();
} catch (Exception e) {
handleException(e);
}
});
taskFuture.get(EXECUTOR_SERVICE_TIMEOUT_MS, MILLISECONDS);
} catch (RuntimeException | ExecutionException | TimeoutException e) {
handleException(e);
}
}
/**
* Submits the given {@link Task} to be executed after the currently running task and all
* previously submitted high-priority tasks have completed.
@ -173,7 +216,8 @@ import java.util.concurrent.RejectedExecutionException;
wrapTaskAndSubmitToExecutorService(releaseTask, /* isFlushOrReleaseTask= */ true);
if (shouldShutdownExecutorService) {
singleThreadExecutorService.shutdown();
if (!singleThreadExecutorService.awaitTermination(RELEASE_WAIT_TIME_MS, MILLISECONDS)) {
if (!singleThreadExecutorService.awaitTermination(
EXECUTOR_SERVICE_TIMEOUT_MS, MILLISECONDS)) {
errorListener.onError(
new VideoFrameProcessingException(
"Release timed out. OpenGL resources may not be cleaned up properly."));