mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
3c5c81fc3e
commit
9c075b692e
@ -327,7 +327,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** See {@link DefaultVideoFrameProcessor#setOutputSurfaceInfo} */
|
/** 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) {
|
if (textureOutputListener != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,11 @@ import androidx.media3.common.util.GlUtil;
|
|||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around a single thread {@link ExecutorService} for executing {@link Task} instances.
|
* 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);
|
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 boolean shouldShutdownExecutorService;
|
||||||
private final ExecutorService singleThreadExecutorService;
|
private final ExecutorService singleThreadExecutorService;
|
||||||
|
private final Future<Thread> threadFuture; // Used to identify the GL thread.
|
||||||
private final ErrorListener errorListener;
|
private final ErrorListener errorListener;
|
||||||
private final Object lock;
|
private final Object lock;
|
||||||
|
|
||||||
@ -80,6 +83,7 @@ import java.util.concurrent.RejectedExecutionException;
|
|||||||
boolean shouldShutdownExecutorService,
|
boolean shouldShutdownExecutorService,
|
||||||
ErrorListener errorListener) {
|
ErrorListener errorListener) {
|
||||||
this.singleThreadExecutorService = singleThreadExecutorService;
|
this.singleThreadExecutorService = singleThreadExecutorService;
|
||||||
|
threadFuture = singleThreadExecutorService.submit(Thread::currentThread);
|
||||||
this.shouldShutdownExecutorService = shouldShutdownExecutorService;
|
this.shouldShutdownExecutorService = shouldShutdownExecutorService;
|
||||||
this.errorListener = errorListener;
|
this.errorListener = errorListener;
|
||||||
lock = new Object();
|
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
|
* Submits the given {@link Task} to be executed after the currently running task and all
|
||||||
* previously submitted high-priority tasks have completed.
|
* previously submitted high-priority tasks have completed.
|
||||||
@ -173,7 +216,8 @@ import java.util.concurrent.RejectedExecutionException;
|
|||||||
wrapTaskAndSubmitToExecutorService(releaseTask, /* isFlushOrReleaseTask= */ true);
|
wrapTaskAndSubmitToExecutorService(releaseTask, /* isFlushOrReleaseTask= */ true);
|
||||||
if (shouldShutdownExecutorService) {
|
if (shouldShutdownExecutorService) {
|
||||||
singleThreadExecutorService.shutdown();
|
singleThreadExecutorService.shutdown();
|
||||||
if (!singleThreadExecutorService.awaitTermination(RELEASE_WAIT_TIME_MS, MILLISECONDS)) {
|
if (!singleThreadExecutorService.awaitTermination(
|
||||||
|
EXECUTOR_SERVICE_TIMEOUT_MS, MILLISECONDS)) {
|
||||||
errorListener.onError(
|
errorListener.onError(
|
||||||
new VideoFrameProcessingException(
|
new VideoFrameProcessingException(
|
||||||
"Release timed out. OpenGL resources may not be cleaned up properly."));
|
"Release timed out. OpenGL resources may not be cleaned up properly."));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user