Gaussian Blur: support blurring without drawing sharp image on top

PiperOrigin-RevId: 598626481
This commit is contained in:
tofunmi 2024-01-15 09:36:57 -08:00 committed by Copybara-Service
parent e51c293f75
commit 0155ae998b
9 changed files with 393 additions and 82 deletions

View file

@ -83,10 +83,11 @@ public class GaussianBlurTest {
// Golden images for these tests were generated on an API 33 emulator. API 26 emulators have a // Golden images for these tests were generated on an API 33 emulator. API 26 emulators have a
// different text rendering implementation that leads to a larger pixel difference. // different text rendering implementation that leads to a larger pixel difference.
@Test @Test
@RequiresNonNull({"textureBitmapReader", "testId"}) @RequiresNonNull({"textureBitmapReader", "testId"})
public void gaussianBlur_blursFrame() throws Exception { public void gaussianBlur_blursFrame() throws Exception {
ImmutableList<Long> frameTimesUs = ImmutableList.of(32_000L); ImmutableList<Long> frameTimesUs = ImmutableList.of(22_000L);
ImmutableList<Long> actualPresentationTimesUs = ImmutableList<Long> actualPresentationTimesUs =
generateAndProcessFrames( generateAndProcessFrames(
BLANK_FRAME_WIDTH, BLANK_FRAME_WIDTH,
@ -96,31 +97,7 @@ public class GaussianBlurTest {
textureBitmapReader, textureBitmapReader,
TEXT_SPAN_CONSUMER); TEXT_SPAN_CONSUMER);
assertThat(actualPresentationTimesUs).containsExactly(32_000L); assertThat(actualPresentationTimesUs).containsExactly(22_000L);
getAndAssertOutputBitmaps(textureBitmapReader, actualPresentationTimesUs, testId, ASSET_PATH);
}
@Test
@RequiresNonNull({"textureBitmapReader", "testId"})
public void gaussianBlur_sigmaChangesWithTime_differentFramesHaveDifferentBlurs()
throws Exception {
ImmutableList<Long> frameTimesUs = ImmutableList.of(32_000L, 71_000L);
ImmutableList<Long> actualPresentationTimesUs =
generateAndProcessFrames(
BLANK_FRAME_WIDTH,
BLANK_FRAME_HEIGHT,
frameTimesUs,
new SeparableConvolution() {
@Override
public ConvolutionFunction1D getConvolution(long presentationTimeUs) {
return new GaussianFunction(
presentationTimeUs < 40_000L ? 5f : 20f, /* numStandardDeviations= */ 2.0f);
}
},
textureBitmapReader,
TEXT_SPAN_CONSUMER);
assertThat(actualPresentationTimesUs).containsExactly(32_000L, 71_000L);
getAndAssertOutputBitmaps(textureBitmapReader, actualPresentationTimesUs, testId, ASSET_PATH); getAndAssertOutputBitmaps(textureBitmapReader, actualPresentationTimesUs, testId, ASSET_PATH);
} }
} }

View file

