Effect: Flip texture in OpenGL instead of allocating a Bitmap.

Reduce short-lived allocations of potentially large objects, like Bitmap.

Unfortunately, this does make the TextureOverlay interface more messy though, requiring a way to signal whether the texture should be flipped vertically.

PiperOrigin-RevId: 584661400
This commit is contained in:
huangdarwin 2023-11-22 10:14:28 -08:00 committed by Copybara-Service
parent 2d77e4d22c
commit dc037b22cd
4 changed files with 58 additions and 63 deletions

View File

@ -20,6 +20,7 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.opengl.Matrix;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.BitmapLoader; import androidx.media3.common.util.BitmapLoader;
@ -39,10 +40,17 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/ */
@UnstableApi @UnstableApi
public abstract class BitmapOverlay extends TextureOverlay { public abstract class BitmapOverlay extends TextureOverlay {
private final float[] flipVerticallyMatrix;
private int lastTextureId; private int lastTextureId;
private @Nullable Bitmap lastBitmap; private @Nullable Bitmap lastBitmap;
BitmapOverlay() { /* package */ BitmapOverlay() {
float[] temp = GlUtil.create4x4IdentityMatrix();
Matrix.scaleM(temp, /* offset */ 0, /* x= */ 1f, /* y= */ -1f, /* z= */ 1f);
flipVerticallyMatrix = temp;
lastTextureId = C.INDEX_UNSET; lastTextureId = C.INDEX_UNSET;
} }
@ -72,11 +80,10 @@ public abstract class BitmapOverlay extends TextureOverlay {
if (bitmap != lastBitmap) { if (bitmap != lastBitmap) {
try { try {
lastBitmap = bitmap; lastBitmap = bitmap;
if (lastTextureId != -1) { if (lastTextureId != C.INDEX_UNSET) {
GlUtil.deleteTexture(lastTextureId); GlUtil.deleteTexture(lastTextureId);
} }
lastTextureId = lastTextureId = GlUtil.createTexture(checkNotNull(lastBitmap));
GlUtil.createTexture(BitmapUtil.flipBitmapVertically(checkNotNull(lastBitmap)));
} catch (GlUtil.GlException e) { } catch (GlUtil.GlException e) {
throw new VideoFrameProcessingException(e); throw new VideoFrameProcessingException(e);
} }
@ -84,6 +91,15 @@ public abstract class BitmapOverlay extends TextureOverlay {
return lastTextureId; return lastTextureId;
} }
@Override
public float[] getVertexTransformation(long presentationTimeUs) {
// Whereas the Android system uses the top-left corner as (0,0) of the
// coordinate system, OpenGL uses the bottom-left corner as (0,0), so the
// texture gets flipped. Flip the texture vertically to ensure the
// orientation of the output is correct.
return flipVerticallyMatrix;
}
/** /**
* Creates a {@link BitmapOverlay} that shows the {@code overlayBitmap} in the same position and * Creates a {@link BitmapOverlay} that shows the {@code overlayBitmap} in the same position and
* size throughout the whole video. * size throughout the whole video.
@ -164,7 +180,7 @@ public abstract class BitmapOverlay extends TextureOverlay {
public void release() throws VideoFrameProcessingException { public void release() throws VideoFrameProcessingException {
super.release(); super.release();
lastBitmap = null; lastBitmap = null;
if (lastTextureId != -1) { if (lastTextureId != C.INDEX_UNSET) {
try { try {
GlUtil.deleteTexture(lastTextureId); GlUtil.deleteTexture(lastTextureId);
} catch (GlUtil.GlException e) { } catch (GlUtil.GlException e) {

View File

@ -1,38 +0,0 @@
/*
* Copyright 2022 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.
*/
package androidx.media3.effect;
import android.graphics.Bitmap;
import android.graphics.Matrix;
/** Utility functions for working with {@link Bitmap}. */
/* package */ final class BitmapUtil {
public static Bitmap flipBitmapVertically(Bitmap bitmap) {
Matrix flip = new Matrix();
flip.postScale(1f, -1f);
return Bitmap.createBitmap(
bitmap,
/* x= */ 0,
/* y= */ 0,
bitmap.getWidth(),
bitmap.getHeight(),
flip,
/* filter= */ true);
}
/** Class only contains static methods. */
private BitmapUtil() {}
}

View File

@ -78,26 +78,24 @@ import com.google.common.collect.ImmutableList;
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
try { try {
glProgram.use(); glProgram.use();
if (!overlays.isEmpty()) { for (int texUnitIndex = 1; texUnitIndex <= overlays.size(); texUnitIndex++) {
for (int texUnitIndex = 1; texUnitIndex <= overlays.size(); texUnitIndex++) { TextureOverlay overlay = overlays.get(texUnitIndex - 1);
TextureOverlay overlay = overlays.get(texUnitIndex - 1); glProgram.setSamplerTexIdUniform(
Util.formatInvariant("uOverlayTexSampler%d", texUnitIndex),
glProgram.setSamplerTexIdUniform( overlay.getTextureId(presentationTimeUs),
Util.formatInvariant("uOverlayTexSampler%d", texUnitIndex), texUnitIndex);
overlay.getTextureId(presentationTimeUs), glProgram.setFloatsUniform(
texUnitIndex); Util.formatInvariant("uVertexTransformationMatrix%d", texUnitIndex),
OverlaySettings overlaySettings = overlay.getOverlaySettings(presentationTimeUs); overlay.getVertexTransformation(presentationTimeUs));
Size overlaySize = overlay.getTextureSize(presentationTimeUs); OverlaySettings overlaySettings = overlay.getOverlaySettings(presentationTimeUs);
Size overlaySize = overlay.getTextureSize(presentationTimeUs);
glProgram.setFloatsUniform( glProgram.setFloatsUniform(
Util.formatInvariant("uTransformationMatrix%d", texUnitIndex), Util.formatInvariant("uTransformationMatrix%d", texUnitIndex),
samplerOverlayMatrixProvider.getTransformationMatrix(overlaySize, overlaySettings)); samplerOverlayMatrixProvider.getTransformationMatrix(overlaySize, overlaySettings));
glProgram.setFloatUniform(
glProgram.setFloatUniform( Util.formatInvariant("uOverlayAlphaScale%d", texUnitIndex), overlaySettings.alphaScale);
Util.formatInvariant("uOverlayAlphaScale%d", texUnitIndex),
overlaySettings.alphaScale);
}
} }
glProgram.setSamplerTexIdUniform("uVideoTexSampler0", inputTexId, /* texUnitIndex= */ 0); glProgram.setSamplerTexIdUniform("uVideoTexSampler0", inputTexId, /* texUnitIndex= */ 0);
glProgram.bindAttributesAndUniforms(); glProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad. // The four-vertex triangle strip forms a quad.
@ -131,6 +129,8 @@ import com.google.common.collect.ImmutableList;
for (int texUnitIndex = 1; texUnitIndex <= numOverlays; texUnitIndex++) { for (int texUnitIndex = 1; texUnitIndex <= numOverlays; texUnitIndex++) {
shader shader
.append(Util.formatInvariant("uniform mat4 uTransformationMatrix%s;\n", texUnitIndex)) .append(Util.formatInvariant("uniform mat4 uTransformationMatrix%s;\n", texUnitIndex))
.append(
Util.formatInvariant("uniform mat4 uVertexTransformationMatrix%s;\n", texUnitIndex))
.append(Util.formatInvariant("varying vec2 vOverlayTexSamplingCoord%s;\n", texUnitIndex)); .append(Util.formatInvariant("varying vec2 vOverlayTexSamplingCoord%s;\n", texUnitIndex));
} }
@ -146,7 +146,9 @@ import com.google.common.collect.ImmutableList;
shader shader
.append(Util.formatInvariant(" vec4 aOverlayPosition%d = \n", texUnitIndex)) .append(Util.formatInvariant(" vec4 aOverlayPosition%d = \n", texUnitIndex))
.append( .append(
Util.formatInvariant(" uTransformationMatrix%s * aFramePosition;\n", texUnitIndex)) Util.formatInvariant(
" uVertexTransformationMatrix%s * uTransformationMatrix%s * aFramePosition;\n",
texUnitIndex, texUnitIndex))
.append( .append(
Util.formatInvariant( Util.formatInvariant(
" vOverlayTexSamplingCoord%d = getTexSamplingCoord(aOverlayPosition%d.xy);\n", " vOverlayTexSamplingCoord%d = getTexSamplingCoord(aOverlayPosition%d.xy);\n",

View File

@ -16,12 +16,15 @@
package androidx.media3.effect; package androidx.media3.effect;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Size; import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
/** Creates overlays from OpenGL textures. */ /** Creates overlays from OpenGL textures. */
@UnstableApi @UnstableApi
public abstract class TextureOverlay { public abstract class TextureOverlay {
private static final float[] IDENTITY_MATRIX = GlUtil.create4x4IdentityMatrix();
/** /**
* Returns the overlay texture identifier displayed at the specified timestamp. * Returns the overlay texture identifier displayed at the specified timestamp.
* *
@ -41,6 +44,18 @@ public abstract class TextureOverlay {
*/ */
public abstract Size getTextureSize(long presentationTimeUs); public abstract Size getTextureSize(long presentationTimeUs);
/**
* Returns a 4x4 OpenGL matrix, controlling how the vertices of the overlay are displayed at the
* specified timestamp.
*
* <p>Applied before {@linkplain #getOverlaySettings overlay settings}.
*
* @param presentationTimeUs The presentation timestamp of the current frame, in microseconds.
*/
public float[] getVertexTransformation(long presentationTimeUs) {
return IDENTITY_MATRIX;
}
/** /**
* Set up resources for the overlay given the input videos dimensions. * Set up resources for the overlay given the input videos dimensions.
* *