From a043c8c2047c150bd433c398a25c7a4fa7d63ae2 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/library/effect/src/main/java/com/google/android/exoplayer2/effect/ScaleToFitTransformation.java b/library/effect/src/main/java/com/google/android/exoplayer2/effect/ScaleToFitTransformation.java index 350f2fd20e..e62892acfc 100644 --- a/library/effect/src/main/java/com/google/android/exoplayer2/effect/ScaleToFitTransformation.java +++ b/library/effect/src/main/java/com/google/android/exoplayer2/effect/ScaleToFitTransformation.java @@ -90,6 +90,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; @@ -101,6 +108,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/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java index 9abfd9ba03..401c5a5556 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerInternal.java @@ -35,6 +35,8 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.audio.AudioProcessor; +import com.google.android.exoplayer2.effect.Presentation; +import com.google.android.exoplayer2.effect.ScaleToFitTransformation; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.mp4.SlowMotionData; import com.google.android.exoplayer2.util.Clock; @@ -44,6 +46,7 @@ import com.google.android.exoplayer2.util.Effect; import com.google.android.exoplayer2.util.FrameProcessor; import com.google.android.exoplayer2.util.HandlerWrapper; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Size; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.lang.annotation.Documented; @@ -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/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java index ef3c0edb12..d15b0ee707 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java @@ -48,6 +48,8 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.audio.SonicAudioProcessor; +import com.google.android.exoplayer2.effect.Presentation; +import com.google.android.exoplayer2.effect.ScaleToFitTransformation; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; @@ -58,6 +60,7 @@ import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.testutil.DumpFileAsserts; import com.google.android.exoplayer2.testutil.FakeClock; +import com.google.android.exoplayer2.util.Effect; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; @@ -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();