Add a method to disallow VFP destroying shared eglContext

Previously in MultiInputVideoGraph, each VFP would destroy the shared
eglContext, such that the same eglContext object is destroyed multiple times.

Adding a flag to disallow this.

The alternative being we could add a flag on the VFP constructor, but I think
that is too subscriptive (meaning if we later might want to add another boolean
to control another GL behaviour, multiple booleans would make the class less
reason-able), and would incur a lot of code changes at places.

PiperOrigin-RevId: 660354367
This commit is contained in:
claincly 2024-08-07 05:47:03 -07:00 committed by Copybara-Service
parent a087f82fa8
commit 8f8e48731e
6 changed files with 56 additions and 17 deletions

View File

@ -77,6 +77,7 @@
such that output is closer to expected. such that output is closer to expected.
* Speed up `DefaultVideoFrameProcessor.queueInputBitmap()`. As a result, * Speed up `DefaultVideoFrameProcessor.queueInputBitmap()`. As a result,
exporting images to videos with `Transformer` is faster. exporting images to videos with `Transformer` is faster.
* Add a `release()` method to `GlObjectsProvider`.
* Muxers: * Muxers:
* IMA extension: * IMA extension:
* Fix bug where clearing the playlist may cause an * Fix bug where clearing the playlist may cause an

View File

@ -81,4 +81,11 @@ public interface GlObjectsProvider {
* @throws GlException If an error occurs during creation. * @throws GlException If an error occurs during creation.
*/ */
GlTextureInfo createBuffersForTexture(int texId, int width, int height) throws GlException; GlTextureInfo createBuffersForTexture(int texId, int width, int height) throws GlException;
/**
* Releases the created objects.
*
* @param eglDisplay The {@link EGLDisplay} to release the objects for.
*/
void release(EGLDisplay eglDisplay) throws GlException;
} }

View File

@ -15,6 +15,8 @@
*/ */
package androidx.media3.effect; package androidx.media3.effect;
import static androidx.media3.common.util.GlUtil.destroyEglContext;
import android.opengl.EGL14; import android.opengl.EGL14;
import android.opengl.EGLContext; import android.opengl.EGLContext;
import android.opengl.EGLDisplay; import android.opengl.EGLDisplay;
@ -25,6 +27,8 @@ import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo; import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.util.ArrayList;
import java.util.List;
// TODO(b/261820382): Add tests for sharing context. // TODO(b/261820382): Add tests for sharing context.
/** /**
@ -38,6 +42,7 @@ import androidx.media3.common.util.UnstableApi;
public final class DefaultGlObjectsProvider implements GlObjectsProvider { public final class DefaultGlObjectsProvider implements GlObjectsProvider {
private final EGLContext sharedEglContext; private final EGLContext sharedEglContext;
private final List<EGLContext> createdEglContexts;
/** Creates an instance with no shared EGL context. */ /** Creates an instance with no shared EGL context. */
public DefaultGlObjectsProvider() { public DefaultGlObjectsProvider() {
@ -51,12 +56,16 @@ public final class DefaultGlObjectsProvider implements GlObjectsProvider {
*/ */
public DefaultGlObjectsProvider(@Nullable EGLContext sharedEglContext) { public DefaultGlObjectsProvider(@Nullable EGLContext sharedEglContext) {
this.sharedEglContext = sharedEglContext != null ? sharedEglContext : EGL14.EGL_NO_CONTEXT; this.sharedEglContext = sharedEglContext != null ? sharedEglContext : EGL14.EGL_NO_CONTEXT;
createdEglContexts = new ArrayList<>();
} }
@Override @Override
public EGLContext createEglContext( public EGLContext createEglContext(
EGLDisplay eglDisplay, int openGlVersion, int[] configAttributes) throws GlUtil.GlException { EGLDisplay eglDisplay, int openGlVersion, int[] configAttributes) throws GlUtil.GlException {
return GlUtil.createEglContext(sharedEglContext, eglDisplay, openGlVersion, configAttributes); EGLContext eglContext =
GlUtil.createEglContext(sharedEglContext, eglDisplay, openGlVersion, configAttributes);
createdEglContexts.add(eglContext);
return eglContext;
} }
@Override @Override
@ -81,4 +90,11 @@ public final class DefaultGlObjectsProvider implements GlObjectsProvider {
int fboId = GlUtil.createFboForTexture(texId); int fboId = GlUtil.createFboForTexture(texId);
return new GlTextureInfo(texId, fboId, /* rboId= */ C.INDEX_UNSET, width, height); return new GlTextureInfo(texId, fboId, /* rboId= */ C.INDEX_UNSET, width, height);
} }
@Override
public void release(EGLDisplay eglDisplay) throws GlUtil.GlException {
for (int i = 0; i < createdEglContexts.size(); i++) {
destroyEglContext(eglDisplay, createdEglContexts.get(i));
}
}
} }

