diff --git a/libraries/effect/src/main/java/androidx/media3/effect/ScaleAndRotateTransformation.java b/libraries/effect/src/main/java/androidx/media3/effect/ScaleAndRotateTransformation.java index 7bbb15a327..d7cd2f9757 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/ScaleAndRotateTransformation.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/ScaleAndRotateTransformation.java @@ -92,17 +92,20 @@ public final class ScaleAndRotateTransformation 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; + /** The counterclockwise rotation, in degrees. */ + public final float rotationDegrees; + private final Matrix transformationMatrix; private @MonotonicNonNull Matrix adjustedTransformationMatrix; - /** - * Creates a new instance. - * - * @param scaleX The multiplier by which the frame will scale horizontally, along the x-axis. - * @param scaleY The multiplier by which the frame will scale vertically, along the y-axis. - * @param rotationDegrees How much to rotate the frame counterclockwise, in degrees. - */ private ScaleAndRotateTransformation(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/test_data/src/test/assets/transformerdumps/mp4/sample.mp4.rotated.dump b/libraries/test_data/src/test/assets/transformerdumps/mp4/sample.mp4.rotated.dump new file mode 100644 index 0000000000..611512f232 --- /dev/null +++ b/libraries/test_data/src/test/assets/transformerdumps/mp4/sample.mp4.rotated.dump @@ -0,0 +1,478 @@ +format 0: + id = 1 + sampleMimeType = video/avc + codecs = avc1.64001F + maxInputSize = 36722 + width = 1080 + height = 720 + frameRate = 29.970028 + rotationDegrees = 90 + metadata = entries=[xyz: latitude=40.68, longitude=-74.5] + initializationData: + data = length 29, hash 4746B5D9 + data = length 10, hash 7A0D0F2B +container metadata = entries=[xyz: latitude=40.68, longitude=-74.5] +format 1: + peakBitrate = 200000 + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 294 + channelCount = 1 + sampleRate = 44100 + language = und + metadata = entries=[TSSE: description=null: values=[Lavf56.1.0], xyz: latitude=40.68, longitude=-74.5] + initializationData: + data = length 2, hash 5F7 +container metadata = entries=[TSSE: description=null: values=[Lavf56.1.0], xyz: latitude=40.68, longitude=-74.5] +sample: + trackIndex = 1 + dataHashCode = 1205768497 + size = 23 + isKeyFrame = true + presentationTimeUs = 44000 +sample: + trackIndex = 1 + dataHashCode = 837571078 + size = 6 + isKeyFrame = true + presentationTimeUs = 67219 +sample: + trackIndex = 1 + dataHashCode = -1991633045 + size = 148 + isKeyFrame = true + presentationTimeUs = 90439 +sample: + trackIndex = 1 + dataHashCode = -822987359 + size = 189 + isKeyFrame = true + presentationTimeUs = 113659 +sample: + trackIndex = 1 + dataHashCode = -1141508176 + size = 205 + isKeyFrame = true + presentationTimeUs = 136879 +sample: + trackIndex = 1 + dataHashCode = -226971245 + size = 210 + isKeyFrame = true + presentationTimeUs = 160099 +sample: + trackIndex = 1 + dataHashCode = -2099636855 + size = 210 + isKeyFrame = true + presentationTimeUs = 183319 +sample: + trackIndex = 1 + dataHashCode = 1541550559 + size = 207 + isKeyFrame = true + presentationTimeUs = 206539 +sample: + trackIndex = 1 + dataHashCode = 411148001 + size = 225 + isKeyFrame = true + presentationTimeUs = 229759 +sample: + trackIndex = 1 + dataHashCode = -897603973 + size = 215 + isKeyFrame = true + presentationTimeUs = 252979 +sample: + trackIndex = 0 + dataHashCode = -770308242 + size = 36692 + isKeyFrame = true + presentationTimeUs = 0 +sample: + trackIndex = 0 + dataHashCode = -732087136 + size = 5312 + isKeyFrame = false + presentationTimeUs = 66733 +sample: + trackIndex = 0 + dataHashCode = 468156717 + size = 599 + isKeyFrame = false + presentationTimeUs = 33366 +sample: + trackIndex = 0 + dataHashCode = 1150349584 + size = 7735 + isKeyFrame = false + presentationTimeUs = 200200 +sample: + trackIndex = 0 + dataHashCode = 1443582006 + size = 987 + isKeyFrame = false + presentationTimeUs = 133466 +sample: + trackIndex = 0 + dataHashCode = -310585145 + size = 673 + isKeyFrame = false + presentationTimeUs = 100100 +sample: + trackIndex = 0 + dataHashCode = 807460688 + size = 523 + isKeyFrame = false + presentationTimeUs = 166833 +sample: + trackIndex = 0 + dataHashCode = 1936487090 + size = 6061 + isKeyFrame = false + presentationTimeUs = 333666 +sample: + trackIndex = 0 + dataHashCode = -32297181 + size = 992 + isKeyFrame = false + presentationTimeUs = 266933 +sample: + trackIndex = 0 + dataHashCode = 1529616406 + size = 623 + isKeyFrame = false + presentationTimeUs = 233566 +sample: + trackIndex = 1 + dataHashCode = 1478106136 + size = 211 + isKeyFrame = true + presentationTimeUs = 276199 +sample: + trackIndex = 1 + dataHashCode = -1380417145 + size = 216 + isKeyFrame = true + presentationTimeUs = 299419 +sample: + trackIndex = 1 + dataHashCode = 780903644 + size = 229 + isKeyFrame = true + presentationTimeUs = 322639 +sample: + trackIndex = 1 + dataHashCode = 586204432 + size = 232 + isKeyFrame = true + presentationTimeUs = 345859 +sample: + trackIndex = 1 + dataHashCode = -2038771492 + size = 235 + isKeyFrame = true + presentationTimeUs = 369079 +sample: + trackIndex = 1 + dataHashCode = -2065161304 + size = 231 + isKeyFrame = true + presentationTimeUs = 392299 +sample: + trackIndex = 1 + dataHashCode = 468662933 + size = 226 + isKeyFrame = true + presentationTimeUs = 415519 +sample: + trackIndex = 1 + dataHashCode = -358398546 + size = 216 + isKeyFrame = true + presentationTimeUs = 438739 +sample: + trackIndex = 1 + dataHashCode = 1767325983 + size = 229 + isKeyFrame = true + presentationTimeUs = 461959 +sample: + trackIndex = 1 + dataHashCode = 1093095458 + size = 219 + isKeyFrame = true + presentationTimeUs = 485179 +sample: + trackIndex = 0 + dataHashCode = 1949198785 + size = 421 + isKeyFrame = false + presentationTimeUs = 300300 +sample: + trackIndex = 0 + dataHashCode = -147880287 + size = 4899 + isKeyFrame = false + presentationTimeUs = 433766 +sample: + trackIndex = 0 + dataHashCode = 1369083472 + size = 568 + isKeyFrame = false + presentationTimeUs = 400400 +sample: + trackIndex = 0 + dataHashCode = 965782073 + size = 620 + isKeyFrame = false + presentationTimeUs = 367033 +sample: + trackIndex = 0 + dataHashCode = -261176150 + size = 5450 + isKeyFrame = false + presentationTimeUs = 567233 +sample: + trackIndex = 0 + dataHashCode = -1830836678 + size = 1051 + isKeyFrame = false + presentationTimeUs = 500500 +sample: + trackIndex = 0 + dataHashCode = 1767407540 + size = 874 + isKeyFrame = false + presentationTimeUs = 467133 +sample: + trackIndex = 0 + dataHashCode = 918440283 + size = 781 + isKeyFrame = false + presentationTimeUs = 533866 +sample: + trackIndex = 0 + dataHashCode = -1408463661 + size = 4725 + isKeyFrame = false + presentationTimeUs = 700700 +sample: + trackIndex = 0 + dataHashCode = 1569455924 + size = 1022 + isKeyFrame = false + presentationTimeUs = 633966 +sample: + trackIndex = 1 + dataHashCode = 1687543702 + size = 241 + isKeyFrame = true + presentationTimeUs = 508399 +sample: + trackIndex = 1 + dataHashCode = 1675188486 + size = 228 + isKeyFrame = true + presentationTimeUs = 531619 +sample: + trackIndex = 1 + dataHashCode = 888567545 + size = 238 + isKeyFrame = true + presentationTimeUs = 554839 +sample: + trackIndex = 1 + dataHashCode = -439631803 + size = 234 + isKeyFrame = true + presentationTimeUs = 578058 +sample: + trackIndex = 1 + dataHashCode = 1606694497 + size = 231 + isKeyFrame = true + presentationTimeUs = 601278 +sample: + trackIndex = 1 + dataHashCode = 1747388653 + size = 217 + isKeyFrame = true + presentationTimeUs = 624498 +sample: + trackIndex = 1 + dataHashCode = -734560004 + size = 239 + isKeyFrame = true + presentationTimeUs = 647718 +sample: + trackIndex = 1 + dataHashCode = -975079040 + size = 243 + isKeyFrame = true + presentationTimeUs = 670938 +sample: + trackIndex = 1 + dataHashCode = -1403504710 + size = 231 + isKeyFrame = true + presentationTimeUs = 694158 +sample: + trackIndex = 1 + dataHashCode = 379512981 + size = 230 + isKeyFrame = true + presentationTimeUs = 717378 +sample: + trackIndex = 0 + dataHashCode = -1723778407 + size = 790 + isKeyFrame = false + presentationTimeUs = 600600 +sample: + trackIndex = 0 + dataHashCode = 1578275472 + size = 610 + isKeyFrame = false + presentationTimeUs = 667333 +sample: + trackIndex = 0 + dataHashCode = 1989768395 + size = 2751 + isKeyFrame = false + presentationTimeUs = 834166 +sample: + trackIndex = 0 + dataHashCode = -1215674502 + size = 745 + isKeyFrame = false + presentationTimeUs = 767433 +sample: + trackIndex = 0 + dataHashCode = -814473606 + size = 621 + isKeyFrame = false + presentationTimeUs = 734066 +sample: + trackIndex = 0 + dataHashCode = 498370894 + size = 505 + isKeyFrame = false + presentationTimeUs = 800800 +sample: + trackIndex = 0 + dataHashCode = -1051506468 + size = 1268 + isKeyFrame = false + presentationTimeUs = 967633 +sample: + trackIndex = 0 + dataHashCode = -1025604144 + size = 880 + isKeyFrame = false + presentationTimeUs = 900900 +sample: + trackIndex = 0 + dataHashCode = -913586520 + size = 530 + isKeyFrame = false + presentationTimeUs = 867533 +sample: + trackIndex = 0 + dataHashCode = 1340459242 + size = 568 + isKeyFrame = false + presentationTimeUs = 934266 +sample: + trackIndex = 1 + dataHashCode = -997198863 + size = 238 + isKeyFrame = true + presentationTimeUs = 740598 +sample: + trackIndex = 1 + dataHashCode = 1394492825 + size = 225 + isKeyFrame = true + presentationTimeUs = 763818 +sample: + trackIndex = 1 + dataHashCode = -885232755 + size = 232 + isKeyFrame = true + presentationTimeUs = 787038 +sample: + trackIndex = 1 + dataHashCode = 260871367 + size = 243 + isKeyFrame = true + presentationTimeUs = 810258 +sample: + trackIndex = 1 + dataHashCode = -1505318960 + size = 232 + isKeyFrame = true + presentationTimeUs = 833478 +sample: + trackIndex = 1 + dataHashCode = -390625371 + size = 237 + isKeyFrame = true + presentationTimeUs = 856698 +sample: + trackIndex = 1 + dataHashCode = 1067950751 + size = 228 + isKeyFrame = true + presentationTimeUs = 879918 +sample: + trackIndex = 1 + dataHashCode = -1179436278 + size = 235 + isKeyFrame = true + presentationTimeUs = 903138 +sample: + trackIndex = 1 + dataHashCode = 1906607774 + size = 264 + isKeyFrame = true + presentationTimeUs = 926358 +sample: + trackIndex = 1 + dataHashCode = -800475828 + size = 257 + isKeyFrame = true + presentationTimeUs = 949578 +sample: + trackIndex = 1 + dataHashCode = 1718972977 + size = 227 + isKeyFrame = true + presentationTimeUs = 972798 +sample: + trackIndex = 1 + dataHashCode = -1120448741 + size = 227 + isKeyFrame = true + presentationTimeUs = 996018 +sample: + trackIndex = 1 + dataHashCode = -1718323210 + size = 235 + isKeyFrame = true + presentationTimeUs = 1019238 +sample: + trackIndex = 1 + dataHashCode = -422416 + size = 229 + isKeyFrame = true + presentationTimeUs = 1042458 +sample: + trackIndex = 1 + dataHashCode = 833757830 + size = 6 + isKeyFrame = true + presentationTimeUs = 1065678 +released = true diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java b/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java index e0e10c64f0..18cb6e2621 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java @@ -83,6 +83,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private boolean isAborted; private @MonotonicNonNull Muxer muxer; + private volatile int additionalRotationDegrees; private volatile int trackCount; public MuxerWrapper(String outputPath, Muxer.Factory muxerFactory, Listener listener) { @@ -95,6 +96,25 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; abortScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); } + /** + * Sets the clockwise rotation to add to the {@linkplain #addTrackFormat(Format) video track's} + * rotation, in degrees. + * + *
This value must be set before any track format is {@linkplain #addTrackFormat(Format) + * added}. + * + *
Can be called from any thread.
+ *
+ * @throws IllegalStateException If a track format was {@linkplain #addTrackFormat(Format) added}
+ * before calling this method.
+ */
+ public void setAdditionalRotationDegrees(int additionalRotationDegrees) {
+ checkState(
+ trackTypeToInfo.size() == 0,
+ "The additional rotation cannot be changed after adding track formats.");
+ this.additionalRotationDegrees = additionalRotationDegrees;
+ }
+
/**
* Sets the number of output tracks.
*
@@ -160,6 +180,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
ensureMuxerInitialized();
+ if (trackType == C.TRACK_TYPE_VIDEO) {
+ format =
+ format
+ .buildUpon()
+ .setRotationDegrees((format.rotationDegrees + additionalRotationDegrees) % 360)
+ .build();
+ }
TrackInfo trackInfo = new TrackInfo(format, muxer.addTrack(format));
trackTypeToInfo.put(trackType, trackInfo);
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 6c37dec937..7b691bf947 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java
@@ -48,6 +48,7 @@ import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.effect.Presentation;
+import androidx.media3.effect.ScaleAndRotateTransformation;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -710,7 +711,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (inputFormat.pixelWidthHeightRatio != 1f) {
return true;
}
- if (!areVideoEffectsAllNoOp(firstEditedMediaItem.effects.videoEffects, inputFormat)) {
+ ImmutableList