From 9945033867ea49f63d3e82329a25d87fbcbdb68c Mon Sep 17 00:00:00 2001 From: huangdarwin Date: Fri, 30 Jun 2023 18:20:59 +0000 Subject: [PATCH] Effect: Create basic VideoCompositor. Allow two DefaultVideoFrameProcessor instances to output to one VideoCompositor, which then outputs a frame. PiperOrigin-RevId: 544705821 --- .../fragment_shader_compositor_es2.glsl | 32 +++ .../media3/effect/VideoCompositor.java | 216 ++++++++++++++++++ .../grayscaleAndRotate180Composite.png | Bin 0 -> 12311 bytes .../grayscale_media3test.png | Bin 0 -> 7240 bytes .../rotate180_media3test.png | Bin 0 -> 7593 bytes .../{mh => }/TextureBitmapReader.java | 17 +- .../transformer/VideoCompositorPixelTest.java | 200 ++++++++++++++++ ...ocessorMultipleTextureOutputPixelTest.java | 8 +- ...oFrameProcessorTextureOutputPixelTest.java | 15 +- .../transformer/mh/FrameDropPixelTest.java | 3 +- 10 files changed, 478 insertions(+), 13 deletions(-) create mode 100644 libraries/effect/src/main/assets/shaders/fragment_shader_compositor_es2.glsl create mode 100644 libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java create mode 100644 libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/grayscaleAndRotate180Composite.png create mode 100644 libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/grayscale_media3test.png create mode 100644 libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/rotate180_media3test.png rename libraries/transformer/src/androidTest/java/androidx/media3/transformer/{mh => }/TextureBitmapReader.java (91%) create mode 100644 libraries/transformer/src/androidTest/java/androidx/media3/transformer/VideoCompositorPixelTest.java diff --git a/libraries/effect/src/main/assets/shaders/fragment_shader_compositor_es2.glsl b/libraries/effect/src/main/assets/shaders/fragment_shader_compositor_es2.glsl new file mode 100644 index 0000000000..cb3f19ce7c --- /dev/null +++ b/libraries/effect/src/main/assets/shaders/fragment_shader_compositor_es2.glsl @@ -0,0 +1,32 @@ +#version 100 +// Copyright 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Basic ES2 compositor shader that samples from a (non-external) textures +// with uTexSampler1 and uTexSampler2, copying each with alpha = .5 to the +// output. +// TODO: b/262694346 - Allow alpha to be customized for each input. +// TODO: b/262694346 - Allow for an arbitrary amount of inputs. + +precision mediump float; +uniform sampler2D uTexSampler1; +uniform sampler2D uTexSampler2; +varying vec2 vTexSamplingCoord; + +void main() { + vec4 inputColor1 = texture2D(uTexSampler1, vTexSamplingCoord); + vec4 inputColor2 = texture2D(uTexSampler2, vTexSamplingCoord); + gl_FragColor = vec4(inputColor1.rgb * 0.5 + inputColor2.rgb * 0.5, 1.0); + gl_FragColor.a = 1.0; +} diff --git a/libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java b/libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java new file mode 100644 index 0000000000..fdd02ec5d5 --- /dev/null +++ b/libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java @@ -0,0 +1,216 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.effect; + +import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.common.util.Assertions.checkState; + +import android.content.Context; +import android.opengl.GLES20; +import androidx.annotation.IntRange; +import androidx.media3.common.GlObjectsProvider; +import androidx.media3.common.GlTextureInfo; +import androidx.media3.common.VideoFrameProcessingException; +import androidx.media3.common.util.GlProgram; +import androidx.media3.common.util.GlUtil; +import androidx.media3.common.util.UnstableApi; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** + * A basic VideoCompositor that takes in frames from exactly 2 input sources and combines it to one + * output. Only tested for 2 frames in, 1 frame out for now. + */ +@UnstableApi +public final class VideoCompositor { + // TODO: b/262694346 - Flesh out this implementation by doing the following: + // * Create on a shared VideoFrameProcessingTaskExecutor with VideoFrameProcessor instances. + // * >1 input/output frame per source. + // * Handle matched timestamps. + // * Handle mismatched timestamps + // * Before allowing customization of this class, add an interface, and rename this class to + // DefaultCompositor. + + private static final String VERTEX_SHADER_PATH = "shaders/vertex_shader_transformation_es2.glsl"; + + private static final String FRAGMENT_SHADER_PATH = "shaders/fragment_shader_compositor_es2.glsl"; + + private final Context context; + private final DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener; + private final GlObjectsProvider glObjectsProvider; + // List of queues of unprocessed frames for each input source. + private final List> inputFrameInfos; + + private final TexturePool outputTexturePool; + // Only used on the GL Thread. + private @MonotonicNonNull GlProgram glProgram; + private long syncObject; + + public VideoCompositor( + Context context, + GlObjectsProvider glObjectsProvider, + DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener, + @IntRange(from = 1) int textureOutputCapacity) { + this.context = context; + this.textureOutputListener = textureOutputListener; + this.glObjectsProvider = glObjectsProvider; + + inputFrameInfos = new ArrayList<>(); + outputTexturePool = + new TexturePool(/* useHighPrecisionColorComponents= */ false, textureOutputCapacity); + } + + /** + * Registers a new input source, and returns a unique {@code inputId} corresponding to this + * source, to be used in {@link #queueInputTexture}. + */ + public synchronized int registerInputSource() { + inputFrameInfos.add(new ArrayDeque<>()); + return inputFrameInfos.size() - 1; + } + + // Below methods must be called on the GL thread. + /** + * Queues an input texture to be composited, for example from an upstream {@link + * DefaultVideoFrameProcessor.TextureOutputListener}. + * + *

