Move flattenForSlowMotion to EditedMediaItem

PiperOrigin-RevId: 504867150
This commit is contained in:
kimvde 2023-01-26 17:45:35 +00:00 committed by christosts
parent 697f84161d
commit c3934c532c
15 changed files with 187 additions and 138 deletions

View File

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

View File

@ -35,7 +35,6 @@ import androidx.media3.transformer.AndroidTestUtil.ForceEncodeEncoderFactory;
import androidx.media3.transformer.DefaultEncoderFactory;
import androidx.media3.transformer.EditedMediaItem;
import androidx.media3.transformer.Effects;
import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.TransformerAndroidTestRunner;
import androidx.media3.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);

View File

@ -76,7 +76,7 @@ public interface AssetLoader {
* this is done on decoded samples.
*
* <p>For more information on slow motion flattening, see {@link
* TransformationRequest.Builder#setFlattenForSlowMotion(boolean)}.
* EditedMediaItem.Builder#setFlattenForSlowMotion(boolean)}.
*/
@CanIgnoreReturnValue
Factory setFlattenVideoForSlowMotion(boolean flattenVideoForSlowMotion);

View File

@ -63,6 +63,7 @@ import org.checkerframework.dataflow.qual.Pure;
long streamStartPositionUs,
long streamOffsetUs,
TransformationRequest transformationRequest,
boolean flattenForSlowMotion,
ImmutableList<AudioProcessor> 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<AudioProcessor>()
.add(new SpeedChangingAudioProcessor(new SegmentSpeedProvider(inputFormat)))

View File

@ -34,20 +34,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

View File

@ -15,10 +15,13 @@
*/
package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.extractor.mp4.Mp4Extractor;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -34,6 +37,7 @@ public class EditedMediaItem {
private boolean removeAudio;
private boolean removeVideo;
private boolean flattenForSlowMotion;
private @MonotonicNonNull Effects effects;
/**
@ -79,6 +83,47 @@ public class EditedMediaItem {
return this;
}
/**
* Sets whether to flatten the {@link MediaItem} if it contains slow motion markers.
*
* <p>The default value is {@code false}.
*
* <p>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.
*
* <p>Only Samsung Extension Format (SEF) slow motion metadata type is supported. Flattening has
* no effect if the input does not contain this metadata type.
*
* <p>For SEF slow motion media, the following assumptions are made on the input:
*
* <ul>
* <li>The input container format is (unfragmented) MP4.
* <li>The input contains an AVC video elementary stream with temporal SVC.
* <li>The recording frame rate of the video is 120 or 240 fps.
* </ul>
*
* <p>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.
*
* <p>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}.
*
@ -100,21 +145,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;
}
}

View File

@ -49,8 +49,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.
*/

View File

@ -47,10 +47,13 @@ import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.metadata.MetadataOutput;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.text.TextOutput;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.video.VideoRendererEventListener;
import androidx.media3.extractor.DefaultExtractorsFactory;
import androidx.media3.extractor.mp4.Mp4Extractor;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
@ -62,34 +65,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
@ -115,6 +134,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,

View File

@ -26,8 +26,6 @@ import androidx.media3.common.C;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.extractor.mp4.Mp4Extractor;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@ -110,7 +108,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;
@ -127,48 +124,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.
*
* <p>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}.
*
* <p>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.
*
* <p>For SEF slow motion media, the following assumptions are made on the input:
*
* <ul>
* <li>The input container format is (unfragmented) MP4.
* <li>The input contains an AVC video elementary stream with temporal SVC.
* <li>The recording frame rate of the video is 120 or 240 fps.
* </ul>
*
* <p>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.
*
* <p>Using slow motion flattening together with {@link
* androidx.media3.common.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.
*
@ -290,17 +251,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.
*
@ -329,13 +283,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;
@ -351,8 +302,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;
@ -360,8 +310,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;

View File

@ -42,9 +42,6 @@ import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.effect.GlEffectsFrameProcessor;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.extractor.DefaultExtractorsFactory;
import androidx.media3.extractor.mp4.Mp4Extractor;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
@ -52,6 +49,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.
@ -86,9 +84,10 @@ public final class Transformer {
private ImmutableList<Effect> videoEffects;
private boolean removeAudio;
private boolean removeVideo;
private boolean flattenForSlowMotion;
private boolean generateSilentAudio;
private ListenerSet<Transformer.Listener> listeners;
@Nullable private AssetLoader.Factory assetLoaderFactory;
private AssetLoader.@MonotonicNonNull Factory assetLoaderFactory;
private FrameProcessor.Factory frameProcessorFactory;
private Codec.EncoderFactory encoderFactory;
private Muxer.Factory muxerFactory;
@ -199,14 +198,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;
}
@ -419,15 +418,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,
@ -436,6 +428,7 @@ public final class Transformer {
videoEffects,
removeAudio,
removeVideo,
flattenForSlowMotion,
generateSilentAudio,
listeners,
assetLoaderFactory,
@ -556,6 +549,7 @@ public final class Transformer {
private final ImmutableList<Effect> videoEffects;
private final boolean removeAudio;
private final boolean removeVideo;
private final boolean flattenForSlowMotion;
private final boolean generateSilentAudio;
private final ListenerSet<Transformer.Listener> listeners;
private final AssetLoader.Factory assetLoaderFactory;
@ -575,6 +569,7 @@ public final class Transformer {
ImmutableList<Effect> videoEffects,
boolean removeAudio,
boolean removeVideo,
boolean flattenForSlowMotion,
boolean generateSilentAudio,
ListenerSet<Listener> listeners,
AssetLoader.Factory assetLoaderFactory,
@ -591,6 +586,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;
@ -712,10 +708,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);
@ -727,10 +729,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);
@ -740,22 +748,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,

View File

@ -35,7 +35,6 @@ import androidx.media3.common.C;
import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Clock;
@ -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<SamplePipeline> 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.

View File

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

View File

@ -27,8 +27,6 @@ import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Clock;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.time.Duration;
@ -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)

View File

@ -16,6 +16,7 @@
package androidx.media3.transformer;
import static androidx.media3.transformer.TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL;
import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.MimeTypes;
@ -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();
}
}

View File

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