View File

@ -420,9 +420,9 @@ public final class DefaultVideoCompositor implements VideoCompositor {
Log.e(TAG, "Error releasing GL resources", e); Log.e(TAG, "Error releasing GL resources", e);
} finally { } finally {
try { try {
GlUtil.destroyEglContext(eglDisplay, eglContext); glObjectsProvider.release(checkNotNull(eglDisplay));
} catch (GlUtil.GlException e) { } catch (GlUtil.GlException e) {
Log.e(TAG, "Error releasing GL context", e); Log.e(TAG, "Error releasing GL objects", e);
} }
} }
} }

View File

@ -19,6 +19,7 @@ import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.GlUtil.getDefaultEglDisplay;
import static androidx.media3.common.util.Util.SDK_INT; import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.effect.DebugTraceUtil.COMPONENT_VFP; import static androidx.media3.effect.DebugTraceUtil.COMPONENT_VFP;
import static androidx.media3.effect.DebugTraceUtil.EVENT_RECEIVE_END_OF_ALL_INPUT; import static androidx.media3.effect.DebugTraceUtil.EVENT_RECEIVE_END_OF_ALL_INPUT;
@ -445,7 +446,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private final Context context; private final Context context;
private final GlObjectsProvider glObjectsProvider; private final GlObjectsProvider glObjectsProvider;
private final EGLDisplay eglDisplay; private final EGLDisplay eglDisplay;
private final EGLContext eglContext;
private final InputSwitcher inputSwitcher; private final InputSwitcher inputSwitcher;
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor; private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
private final VideoFrameProcessor.Listener listener; private final VideoFrameProcessor.Listener listener;
@ -483,10 +483,9 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
Context context, Context context,
GlObjectsProvider glObjectsProvider, GlObjectsProvider glObjectsProvider,
EGLDisplay eglDisplay, EGLDisplay eglDisplay,
EGLContext eglContext,
InputSwitcher inputSwitcher, InputSwitcher inputSwitcher,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor, VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
VideoFrameProcessor.Listener listener, Listener listener,
Executor listenerExecutor, Executor listenerExecutor,
FinalShaderProgramWrapper finalShaderProgramWrapper, FinalShaderProgramWrapper finalShaderProgramWrapper,
boolean renderFramesAutomatically, boolean renderFramesAutomatically,
@ -494,7 +493,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
this.context = context; this.context = context;
this.glObjectsProvider = glObjectsProvider; this.glObjectsProvider = glObjectsProvider;
this.eglDisplay = eglDisplay; this.eglDisplay = eglDisplay;
this.eglContext = eglContext;
this.inputSwitcher = inputSwitcher; this.inputSwitcher = inputSwitcher;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor; this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
this.listener = listener; this.listener = listener;
@ -820,7 +818,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
boolean experimentalAdjustSurfaceTextureTransformationMatrix, boolean experimentalAdjustSurfaceTextureTransformationMatrix,
boolean experimentalRepeatInputBitmapWithoutResampling) boolean experimentalRepeatInputBitmapWithoutResampling)
throws GlUtil.GlException, VideoFrameProcessingException { throws GlUtil.GlException, VideoFrameProcessingException {
EGLDisplay eglDisplay = GlUtil.getDefaultEglDisplay(); EGLDisplay eglDisplay = getDefaultEglDisplay();
int[] configAttributes = int[] configAttributes =
ColorInfo.isTransferHdr(outputColorInfo) ColorInfo.isTransferHdr(outputColorInfo)
? GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_1010102 ? GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_1010102
@ -873,7 +871,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
context, context,
glObjectsProvider, glObjectsProvider,
eglDisplay, eglDisplay,
eglContextAndPlaceholderSurface.first,
inputSwitcher, inputSwitcher,
videoFrameProcessingTaskExecutor, videoFrameProcessingTaskExecutor,
listener, listener,
@ -1106,9 +1103,9 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
} }
} finally { } finally {
try { try {
GlUtil.destroyEglContext(eglDisplay, eglContext); glObjectsProvider.release(eglDisplay);
} catch (GlUtil.GlException e) { } catch (GlUtil.GlException e) {
Log.e(TAG, "Error releasing GL context", e); Log.e(TAG, "Error releasing GL objects", e);
} }
} }
} }

View File

