From 341f2d6b3a272d7c3bdb88969368c4ccc82487eb Mon Sep 17 00:00:00 2001 From: kimvde Date: Thu, 26 Jan 2023 17:45:35 +0000 Subject: [PATCH] Move flattenForSlowMotion to EditedMediaItem PiperOrigin-RevId: 504867150 --- .../transformerdemo/TransformerActivity.java | 4 +- .../transformer/mh/TransformationTest.java | 11 ++-- .../exoplayer2/transformer/AssetLoader.java | 2 +- .../transformer/AudioSamplePipeline.java | 3 +- .../DefaultAssetLoaderFactory.java | 24 ++++++-- .../transformer/EditedMediaItem.java | 56 +++++++++++++++++- .../exoplayer2/transformer/Effects.java | 3 +- .../transformer/ExoPlayerAssetLoader.java | 47 +++++++++++---- .../transformer/TransformationRequest.java | 57 +------------------ .../exoplayer2/transformer/Transformer.java | 52 +++++++++-------- .../transformer/TransformerInternal.java | 33 +++++------ .../EditedMediaItemBuilderTest.java | 15 +++++ .../transformer/ExoPlayerAssetLoaderTest.java | 5 +- .../TransformationRequestTest.java | 4 +- .../transformer/TransformerEndToEndTest.java | 9 +-- 15 files changed, 187 insertions(+), 138 deletions(-) diff --git a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java index af000fe65b..59ffd6d6fd 100644 --- a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java +++ b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java @@ -272,8 +272,6 @@ public final class TransformerActivity extends AppCompatActivity { Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this); if (bundle != null) { TransformationRequest.Builder requestBuilder = new TransformationRequest.Builder(); - requestBuilder.setFlattenForSlowMotion( - bundle.getBoolean(ConfigurationActivity.SHOULD_FLATTEN_FOR_SLOW_MOTION)); @Nullable String audioMimeType = bundle.getString(ConfigurationActivity.AUDIO_MIME_TYPE); if (audioMimeType != null) { requestBuilder.setAudioMimeType(audioMimeType); @@ -352,6 +350,8 @@ public final class TransformerActivity extends AppCompatActivity { return editedMediaItemBuilder .setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO)) .setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO)) + .setFlattenForSlowMotion( + bundle.getBoolean(ConfigurationActivity.SHOULD_FLATTEN_FOR_SLOW_MOTION)) .setEffects(new Effects(audioProcessors, videoEffects)) .build(); } diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/TransformationTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/TransformationTest.java index 3274db3162..4120675092 100644 --- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/TransformationTest.java +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/TransformationTest.java @@ -35,7 +35,6 @@ import com.google.android.exoplayer2.transformer.AndroidTestUtil.ForceEncodeEnco import com.google.android.exoplayer2.transformer.DefaultEncoderFactory; import com.google.android.exoplayer2.transformer.EditedMediaItem; import com.google.android.exoplayer2.transformer.Effects; -import com.google.android.exoplayer2.transformer.TransformationRequest; import com.google.android.exoplayer2.transformer.Transformer; import com.google.android.exoplayer2.transformer.TransformerAndroidTestRunner; import com.google.android.exoplayer2.transformer.VideoEncoderSettings; @@ -203,13 +202,11 @@ public class TransformationTest { return; } - Transformer transformer = - new Transformer.Builder(context) - .setTransformationRequest( - new TransformationRequest.Builder().setFlattenForSlowMotion(true).build()) - .build(); + Transformer transformer = new Transformer.Builder(context).build(); EditedMediaItem editedMediaItem = - new EditedMediaItem.Builder(MediaItem.fromUri(Uri.parse(MP4_ASSET_SEF_URI_STRING))).build(); + new EditedMediaItem.Builder(MediaItem.fromUri(Uri.parse(MP4_ASSET_SEF_URI_STRING))) + .setFlattenForSlowMotion(true) + .build(); new TransformerAndroidTestRunner.Builder(context, transformer) .build() .run(testId, editedMediaItem); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AssetLoader.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AssetLoader.java index d1b6893c0e..a6c1d44efa 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AssetLoader.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AssetLoader.java @@ -74,7 +74,7 @@ public interface AssetLoader { * this is done on decoded samples. * *

For more information on slow motion flattening, see {@link - * TransformationRequest.Builder#setFlattenForSlowMotion(boolean)}. + * EditedMediaItem.Builder#setFlattenForSlowMotion(boolean)}. */ @CanIgnoreReturnValue Factory setFlattenVideoForSlowMotion(boolean flattenVideoForSlowMotion); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AudioSamplePipeline.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AudioSamplePipeline.java index 338e088d06..64f0cda487 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AudioSamplePipeline.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/AudioSamplePipeline.java @@ -63,6 +63,7 @@ import org.checkerframework.dataflow.qual.Pure; long streamStartPositionUs, long streamOffsetUs, TransformationRequest transformationRequest, + boolean flattenForSlowMotion, ImmutableList audioProcessors, long generateSilentAudioDurationUs, Codec.EncoderFactory encoderFactory, @@ -89,7 +90,7 @@ import org.checkerframework.dataflow.qual.Pure; encoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED); encoderOutputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED); - if (transformationRequest.flattenForSlowMotion) { + if (flattenForSlowMotion) { audioProcessors = new ImmutableList.Builder() .add(new SpeedChangingAudioProcessor(new SegmentSpeedProvider(inputFormat))) diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultAssetLoaderFactory.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultAssetLoaderFactory.java index 6f36bfa81c..ca491e273b 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultAssetLoaderFactory.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultAssetLoaderFactory.java @@ -32,20 +32,34 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory { * Creates an instance. * * @param context The {@link Context}. - * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to - * transform when an {@link ExoPlayerAssetLoader} is used. * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if * necessary). * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for * testing. */ + public DefaultAssetLoaderFactory( + Context context, Codec.DecoderFactory decoderFactory, Clock clock) { + assetLoaderFactory = new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock); + } + + /** + * Creates an instance. + * + * @param context The {@link Context}. + * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if + * necessary). + * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for + * testing. + * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to + * transform when an {@link ExoPlayerAssetLoader} is used. + */ public DefaultAssetLoaderFactory( Context context, - MediaSource.Factory mediaSourceFactory, Codec.DecoderFactory decoderFactory, - Clock clock) { + Clock clock, + MediaSource.Factory mediaSourceFactory) { assetLoaderFactory = - new ExoPlayerAssetLoader.Factory(context, mediaSourceFactory, decoderFactory, clock); + new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory); } @Override diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EditedMediaItem.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EditedMediaItem.java index 19e982c9f5..8958948f33 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EditedMediaItem.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EditedMediaItem.java @@ -15,9 +15,12 @@ */ package com.google.android.exoplayer2.transformer; +import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkState; import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; +import com.google.android.exoplayer2.source.MediaSource; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -32,6 +35,7 @@ public class EditedMediaItem { private boolean removeAudio; private boolean removeVideo; + private boolean flattenForSlowMotion; private @MonotonicNonNull Effects effects; /** @@ -77,6 +81,47 @@ public class EditedMediaItem { return this; } + /** + * Sets whether to flatten the {@link MediaItem} if it contains slow motion markers. + * + *

The default value is {@code false}. + * + *

The flattened output is obtained by removing the slow motion metadata and by actually + * slowing down the parts of the video and audio streams defined in this metadata. + * + *

Only Samsung Extension Format (SEF) slow motion metadata type is supported. Flattening has + * no effect if the input does not contain this metadata type. + * + *

For SEF slow motion media, the following assumptions are made on the input: + * + *

+ * + *

If using an {@link ExoPlayerAssetLoader.Factory} with a provided {@link + * MediaSource.Factory}, make sure that {@link Mp4Extractor#FLAG_READ_SEF_DATA} is set on the + * {@link Mp4Extractor} used. Otherwise, the slow motion metadata will be ignored and the input + * won't be flattened. + * + *

Using slow motion flattening together with {@link MediaItem.ClippingConfiguration} is not + * supported yet. + * + * @param flattenForSlowMotion Whether to flatten for slow motion. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) { + // TODO(b/233986762): Support clipping with SEF flattening. + checkArgument( + mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET) + || !flattenForSlowMotion, + "Slow motion flattening is not supported when clipping is requested"); + this.flattenForSlowMotion = flattenForSlowMotion; + return this; + } + /** * Sets the {@link Effects} to apply to the {@link MediaItem}. * @@ -98,21 +143,28 @@ public class EditedMediaItem { new Effects( /* audioProcessors= */ ImmutableList.of(), /* videoEffects= */ ImmutableList.of()); } - return new EditedMediaItem(mediaItem, removeAudio, removeVideo, effects); + return new EditedMediaItem( + mediaItem, removeAudio, removeVideo, flattenForSlowMotion, effects); } } /* package */ final MediaItem mediaItem; /* package */ final boolean removeAudio; /* package */ final boolean removeVideo; + /* package */ final boolean flattenForSlowMotion; /* package */ final Effects effects; private EditedMediaItem( - MediaItem mediaItem, boolean removeAudio, boolean removeVideo, Effects effects) { + MediaItem mediaItem, + boolean removeAudio, + boolean removeVideo, + boolean flattenForSlowMotion, + Effects effects) { checkState(!removeAudio || !removeVideo, "Audio and video cannot both be removed"); this.mediaItem = mediaItem; this.removeAudio = removeAudio; this.removeVideo = removeVideo; + this.flattenForSlowMotion = flattenForSlowMotion; this.effects = effects; } } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Effects.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Effects.java index 1a5b2ed62d..12a458bde3 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Effects.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Effects.java @@ -47,8 +47,7 @@ public final class Effects { * They are applied in the order of the list, and buffers will only be modified by that {@link * AudioProcessor} if it {@link AudioProcessor#isActive()} based on the current configuration. * @param videoEffects The list of {@link Effect} instances to apply to each video frame. They are - * applied in the order of the list, after {@linkplain - * TransformationRequest.Builder#setFlattenForSlowMotion(boolean) slow-motion flattening}. + * applied in the order of the list. * @param frameProcessorFactory The {@link FrameProcessor.Factory} for the {@link FrameProcessor} * to use when applying the {@code videoEffects} to the video frames. */ diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoader.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoader.java index db1883cba0..96c0c96edd 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoader.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoader.java @@ -44,7 +44,10 @@ import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Tracks; import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; +import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; import com.google.android.exoplayer2.metadata.MetadataOutput; +import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; @@ -60,34 +63,50 @@ public final class ExoPlayerAssetLoader implements AssetLoader { public static final class Factory implements AssetLoader.Factory { private final Context context; - private final MediaSource.Factory mediaSourceFactory; private final Codec.DecoderFactory decoderFactory; private final Clock clock; + @Nullable private final MediaSource.Factory mediaSourceFactory; private boolean removeAudio; private boolean removeVideo; private boolean flattenVideoForSlowMotion; /** - * Creates an instance. + * Creates an instance using a {@link DefaultMediaSourceFactory}. * * @param context The {@link Context}. - * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to - * transform. * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if * necessary). * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for * testing. */ - public Factory( - Context context, - MediaSource.Factory mediaSourceFactory, - Codec.DecoderFactory decoderFactory, - Clock clock) { + public Factory(Context context, Codec.DecoderFactory decoderFactory, Clock clock) { this.context = context; - this.mediaSourceFactory = mediaSourceFactory; this.decoderFactory = decoderFactory; this.clock = clock; + this.mediaSourceFactory = null; + } + + /** + * Creates an instance. + * + * @param context The {@link Context}. + * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if + * necessary). + * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for + * testing. + * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to + * transform. + */ + public Factory( + Context context, + Codec.DecoderFactory decoderFactory, + Clock clock, + MediaSource.Factory mediaSourceFactory) { + this.context = context; + this.decoderFactory = decoderFactory; + this.clock = clock; + this.mediaSourceFactory = mediaSourceFactory; } @Override @@ -113,6 +132,14 @@ public final class ExoPlayerAssetLoader implements AssetLoader { @Override public AssetLoader createAssetLoader(MediaItem mediaItem, Looper looper, Listener listener) { + MediaSource.Factory mediaSourceFactory = this.mediaSourceFactory; + if (mediaSourceFactory == null) { + DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); + if (flattenVideoForSlowMotion) { + defaultExtractorsFactory.setMp4ExtractorFlags(Mp4Extractor.FLAG_READ_SEF_DATA); + } + mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory); + } return new ExoPlayerAssetLoader( context, mediaItem, diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationRequest.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationRequest.java index 9326df295e..aecf3ac3e0 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationRequest.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationRequest.java @@ -23,8 +23,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; -import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -108,7 +106,6 @@ public final class TransformationRequest { /** A builder for {@link TransformationRequest} instances. */ public static final class Builder { - private boolean flattenForSlowMotion; private int outputHeight; @Nullable private String audioMimeType; @Nullable private String videoMimeType; @@ -125,48 +122,12 @@ public final class TransformationRequest { } private Builder(TransformationRequest transformationRequest) { - this.flattenForSlowMotion = transformationRequest.flattenForSlowMotion; this.outputHeight = transformationRequest.outputHeight; this.audioMimeType = transformationRequest.audioMimeType; this.videoMimeType = transformationRequest.videoMimeType; this.hdrMode = transformationRequest.hdrMode; } - /** - * Sets whether the input should be flattened for media containing slow motion markers. - * - *

The transformed output is obtained by removing the slow motion metadata and by actually - * slowing down the parts of the video and audio streams defined in this metadata. The default - * value for {@code flattenForSlowMotion} is {@code false}. - * - *

Only Samsung Extension Format (SEF) slow motion metadata type is supported. The - * transformation has no effect if the input does not contain this metadata type. - * - *

For SEF slow motion media, the following assumptions are made on the input: - * - *

- * - *

If using an {@link ExoPlayerAssetLoader.Factory} with a provided {@link - * MediaSource.Factory}, make sure that {@link Mp4Extractor#FLAG_READ_SEF_DATA} is set on the - * {@link Mp4Extractor} used. Otherwise, the slow motion metadata will be ignored and the input - * won't be flattened. - * - *

Using slow motion flattening together with {@link - * com.google.android.exoplayer2.MediaItem.ClippingConfiguration} is not supported yet. - * - * @param flattenForSlowMotion Whether to flatten for slow motion. - * @return This builder. - */ - @CanIgnoreReturnValue - public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) { - this.flattenForSlowMotion = flattenForSlowMotion; - return this; - } - /** * Sets the output resolution using the output height of the displayed video. * @@ -288,17 +249,10 @@ public final class TransformationRequest { /** Builds a {@link TransformationRequest} instance. */ public TransformationRequest build() { - return new TransformationRequest( - flattenForSlowMotion, outputHeight, audioMimeType, videoMimeType, hdrMode); + return new TransformationRequest(outputHeight, audioMimeType, videoMimeType, hdrMode); } } - /** - * Whether the input should be flattened for media containing slow motion markers. - * - * @see Builder#setFlattenForSlowMotion(boolean) - */ - public final boolean flattenForSlowMotion; /** * The requested height of the output video, or {@link C#LENGTH_UNSET} if inferred from the input. * @@ -327,13 +281,10 @@ public final class TransformationRequest { public final @HdrMode int hdrMode; private TransformationRequest( - boolean flattenForSlowMotion, int outputHeight, @Nullable String audioMimeType, @Nullable String videoMimeType, @HdrMode int hdrMode) { - - this.flattenForSlowMotion = flattenForSlowMotion; this.outputHeight = outputHeight; this.audioMimeType = audioMimeType; this.videoMimeType = videoMimeType; @@ -349,8 +300,7 @@ public final class TransformationRequest { return false; } TransformationRequest that = (TransformationRequest) o; - return flattenForSlowMotion == that.flattenForSlowMotion - && outputHeight == that.outputHeight + return outputHeight == that.outputHeight && Util.areEqual(audioMimeType, that.audioMimeType) && Util.areEqual(videoMimeType, that.videoMimeType) && hdrMode == that.hdrMode; @@ -358,8 +308,7 @@ public final class TransformationRequest { @Override public int hashCode() { - int result = (flattenForSlowMotion ? 1 : 0); - result = 31 * result + outputHeight; + int result = outputHeight; result = 31 * result + (audioMimeType != null ? audioMimeType.hashCode() : 0); result = 31 * result + (videoMimeType != null ? videoMimeType.hashCode() : 0); result = 31 * result + hdrMode; diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index 91dafe7a34..913eb63223 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -32,10 +32,7 @@ import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.audio.AudioProcessor; import com.google.android.exoplayer2.audio.SonicAudioProcessor; import com.google.android.exoplayer2.effect.GlEffectsFrameProcessor; -import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; -import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; -import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.DebugViewProvider; import com.google.android.exoplayer2.util.Effect; @@ -51,6 +48,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.List; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * A transformer to transform media inputs. @@ -84,9 +82,10 @@ public final class Transformer { private ImmutableList videoEffects; private boolean removeAudio; private boolean removeVideo; + private boolean flattenForSlowMotion; private boolean generateSilentAudio; private ListenerSet listeners; - @Nullable private AssetLoader.Factory assetLoaderFactory; + private AssetLoader.@MonotonicNonNull Factory assetLoaderFactory; private FrameProcessor.Factory frameProcessorFactory; private Codec.EncoderFactory encoderFactory; private Muxer.Factory muxerFactory; @@ -197,14 +196,14 @@ public final class Transformer { } /** - * @deprecated Use {@link TransformationRequest.Builder#setFlattenForSlowMotion(boolean)} - * instead. + * @deprecated Use {@link EditedMediaItem.Builder#setFlattenForSlowMotion(boolean)} to flatten + * the {@link EditedMediaItem} passed to {@link #startTransformation(EditedMediaItem, + * String)} or {@link #startTransformation(EditedMediaItem, ParcelFileDescriptor)} instead. */ @CanIgnoreReturnValue @Deprecated public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) { - transformationRequest = - transformationRequest.buildUpon().setFlattenForSlowMotion(flattenForSlowMotion).build(); + this.flattenForSlowMotion = flattenForSlowMotion; return this; } @@ -417,15 +416,8 @@ public final class Transformer { checkSampleMimeType(transformationRequest.videoMimeType); } if (assetLoaderFactory == null) { - DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); - if (transformationRequest.flattenForSlowMotion) { - defaultExtractorsFactory.setMp4ExtractorFlags(Mp4Extractor.FLAG_READ_SEF_DATA); - } - MediaSource.Factory mediaSourceFactory = - new DefaultMediaSourceFactory(context, defaultExtractorsFactory); - Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context); assetLoaderFactory = - new DefaultAssetLoaderFactory(context, mediaSourceFactory, decoderFactory, clock); + new DefaultAssetLoaderFactory(context, new DefaultDecoderFactory(context), clock); } return new Transformer( context, @@ -434,6 +426,7 @@ public final class Transformer { videoEffects, removeAudio, removeVideo, + flattenForSlowMotion, generateSilentAudio, listeners, assetLoaderFactory, @@ -554,6 +547,7 @@ public final class Transformer { private final ImmutableList videoEffects; private final boolean removeAudio; private final boolean removeVideo; + private final boolean flattenForSlowMotion; private final boolean generateSilentAudio; private final ListenerSet listeners; private final AssetLoader.Factory assetLoaderFactory; @@ -573,6 +567,7 @@ public final class Transformer { ImmutableList videoEffects, boolean removeAudio, boolean removeVideo, + boolean flattenForSlowMotion, boolean generateSilentAudio, ListenerSet listeners, AssetLoader.Factory assetLoaderFactory, @@ -589,6 +584,7 @@ public final class Transformer { this.videoEffects = videoEffects; this.removeAudio = removeAudio; this.removeVideo = removeVideo; + this.flattenForSlowMotion = flattenForSlowMotion; this.generateSilentAudio = generateSilentAudio; this.listeners = listeners; this.assetLoaderFactory = assetLoaderFactory; @@ -710,10 +706,16 @@ public final class Transformer { */ @Deprecated public void startTransformation(MediaItem mediaItem, String path) { + if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET) + && flattenForSlowMotion) { + throw new IllegalArgumentException( + "Clipping is not supported when slow motion flattening is requested"); + } EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem) .setRemoveAudio(removeAudio) .setRemoveVideo(removeVideo) + .setFlattenForSlowMotion(flattenForSlowMotion) .setEffects(new Effects(audioProcessors, videoEffects, frameProcessorFactory)) .build(); startTransformationInternal(editedMediaItem, path, /* parcelFileDescriptor= */ null); @@ -725,10 +727,16 @@ public final class Transformer { @Deprecated @RequiresApi(26) public void startTransformation(MediaItem mediaItem, ParcelFileDescriptor parcelFileDescriptor) { + if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET) + && flattenForSlowMotion) { + throw new IllegalArgumentException( + "Clipping is not supported when slow motion flattening is requested"); + } EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem) .setRemoveAudio(removeAudio) .setRemoveVideo(removeVideo) + .setFlattenForSlowMotion(flattenForSlowMotion) .setEffects(new Effects(audioProcessors, videoEffects, frameProcessorFactory)) .build(); startTransformationInternal(editedMediaItem, /* path= */ null, parcelFileDescriptor); @@ -738,22 +746,16 @@ public final class Transformer { EditedMediaItem editedMediaItem, @Nullable String path, @Nullable ParcelFileDescriptor parcelFileDescriptor) { - MediaItem mediaItem = editedMediaItem.mediaItem; - if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET) - && transformationRequest.flattenForSlowMotion) { - // TODO(b/233986762): Support clipping with SEF flattening. - throw new IllegalArgumentException( - "Clipping is not supported when slow motion flattening is requested"); - } verifyApplicationThread(); if (transformerInternal != null) { throw new IllegalStateException("There is already a transformation in progress."); } TransformerInternalListener transformerInternalListener = - new TransformerInternalListener(mediaItem); + new TransformerInternalListener(editedMediaItem.mediaItem); HandlerWrapper applicationHandler = clock.createHandler(looper, /* callback= */ null); FallbackListener fallbackListener = - new FallbackListener(mediaItem, listeners, applicationHandler, transformationRequest); + new FallbackListener( + editedMediaItem.mediaItem, listeners, applicationHandler, transformationRequest); transformerInternal = new TransformerInternal( context, 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 faa9fb2c35..6a88fc1eab 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 @@ -33,7 +33,6 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.effect.Presentation; import com.google.android.exoplayer2.effect.ScaleToFitTransformation; import com.google.android.exoplayer2.metadata.Metadata; @@ -99,7 +98,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final HandlerThread internalHandlerThread; private final HandlerWrapper internalHandler; private final AssetLoader assetLoader; - private final Effects effects; private final List samplePipelines; private final MuxerWrapper muxerWrapper; private final ConditionVariable transformerConditionVariable; @@ -138,15 +136,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; internalHandlerThread = new HandlerThread("Transformer:Internal"); internalHandlerThread.start(); Looper internalLooper = internalHandlerThread.getLooper(); - MediaItem mediaItem = editedMediaItem.mediaItem; - ComponentListener componentListener = new ComponentListener(mediaItem, fallbackListener); + ComponentListener componentListener = new ComponentListener(editedMediaItem, fallbackListener); assetLoader = assetLoaderFactory .setRemoveAudio(editedMediaItem.removeAudio) .setRemoveVideo(editedMediaItem.removeVideo) - .setFlattenVideoForSlowMotion(transformationRequest.flattenForSlowMotion) - .createAssetLoader(mediaItem, internalLooper, componentListener); - effects = editedMediaItem.effects; + .setFlattenVideoForSlowMotion(editedMediaItem.flattenForSlowMotion) + .createAssetLoader(editedMediaItem.mediaItem, internalLooper, componentListener); samplePipelines = new ArrayList<>(); muxerWrapper = new MuxerWrapper(outputPath, outputParcelFileDescriptor, muxerFactory, componentListener); @@ -324,7 +320,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private class ComponentListener implements AssetLoader.Listener, MuxerWrapper.Listener { - private final MediaItem mediaItem; + private final EditedMediaItem editedMediaItem; private final FallbackListener fallbackListener; private final AtomicInteger trackCount; @@ -332,8 +328,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private volatile long durationUs; - public ComponentListener(MediaItem mediaItem, FallbackListener fallbackListener) { - this.mediaItem = mediaItem; + public ComponentListener(EditedMediaItem editedMediaItem, FallbackListener fallbackListener) { + this.editedMediaItem = editedMediaItem; this.fallbackListener = fallbackListener; trackCount = new AtomicInteger(); durationUs = C.TIME_UNSET; @@ -479,7 +475,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; streamStartPositionUs, streamOffsetUs, transformationRequest, - effects.audioProcessors, + editedMediaItem.flattenForSlowMotion, + editedMediaItem.effects.audioProcessors, generateSilentAudio ? durationUs : C.TIME_UNSET, encoderFactory, muxerWrapper, @@ -491,8 +488,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; streamStartPositionUs, streamOffsetUs, transformationRequest, - effects.videoEffects, - effects.frameProcessorFactory, + editedMediaItem.effects.videoEffects, + editedMediaItem.effects.frameProcessorFactory, encoderFactory, muxerWrapper, /* errorConsumer= */ this::onTransformationError, @@ -520,10 +517,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; && !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) { return true; } - if (transformationRequest.flattenForSlowMotion && isSlowMotion(inputFormat)) { + if (editedMediaItem.flattenForSlowMotion && isSlowMotion(inputFormat)) { return true; } - if (!effects.audioProcessors.isEmpty()) { + if (!editedMediaItem.effects.audioProcessors.isEmpty()) { return true; } if (generateSilentAudio) { @@ -549,7 +546,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private boolean shouldTranscodeVideo( Format inputFormat, long streamStartPositionUs, long streamOffsetUs) { if ((streamStartPositionUs - streamOffsetUs) != 0 - && !mediaItem.clippingConfiguration.startsAtKeyFrame) { + && !editedMediaItem.mediaItem.clippingConfiguration.startsAtKeyFrame) { return true; } if (encoderFactory.videoNeedsEncoding()) { @@ -571,8 +568,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } // TODO(b/265927935): consider generalizing this logic. - for (int i = 0; i < effects.videoEffects.size(); i++) { - Effect videoEffect = effects.videoEffects.get(i); + for (int i = 0; i < editedMediaItem.effects.videoEffects.size(); i++) { + Effect videoEffect = editedMediaItem.effects.videoEffects.get(i); if (videoEffect instanceof Presentation) { Presentation presentation = (Presentation) videoEffect; // The decoder rotates encoded frames for display by inputFormat.rotationDegrees. diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/EditedMediaItemBuilderTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/EditedMediaItemBuilderTest.java index d2d69dc6f8..e72d4b4135 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/EditedMediaItemBuilderTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/EditedMediaItemBuilderTest.java @@ -38,4 +38,19 @@ public final class EditedMediaItemBuilderTest { .setRemoveVideo(true) .build()); } + + @Test + public void setFlattenForSlowMotion_forClippedMediaItem_throws() { + MediaItem.ClippingConfiguration clippingConfiguration = + new MediaItem.ClippingConfiguration.Builder().setStartPositionMs(1000).build(); + MediaItem mediaItem = + new MediaItem.Builder() + .setUri("Uri") + .setClippingConfiguration(clippingConfiguration) + .build(); + + assertThrows( + IllegalArgumentException.class, + () -> new EditedMediaItem.Builder(mediaItem).setFlattenForSlowMotion(true).build()); + } } diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoaderTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoaderTest.java index d6ef89220d..e65351111c 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoaderTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/ExoPlayerAssetLoaderTest.java @@ -28,8 +28,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; -import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; -import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.util.Clock; import java.time.Duration; import java.util.concurrent.atomic.AtomicBoolean; @@ -118,10 +116,9 @@ public class ExoPlayerAssetLoaderTest { private static AssetLoader getAssetLoader( Looper looper, AssetLoader.Listener listener, Clock clock) { Context context = ApplicationProvider.getApplicationContext(); - MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(context); Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context); MediaItem mediaItem = MediaItem.fromUri("asset:///media/mp4/sample.mp4"); - return new ExoPlayerAssetLoader.Factory(context, mediaSourceFactory, decoderFactory, clock) + return new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock) .setRemoveAudio(false) .setRemoveVideo(false) .setFlattenVideoForSlowMotion(false) diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformationRequestTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformationRequestTest.java index f24fb49941..6caef1c114 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformationRequestTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/TransformationRequestTest.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.transformer; +import static com.google.android.exoplayer2.transformer.TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -35,9 +36,10 @@ public class TransformationRequestTest { private static TransformationRequest createTestTransformationRequest() { return new TransformationRequest.Builder() - .setFlattenForSlowMotion(true) + .setResolution(720) .setAudioMimeType(MimeTypes.AUDIO_AAC) .setVideoMimeType(MimeTypes.VIDEO_H264) + .setHdrMode(HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL) .build(); } } 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 f660efa137..76a3924d3a 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 @@ -502,13 +502,10 @@ public final class TransformerEndToEndTest { @Test public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception { - Transformer transformer = - createTransformerBuilder(/* enableFallback= */ false) - .setTransformationRequest( - new TransformationRequest.Builder().setFlattenForSlowMotion(true).build()) - .build(); + Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build(); EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION)) + .setFlattenForSlowMotion(true) .build(); transformer.startTransformation(editedMediaItem, outputPath); @@ -645,7 +642,7 @@ public final class TransformerEndToEndTest { context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10)); Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context); AssetLoader.Factory assetLoaderFactory = - new ExoPlayerAssetLoader.Factory(context, mediaSourceFactory, decoderFactory, clock); + new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory); Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1); Transformer transformer = createTransformerBuilder(/* enableFallback= */ false)