Effect: Create GlEffect.isNoOp to simplify Transformer.

This will also allow us to skip transcodes in the future for other effects, like
cropping, color effects, etc.

PiperOrigin-RevId: 507765618
This commit is contained in:
huangdarwin 2023-02-07 14:43:44 +00:00 committed by microkatz
parent c434cc0c9f
commit 0c0c972ea0
8 changed files with 89 additions and 39 deletions

View File

@ -93,4 +93,12 @@ public final class Crop implements MatrixTransformation {
public Matrix getMatrix(long presentationTimeUs) { public Matrix getMatrix(long presentationTimeUs) {
return checkStateNotNull(transformationMatrix, "configure must be called first"); return checkStateNotNull(transformationMatrix, "configure must be called first");
} }
@Override
public boolean isNoOp(int inputWidth, int inputHeight) {
Size outputSize = configure(inputWidth, inputHeight);
return checkStateNotNull(transformationMatrix).isIdentity()
&& inputWidth == outputSize.getWidth()
&& inputHeight == outputSize.getHeight();
}
} }

View File

@ -40,4 +40,19 @@ public interface GlEffect extends Effect {
*/ */
GlShaderProgram toGlShaderProgram(Context context, boolean useHdr) GlShaderProgram toGlShaderProgram(Context context, boolean useHdr)
throws FrameProcessingException; throws FrameProcessingException;
/**
* Returns whether a {@link GlEffect} applies no change at every timestamp.
*
* <p>This can be used as a hint to skip this instance.
*
* @param inputWidth The input frame width, in pixels.
* @param inputHeight The input frame height, in pixels.
*/
default boolean isNoOp(int inputWidth, int inputHeight) {
// TODO(b/265927935): Generalize this logic by implementing this method on all
// subclasses, and deleting the default implementation here. Otherwise, some no-op effects may
// not be properly detected or handled.
return false;
}
} }

View File