@ -21,6 +21,8 @@ import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.GlUtil.destroyEglContext;
import static androidx.media3.common.util.GlUtil.getDefaultEglDisplay;
import static androidx.media3.common.util.Util.contains; import static androidx.media3.common.util.Util.contains;
import static androidx.media3.common.util.Util.newSingleThreadScheduledExecutor; import static androidx.media3.common.util.Util.newSingleThreadScheduledExecutor;
import static androidx.media3.effect.DebugTraceUtil.COMPONENT_COMPOSITOR; import static androidx.media3.effect.DebugTraceUtil.COMPONENT_COMPOSITOR;
@ -47,6 +49,8 @@ import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.VideoGraph; import androidx.media3.common.VideoGraph;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.GlUtil.GlException;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -61,6 +65,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@UnstableApi @UnstableApi
public abstract class MultipleInputVideoGraph implements VideoGraph { public abstract class MultipleInputVideoGraph implements VideoGraph {
private static final String TAG = "MultiInputVG";
private static final String SHARED_EXECUTOR_NAME = "Effect:MultipleInputVideoGraph:Thread"; private static final String SHARED_EXECUTOR_NAME = "Effect:MultipleInputVideoGraph:Thread";
private static final long RELEASE_WAIT_TIME_MS = 1_000; private static final long RELEASE_WAIT_TIME_MS = 1_000;
@ -70,7 +75,7 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
private final Context context; private final Context context;
private final ColorInfo outputColorInfo; private final ColorInfo outputColorInfo;
private final GlObjectsProvider glObjectsProvider; private final SingleContextGlObjectsProvider glObjectsProvider;
private final DebugViewProvider debugViewProvider; private final DebugViewProvider debugViewProvider;
private final VideoGraph.Listener listener; private final VideoGraph.Listener listener;
private final Executor listenerExecutor; private final Executor listenerExecutor;
@ -307,6 +312,15 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
compositionVideoFrameProcessor = null; compositionVideoFrameProcessor = null;
} }
try {
// The eglContext is not released by any of the frame processors.
if (glObjectsProvider.singleEglContext != null) {
destroyEglContext(getDefaultEglDisplay(), glObjectsProvider.singleEglContext);
}
} catch (GlUtil.GlException e) {
Log.e(TAG, "Error releasing GL context", e);
}
sharedExecutorService.shutdown(); sharedExecutorService.shutdown();
try { try {
sharedExecutorService.awaitTermination(RELEASE_WAIT_TIME_MS, MILLISECONDS); sharedExecutorService.awaitTermination(RELEASE_WAIT_TIME_MS, MILLISECONDS);
@ -472,8 +486,7 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
@Override @Override
public EGLContext createEglContext( public EGLContext createEglContext(
EGLDisplay eglDisplay, int openGlVersion, int[] configAttributes) EGLDisplay eglDisplay, int openGlVersion, int[] configAttributes) throws GlException {
throws GlUtil.GlException {
if (singleEglContext == null) { if (singleEglContext == null) {
singleEglContext = singleEglContext =
glObjectsProvider.createEglContext(eglDisplay, openGlVersion, configAttributes); glObjectsProvider.createEglContext(eglDisplay, openGlVersion, configAttributes);
@ -487,21 +500,26 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
Object surface, Object surface,
@C.ColorTransfer int colorTransfer, @C.ColorTransfer int colorTransfer,
boolean isEncoderInputSurface) boolean isEncoderInputSurface)
throws GlUtil.GlException { throws GlException {
return glObjectsProvider.createEglSurface( return glObjectsProvider.createEglSurface(
eglDisplay, surface, colorTransfer, isEncoderInputSurface); eglDisplay, surface, colorTransfer, isEncoderInputSurface);
} }
@Override @Override
public EGLSurface createFocusedPlaceholderEglSurface( public EGLSurface createFocusedPlaceholderEglSurface(
EGLContext eglContext, EGLDisplay eglDisplay) throws GlUtil.GlException { EGLContext eglContext, EGLDisplay eglDisplay) throws GlException {
return glObjectsProvider.createFocusedPlaceholderEglSurface(eglContext, eglDisplay); return glObjectsProvider.createFocusedPlaceholderEglSurface(eglContext, eglDisplay);
} }
@Override @Override
public GlTextureInfo createBuffersForTexture(int texId, int width, int height) public GlTextureInfo createBuffersForTexture(int texId, int width, int height)
throws GlUtil.GlException { throws GlException {
return glObjectsProvider.createBuffersForTexture(texId, width, height); return glObjectsProvider.createBuffersForTexture(texId, width, height);
} }
@Override
public void release(EGLDisplay eglDisplay) {
// The eglContext is released in the VideoGraph after all VideoFrameProcessors are released.
}
} }
} }