From d4db33a5357f14ae170ab8bfddc367d884f3f64a Mon Sep 17 00:00:00 2001 From: kimvde Date: Wed, 18 Jan 2023 14:54:00 +0000 Subject: [PATCH] Avoid re-encoding if video effects are no-op This is to avoid regressions introduced by removing the convenience methods from TransformationRequest. PiperOrigin-RevId: 502864512 --- .../effect/ScaleToFitTransformation.java | 10 +++++ .../transformer/TransformerInternal.java | 39 ++++++++++++++----- .../transformer/TransformerEndToEndTest.java | 20 ++++++++++ 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/libraries/effect/src/main/java/androidx/media3/effect/ScaleToFitTransformation.java b/libraries/effect/src/main/java/androidx/media3/effect/ScaleToFitTransformation.java index c72a188dce..15d879115f 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/ScaleToFitTransformation.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/ScaleToFitTransformation.java @@ -92,6 +92,13 @@ public final class ScaleToFitTransformation implements MatrixTransformation { } } + /** The multiplier by which the frame will scale horizontally, along the x-axis. */ + public final float scaleX; + /** The multiplier by which the frame will scale vertically, along the y-axis. */ + public final float scaleY; + /** How much to rotate the frame counterclockwise, in degrees. */ + public final float rotationDegrees; + private final Matrix transformationMatrix; private @MonotonicNonNull Matrix adjustedTransformationMatrix; @@ -103,6 +110,9 @@ public final class ScaleToFitTransformation implements MatrixTransformation { * @param rotationDegrees How much to rotate the frame counterclockwise, in degrees. */ private ScaleToFitTransformation(float scaleX, float scaleY, float rotationDegrees) { + this.scaleX = scaleX; + this.scaleY = scaleY; + this.rotationDegrees = rotationDegrees; transformationMatrix = new Matrix(); transformationMatrix.postScale(scaleX, scaleY); transformationMatrix.postRotate(rotationDegrees); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java index fcf161070a..215317d711 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java @@ -43,6 +43,9 @@ import androidx.media3.common.audio.AudioProcessor; import androidx.media3.common.util.Clock; import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.HandlerWrapper; +import androidx.media3.common.util.Size; +import androidx.media3.effect.Presentation; +import androidx.media3.effect.ScaleToFitTransformation; import androidx.media3.extractor.metadata.mp4.SlowMotionData; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -577,16 +580,34 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; if (inputFormat.pixelWidthHeightRatio != 1f) { return true; } - // The decoder rotates encoded frames for display by inputFormat.rotationDegrees. - int decodedHeight = - (inputFormat.rotationDegrees % 180 == 0) ? inputFormat.height : inputFormat.width; - if (transformationRequest.outputHeight != C.LENGTH_UNSET - && transformationRequest.outputHeight != decodedHeight) { - return true; - } - if (!videoEffects.isEmpty()) { - return true; + + // TODO(b/265927935): consider generalizing this logic. + for (int i = 0; i < videoEffects.size(); i++) { + Effect videoEffect = videoEffects.get(i); + if (videoEffect instanceof Presentation) { + Presentation presentation = (Presentation) videoEffect; + // The decoder rotates encoded frames for display by inputFormat.rotationDegrees. + int decodedWidth = + (inputFormat.rotationDegrees % 180 == 0) ? inputFormat.width : inputFormat.height; + int decodedHeight = + (inputFormat.rotationDegrees % 180 == 0) ? inputFormat.height : inputFormat.width; + Size outputSize = presentation.configure(decodedWidth, decodedHeight); + if (outputSize.getWidth() != decodedWidth || outputSize.getHeight() != decodedHeight) { + return true; + } + } else if (videoEffect instanceof ScaleToFitTransformation) { + ScaleToFitTransformation scaleToFitTransformation = + (ScaleToFitTransformation) videoEffect; + if (scaleToFitTransformation.scaleX != 1f + || scaleToFitTransformation.scaleY != 1f + || scaleToFitTransformation.rotationDegrees != 0f) { + return true; + } + } else { + return true; + } } + return false; } } diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java index 6324ee38f9..3f88e8d4a0 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java @@ -43,11 +43,14 @@ import android.os.ParcelFileDescriptor; import android.view.Surface; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.Effect; import androidx.media3.common.Format; import androidx.media3.common.MediaItem; import androidx.media3.common.MimeTypes; import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.common.util.Util; +import androidx.media3.effect.Presentation; +import androidx.media3.effect.ScaleToFitTransformation; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.extractor.DefaultExtractorsFactory; @@ -728,6 +731,23 @@ public final class TransformerEndToEndTest { assertThat(transformationException).hasCauseThat().isInstanceOf(IllegalStateException.class); } + @Test + public void startTransformation_withNoOpEffects_transmuxes() throws Exception { + MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ONLY); + int mediaItemHeightPixels = 720; + List videoEffects = new ArrayList<>(); + videoEffects.add(Presentation.createForHeight(mediaItemHeightPixels)); + videoEffects.add(new ScaleToFitTransformation.Builder().build()); + Transformer transformer = + createTransformerBuilder(/* enableFallback= */ false).setVideoEffects(videoEffects).build(); + + transformer.startTransformation(mediaItem, outputPath); + TransformerTestRunner.runLooper(transformer); + + // Video transcoding in unit tests is not supported. + DumpFileAsserts.assertOutput(context, testMuxer, getDumpFileName(FILE_VIDEO_ONLY)); + } + @Test public void getProgress_knownDuration_returnsConsistentStates() throws Exception { Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();