@ -214,6 +214,14 @@ public final class Presentation implements MatrixTransformation {
return checkStateNotNull(transformationMatrix, "configure must be called first"); return checkStateNotNull(transformationMatrix, "configure must be called first");
} }
@Override
public boolean isNoOp(int inputWidth, int inputHeight) {
configure(inputWidth, inputHeight);
return checkStateNotNull(transformationMatrix).isIdentity()
&& inputWidth == Math.round(outputWidth)
&& inputHeight == Math.round(outputHeight);
}
@RequiresNonNull("transformationMatrix") @RequiresNonNull("transformationMatrix")
private void applyAspectRatio() { private void applyAspectRatio() {
float inputAspectRatio = outputWidth / outputHeight; float inputAspectRatio = outputWidth / outputHeight;

View File

@ -92,13 +92,6 @@ 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 final Matrix transformationMatrix;
private @MonotonicNonNull Matrix adjustedTransformationMatrix; private @MonotonicNonNull Matrix adjustedTransformationMatrix;
@ -110,9 +103,6 @@ public final class ScaleToFitTransformation implements MatrixTransformation {
* @param rotationDegrees How much to rotate the frame counterclockwise, in degrees. * @param rotationDegrees How much to rotate the frame counterclockwise, in degrees.
*/ */
private ScaleToFitTransformation(float scaleX, float scaleY, float rotationDegrees) { private ScaleToFitTransformation(float scaleX, float scaleY, float rotationDegrees) {
this.scaleX = scaleX;
this.scaleY = scaleY;
this.rotationDegrees = rotationDegrees;
transformationMatrix = new Matrix(); transformationMatrix = new Matrix();
transformationMatrix.postScale(scaleX, scaleY); transformationMatrix.postScale(scaleX, scaleY);
transformationMatrix.postRotate(rotationDegrees); transformationMatrix.postRotate(rotationDegrees);
@ -162,4 +152,12 @@ public final class ScaleToFitTransformation implements MatrixTransformation {
public Matrix getMatrix(long presentationTimeUs) { public Matrix getMatrix(long presentationTimeUs) {
return checkStateNotNull(adjustedTransformationMatrix, "configure must be called first"); return checkStateNotNull(adjustedTransformationMatrix, "configure must be called first");
} }
@Override
public boolean isNoOp(int inputWidth, int inputHeight) {
Size outputSize = configure(inputWidth, inputHeight);
return checkStateNotNull(adjustedTransformationMatrix).isIdentity()
&& inputWidth == outputSize.getWidth()
&& inputHeight == outputSize.getHeight();
}
} }

View File

@ -37,7 +37,9 @@ public final class CropTest {
Crop crop = new Crop(/* left= */ -1, /* right= */ 1, /* bottom= */ -1, /* top= */ 1); Crop crop = new Crop(/* left= */ -1, /* right= */ 1, /* bottom= */ -1, /* top= */ 1);
Size outputSize = crop.configure(inputWidth, inputHeight); Size outputSize = crop.configure(inputWidth, inputHeight);
boolean isNoOp = crop.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isTrue();
assertThat(outputSize.getWidth()).isEqualTo(inputWidth); assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@ -53,7 +55,9 @@ public final class CropTest {
Crop crop = new Crop(left, right, bottom, top); Crop crop = new Crop(left, right, bottom, top);
Size outputSize = crop.configure(inputWidth, inputHeight); Size outputSize = crop.configure(inputWidth, inputHeight);
boolean isNoOp = crop.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isFalse();
int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC); int expectedPostCropWidth = Math.round(inputWidth * (right - left) / GlUtil.LENGTH_NDC);
int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC); int expectedPostCropHeight = Math.round(inputHeight * (top - bottom) / GlUtil.LENGTH_NDC);
assertThat(outputSize.getWidth()).isEqualTo(expectedPostCropWidth); assertThat(outputSize.getWidth()).isEqualTo(expectedPostCropWidth);

View File

@ -37,7 +37,9 @@ public final class PresentationTest {
Presentation presentation = Presentation.createForHeight(C.LENGTH_UNSET); Presentation presentation = Presentation.createForHeight(C.LENGTH_UNSET);
Size outputSize = presentation.configure(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
boolean isNoOp = presentation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isTrue();
assertThat(outputSize.getWidth()).isEqualTo(inputWidth); assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@ -50,7 +52,9 @@ public final class PresentationTest {
Presentation presentation = Presentation.createForHeight(requestedHeight); Presentation presentation = Presentation.createForHeight(requestedHeight);
Size outputSize = presentation.configure(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
boolean isNoOp = presentation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isFalse();
assertThat(outputSize.getWidth()).isEqualTo(requestedHeight * inputWidth / inputHeight); assertThat(outputSize.getWidth()).isEqualTo(requestedHeight * inputWidth / inputHeight);
assertThat(outputSize.getHeight()).isEqualTo(requestedHeight); assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
} }
@ -64,7 +68,9 @@ public final class PresentationTest {
Presentation.createForAspectRatio(aspectRatio, Presentation.LAYOUT_SCALE_TO_FIT); Presentation.createForAspectRatio(aspectRatio, Presentation.LAYOUT_SCALE_TO_FIT);
Size outputSize = presentation.configure(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
boolean isNoOp = presentation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isFalse();
assertThat(outputSize.getWidth()).isEqualTo(Math.round(aspectRatio * inputHeight)); assertThat(outputSize.getWidth()).isEqualTo(Math.round(aspectRatio * inputHeight));
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@ -80,7 +86,9 @@ public final class PresentationTest {
requestedWidth, requestedHeight, Presentation.LAYOUT_SCALE_TO_FIT); requestedWidth, requestedHeight, Presentation.LAYOUT_SCALE_TO_FIT);
Size outputSize = presentation.configure(inputWidth, inputHeight); Size outputSize = presentation.configure(inputWidth, inputHeight);
boolean isNoOp = presentation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isFalse();
assertThat(outputSize.getWidth()).isEqualTo(requestedWidth); assertThat(outputSize.getWidth()).isEqualTo(requestedWidth);
assertThat(outputSize.getHeight()).isEqualTo(requestedHeight); assertThat(outputSize.getHeight()).isEqualTo(requestedHeight);
} }

View File

@ -39,7 +39,9 @@ public final class ScaleToFitTransformationTest {
new ScaleToFitTransformation.Builder().build(); new ScaleToFitTransformation.Builder().build();
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
boolean isNoOp = scaleToFitTransformation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isTrue();
assertThat(outputSize.getWidth()).isEqualTo(inputWidth); assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@ -54,7 +56,9 @@ public final class ScaleToFitTransformationTest {
.build(); .build();
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
boolean isNoOp = scaleToFitTransformation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isFalse();
assertThat(outputSize.getWidth()).isEqualTo(Math.round(inputWidth * .5f)); assertThat(outputSize.getWidth()).isEqualTo(Math.round(inputWidth * .5f));
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@ -67,7 +71,9 @@ public final class ScaleToFitTransformationTest {
new ScaleToFitTransformation.Builder().setScale(/* scaleX= */ 2f, /* scaleY= */ 1f).build(); new ScaleToFitTransformation.Builder().setScale(/* scaleX= */ 2f, /* scaleY= */ 1f).build();
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
boolean isNoOp = scaleToFitTransformation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isFalse();
assertThat(outputSize.getWidth()).isEqualTo(inputWidth * 2); assertThat(outputSize.getWidth()).isEqualTo(inputWidth * 2);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight); assertThat(outputSize.getHeight()).isEqualTo(inputHeight);
} }
@ -80,7 +86,9 @@ public final class ScaleToFitTransformationTest {
new ScaleToFitTransformation.Builder().setScale(/* scaleX= */ 1f, /* scaleY= */ 2f).build(); new ScaleToFitTransformation.Builder().setScale(/* scaleX= */ 1f, /* scaleY= */ 2f).build();
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
boolean isNoOp = scaleToFitTransformation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isFalse();
assertThat(outputSize.getWidth()).isEqualTo(inputWidth); assertThat(outputSize.getWidth()).isEqualTo(inputWidth);
assertThat(outputSize.getHeight()).isEqualTo(inputHeight * 2); assertThat(outputSize.getHeight()).isEqualTo(inputHeight * 2);
} }
@ -93,7 +101,9 @@ public final class ScaleToFitTransformationTest {
new ScaleToFitTransformation.Builder().setRotationDegrees(90).build(); new ScaleToFitTransformation.Builder().setRotationDegrees(90).build();
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
boolean isNoOp = scaleToFitTransformation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isFalse();
assertThat(outputSize.getWidth()).isEqualTo(inputHeight); assertThat(outputSize.getWidth()).isEqualTo(inputHeight);
assertThat(outputSize.getHeight()).isEqualTo(inputWidth); assertThat(outputSize.getHeight()).isEqualTo(inputWidth);
} }
@ -107,7 +117,9 @@ public final class ScaleToFitTransformationTest {
long expectedOutputWidthHeight = 247; long expectedOutputWidthHeight = 247;
Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight); Size outputSize = scaleToFitTransformation.configure(inputWidth, inputHeight);
boolean isNoOp = scaleToFitTransformation.isNoOp(inputWidth, inputHeight);
assertThat(isNoOp).isFalse();
assertThat(outputSize.getWidth()).isEqualTo(expectedOutputWidthHeight); assertThat(outputSize.getWidth()).isEqualTo(expectedOutputWidthHeight);
assertThat(outputSize.getHeight()).isEqualTo(expectedOutputWidthHeight); assertThat(outputSize.getHeight()).isEqualTo(expectedOutputWidthHeight);
} }

View File

@ -39,9 +39,7 @@ import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.Size; import androidx.media3.effect.GlEffect;
import androidx.media3.effect.Presentation;
import androidx.media3.effect.ScaleToFitTransformation;
import androidx.media3.extractor.metadata.mp4.SlowMotionData; import androidx.media3.extractor.metadata.mp4.SlowMotionData;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
@ -565,35 +563,34 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (inputFormat.pixelWidthHeightRatio != 1f) { if (inputFormat.pixelWidthHeightRatio != 1f) {
return true; return true;
} }
if (!areVideoEffectsAllNoOp(firstEditedMediaItem.effects.videoEffects, inputFormat)) {
return true;
}
return false;
}
// TODO(b/265927935): consider generalizing this logic. /**
for (int i = 0; i < firstEditedMediaItem.effects.videoEffects.size(); i++) { * Returns whether all {@code videoEffects} are {@linkplain GlEffect#isNoOp(int, int) no-ops},
Effect videoEffect = firstEditedMediaItem.effects.videoEffects.get(i); * given an input {@link Format}.
if (videoEffect instanceof Presentation) { */
Presentation presentation = (Presentation) videoEffect; private boolean areVideoEffectsAllNoOp(ImmutableList<Effect> videoEffects, Format inputFormat) {
// The decoder rotates encoded frames for display by inputFormat.rotationDegrees.
int decodedWidth = int decodedWidth =
(inputFormat.rotationDegrees % 180 == 0) ? inputFormat.width : inputFormat.height; (inputFormat.rotationDegrees % 180 == 0) ? inputFormat.width : inputFormat.height;
int decodedHeight = int decodedHeight =
(inputFormat.rotationDegrees % 180 == 0) ? inputFormat.height : inputFormat.width; (inputFormat.rotationDegrees % 180 == 0) ? inputFormat.height : inputFormat.width;
Size outputSize = presentation.configure(decodedWidth, decodedHeight); for (int i = 0; i < videoEffects.size(); i++) {
if (outputSize.getWidth() != decodedWidth || outputSize.getHeight() != decodedHeight) { Effect videoEffect = videoEffects.get(i);
return true; if (!(videoEffect instanceof GlEffect)) {
// We cannot confirm whether Effect instances that are not GlEffect instances are
// no-ops.
return false;
} }
} else if (videoEffect instanceof ScaleToFitTransformation) { GlEffect glEffect = (GlEffect) videoEffect;
ScaleToFitTransformation scaleToFitTransformation = if (!glEffect.isNoOp(decodedWidth, decodedHeight)) {
(ScaleToFitTransformation) videoEffect;
if (scaleToFitTransformation.scaleX != 1f
|| scaleToFitTransformation.scaleY != 1f
|| scaleToFitTransformation.rotationDegrees != 0f) {
return true;
}
} else {
return true;
}
}
return false; return false;
} }
} }
return true;
}
}
} }