@ -0,0 +1,142 @@
/*
* 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.effect.EffectsTestUtil.generateAndProcessFrames;
import static androidx.media3.effect.EffectsTestUtil.getAndAssertOutputBitmaps;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.graphics.Color;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.TypefaceSpan;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.Consumer;
import androidx.media3.test.utils.TextureBitmapReader;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
/** Tests for {@link GaussianBlur}. */
@RunWith(AndroidJUnit4.class)
public class GaussianBlurWithFrameOverlaidTest {
@Rule public final TestName testName = new TestName();
private static final String ASSET_PATH = "media/bitmap/GaussianBlurWithFrameOverlaidTest";
private static final int BLANK_FRAME_WIDTH = 200;
private static final int BLANK_FRAME_HEIGHT = 100;
private static final Consumer<SpannableString> TEXT_SPAN_CONSUMER =
(text) -> {
text.setSpan(
new BackgroundColorSpan(Color.BLUE),
/* start= */ 0,
text.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(
new ForegroundColorSpan(Color.WHITE),
/* start= */ 0,
text.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(
new AbsoluteSizeSpan(/* size= */ 100),
/* start= */ 0,
text.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(
new TypefaceSpan(/* family= */ "sans-serif"),
/* start= */ 0,
text.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
};
private @MonotonicNonNull String testId;
private @MonotonicNonNull TextureBitmapReader textureBitmapReader;
@EnsuresNonNull({"textureBitmapReader", "testId"})
@Before
public void setUp() {
textureBitmapReader = new TextureBitmapReader();
testId = testName.getMethodName();
}
// Golden images for these tests were generated on an API 33 emulator. API 26 emulators have a
// different text rendering implementation that leads to a larger pixel difference.
@Test
@RequiresNonNull({"textureBitmapReader", "testId"})
public void gaussianBlurWithFrameOverlaid_blursFrameAndOverlaysSharpImage() throws Exception {
ImmutableList<Long> frameTimesUs = ImmutableList.of(32_000L);
ImmutableList<Long> actualPresentationTimesUs =
generateAndProcessFrames(
BLANK_FRAME_WIDTH,
BLANK_FRAME_HEIGHT,
frameTimesUs,
new GaussianBlurWithFrameOverlaid(
/* sigma= */ 5f, /* scaleSharpX= */ 0.5f, /* scaleSharpY= */ 1f),
textureBitmapReader,
TEXT_SPAN_CONSUMER);
assertThat(actualPresentationTimesUs).containsExactly(32_000L);
getAndAssertOutputBitmaps(textureBitmapReader, actualPresentationTimesUs, testId, ASSET_PATH);
}
@Test
@RequiresNonNull({"textureBitmapReader", "testId"})
public void gaussianBlurWithFrameOverlaid_sigmaChangesWithTime_differentFramesHaveDifferentBlurs()
throws Exception {
ImmutableList<Long> frameTimesUs = ImmutableList.of(32_000L, 71_000L);
ImmutableList<Long> actualPresentationTimesUs =
generateAndProcessFrames(
BLANK_FRAME_WIDTH,
BLANK_FRAME_HEIGHT,
frameTimesUs,
new SeparableConvolution() {
@Override
public ConvolutionFunction1D getConvolution(long presentationTimeUs) {
return new GaussianFunction(
presentationTimeUs < 40_000L ? 5f : 20f, /* numStandardDeviations= */ 2.0f);
}
@Override
public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr)
throws VideoFrameProcessingException {
return new SharpSeparableConvolutionShaderProgram(
context,
useHdr,
/* convolution= */ this,
/* scaleFactor= */
/* scaleSharpX= */ 0.5f,
/* scaleSharpY= */ 1f);
}
},
textureBitmapReader,
TEXT_SPAN_CONSUMER);
assertThat(actualPresentationTimesUs).containsExactly(32_000L, 71_000L);
getAndAssertOutputBitmaps(textureBitmapReader, actualPresentationTimesUs, testId, ASSET_PATH);
}
}

View file

@ -0,0 +1,83 @@
/*
* 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.
*/
package androidx.media3.effect;
import android.content.Context;
import androidx.annotation.FloatRange;
import androidx.annotation.RequiresApi;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.UnstableApi;
/**
* A {@link SeparableConvolution} to apply a Gaussian blur on image data.
*
* <p>The width of the blur is specified in pixels and applied symmetrically.
*/
@UnstableApi
@RequiresApi(26) // See SeparableConvolution.
public final class GaussianBlurWithFrameOverlaid extends SeparableConvolution {
private final float sigma;
private final float numStandardDeviations;
private final float scaleSharpX;
private final float scaleSharpY;
/**
* Creates an instance.
*
* @param sigma The half-width of 1 standard deviation, in pixels.
* @param numStandardDeviations The size of function domain, measured in the number of standard
* deviations.
* @param scaleSharpX The scaling factor used to determine the size of the sharp image in the
* output frame relative to the whole output frame in the horizontal direction.
* @param scaleSharpY The scaling factor used to determine the size of the sharp image in the
* output frame relative to the whole output frame in the vertical direction.
*/
public GaussianBlurWithFrameOverlaid(
@FloatRange(from = 0.0, fromInclusive = false) float sigma,
@FloatRange(from = 0.0, fromInclusive = false) float numStandardDeviations,
float scaleSharpX,
float scaleSharpY) {
this.sigma = sigma;
this.numStandardDeviations = numStandardDeviations;
this.scaleSharpX = scaleSharpX;
this.scaleSharpY = scaleSharpY;
}
/**
* Creates an instance with {@code numStandardDeviations} set to {@code 2.0f}.
*
* @param sigma The half-width of 1 standard deviation, in pixels.
* @param scaleSharpX The scaling factor used to determine the size of the sharp image in the
* output frame relative to the whole output frame in the horizontal direction.
* @param scaleSharpY The scaling factor used to determine the size of the sharp image in the
* output frame relative to the whole output frame in the vertical direction.
*/
public GaussianBlurWithFrameOverlaid(float sigma, float scaleSharpX, float scaleSharpY) {
this(sigma, /* numStandardDeviations= */ 2.0f, scaleSharpX, scaleSharpY);
}
@Override
public ConvolutionFunction1D getConvolution(long presentationTimeUs) {
return new GaussianFunction(sigma, numStandardDeviations);
}
@Override
public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr)
throws VideoFrameProcessingException {
return new SharpSeparableConvolutionShaderProgram(
context, useHdr, /* convolution= */ this, scaleSharpX, scaleSharpY);
}
}

View file

@ -29,21 +29,25 @@ import androidx.media3.common.util.UnstableApi;
@UnstableApi @UnstableApi
@RequiresApi(26) // See SeparableConvolutionShaderProgram. @RequiresApi(26) // See SeparableConvolutionShaderProgram.
public abstract class SeparableConvolution implements GlEffect { public abstract class SeparableConvolution implements GlEffect {
private final float scaleFactor; private final float scaleWidth;
private final float scaleHeight;
/** Creates an instance with a {@code scaleFactor} of {@code 1}. */ /** Creates an instance with {@code scaleWidth} and {@code scaleHeight} set to {@code 1.0f}. */
public SeparableConvolution() { public SeparableConvolution() {
this(/* scaleFactor= */ 1.0f); this(/* scaleWidth= */ 1.0f, /* scaleHeight= */ 1.0f);
} }
/** /**
* Creates an instance. * Creates an instance.
* *
* @param scaleFactor The scaling factor used to determine the size of the output relative to the * @param scaleWidth The scaling factor used to determine the width of the output relative to the
* input. The aspect ratio remains constant. * input.
* @param scaleHeight The scaling factor used to determine the height of the output relative to
* the input.
*/ */
public SeparableConvolution(float scaleFactor) { public SeparableConvolution(float scaleWidth, float scaleHeight) {
this.scaleFactor = scaleFactor; this.scaleWidth = scaleWidth;
this.scaleHeight = scaleHeight;
} }
/** /**
@ -56,6 +60,7 @@ public abstract class SeparableConvolution implements GlEffect {
@Override @Override
public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr) public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr)
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
return new SeparableConvolutionShaderProgram(context, useHdr, this, scaleFactor); return new SeparableConvolutionShaderProgram(
context, useHdr, /* convolution= */ this, scaleWidth, scaleHeight);
} }
} }

