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:
parent
2d77e4d22c
commit
dc037b22cd
@ -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) {
|
||||||
|
@ -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() {}
|
|
||||||
}
|
|
@ -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(
|
glProgram.setSamplerTexIdUniform(
|
||||||
Util.formatInvariant("uOverlayTexSampler%d", texUnitIndex),
|
Util.formatInvariant("uOverlayTexSampler%d", texUnitIndex),
|
||||||
overlay.getTextureId(presentationTimeUs),
|
overlay.getTextureId(presentationTimeUs),
|
||||||
texUnitIndex);
|
texUnitIndex);
|
||||||
|
glProgram.setFloatsUniform(
|
||||||
|
Util.formatInvariant("uVertexTransformationMatrix%d", texUnitIndex),
|
||||||
|
overlay.getVertexTransformation(presentationTimeUs));
|
||||||
OverlaySettings overlaySettings = overlay.getOverlaySettings(presentationTimeUs);
|
OverlaySettings overlaySettings = overlay.getOverlaySettings(presentationTimeUs);
|
||||||
Size overlaySize = overlay.getTextureSize(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),
|
Util.formatInvariant("uOverlayAlphaScale%d", texUnitIndex), overlaySettings.alphaScale);
|
||||||
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",
|
||||||
|
@ -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 video’s dimensions.
|
* Set up resources for the overlay given the input video’s dimensions.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user