Move flattenForSlowMotion to EditedMediaItem
PiperOrigin-RevId: 504867150
This commit is contained in:
parent
697f84161d
commit
c3934c532c
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)))
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user