Internal-only change

PiperOrigin-RevId: 743468059
This commit is contained in:
dancho 2025-04-03 02:03:54 -07:00 committed by Copybara-Service
parent bd14b753ee
commit 769aca2e32
5 changed files with 44 additions and 19 deletions

View File

@ -501,6 +501,8 @@ public class TransformerEndToEndTest {
.build() .build()
.run(testId, editedMediaItem); .run(testId, editedMediaItem);
Format format = retrieveTrackFormat(context, result.filePath, C.TRACK_TYPE_VIDEO);
assertThat(format.rotationDegrees).isEqualTo(90);
assertThat(result.exportResult.width).isEqualTo(outputFormat.width); assertThat(result.exportResult.width).isEqualTo(outputFormat.width);
} }
@ -527,6 +529,8 @@ public class TransformerEndToEndTest {
.build() .build()
.run(testId, editedMediaItem); .run(testId, editedMediaItem);
Format format = retrieveTrackFormat(context, result.filePath, C.TRACK_TYPE_VIDEO);
assertThat(format.rotationDegrees).isEqualTo(0);
assertThat(result.exportResult.width).isEqualTo(inputFormat.width); assertThat(result.exportResult.width).isEqualTo(inputFormat.width);
} }

View File

@ -97,6 +97,9 @@ public final class Transformer {
/** A builder for {@link Transformer} instances. */ /** A builder for {@link Transformer} instances. */
public static final class Builder { public static final class Builder {
private static final ImmutableList<Integer> ALL_ROTATION_DEGREES =
ImmutableList.of(0, 90, 180, 270);
// Mandatory field. // Mandatory field.
private final Context context; private final Context context;
@ -109,7 +112,7 @@ public final class Transformer {
private boolean removeAudio; private boolean removeAudio;
private boolean removeVideo; private boolean removeVideo;
private boolean trimOptimizationEnabled; private boolean trimOptimizationEnabled;
private boolean portraitEncodingEnabled; private ImmutableList<Integer> allowedEncodingRotationDegrees;
private boolean fileStartsOnVideoFrameEnabled; private boolean fileStartsOnVideoFrameEnabled;
private boolean usePlatformDiagnostics; private boolean usePlatformDiagnostics;
private long maxDelayBetweenMuxerSamplesMs; private long maxDelayBetweenMuxerSamplesMs;
@ -150,6 +153,7 @@ public final class Transformer {
metricsReporterFactory = metricsReporterFactory =
new EditingMetricsCollector.DefaultMetricsReporter.Factory(context); new EditingMetricsCollector.DefaultMetricsReporter.Factory(context);
} }
allowedEncodingRotationDegrees = ALL_ROTATION_DEGREES;
} }
/** Creates a builder with the values of the provided {@link Transformer}. */ /** Creates a builder with the values of the provided {@link Transformer}. */
@ -163,7 +167,7 @@ public final class Transformer {
this.removeAudio = transformer.removeAudio; this.removeAudio = transformer.removeAudio;
this.removeVideo = transformer.removeVideo; this.removeVideo = transformer.removeVideo;
this.trimOptimizationEnabled = transformer.trimOptimizationEnabled; this.trimOptimizationEnabled = transformer.trimOptimizationEnabled;
this.portraitEncodingEnabled = transformer.portraitEncodingEnabled; this.allowedEncodingRotationDegrees = transformer.allowedEncodingRotationDegrees;
this.fileStartsOnVideoFrameEnabled = transformer.fileStartsOnVideoFrameEnabled; this.fileStartsOnVideoFrameEnabled = transformer.fileStartsOnVideoFrameEnabled;
this.usePlatformDiagnostics = transformer.usePlatformDiagnostics; this.usePlatformDiagnostics = transformer.usePlatformDiagnostics;
this.maxDelayBetweenMuxerSamplesMs = transformer.maxDelayBetweenMuxerSamplesMs; this.maxDelayBetweenMuxerSamplesMs = transformer.maxDelayBetweenMuxerSamplesMs;
@ -286,7 +290,7 @@ public final class Transformer {
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public Builder setPortraitEncodingEnabled(boolean enabled) { public Builder setPortraitEncodingEnabled(boolean enabled) {
portraitEncodingEnabled = enabled; allowedEncodingRotationDegrees = enabled ? ImmutableList.of(0) : ALL_ROTATION_DEGREES;
return this; return this;
} }
@ -597,7 +601,7 @@ public final class Transformer {
removeAudio, removeAudio,
removeVideo, removeVideo,
trimOptimizationEnabled, trimOptimizationEnabled,
portraitEncodingEnabled, allowedEncodingRotationDegrees,
fileStartsOnVideoFrameEnabled, fileStartsOnVideoFrameEnabled,
usePlatformDiagnostics, usePlatformDiagnostics,
maxDelayBetweenMuxerSamplesMs, maxDelayBetweenMuxerSamplesMs,
@ -783,7 +787,7 @@ public final class Transformer {
private final boolean removeAudio; private final boolean removeAudio;
private final boolean removeVideo; private final boolean removeVideo;
private final boolean trimOptimizationEnabled; private final boolean trimOptimizationEnabled;
private final boolean portraitEncodingEnabled; private final ImmutableList<Integer> allowedEncodingRotationDegrees;
private final boolean fileStartsOnVideoFrameEnabled; private final boolean fileStartsOnVideoFrameEnabled;
private final boolean usePlatformDiagnostics; private final boolean usePlatformDiagnostics;
private final long maxDelayBetweenMuxerSamplesMs; private final long maxDelayBetweenMuxerSamplesMs;
@ -825,7 +829,7 @@ public final class Transformer {
boolean removeAudio, boolean removeAudio,
boolean removeVideo, boolean removeVideo,
boolean trimOptimizationEnabled, boolean trimOptimizationEnabled,
boolean portraitEncodingEnabled, ImmutableList<Integer> allowedEncodingRotationDegrees,
boolean fileStartsOnVideoFrameEnabled, boolean fileStartsOnVideoFrameEnabled,
boolean usePlatformDiagnostics, boolean usePlatformDiagnostics,
long maxDelayBetweenMuxerSamplesMs, long maxDelayBetweenMuxerSamplesMs,
@ -848,7 +852,7 @@ public final class Transformer {
this.removeAudio = removeAudio; this.removeAudio = removeAudio;
this.removeVideo = removeVideo; this.removeVideo = removeVideo;
this.trimOptimizationEnabled = trimOptimizationEnabled; this.trimOptimizationEnabled = trimOptimizationEnabled;
this.portraitEncodingEnabled = portraitEncodingEnabled; this.allowedEncodingRotationDegrees = allowedEncodingRotationDegrees;
this.fileStartsOnVideoFrameEnabled = fileStartsOnVideoFrameEnabled; this.fileStartsOnVideoFrameEnabled = fileStartsOnVideoFrameEnabled;
this.usePlatformDiagnostics = usePlatformDiagnostics; this.usePlatformDiagnostics = usePlatformDiagnostics;
this.maxDelayBetweenMuxerSamplesMs = maxDelayBetweenMuxerSamplesMs; this.maxDelayBetweenMuxerSamplesMs = maxDelayBetweenMuxerSamplesMs;
@ -1639,7 +1643,7 @@ public final class Transformer {
audioMixerFactory, audioMixerFactory,
videoFrameProcessorFactory, videoFrameProcessorFactory,
encoderFactory, encoderFactory,
portraitEncodingEnabled, allowedEncodingRotationDegrees,
maxFramesInEncoder, maxFramesInEncoder,
muxerWrapper, muxerWrapper,
componentListener, componentListener,

View File

@ -148,7 +148,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final Object setMaxSequenceDurationUsLock; private final Object setMaxSequenceDurationUsLock;
private final Object progressLock; private final Object progressLock;
private final ProgressHolder internalProgressHolder; private final ProgressHolder internalProgressHolder;
private final boolean portraitEncodingEnabled; private final ImmutableList<Integer> allowedEncodingRotationDegrees;
private final int maxFramesInEncoder; private final int maxFramesInEncoder;
private boolean isDrainingExporters; private boolean isDrainingExporters;
@ -194,7 +194,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
AudioMixer.Factory audioMixerFactory, AudioMixer.Factory audioMixerFactory,
VideoFrameProcessor.Factory videoFrameProcessorFactory, VideoFrameProcessor.Factory videoFrameProcessorFactory,
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
boolean portraitEncodingEnabled, ImmutableList<Integer> allowedEncodingRotationDegrees,
int maxFramesInEncoder, int maxFramesInEncoder,
MuxerWrapper muxerWrapper, MuxerWrapper muxerWrapper,
Listener listener, Listener listener,
@ -207,7 +207,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.context = context; this.context = context;
this.composition = composition; this.composition = composition;
this.encoderFactory = new CapturingEncoderFactory(encoderFactory); this.encoderFactory = new CapturingEncoderFactory(encoderFactory);
this.portraitEncodingEnabled = portraitEncodingEnabled; this.allowedEncodingRotationDegrees = allowedEncodingRotationDegrees;
this.maxFramesInEncoder = maxFramesInEncoder; this.maxFramesInEncoder = maxFramesInEncoder;
this.listener = listener; this.listener = listener;
this.applicationHandler = applicationHandler; this.applicationHandler = applicationHandler;
@ -746,7 +746,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
debugViewProvider, debugViewProvider,
videoSampleTimestampOffsetUs, videoSampleTimestampOffsetUs,
/* hasMultipleInputs= */ assetLoaderInputTracker.hasMultipleConcurrentVideoTracks(), /* hasMultipleInputs= */ assetLoaderInputTracker.hasMultipleConcurrentVideoTracks(),
portraitEncodingEnabled, allowedEncodingRotationDegrees,
maxFramesInEncoder, maxFramesInEncoder,
logSessionId)); logSessionId));
} }

View File

@ -60,6 +60,7 @@ import androidx.media3.common.util.TimestampIterator;
import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.effect.MultipleInputVideoGraph; import androidx.media3.effect.MultipleInputVideoGraph;
import androidx.media3.effect.SingleInputVideoGraph; import androidx.media3.effect.SingleInputVideoGraph;
import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -99,7 +100,7 @@ import org.checkerframework.dataflow.qual.Pure;
DebugViewProvider debugViewProvider, DebugViewProvider debugViewProvider,
long initialTimestampOffsetUs, long initialTimestampOffsetUs,
boolean hasMultipleInputs, boolean hasMultipleInputs,
boolean portraitEncodingEnabled, ImmutableList<Integer> allowedEncodingRotationDegrees,
int maxFramesInEncoder, int maxFramesInEncoder,
@Nullable LogSessionId logSessionId) @Nullable LogSessionId logSessionId)
throws ExportException { throws ExportException {
@ -136,7 +137,7 @@ import org.checkerframework.dataflow.qual.Pure;
new EncoderWrapper( new EncoderWrapper(
encoderFactory, encoderFactory,
firstInputFormat.buildUpon().setColorInfo(videoGraphOutputColor).build(), firstInputFormat.buildUpon().setColorInfo(videoGraphOutputColor).build(),
portraitEncodingEnabled, allowedEncodingRotationDegrees,
muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_VIDEO), muxerWrapper.getSupportedSampleMimeTypes(C.TRACK_TYPE_VIDEO),
transformationRequest, transformationRequest,
fallbackListener, fallbackListener,
@ -249,7 +250,7 @@ import org.checkerframework.dataflow.qual.Pure;
private final Codec.EncoderFactory encoderFactory; private final Codec.EncoderFactory encoderFactory;
private final Format inputFormat; private final Format inputFormat;
private final boolean portraitEncodingEnabled; private final ImmutableList<Integer> allowedEncodingRotationDegrees;
private final List<String> muxerSupportedMimeTypes; private final List<String> muxerSupportedMimeTypes;
private final TransformationRequest transformationRequest; private final TransformationRequest transformationRequest;
private final FallbackListener fallbackListener; private final FallbackListener fallbackListener;
@ -266,7 +267,7 @@ import org.checkerframework.dataflow.qual.Pure;
public EncoderWrapper( public EncoderWrapper(
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
Format inputFormat, Format inputFormat,
boolean portraitEncodingEnabled, ImmutableList<Integer> allowedEncodingRotationDegrees,
List<String> muxerSupportedMimeTypes, List<String> muxerSupportedMimeTypes,
TransformationRequest transformationRequest, TransformationRequest transformationRequest,
FallbackListener fallbackListener, FallbackListener fallbackListener,
@ -274,7 +275,7 @@ import org.checkerframework.dataflow.qual.Pure;
checkArgument(inputFormat.colorInfo != null); checkArgument(inputFormat.colorInfo != null);
this.encoderFactory = encoderFactory; this.encoderFactory = encoderFactory;
this.inputFormat = inputFormat; this.inputFormat = inputFormat;
this.portraitEncodingEnabled = portraitEncodingEnabled; this.allowedEncodingRotationDegrees = allowedEncodingRotationDegrees;
this.muxerSupportedMimeTypes = muxerSupportedMimeTypes; this.muxerSupportedMimeTypes = muxerSupportedMimeTypes;
this.transformationRequest = transformationRequest; this.transformationRequest = transformationRequest;
this.fallbackListener = fallbackListener; this.fallbackListener = fallbackListener;
@ -319,7 +320,7 @@ import org.checkerframework.dataflow.qual.Pure;
// frame before encoding, so the encoded frame's width >= height. In this case, the VideoGraph // frame before encoding, so the encoded frame's width >= height. In this case, the VideoGraph
// rotates the decoded video frames counter-clockwise, and the muxer adds a clockwise rotation // rotates the decoded video frames counter-clockwise, and the muxer adds a clockwise rotation
// to the metadata. // to the metadata.
if (requestedWidth < requestedHeight && !portraitEncodingEnabled) { if (requestedWidth < requestedHeight) {
int temp = requestedWidth; int temp = requestedWidth;
requestedWidth = requestedHeight; requestedWidth = requestedHeight;
requestedHeight = temp; requestedHeight = temp;
@ -333,6 +334,22 @@ import org.checkerframework.dataflow.qual.Pure;
outputRotationDegrees = inputFormat.rotationDegrees; outputRotationDegrees = inputFormat.rotationDegrees;
} }
if (!allowedEncodingRotationDegrees.contains(outputRotationDegrees)) {
int alternativeOutputRotationDegreesWithSameWidthAndHeight =
(outputRotationDegrees + 180) % 360;
if (allowedEncodingRotationDegrees.contains(
alternativeOutputRotationDegreesWithSameWidthAndHeight)) {
outputRotationDegrees = alternativeOutputRotationDegreesWithSameWidthAndHeight;
} else {
// No allowed rotation of the same orientation. Swap width and height, and use any allowed
// orientation.
int temp = requestedWidth;
requestedWidth = requestedHeight;
requestedHeight = temp;
outputRotationDegrees = allowedEncodingRotationDegrees.get(0);
}
}
// Rotation is handled by this class. The encoder must see a video with zero degrees rotation. // Rotation is handled by this class. The encoder must see a video with zero degrees rotation.
Format requestedEncoderFormat = Format requestedEncoderFormat =
new Format.Builder() new Format.Builder()

View File

@ -73,7 +73,7 @@ public final class VideoEncoderWrapperTest {
.setSampleMimeType(MimeTypes.VIDEO_H264) .setSampleMimeType(MimeTypes.VIDEO_H264)
.setColorInfo(ColorInfo.SDR_BT709_LIMITED) .setColorInfo(ColorInfo.SDR_BT709_LIMITED)
.build(), .build(),
/* portraitEncodingEnabled= */ false, /* allowedEncodingRotationDegrees= */ ImmutableList.of(0, 90, 180, 270),
/* muxerSupportedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264), /* muxerSupportedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264),
emptyTransformationRequest, emptyTransformationRequest,
fallbackListener, fallbackListener,