diff --git a/libraries/effect/src/androidTest/java/androidx/media3/effect/GaussianBlurTest.java b/libraries/effect/src/androidTest/java/androidx/media3/effect/GaussianBlurTest.java index bb2cd74f1f..c967e0f225 100644 --- a/libraries/effect/src/androidTest/java/androidx/media3/effect/GaussianBlurTest.java +++ b/libraries/effect/src/androidTest/java/androidx/media3/effect/GaussianBlurTest.java @@ -99,4 +99,28 @@ public class GaussianBlurTest { assertThat(actualPresentationTimesUs).containsExactly(32_000L); getAndAssertOutputBitmaps(textureBitmapReader, actualPresentationTimesUs, testId, ASSET_PATH); } + + @Test + @RequiresNonNull({"textureBitmapReader", "testId"}) + public void gaussianBlur_sigmaChangesWithTime_differentFramesHaveDifferentBlurs() + throws Exception { + ImmutableList frameTimesUs = ImmutableList.of(32_000L, 71_000L); + ImmutableList 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); + } } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/GaussianBlur.java b/libraries/effect/src/main/java/androidx/media3/effect/GaussianBlur.java index c7a699dba0..e4c2e06939 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/GaussianBlur.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/GaussianBlur.java @@ -51,11 +51,10 @@ public final class GaussianBlur extends SeparableConvolution { */ public GaussianBlur(float sigma) { this(sigma, /* numStandardDeviations= */ 2.0f); - ; } @Override - public ConvolutionFunction1D getConvolution() { + public ConvolutionFunction1D getConvolution(long presentationTimeUs) { return new GaussianFunction(sigma, numStandardDeviations); } } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/GaussianFunction.java b/libraries/effect/src/main/java/androidx/media3/effect/GaussianFunction.java index 5eeec38439..0cffa35c0d 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/GaussianFunction.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/GaussianFunction.java @@ -21,7 +21,9 @@ import static java.lang.Math.exp; import static java.lang.Math.sqrt; import androidx.annotation.FloatRange; +import androidx.annotation.Nullable; import androidx.media3.common.util.UnstableApi; +import java.util.Objects; /** * Implementation of a symmetric Gaussian function with a limited domain. @@ -68,4 +70,21 @@ public final class GaussianFunction implements ConvolutionFunction1D { return (float) (exp(-samplePositionOverSigma * samplePositionOverSigma / 2) / sqrt(2 * PI) / sigma); } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof GaussianFunction)) { + return false; + } + GaussianFunction that = (GaussianFunction) o; + return Float.compare(that.sigma, sigma) == 0 && Float.compare(that.numStdDev, numStdDev) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(sigma, numStdDev); + } } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/SeparableConvolution.java b/libraries/effect/src/main/java/androidx/media3/effect/SeparableConvolution.java index 1fd760bd96..2cf51f5db6 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/SeparableConvolution.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/SeparableConvolution.java @@ -46,8 +46,12 @@ public abstract class SeparableConvolution implements GlEffect { this.scaleFactor = scaleFactor; } - /** Returns a {@linkplain ConvolutionFunction1D 1D convolution function}. */ - public abstract ConvolutionFunction1D getConvolution(); + /** + * Returns a {@linkplain ConvolutionFunction1D 1D convolution function}. + * + * @param presentationTimeUs The presentation timestamp of the input frame, in microseconds. + */ + public abstract ConvolutionFunction1D getConvolution(long presentationTimeUs); @Override public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr) diff --git a/libraries/effect/src/main/java/androidx/media3/effect/SeparableConvolutionShaderProgram.java b/libraries/effect/src/main/java/androidx/media3/effect/SeparableConvolutionShaderProgram.java index 70ace03a9c..1b2b7aeab9 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/SeparableConvolutionShaderProgram.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/SeparableConvolutionShaderProgram.java @@ -34,6 +34,7 @@ import com.google.common.util.concurrent.MoreExecutors; import java.io.IOException; import java.nio.ShortBuffer; import java.util.concurrent.Executor; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * A {@link GlShaderProgram} for performing separable convolutions. @@ -87,6 +88,7 @@ import java.util.concurrent.Executor; private float functionLutCenterX; private float functionLutDomainStart; private float functionLutWidth; + private @MonotonicNonNull ConvolutionFunction1D lastConvolutionFunction; /** * Creates an instance. @@ -108,11 +110,12 @@ import java.util.concurrent.Executor; inputListener = new InputListener() {}; outputListener = new OutputListener() {}; errorListener = (frameProcessingException) -> {}; - errorListenerExecutor = MoreExecutors.directExecutor(); lastInputSize = Size.ZERO; intermediateSize = Size.ZERO; outputSize = Size.ZERO; + lastConvolutionFunction = null; + try { glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); sharpTransformGlProgram = @@ -160,7 +163,7 @@ import java.util.concurrent.Executor; + " first."); try { ensureTexturesAreConfigured( - glObjectsProvider, new Size(inputTexture.width, inputTexture.height)); + glObjectsProvider, new Size(inputTexture.width, inputTexture.height), presentationTimeUs); outputTextureInUse = true; renderHorizontal(inputTexture); renderVertical(); @@ -269,10 +272,15 @@ import java.util.concurrent.Executor; renderOnePass(intermediateTexture.texId, /* isHorizontal= */ false); } - private void ensureTexturesAreConfigured(GlObjectsProvider glObjectsProvider, Size inputSize) + private void ensureTexturesAreConfigured( + GlObjectsProvider glObjectsProvider, Size inputSize, long presentationTimeUs) throws GlUtil.GlException { - // Always update the function texture, as it could change on each render cycle. - updateFunctionTexture(glObjectsProvider); + ConvolutionFunction1D currentConvolutionFunction = + convolution.getConvolution(presentationTimeUs); + if (!currentConvolutionFunction.equals(lastConvolutionFunction)) { + updateFunctionTexture(glObjectsProvider, currentConvolutionFunction); + lastConvolutionFunction = currentConvolutionFunction; + } // Only update intermediate and output textures if the size changes. if (inputSize.equals(lastInputSize)) { @@ -295,11 +303,10 @@ import java.util.concurrent.Executor; * Creates a function lookup table for the convolution, and stores it in a 16b floating point * texture for GPU access. */ - private void updateFunctionTexture(GlObjectsProvider glObjectsProvider) + private void updateFunctionTexture( + GlObjectsProvider glObjectsProvider, ConvolutionFunction1D convolutionFunction) throws GlUtil.GlException { - ConvolutionFunction1D convolutionFunction = convolution.getConvolution(); - int lutRasterSize = (int) Math.ceil( diff --git a/libraries/test_data/src/test/assets/media/bitmap/GaussianBlurTest/pts_71000.png b/libraries/test_data/src/test/assets/media/bitmap/GaussianBlurTest/pts_71000.png new file mode 100644 index 0000000000..fbcd00b885 Binary files /dev/null and b/libraries/test_data/src/test/assets/media/bitmap/GaussianBlurTest/pts_71000.png differ