Transmux video if rotation is only effect applied

PiperOrigin-RevId: 535554628
(cherry picked from commit f4d1a6c453d701504a865a4dc9d50a2f87d77968)
This commit is contained in:
kimvde 2023-05-26 08:55:10 +00:00 committed by Tofunmi Adigun-Hameed
parent b79ee36661
commit 2ed784f156
5 changed files with 563 additions and 8 deletions

View File

@ -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);

View File

@ -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

View File

@ -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.
*
* <p>This value must be set before any track format is {@linkplain #addTrackFormat(Format)
* added}.
*
* <p>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);

View File

@ -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<Effect> videoEffects = firstEditedMediaItem.effects.videoEffects;
return !videoEffects.isEmpty()
&& !areVideoEffectsAllNoOp(videoEffects, inputFormat)
&& !hasOnlyRegularRotationEffect(videoEffects);
}
private boolean hasOnlyRegularRotationEffect(ImmutableList<Effect> videoEffects) {
if (videoEffects.size() != 1) {
return false;
}
Effect videoEffect = videoEffects.get(0);
if (!(videoEffect instanceof ScaleAndRotateTransformation)) {
return false;
}
ScaleAndRotateTransformation scaleAndRotateTransformation =
(ScaleAndRotateTransformation) videoEffect;
if (scaleAndRotateTransformation.scaleX != 1f
|| scaleAndRotateTransformation.scaleY != 1f) {
return false;
}
float rotationDegrees = scaleAndRotateTransformation.rotationDegrees;
if (rotationDegrees == 90f || rotationDegrees == 180f || rotationDegrees == 270f) {
// The MuxerWrapper rotation is clockwise while the ScaleAndRotateTransformation rotation
// is counterclockwise.
muxerWrapper.setAdditionalRotationDegrees(360 - Math.round(rotationDegrees));
return true;
}
return false;

View File

@ -975,6 +975,28 @@ public final class MediaItemExportTest {
context, checkNotNull(testMuxerHolder.testMuxer), getDumpFileName(FILE_VIDEO_ONLY));
}
@Test
public void start_withOnlyRegularRotationEffect_transmuxesAndRotates() throws Exception {
Transformer transformer =
createTransformerBuilder(testMuxerHolder, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
ImmutableList<Effect> videoEffects =
ImmutableList.of(
new ScaleAndRotateTransformation.Builder().setRotationDegrees(270).build());
Effects effects = new Effects(/* audioProcessors= */ ImmutableList.of(), videoEffects);
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
transformer.start(editedMediaItem, outputPath);
TransformerTestRunner.runLooper(transformer);
// Video transcoding in unit tests is not supported.
DumpFileAsserts.assertOutput(
context,
checkNotNull(testMuxerHolder.testMuxer),
getDumpFileName(FILE_AUDIO_VIDEO + ".rotated"));
}
@Test
public void getProgress_knownDuration_returnsConsistentStates() throws Exception {
Transformer transformer =