diff --git a/demos/transformer/src/withMediaPipe/java/androidx/media3/demo/transformer/MediaPipeShaderProgram.java b/demos/transformer/src/withMediaPipe/java/androidx/media3/demo/transformer/MediaPipeShaderProgram.java index b2cdc988f8..48f216da75 100644 --- a/demos/transformer/src/withMediaPipe/java/androidx/media3/demo/transformer/MediaPipeShaderProgram.java +++ b/demos/transformer/src/withMediaPipe/java/androidx/media3/demo/transformer/MediaPipeShaderProgram.java @@ -166,7 +166,8 @@ import java.util.concurrent.Future; @Override public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) { AppTextureFrame appTextureFrame = - new AppTextureFrame(inputTexture.texId, inputTexture.width, inputTexture.height); + new AppTextureFrame( + inputTexture.getTexId(), inputTexture.getWidth(), inputTexture.getHeight()); // TODO(b/238302213): Handle timestamps restarting from 0 when applying effects to a playlist. // MediaPipe will fail if the timestamps are not monotonically increasing. // Also make sure that a MediaPipe graph producing additional frames only starts producing diff --git a/libraries/common/src/main/java/androidx/media3/common/GlTextureInfo.java b/libraries/common/src/main/java/androidx/media3/common/GlTextureInfo.java index f073e9d137..3ceda67e8e 100644 --- a/libraries/common/src/main/java/androidx/media3/common/GlTextureInfo.java +++ b/libraries/common/src/main/java/androidx/media3/common/GlTextureInfo.java @@ -15,13 +15,14 @@ */ package androidx.media3.common; +import static androidx.media3.common.util.Assertions.checkState; + +import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.UnstableApi; /** Contains information describing an OpenGL texture. */ @UnstableApi public final class GlTextureInfo { - // TODO: b/262694346 - Add a release() method for GlTextureInfo. - /** A {@link GlTextureInfo} instance with all fields unset. */ public static final GlTextureInfo UNSET = new GlTextureInfo( @@ -31,22 +32,13 @@ public final class GlTextureInfo { /* width= */ C.LENGTH_UNSET, /* height= */ C.LENGTH_UNSET); - /** The OpenGL texture identifier, or {@link C#INDEX_UNSET} if not specified. */ - public final int texId; - /** - * Identifier of a framebuffer object associated with the texture, or {@link C#INDEX_UNSET} if not - * specified. - */ - public final int fboId; - /** - * Identifier of a renderbuffer object attached with the framebuffer, or {@link C#INDEX_UNSET} if - * not specified. - */ - public final int rboId; - /** The width of the texture, in pixels, or {@link C#LENGTH_UNSET} if not specified. */ - public final int width; - /** The height of the texture, in pixels, or {@link C#LENGTH_UNSET} if not specified. */ - public final int height; + private final int texId; + private final int fboId; + private final int rboId; + private final int width; + private final int height; + + private boolean isReleased; /** * Creates a new instance. @@ -66,4 +58,53 @@ public final class GlTextureInfo { this.width = width; this.height = height; } + + /** The OpenGL texture identifier, or {@link C#INDEX_UNSET} if not specified. */ + public int getTexId() { + checkState(!isReleased); + return texId; + } + + /** + * Identifier of a framebuffer object associated with the texture, or {@link C#INDEX_UNSET} if not + * specified. + */ + public int getFboId() { + checkState(!isReleased); + return fboId; + } + + /** + * Identifier of a renderbuffer object attached with the framebuffer, or {@link C#INDEX_UNSET} if + * not specified. + */ + public int getRboId() { + checkState(!isReleased); + return rboId; + } + + /** The width of the texture, in pixels, or {@link C#LENGTH_UNSET} if not specified. */ + public int getWidth() { + checkState(!isReleased); + return width; + } + + /** The height of the texture, in pixels, or {@link C#LENGTH_UNSET} if not specified. */ + public int getHeight() { + checkState(!isReleased); + return height; + } + + public void release() throws GlUtil.GlException { + isReleased = true; + if (texId != C.INDEX_UNSET) { + GlUtil.deleteTexture(texId); + } + if (fboId != C.INDEX_UNSET) { + GlUtil.deleteFbo(fboId); + } + if (rboId != C.INDEX_UNSET) { + GlUtil.deleteRbo(rboId); + } + } } diff --git a/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java b/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java index e20da85644..d9072b9cca 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java @@ -494,36 +494,6 @@ public final class GlUtil { Api17.focusFramebufferUsingCurrentContext(framebuffer, width, height); } - /** - * Deletes a GL texture. - * - * @param textureId The ID of the texture to delete. - */ - public static void deleteTexture(int textureId) throws GlException { - GLES20.glDeleteTextures(/* n= */ 1, new int[] {textureId}, /* offset= */ 0); - checkGlError(); - } - - /** - * Destroys the {@link EGLContext} identified by the provided {@link EGLDisplay} and {@link - * EGLContext}. - */ - @RequiresApi(17) - public static void destroyEglContext( - @Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) throws GlException { - Api17.destroyEglContext(eglDisplay, eglContext); - } - - /** - * Destroys the {@link EGLSurface} identified by the provided {@link EGLDisplay} and {@link - * EGLSurface}. - */ - @RequiresApi(17) - public static void destroyEglSurface( - @Nullable EGLDisplay eglDisplay, @Nullable EGLSurface eglSurface) throws GlException { - Api17.destroyEglSurface(eglDisplay, eglSurface); - } - /** * Allocates a FloatBuffer with the given data. * @@ -656,11 +626,46 @@ public final class GlUtil { return fboId[0]; } - /** Deletes a framebuffer. */ + /** + * Deletes a GL texture. + * + * @param textureId The ID of the texture to delete. + */ + public static void deleteTexture(int textureId) throws GlException { + GLES20.glDeleteTextures(/* n= */ 1, new int[] {textureId}, /* offset= */ 0); + checkGlError(); + } + + /** + * Destroys the {@link EGLContext} identified by the provided {@link EGLDisplay} and {@link + * EGLContext}. + */ + @RequiresApi(17) + public static void destroyEglContext( + @Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) throws GlException { + Api17.destroyEglContext(eglDisplay, eglContext); + } + + /** + * Destroys the {@link EGLSurface} identified by the provided {@link EGLDisplay} and {@link + * EGLSurface}. + */ + @RequiresApi(17) + public static void destroyEglSurface( + @Nullable EGLDisplay eglDisplay, @Nullable EGLSurface eglSurface) throws GlException { + Api17.destroyEglSurface(eglDisplay, eglSurface); + } + + /** Deletes a framebuffer, or silently ignores the method call if {@code fboId} is unused. */ public static void deleteFbo(int fboId) throws GlException { - int[] fboIdArray = new int[1]; - fboIdArray[0] = fboId; - GLES20.glDeleteFramebuffers(/* n= */ 1, fboIdArray, /* offset= */ 0); + GLES20.glDeleteFramebuffers(/* n= */ 1, new int[] {fboId}, /* offset= */ 0); + checkGlError(); + } + + /** Deletes a renderbuffer, or silently ignores the method call if {@code rboId} is unused. */ + public static void deleteRbo(int rboId) throws GlException { + GLES20.glDeleteRenderbuffers( + /* n= */ 1, /* renderbuffers= */ new int[] {rboId}, /* offset= */ 0); checkGlError(); } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/BaseGlShaderProgram.java b/libraries/effect/src/main/java/androidx/media3/effect/BaseGlShaderProgram.java index 3ceb2924ec..9bb384c36b 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/BaseGlShaderProgram.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/BaseGlShaderProgram.java @@ -129,7 +129,7 @@ public abstract class BaseGlShaderProgram implements GlShaderProgram { @Override public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) { try { - Size outputTextureSize = configure(inputTexture.width, inputTexture.height); + Size outputTextureSize = configure(inputTexture.getWidth(), inputTexture.getHeight()); outputTexturePool.ensureConfigured( outputTextureSize.getWidth(), outputTextureSize.getHeight()); frameProcessingStarted = true; @@ -139,9 +139,9 @@ public abstract class BaseGlShaderProgram implements GlShaderProgram { // Copy frame to fbo. GlUtil.focusFramebufferUsingCurrentContext( - outputTexture.fboId, outputTexture.width, outputTexture.height); + outputTexture.getFboId(), outputTexture.getWidth(), outputTexture.getHeight()); GlUtil.clearOutputFrame(); - drawFrame(inputTexture.texId, presentationTimeUs); + drawFrame(inputTexture.getTexId(), presentationTimeUs); inputListener.onInputFrameProcessed(inputTexture); outputListener.onOutputFrameAvailable(outputTexture, presentationTimeUs); } catch (VideoFrameProcessingException | GlUtil.GlException | NoSuchElementException e) { diff --git a/libraries/effect/src/main/java/androidx/media3/effect/BitmapTextureManager.java b/libraries/effect/src/main/java/androidx/media3/effect/BitmapTextureManager.java index 8c5371669c..4c521155cb 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/BitmapTextureManager.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/BitmapTextureManager.java @@ -123,7 +123,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; videoFrameProcessingTaskExecutor.submit( () -> { if (currentGlTextureInfo != null) { - GlUtil.deleteTexture(currentGlTextureInfo.texId); + currentGlTextureInfo.release(); } }); } @@ -160,7 +160,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; int currentTexId; try { if (currentGlTextureInfo != null) { - GlUtil.deleteTexture(currentGlTextureInfo.texId); + currentGlTextureInfo.release(); } currentTexId = GlUtil.createTexture( diff --git a/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java b/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java index 35af4c3b33..844ce07454 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java @@ -316,7 +316,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; GlTextureInfo inputTexture, long presentationTimeUs, long renderTimeNs) { try { if (renderTimeNs == VideoFrameProcessor.DROP_OUTPUT_FRAME - || !ensureConfigured(inputTexture.width, inputTexture.height)) { + || !ensureConfigured(inputTexture.getWidth(), inputTexture.getHeight())) { inputListener.onInputFrameProcessed(inputTexture); return; // Drop frames when requested, or there is no output surface and output texture. } @@ -352,7 +352,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; outputSurfaceInfo.width, outputSurfaceInfo.height); GlUtil.clearOutputFrame(); - defaultShaderProgram.drawFrame(inputTexture.texId, presentationTimeUs); + defaultShaderProgram.drawFrame(inputTexture.getTexId(), presentationTimeUs); EGLExt.eglPresentationTimeANDROID( eglDisplay, @@ -369,9 +369,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; GlTextureInfo outputTexture = outputTexturePool.useTexture(); outputTextureTimestamps.add(presentationTimeUs); GlUtil.focusFramebufferUsingCurrentContext( - outputTexture.fboId, outputTexture.width, outputTexture.height); + outputTexture.getFboId(), outputTexture.getWidth(), outputTexture.getHeight()); GlUtil.clearOutputFrame(); - checkNotNull(defaultShaderProgram).drawFrame(inputTexture.texId, presentationTimeUs); + checkNotNull(defaultShaderProgram).drawFrame(inputTexture.getTexId(), presentationTimeUs); // TODO(b/262694346): If Compositor's VFPs all use the same context, media3 should be able to // avoid calling glFinish, and require the onTextureRendered listener to decide whether to // glFinish. Consider removing glFinish and requiring onTextureRendered to handle @@ -519,10 +519,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; int configuredColorTransfer = defaultShaderProgram.getOutputColorTransfer(); defaultShaderProgram.setOutputColorTransfer( debugSurfaceViewWrapper.outputColorTransfer); - defaultShaderProgram.drawFrame(inputTexture.texId, presentationTimeUs); + defaultShaderProgram.drawFrame(inputTexture.getTexId(), presentationTimeUs); defaultShaderProgram.setOutputColorTransfer(configuredColorTransfer); } else { - defaultShaderProgram.drawFrame(inputTexture.texId, presentationTimeUs); + defaultShaderProgram.drawFrame(inputTexture.getTexId(), presentationTimeUs); } }, glObjectsProvider); diff --git a/libraries/effect/src/main/java/androidx/media3/effect/TexIdTextureManager.java b/libraries/effect/src/main/java/androidx/media3/effect/TexIdTextureManager.java index c2fe895193..41474dc7c6 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/TexIdTextureManager.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/TexIdTextureManager.java @@ -61,7 +61,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public void onInputFrameProcessed(GlTextureInfo inputTexture) { videoFrameProcessingTaskExecutor.submit( - () -> checkNotNull(frameProcessedListener).onInputFrameProcessed(inputTexture.texId)); + () -> checkNotNull(frameProcessedListener).onInputFrameProcessed(inputTexture.getTexId())); } @Override diff --git a/libraries/effect/src/main/java/androidx/media3/effect/TexturePool.java b/libraries/effect/src/main/java/androidx/media3/effect/TexturePool.java index e86e6fdb8d..78f8f5eb50 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/TexturePool.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/TexturePool.java @@ -86,7 +86,7 @@ import java.util.Queue; return; } GlTextureInfo texture = getIteratorToAllTextures().next(); - if (texture.width != width || texture.height != height) { + if (texture.getWidth() != width || texture.getHeight() != height) { deleteAllTextures(); createTextures(width, height); } @@ -141,9 +141,7 @@ import java.util.Queue; public void deleteAllTextures() throws GlUtil.GlException { Iterator allTextures = getIteratorToAllTextures(); while (allTextures.hasNext()) { - GlTextureInfo textureInfo = allTextures.next(); - GlUtil.deleteTexture(textureInfo.texId); - GlUtil.deleteFbo(textureInfo.fboId); + allTextures.next().release(); } freeTextures.clear(); inUseTextures.clear(); diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/VideoFrameProcessorTestRunner.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/VideoFrameProcessorTestRunner.java index 17d1dce682..ed00d9c5cb 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/VideoFrameProcessorTestRunner.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/VideoFrameProcessorTestRunner.java @@ -353,7 +353,7 @@ public final class VideoFrameProcessorTestRunner { public void queueInputTexture(GlTextureInfo inputTexture, long pts) { videoFrameProcessor.setInputFrameInfo( - new FrameInfo.Builder(inputTexture.width, inputTexture.height) + new FrameInfo.Builder(inputTexture.getWidth(), inputTexture.getHeight()) .setPixelWidthHeightRatio(pixelWidthHeightRatio) .build()); videoFrameProcessor.registerInputStream(INPUT_TYPE_TEXTURE_ID); @@ -365,7 +365,7 @@ public final class VideoFrameProcessorTestRunner { throw new VideoFrameProcessingException(e); } }); - videoFrameProcessor.queueInputTexture(inputTexture.texId, pts); + videoFrameProcessor.queueInputTexture(inputTexture.getTexId(), pts); } public void endFrameProcessing() throws InterruptedException { diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/DefaultVideoFrameProcessorTextureOutputPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/DefaultVideoFrameProcessorTextureOutputPixelTest.java index 3aec9e33eb..f900f803af 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/DefaultVideoFrameProcessorTextureOutputPixelTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/DefaultVideoFrameProcessorTextureOutputPixelTest.java @@ -608,10 +608,12 @@ public final class DefaultVideoFrameProcessorTextureOutputPixelTest { throws VideoFrameProcessingException { try { GlUtil.focusFramebufferUsingCurrentContext( - outputTexture.fboId, outputTexture.width, outputTexture.height); + outputTexture.getFboId(), outputTexture.getWidth(), outputTexture.getHeight()); outputBitmap = createBitmapFromCurrentGlFrameBuffer( - outputTexture.width, outputTexture.height, useHighPrecisionColorComponents); + outputTexture.getWidth(), + outputTexture.getHeight(), + useHighPrecisionColorComponents); } catch (GlUtil.GlException e) { throw new VideoFrameProcessingException(e); }