Track validity of debug SurfaceView
The debug surface view's output surface can become invalid during a transformation due to the parent activity pausing, for example. This can currently cause a crash when backing out of the `TransformerActivity` in the demo because the surface can be destroyed before the transformer has fully canceled. Also clarify naming of the outputSurface and inline the private method that created `EGLSurface`s (it was shorter after removing the debug preview). PiperOrigin-RevId: 449963440
This commit is contained in:
parent
001090cc43
commit
ce1ac5894d
@ -32,7 +32,9 @@ import android.opengl.EGLSurface;
|
||||
import android.opengl.GLES20;
|
||||
import android.util.Size;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.media3.common.C;
|
||||
@ -283,19 +285,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
private int outputWidth;
|
||||
private int outputHeight;
|
||||
private @MonotonicNonNull Surface outputSurface;
|
||||
|
||||
/**
|
||||
* Wraps the output {@link Surface} that is populated with the output of the final {@link
|
||||
* GlFrameProcessor} for each frame.
|
||||
*/
|
||||
private @MonotonicNonNull EGLSurface eglSurface;
|
||||
|
||||
private int debugPreviewWidth;
|
||||
private int debugPreviewHeight;
|
||||
private @MonotonicNonNull EGLSurface outputEglSurface;
|
||||
/**
|
||||
* Wraps a debug {@link SurfaceView} that is populated with the output of the final {@link
|
||||
* GlFrameProcessor} for each frame.
|
||||
*/
|
||||
private @MonotonicNonNull EGLSurface debugPreviewEglSurface;
|
||||
private @MonotonicNonNull SurfaceViewWrapper debugSurfaceViewWrapper;
|
||||
|
||||
private boolean inputStreamEnded;
|
||||
|
||||
@ -326,8 +327,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
textureTransformMatrix = new float[16];
|
||||
outputWidth = C.LENGTH_UNSET;
|
||||
outputHeight = C.LENGTH_UNSET;
|
||||
debugPreviewWidth = C.LENGTH_UNSET;
|
||||
debugPreviewHeight = C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -358,18 +357,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@Nullable SurfaceView debugSurfaceView) {
|
||||
// TODO(b/218488308): Don't override output size for encoder fallback. Instead allow the final
|
||||
// GlFrameProcessor to be re-configured or append another GlFrameProcessor.
|
||||
this.outputSurface = outputSurface;
|
||||
this.outputWidth = outputWidth;
|
||||
this.outputHeight = outputHeight;
|
||||
|
||||
if (debugSurfaceView != null) {
|
||||
debugPreviewWidth = debugSurfaceView.getWidth();
|
||||
debugPreviewHeight = debugSurfaceView.getHeight();
|
||||
debugSurfaceViewWrapper = new SurfaceViewWrapper(debugSurfaceView);
|
||||
}
|
||||
|
||||
futures.add(
|
||||
singleThreadExecutorService.submit(
|
||||
() -> createOpenGlSurfaces(outputSurface, debugSurfaceView)));
|
||||
|
||||
inputSurfaceTexture.setOnFrameAvailableListener(
|
||||
surfaceTexture -> {
|
||||
if (stopProcessing.get()) {
|
||||
@ -451,36 +446,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
inputSurface.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the OpenGL surfaces.
|
||||
*
|
||||
* <p>This method must be called on the {@linkplain #THREAD_NAME background thread}.
|
||||
*/
|
||||
@WorkerThread
|
||||
private void createOpenGlSurfaces(Surface outputSurface, @Nullable SurfaceView debugSurfaceView) {
|
||||
try {
|
||||
checkState(Thread.currentThread().getName().equals(THREAD_NAME));
|
||||
checkStateNotNull(eglDisplay);
|
||||
|
||||
if (enableExperimentalHdrEditing) {
|
||||
// TODO(b/227624622): Don't assume BT.2020 PQ input/output.
|
||||
eglSurface = GlUtil.getEglSurfaceBt2020Pq(eglDisplay, outputSurface);
|
||||
if (debugSurfaceView != null) {
|
||||
debugPreviewEglSurface =
|
||||
GlUtil.getEglSurfaceBt2020Pq(eglDisplay, checkNotNull(debugSurfaceView.getHolder()));
|
||||
}
|
||||
} else {
|
||||
eglSurface = GlUtil.getEglSurface(eglDisplay, outputSurface);
|
||||
if (debugSurfaceView != null) {
|
||||
debugPreviewEglSurface =
|
||||
GlUtil.getEglSurface(eglDisplay, checkNotNull(debugSurfaceView.getHolder()));
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
listener.onFrameProcessingError(new FrameProcessingException(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an input frame.
|
||||
*
|
||||
@ -495,7 +460,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
long presentationTimeUs = C.TIME_UNSET;
|
||||
try {
|
||||
checkState(Thread.currentThread().getName().equals(THREAD_NAME));
|
||||
checkStateNotNull(eglSurface, "No output surface set.");
|
||||
|
||||
if (outputEglSurface == null) {
|
||||
checkStateNotNull(outputSurface);
|
||||
if (enableExperimentalHdrEditing) {
|
||||
// TODO(b/227624622): Don't assume BT.2020 PQ input/output.
|
||||
outputEglSurface = GlUtil.getEglSurfaceBt2020Pq(eglDisplay, outputSurface);
|
||||
} else {
|
||||
outputEglSurface = GlUtil.getEglSurface(eglDisplay, outputSurface);
|
||||
}
|
||||
}
|
||||
|
||||
inputSurfaceTexture.updateTexImage();
|
||||
long presentationTimeNs = inputSurfaceTexture.getTimestamp();
|
||||
@ -513,26 +487,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
GlUtil.focusFramebuffer(
|
||||
eglDisplay,
|
||||
eglContext,
|
||||
eglSurface,
|
||||
outputEglSurface,
|
||||
framebuffers[i],
|
||||
intermediateSize.getWidth(),
|
||||
intermediateSize.getHeight());
|
||||
clearOutputFrame();
|
||||
frameProcessors.get(i).drawFrame(presentationTimeUs);
|
||||
}
|
||||
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, outputWidth, outputHeight);
|
||||
GlUtil.focusEglSurface(eglDisplay, eglContext, outputEglSurface, outputWidth, outputHeight);
|
||||
clearOutputFrame();
|
||||
getLast(frameProcessors).drawFrame(presentationTimeUs);
|
||||
|
||||
EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, presentationTimeNs);
|
||||
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
|
||||
EGLExt.eglPresentationTimeANDROID(eglDisplay, outputEglSurface, presentationTimeNs);
|
||||
EGL14.eglSwapBuffers(eglDisplay, outputEglSurface);
|
||||
|
||||
if (debugPreviewEglSurface != null) {
|
||||
GlUtil.focusEglSurface(
|
||||
eglDisplay, eglContext, debugPreviewEglSurface, debugPreviewWidth, debugPreviewHeight);
|
||||
clearOutputFrame();
|
||||
getLast(frameProcessors).drawFrame(presentationTimeUs);
|
||||
EGL14.eglSwapBuffers(eglDisplay, debugPreviewEglSurface);
|
||||
if (debugSurfaceViewWrapper != null) {
|
||||
long framePresentationTimeUs = presentationTimeUs;
|
||||
debugSurfaceViewWrapper.maybeRenderToSurfaceView(
|
||||
() -> {
|
||||
clearOutputFrame();
|
||||
try {
|
||||
getLast(frameProcessors).drawFrame(framePresentationTimeUs);
|
||||
} catch (FrameProcessingException e) {
|
||||
Log.d(TAG, "Error rendering to debug preview", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkState(pendingFrameCount.getAndDecrement() > 0);
|
||||
@ -568,4 +547,73 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
listener.onFrameProcessingError(new FrameProcessingException(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around a {@link SurfaceView} that keeps track of whether the output surface is valid,
|
||||
* and makes rendering a no-op if not.
|
||||
*/
|
||||
private final class SurfaceViewWrapper implements SurfaceHolder.Callback {
|
||||
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private Surface surface;
|
||||
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private EGLSurface eglSurface;
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public SurfaceViewWrapper(SurfaceView surfaceView) {
|
||||
surfaceView.getHolder().addCallback(this);
|
||||
surface = surfaceView.getHolder().getSurface();
|
||||
width = surfaceView.getWidth();
|
||||
height = surfaceView.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the wrapped surface view's surface as an {@link EGLSurface}, renders using {@code
|
||||
* renderRunnable} and swaps buffers, if the view's holder has a valid surface. Does nothing
|
||||
* otherwise.
|
||||
*/
|
||||
@WorkerThread
|
||||
public synchronized void maybeRenderToSurfaceView(Runnable renderRunnable) {
|
||||
if (surface == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (eglSurface == null) {
|
||||
if (enableExperimentalHdrEditing) {
|
||||
eglSurface = GlUtil.getEglSurfaceBt2020Pq(eglDisplay, surface);
|
||||
} else {
|
||||
eglSurface = GlUtil.getEglSurface(eglDisplay, surface);
|
||||
}
|
||||
}
|
||||
EGLSurface eglSurface = this.eglSurface;
|
||||
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, width, height);
|
||||
renderRunnable.run();
|
||||
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {}
|
||||
|
||||
@Override
|
||||
public synchronized void surfaceChanged(
|
||||
SurfaceHolder holder, int format, int width, int height) {
|
||||
surface = holder.getSurface();
|
||||
eglSurface = null;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void surfaceDestroyed(SurfaceHolder holder) {
|
||||
surface = null;
|
||||
eglSurface = null;
|
||||
width = C.LENGTH_UNSET;
|
||||
height = C.LENGTH_UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user