Each input source must have a unique {@code inputId} returned from {@link + * #registerInputSource}. + */ + public void queueInputTexture( + int inputId, + GlTextureInfo inputTexture, + long presentationTimeUs, + DefaultVideoFrameProcessor.ReleaseOutputTextureCallback releaseTextureCallback) + throws VideoFrameProcessingException { + InputFrameInfo inputFrameInfo = + new InputFrameInfo(inputTexture, presentationTimeUs, releaseTextureCallback); + checkNotNull(inputFrameInfos.get(inputId)).add(inputFrameInfo); + + if (isReadyToComposite()) { + compositeToOutputTexture(); + } + } + + private boolean isReadyToComposite() { + // TODO: b/262694346 - Use timestamps to determine when to composite instead of number of + // frames. + for (int inputId = 0; inputId < inputFrameInfos.size(); inputId++) { + if (checkNotNull(inputFrameInfos.get(inputId)).isEmpty()) { + return false; + } + } + return true; + } + + private void compositeToOutputTexture() throws VideoFrameProcessingException { + List framesToComposite = new ArrayList<>(); + for (int inputId = 0; inputId < inputFrameInfos.size(); inputId++) { + framesToComposite.add(checkNotNull(inputFrameInfos.get(inputId)).remove()); + } + + ensureGlProgramConfigured(); + // TODO: b/262694346 - + // * Support an arbitrary number of inputs. + // * Allow different frame dimensions. + InputFrameInfo inputFrame1 = framesToComposite.get(0); + InputFrameInfo inputFrame2 = framesToComposite.get(1); + checkState(inputFrame1.texture.getWidth() == inputFrame2.texture.getWidth()); + checkState(inputFrame1.texture.getHeight() == inputFrame2.texture.getHeight()); + try { + outputTexturePool.ensureConfigured( + glObjectsProvider, inputFrame1.texture.getWidth(), inputFrame1.texture.getHeight()); + GlTextureInfo outputTexture = outputTexturePool.useTexture(); + + drawFrame(inputFrame1.texture, inputFrame2.texture, outputTexture); + syncObject = GlUtil.createGlSyncFence(); + + for (int i = 0; i < framesToComposite.size(); i++) { + InputFrameInfo inputFrameInfo = framesToComposite.get(i); + inputFrameInfo.releaseCallback.release(inputFrameInfo.presentationTimeUs); + } + + // TODO: b/262694346 - Use presentationTimeUs here for freeing textures. + textureOutputListener.onTextureRendered( + checkNotNull(outputTexture), + /* presentationTimeUs= */ 0, + (presentationTimeUs) -> outputTexturePool.freeTexture(), + syncObject); + } catch (GlUtil.GlException e) { + throw VideoFrameProcessingException.from(e); + } + } + + private void ensureGlProgramConfigured() throws VideoFrameProcessingException { + if (glProgram != null) { + return; + } + try { + glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); + glProgram.setBufferAttribute( + "aFramePosition", + GlUtil.getNormalizedCoordinateBounds(), + GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); + } catch (GlUtil.GlException | IOException e) { + throw new VideoFrameProcessingException(e); + } + } + + private void drawFrame( + GlTextureInfo inputTexture1, GlTextureInfo inputTexture2, GlTextureInfo outputTexture) + throws GlUtil.GlException { + GlUtil.focusFramebufferUsingCurrentContext( + outputTexture.getFboId(), outputTexture.getWidth(), outputTexture.getHeight()); + GlUtil.clearFocusedBuffers(); + + GlProgram glProgram = checkNotNull(this.glProgram); + glProgram.use(); + glProgram.setSamplerTexIdUniform( + "uTexSampler1", inputTexture1.getTexId(), /* texUnitIndex= */ 0); + glProgram.setSamplerTexIdUniform( + "uTexSampler2", inputTexture2.getTexId(), /* texUnitIndex= */ 1); + + glProgram.setFloatsUniform("uTexTransformationMatrix", GlUtil.create4x4IdentityMatrix()); + glProgram.setFloatsUniform("uTransformationMatrix", GlUtil.create4x4IdentityMatrix()); + glProgram.setBufferAttribute( + "aFramePosition", + GlUtil.getNormalizedCoordinateBounds(), + GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); + glProgram.bindAttributesAndUniforms(); + // The four-vertex triangle strip forms a quad. + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); + } + + /** Holds information on a frame and how to release it. */ + private static final class InputFrameInfo { + public final GlTextureInfo texture; + public final long presentationTimeUs; + public final DefaultVideoFrameProcessor.ReleaseOutputTextureCallback releaseCallback; + + public InputFrameInfo( + GlTextureInfo texture, + long presentationTimeUs, + DefaultVideoFrameProcessor.ReleaseOutputTextureCallback releaseCallback) { + this.texture = texture; + this.presentationTimeUs = presentationTimeUs; + this.releaseCallback = releaseCallback; + } + } +} diff --git a/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/grayscaleAndRotate180Composite.png b/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/grayscaleAndRotate180Composite.png new file mode 100644 index 0000000000000000000000000000000000000000..28916681b35f2e21537194301fc420415914722b GIT binary patch literal 12311 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 qO$Bcf5D>pdUx}y&#@pKYGdvDgK)2*Lmesr literal 0 HcmV?d00001 diff --git a/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/grayscale_media3test.png b/libraries/test_data/src/test/assets/media/bitmap/sample_mp4_first_frame/electrical_colors/grayscale_media3test.png new file mode 100644 index 0000000000000000000000000000000000000000..ec10f79f10275b55d2ba9e9facbfbc05a1fb12d6 GIT binary patch literal 7240 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 compositedOutputBitmap = new AtomicReference<>(); + VideoCompositor videoCompositor = + new VideoCompositor( + getApplicationContext(), + sharedGlObjectsProvider, + /* textureOutputListener= */ (outputTexture, + presentationTimeUs, + releaseOutputTextureCallback, + syncObject) -> { + try { + GlUtil.awaitSyncObject(syncObject); + compositedOutputBitmap.set( + BitmapPixelTestUtil.createArgb8888BitmapFromCurrentGlFramebuffer( + outputTexture.getWidth(), outputTexture.getHeight())); + } catch (GlUtil.GlException e) { + throw VideoFrameProcessingException.from(e); + } finally { + releaseOutputTextureCallback.release(presentationTimeUs); + } + }, + /* textureOutputCapacity= */ 1); + TextureBitmapReader inputTextureBitmapReader1 = new TextureBitmapReader(); + VideoFrameProcessorTestRunner inputVfpTestRunner1 = + getFrameProcessorTestRunnerBuilder( + testId, inputTextureBitmapReader1, videoCompositor, sharedGlObjectsProvider) + .setEffects(GRAYSCALE) + .build(); + this.inputVfpTestRunner1 = inputVfpTestRunner1; + TextureBitmapReader inputTextureBitmapReader2 = new TextureBitmapReader(); + VideoFrameProcessorTestRunner inputVfpTestRunner2 = + getFrameProcessorTestRunnerBuilder( + testId, inputTextureBitmapReader2, videoCompositor, sharedGlObjectsProvider) + .setEffects(ROTATE_180) + .build(); + this.inputVfpTestRunner2 = inputVfpTestRunner2; + + // Queue 1 input bitmap from each input VideoFrameProcessor source. + inputVfpTestRunner1.queueInputBitmap( + readBitmap(ORIGINAL_PNG_ASSET_PATH), + /* durationUs= */ 1 * C.MICROS_PER_SECOND, + /* offsetToAddUs= */ 0, + /* frameRate= */ 1); + inputVfpTestRunner1.endFrameProcessing(); + inputVfpTestRunner2.queueInputBitmap( + readBitmap(ORIGINAL_PNG_ASSET_PATH), + /* durationUs= */ 1 * C.MICROS_PER_SECOND, + /* offsetToAddUs= */ 0, + /* frameRate= */ 1); + inputVfpTestRunner2.endFrameProcessing(); + + // Check that VideoFrameProcessor and VideoCompositor outputs match expected bitmaps. + Bitmap actualCompositorInputBitmap1 = checkNotNull(inputTextureBitmapReader1).getBitmap(); + saveAndAssertBitmapMatchesExpected( + testId, + actualCompositorInputBitmap1, + /* actualBitmapLabel= */ "actualCompositorInputBitmap1", + GRAYSCALE_PNG_ASSET_PATH); + Bitmap actualCompositorInputBitmap2 = checkNotNull(inputTextureBitmapReader2).getBitmap(); + saveAndAssertBitmapMatchesExpected( + testId, + actualCompositorInputBitmap2, + /* actualBitmapLabel= */ "actualCompositorInputBitmap2", + ROTATE180_PNG_ASSET_PATH); + Bitmap compositorOutputBitmap = compositedOutputBitmap.get(); + saveAndAssertBitmapMatchesExpected( + testId, + compositorOutputBitmap, + /* actualBitmapLabel= */ "compositorOutputBitmap", + GRAYSCALE_AND_ROTATE180_COMPOSITE_PNG_ASSET_PATH); + } + + private void saveAndAssertBitmapMatchesExpected( + String testId, Bitmap actualBitmap, String actualBitmapLabel, String expectedBitmapAssetPath) + throws IOException { + maybeSaveTestBitmap(testId, actualBitmapLabel, actualBitmap, /* path= */ null); + float averagePixelAbsoluteDifference = + BitmapPixelTestUtil.getBitmapAveragePixelAbsoluteDifferenceArgb8888( + readBitmap(expectedBitmapAssetPath), actualBitmap, testId); + assertThat(averagePixelAbsoluteDifference) + .isAtMost(MAXIMUM_AVERAGE_PIXEL_ABSOLUTE_DIFFERENCE_DIFFERENT_DEVICE); + } + + private static VideoFrameProcessorTestRunner.Builder getFrameProcessorTestRunnerBuilder( + String testId, + TextureBitmapReader textureBitmapReader, + VideoCompositor videoCompositor, + GlObjectsProvider glObjectsProvider) { + int inputId = videoCompositor.registerInputSource(); + VideoFrameProcessor.Factory defaultVideoFrameProcessorFactory = + new DefaultVideoFrameProcessor.Factory.Builder() + .setGlObjectsProvider(glObjectsProvider) + .setTextureOutput( + /* textureOutputListener= */ (GlTextureInfo outputTexture, + long presentationTimeUs, + DefaultVideoFrameProcessor.ReleaseOutputTextureCallback + releaseOutputTextureCallback, + long syncObject) -> { + GlUtil.awaitSyncObject(syncObject); + textureBitmapReader.readBitmap(outputTexture, presentationTimeUs); + videoCompositor.queueInputTexture( + inputId, outputTexture, presentationTimeUs, releaseOutputTextureCallback); + }, + /* textureOutputCapacity= */ 1) + .build(); + return new VideoFrameProcessorTestRunner.Builder() + .setTestId(testId) + .setVideoFrameProcessorFactory(defaultVideoFrameProcessorFactory) + .setInputType(INPUT_TYPE_BITMAP) + .setInputColorInfo(ColorInfo.SRGB_BT709_FULL) + .setBitmapReader(textureBitmapReader); + } +} diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/DefaultVideoFrameProcessorMultipleTextureOutputPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/DefaultVideoFrameProcessorMultipleTextureOutputPixelTest.java index 6dcdbe569f..6adc4460c8 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/DefaultVideoFrameProcessorMultipleTextureOutputPixelTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/DefaultVideoFrameProcessorMultipleTextureOutputPixelTest.java @@ -29,6 +29,7 @@ import androidx.media3.common.VideoFrameProcessor; import androidx.media3.effect.DefaultVideoFrameProcessor; import androidx.media3.test.utils.BitmapPixelTestUtil; import androidx.media3.test.utils.VideoFrameProcessorTestRunner; +import androidx.media3.transformer.TextureBitmapReader; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.Set; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -138,9 +139,12 @@ public class DefaultVideoFrameProcessorMultipleTextureOutputPixelTest { VideoFrameProcessor.Factory defaultVideoFrameProcessorFactory = new DefaultVideoFrameProcessor.Factory.Builder() .setTextureOutput( - (outputTexture, presentationTimeUs, releaseOutputTextureCallback, token) -> + (outputTexture, + presentationTimeUs, + releaseOutputTextureCallback, + unusedSyncObject) -> checkNotNull(textureBitmapReader) - .readBitmapFromTexture( + .readBitmapAndReleaseTexture( outputTexture, presentationTimeUs, releaseOutputTextureCallback), /* textureOutputCapacity= */ 1) .build(); 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 c60e1f4232..1015ce79d4 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 @@ -48,6 +48,7 @@ import androidx.media3.test.utils.BitmapPixelTestUtil; import androidx.media3.test.utils.VideoFrameProcessorTestRunner; import androidx.media3.transformer.AndroidTestUtil; import androidx.media3.transformer.EncoderUtil; +import androidx.media3.transformer.TextureBitmapReader; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import java.util.List; @@ -533,8 +534,11 @@ public final class DefaultVideoFrameProcessorTextureOutputPixelTest { DefaultVideoFrameProcessor.Factory defaultVideoFrameProcessorFactory = new DefaultVideoFrameProcessor.Factory.Builder() .setTextureOutput( - (outputTexture, presentationTimeUs1, releaseOutputTextureCallback1, token1) -> - bitmapReader.readBitmapFromTexture( + (outputTexture, + presentationTimeUs1, + releaseOutputTextureCallback1, + unusedSyncObject) -> + bitmapReader.readBitmapAndReleaseTexture( outputTexture, presentationTimeUs1, releaseOutputTextureCallback1), /* textureOutputCapacity= */ 1) .setGlObjectsProvider(contextSharingGlObjectsProvider) @@ -565,8 +569,11 @@ public final class DefaultVideoFrameProcessorTextureOutputPixelTest { DefaultVideoFrameProcessor.Factory defaultVideoFrameProcessorFactory = new DefaultVideoFrameProcessor.Factory.Builder() .setTextureOutput( - (outputTexture, presentationTimeUs, releaseOutputTextureCallback, token) -> - textureBitmapReader.readBitmapFromTexture( + (outputTexture, + presentationTimeUs, + releaseOutputTextureCallback, + unusedSyncObject) -> + textureBitmapReader.readBitmapAndReleaseTexture( outputTexture, presentationTimeUs, releaseOutputTextureCallback), /* textureOutputCapacity= */ 1) .build(); diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/FrameDropPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/FrameDropPixelTest.java index 914071787d..50c24e5439 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/FrameDropPixelTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/FrameDropPixelTest.java @@ -31,6 +31,7 @@ import androidx.media3.common.VideoFrameProcessor; import androidx.media3.effect.DefaultVideoFrameProcessor; import androidx.media3.effect.FrameDropEffect; import androidx.media3.test.utils.VideoFrameProcessorTestRunner; +import androidx.media3.transformer.TextureBitmapReader; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -170,7 +171,7 @@ public class FrameDropPixelTest { .setTextureOutput( (outputTexture, presentationTimeUs, releaseOutputTextureCallback, token) -> checkNotNull(textureBitmapReader) - .readBitmapFromTexture( + .readBitmapAndReleaseTexture( outputTexture, presentationTimeUs, releaseOutputTextureCallback), /* textureOutputCapacity= */ 1) .build();