View file

@ -15,13 +15,11 @@
*/ */
package androidx.media3.effect; package androidx.media3.effect;
import static androidx.media3.effect.MatrixUtils.getGlMatrixArray;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.opengl.GLUtils; import android.opengl.GLUtils;
import androidx.annotation.CallSuper;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.media3.common.GlObjectsProvider; import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo; import androidx.media3.common.GlTextureInfo;
@ -30,6 +28,7 @@ import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil; 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 com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException; import java.io.IOException;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
@ -43,7 +42,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* on a second pass. * on a second pass.
*/ */
@RequiresApi(26) // Uses Bitmap.Config.RGBA_F16. @RequiresApi(26) // Uses Bitmap.Config.RGBA_F16.
/* package */ final class SeparableConvolutionShaderProgram implements GlShaderProgram { @UnstableApi
public class SeparableConvolutionShaderProgram implements GlShaderProgram {
private static final String VERTEX_SHADER_PATH = "shaders/vertex_shader_transformation_es2.glsl"; private static final String VERTEX_SHADER_PATH = "shaders/vertex_shader_transformation_es2.glsl";
private static final String FRAGMENT_SHADER_PATH = private static final String FRAGMENT_SHADER_PATH =
"shaders/fragment_shader_separable_convolution_es2.glsl"; "shaders/fragment_shader_separable_convolution_es2.glsl";
@ -67,27 +67,26 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// END FP16 copied code. // END FP16 copied code.
private final GlProgram glProgram; private final GlProgram glProgram;
private final GlProgram sharpTransformGlProgram;
private final float[] sharpTransformMatrixValues;
private final boolean useHdr; private final boolean useHdr;
private final SeparableConvolution convolution; private final SeparableConvolution convolution;
private final float scaleFactor; private final float scaleWidth;
private final float scaleHeight;
private GlShaderProgram.InputListener inputListener; private GlShaderProgram.InputListener inputListener;
private GlShaderProgram.OutputListener outputListener; private GlShaderProgram.OutputListener outputListener;
private GlShaderProgram.ErrorListener errorListener; private GlShaderProgram.ErrorListener errorListener;
private Executor errorListenerExecutor; private Executor errorListenerExecutor;
private Size outputSize;
private Size lastInputSize;
private Size intermediateSize;
private GlTextureInfo outputTexture;
private boolean outputTextureInUse; private boolean outputTextureInUse;
private GlTextureInfo outputTexture;
private GlTextureInfo intermediateTexture; private GlTextureInfo intermediateTexture;
private GlTextureInfo functionLutTexture; // Values for the function LUT as a texture. private GlTextureInfo functionLutTexture; // Values for the function LUT as a texture.
private float functionLutTexelStep; private float functionLutTexelStep;
private float functionLutCenterX; private float functionLutCenterX;
private float functionLutDomainStart; private float functionLutDomainStart;
private float functionLutWidth; private float functionLutWidth;
private Size outputSize;
private Size lastInputSize;
private Size intermediateSize;
private @MonotonicNonNull ConvolutionFunction1D lastConvolutionFunction; private @MonotonicNonNull ConvolutionFunction1D lastConvolutionFunction;
/** /**
@ -97,20 +96,30 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be * @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be
* in linear RGB BT.2020. If {@code false}, colors will be in linear RGB BT.709. * in linear RGB BT.2020. If {@code false}, colors will be in linear RGB BT.709.
* @param convolution The {@link SeparableConvolution} to apply in each direction. * @param convolution The {@link SeparableConvolution} to apply in each direction.
* @param scaleFactor The scaling factor used to determine the size of the output relative to the * @param scaleWidth The scaling factor used to determine the width of the output relative to the
* input. The aspect ratio remains constant. * input.
* @param scaleHeight The scaling factor used to determine the height of the output relative to
* the input.
* @throws VideoFrameProcessingException If a problem occurs while reading shader files. * @throws VideoFrameProcessingException If a problem occurs while reading shader files.
*/ */
public SeparableConvolutionShaderProgram( public SeparableConvolutionShaderProgram(
Context context, boolean useHdr, SeparableConvolution convolution, float scaleFactor) Context context,
boolean useHdr,
SeparableConvolution convolution,
float scaleWidth,
float scaleHeight)
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
this.useHdr = useHdr; this.useHdr = useHdr;
this.convolution = convolution; this.convolution = convolution;
this.scaleFactor = scaleFactor; this.scaleWidth = scaleWidth;
this.scaleHeight = scaleHeight;
inputListener = new InputListener() {}; inputListener = new InputListener() {};
outputListener = new OutputListener() {}; outputListener = new OutputListener() {};
errorListener = (frameProcessingException) -> {}; errorListener = (frameProcessingException) -> {};
errorListenerExecutor = MoreExecutors.directExecutor(); errorListenerExecutor = MoreExecutors.directExecutor();
functionLutTexture = GlTextureInfo.UNSET;
intermediateTexture = GlTextureInfo.UNSET;
outputTexture = GlTextureInfo.UNSET;
lastInputSize = Size.ZERO; lastInputSize = Size.ZERO;
intermediateSize = Size.ZERO; intermediateSize = Size.ZERO;
outputSize = Size.ZERO; outputSize = Size.ZERO;
@ -118,25 +127,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
try { try {
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
sharpTransformGlProgram =
new GlProgram(
context,
"shaders/vertex_shader_transformation_es2.glsl",
"shaders/fragment_shader_copy_es2.glsl");
} catch (IOException | GlUtil.GlException e) { } catch (IOException | GlUtil.GlException e) {
throw new VideoFrameProcessingException(e); throw new VideoFrameProcessingException(e);
} }
Matrix sharpTransformMatrix = new Matrix();
sharpTransformMatrix.setScale(/* sx= */ .5f, /* sy= */ 1f);
sharpTransformMatrixValues = getGlMatrixArray(sharpTransformMatrix);
functionLutTexture = GlTextureInfo.UNSET;
intermediateTexture = GlTextureInfo.UNSET;
outputTexture = GlTextureInfo.UNSET;
} }
@Override @Override
public void setInputListener(InputListener inputListener) { public final void setInputListener(InputListener inputListener) {
this.inputListener = inputListener; this.inputListener = inputListener;
if (!outputTextureInUse) { if (!outputTextureInUse) {
inputListener.onReadyToAcceptInputFrame(); inputListener.onReadyToAcceptInputFrame();
@ -144,18 +141,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@Override @Override
public void setOutputListener(OutputListener outputListener) { public final void setOutputListener(OutputListener outputListener) {
this.outputListener = outputListener; this.outputListener = outputListener;
} }
@Override @Override
public void setErrorListener(Executor errorListenerExecutor, ErrorListener errorListener) { public final void setErrorListener(Executor errorListenerExecutor, ErrorListener errorListener) {
this.errorListenerExecutor = errorListenerExecutor; this.errorListenerExecutor = errorListenerExecutor;
this.errorListener = errorListener; this.errorListener = errorListener;
} }
@Override @Override
public void queueInputFrame( public final void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) { GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
Assertions.checkState( Assertions.checkState(
!outputTextureInUse, !outputTextureInUse,
@ -167,17 +164,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputTextureInUse = true; outputTextureInUse = true;
renderHorizontal(inputTexture); renderHorizontal(inputTexture);
renderVertical(); renderVertical();
float[] identityMatrix = GlUtil.create4x4IdentityMatrix();
sharpTransformGlProgram.use(); onBlurRendered(inputTexture);
sharpTransformGlProgram.setSamplerTexIdUniform(
"uTexSampler", inputTexture.texId, /* texUnitIndex= */ 0);
sharpTransformGlProgram.setFloatsUniform("uTexTransformationMatrix", identityMatrix);
sharpTransformGlProgram.setFloatsUniform("uTransformationMatrix", sharpTransformMatrixValues);
sharpTransformGlProgram.setBufferAttribute(
"aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
sharpTransformGlProgram.bindAttributesAndUniforms();
// The four-vertex triangle strip forms a quad. // The four-vertex triangle strip forms a quad.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* i1= */ 0, /* i2= */ 4); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* i1= */ 0, /* i2= */ 4);
@ -191,36 +179,48 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@Override @Override
public void releaseOutputFrame(GlTextureInfo outputTexture) { public final void releaseOutputFrame(GlTextureInfo outputTexture) {
outputTextureInUse = false; outputTextureInUse = false;
inputListener.onReadyToAcceptInputFrame(); inputListener.onReadyToAcceptInputFrame();
} }
@Override @Override
public void signalEndOfCurrentInputStream() { public final void signalEndOfCurrentInputStream() {
outputListener.onCurrentOutputStreamEnded(); outputListener.onCurrentOutputStreamEnded();
} }
@Override @Override
public void flush() { public final void flush() {
outputTextureInUse = false; outputTextureInUse = false;
inputListener.onFlush(); inputListener.onFlush();
inputListener.onReadyToAcceptInputFrame(); inputListener.onReadyToAcceptInputFrame();
} }
@Override @Override
@CallSuper
public void release() throws VideoFrameProcessingException { public void release() throws VideoFrameProcessingException {
try { try {
outputTexture.release(); outputTexture.release();
intermediateTexture.release(); intermediateTexture.release();
functionLutTexture.release(); functionLutTexture.release();
glProgram.delete(); glProgram.delete();
sharpTransformGlProgram.delete();
} catch (GlUtil.GlException e) { } catch (GlUtil.GlException e) {
throw new VideoFrameProcessingException(e); throw new VideoFrameProcessingException(e);
} }
} }
/**
* Called when the blur has been rendered onto the frame.
*
* <p>The default implementation is a no-op.
*
* @param inputTexture The input texture.
* @throws GlUtil.GlException If an error occurs.
*/
protected void onBlurRendered(GlTextureInfo inputTexture) throws GlUtil.GlException {
// Do nothing.
}
private void renderOnePass(int inputTexId, boolean isHorizontal) throws GlUtil.GlException { private void renderOnePass(int inputTexId, boolean isHorizontal) throws GlUtil.GlException {
int size = isHorizontal ? lastInputSize.getWidth() : intermediateSize.getHeight(); int size = isHorizontal ? lastInputSize.getWidth() : intermediateSize.getHeight();
glProgram.use(); glProgram.use();
@ -252,8 +252,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
glProgram.setFloatsUniform("uTexTransformationMatrix", identityMatrix); glProgram.setFloatsUniform("uTexTransformationMatrix", identityMatrix);
return new Size( return new Size(
(int) (inputSize.getWidth() * scaleFactor * 2), (int) (inputSize.getWidth() * scaleWidth), (int) (inputSize.getHeight() * scaleHeight));
(int) (inputSize.getHeight() * scaleFactor));
} }
private void renderHorizontal(GlTextureInfo inputTexture) throws GlUtil.GlException { private void renderHorizontal(GlTextureInfo inputTexture) throws GlUtil.GlException {

View file

@ -0,0 +1,105 @@
/*
* Copyright 2024 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 static androidx.media3.effect.MatrixUtils.getGlMatrixArray;
import android.content.Context;
import android.graphics.Matrix;
import androidx.annotation.RequiresApi;
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;
/**
* An extension of {@link SeparableConvolutionShaderProgram} that draws the sharp version of the
* input frame on top of the output convolution.
*/
@UnstableApi
@RequiresApi(26) // See SeparableConvolutionShaderProgram.
/* package */ final class SharpSeparableConvolutionShaderProgram
extends SeparableConvolutionShaderProgram {
private final GlProgram sharpTransformGlProgram;
private final float[] sharpTransformMatrixValues;
/**
* Creates an instance.
*
* @param context The {@link Context}.
* @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be
* in linear RGB BT.2020. If {@code false}, colors will be in linear RGB BT.709.
* @param convolution The {@link SeparableConvolution} to apply in each direction.
* @param scaleSharpX The scaling factor used to determine the size of the sharp image in the
* output frame relative to the whole output frame in the horizontal direction.
* @param scaleSharpY The scaling factor used to determine the size of the sharp image in the
* output frame relative to the whole output frame in the vertical direction.
* @throws VideoFrameProcessingException If a problem occurs while reading shader files.
*/
public SharpSeparableConvolutionShaderProgram(
Context context,
boolean useHdr,
SeparableConvolution convolution,
float scaleSharpX,
float scaleSharpY)
throws VideoFrameProcessingException {
super(
context,
useHdr,
convolution,
/* scaleWidth= */ 1 / scaleSharpX,
/* scaleHeight= */ 1 / scaleSharpY);
try {
sharpTransformGlProgram =
new GlProgram(
context,
"shaders/vertex_shader_transformation_es2.glsl",
"shaders/fragment_shader_copy_es2.glsl");
} catch (IOException | GlUtil.GlException e) {
throw new VideoFrameProcessingException(e);
}
Matrix sharpTransformMatrix = new Matrix();
sharpTransformMatrix.setScale(scaleSharpX, scaleSharpY);
sharpTransformMatrixValues = getGlMatrixArray(sharpTransformMatrix);
}
@Override
protected void onBlurRendered(GlTextureInfo inputTexture) throws GlUtil.GlException {
float[] identityMatrix = GlUtil.create4x4IdentityMatrix();
sharpTransformGlProgram.use();
sharpTransformGlProgram.setSamplerTexIdUniform(
"uTexSampler", inputTexture.texId, /* texUnitIndex= */ 0);
sharpTransformGlProgram.setFloatsUniform("uTexTransformationMatrix", identityMatrix);
sharpTransformGlProgram.setFloatsUniform("uTransformationMatrix", sharpTransformMatrixValues);
sharpTransformGlProgram.setBufferAttribute(
"aFramePosition",
GlUtil.getNormalizedCoordinateBounds(),
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
sharpTransformGlProgram.bindAttributesAndUniforms();
}
@Override
public void release() throws VideoFrameProcessingException {
super.release();
try {
sharpTransformGlProgram.delete();
} catch (GlUtil.GlException e) {
throw new VideoFrameProcessingException(e);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB