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);
|
Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this);
|
||||||
if (bundle != null) {
|
if (bundle != null) {
|
||||||
TransformationRequest.Builder requestBuilder = new TransformationRequest.Builder();
|
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);
|
@Nullable String audioMimeType = bundle.getString(ConfigurationActivity.AUDIO_MIME_TYPE);
|
||||||
if (audioMimeType != null) {
|
if (audioMimeType != null) {
|
||||||
requestBuilder.setAudioMimeType(audioMimeType);
|
requestBuilder.setAudioMimeType(audioMimeType);
|
||||||
@ -352,6 +350,8 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
return editedMediaItemBuilder
|
return editedMediaItemBuilder
|
||||||
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
|
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
|
||||||
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
|
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
|
||||||
|
.setFlattenForSlowMotion(
|
||||||
|
bundle.getBoolean(ConfigurationActivity.SHOULD_FLATTEN_FOR_SLOW_MOTION))
|
||||||
.setEffects(new Effects(audioProcessors, videoEffects))
|
.setEffects(new Effects(audioProcessors, videoEffects))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ import androidx.media3.transformer.AndroidTestUtil.ForceEncodeEncoderFactory;
|
|||||||
import androidx.media3.transformer.DefaultEncoderFactory;
|
import androidx.media3.transformer.DefaultEncoderFactory;
|
||||||
import androidx.media3.transformer.EditedMediaItem;
|
import androidx.media3.transformer.EditedMediaItem;
|
||||||
import androidx.media3.transformer.Effects;
|
import androidx.media3.transformer.Effects;
|
||||||
import androidx.media3.transformer.TransformationRequest;
|
|
||||||
import androidx.media3.transformer.Transformer;
|
import androidx.media3.transformer.Transformer;
|
||||||
import androidx.media3.transformer.TransformerAndroidTestRunner;
|
import androidx.media3.transformer.TransformerAndroidTestRunner;
|
||||||
import androidx.media3.transformer.VideoEncoderSettings;
|
import androidx.media3.transformer.VideoEncoderSettings;
|
||||||
@ -203,13 +202,11 @@ public class TransformationTest {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Transformer transformer =
|
Transformer transformer = new Transformer.Builder(context).build();
|
||||||
new Transformer.Builder(context)
|
|
||||||
.setTransformationRequest(
|
|
||||||
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
|
|
||||||
.build();
|
|
||||||
EditedMediaItem editedMediaItem =
|
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)
|
new TransformerAndroidTestRunner.Builder(context, transformer)
|
||||||
.build()
|
.build()
|
||||||
.run(testId, editedMediaItem);
|
.run(testId, editedMediaItem);
|
||||||
|
@ -76,7 +76,7 @@ public interface AssetLoader {
|
|||||||
* this is done on decoded samples.
|
* this is done on decoded samples.
|
||||||
*
|
*
|
||||||
* <p>For more information on slow motion flattening, see {@link
|
* <p>For more information on slow motion flattening, see {@link
|
||||||
* TransformationRequest.Builder#setFlattenForSlowMotion(boolean)}.
|
* EditedMediaItem.Builder#setFlattenForSlowMotion(boolean)}.
|
||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
Factory setFlattenVideoForSlowMotion(boolean flattenVideoForSlowMotion);
|
Factory setFlattenVideoForSlowMotion(boolean flattenVideoForSlowMotion);
|
||||||
|
@ -63,6 +63,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
long streamStartPositionUs,
|
long streamStartPositionUs,
|
||||||
long streamOffsetUs,
|
long streamOffsetUs,
|
||||||
TransformationRequest transformationRequest,
|
TransformationRequest transformationRequest,
|
||||||
|
boolean flattenForSlowMotion,
|
||||||
ImmutableList<AudioProcessor> audioProcessors,
|
ImmutableList<AudioProcessor> audioProcessors,
|
||||||
long generateSilentAudioDurationUs,
|
long generateSilentAudioDurationUs,
|
||||||
Codec.EncoderFactory encoderFactory,
|
Codec.EncoderFactory encoderFactory,
|
||||||
@ -89,7 +90,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
encoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
encoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
encoderOutputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
encoderOutputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
|
|
||||||
if (transformationRequest.flattenForSlowMotion) {
|
if (flattenForSlowMotion) {
|
||||||
audioProcessors =
|
audioProcessors =
|
||||||
new ImmutableList.Builder<AudioProcessor>()
|
new ImmutableList.Builder<AudioProcessor>()
|
||||||
.add(new SpeedChangingAudioProcessor(new SegmentSpeedProvider(inputFormat)))
|
.add(new SpeedChangingAudioProcessor(new SegmentSpeedProvider(inputFormat)))
|
||||||
|
@ -34,20 +34,34 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory {
|
|||||||
* Creates an instance.
|
* Creates an instance.
|
||||||
*
|
*
|
||||||
* @param context The {@link Context}.
|
* @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
|
* @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
|
||||||
* necessary).
|
* necessary).
|
||||||
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
|
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
|
||||||
* testing.
|
* 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(
|
public DefaultAssetLoaderFactory(
|
||||||
Context context,
|
Context context,
|
||||||
MediaSource.Factory mediaSourceFactory,
|
|
||||||
Codec.DecoderFactory decoderFactory,
|
Codec.DecoderFactory decoderFactory,
|
||||||
Clock clock) {
|
Clock clock,
|
||||||
|
MediaSource.Factory mediaSourceFactory) {
|
||||||
assetLoaderFactory =
|
assetLoaderFactory =
|
||||||
new ExoPlayerAssetLoader.Factory(context, mediaSourceFactory, decoderFactory, clock);
|
new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,10 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.transformer;
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
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.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@ -34,6 +37,7 @@ public class EditedMediaItem {
|
|||||||
|
|
||||||
private boolean removeAudio;
|
private boolean removeAudio;
|
||||||
private boolean removeVideo;
|
private boolean removeVideo;
|
||||||
|
private boolean flattenForSlowMotion;
|
||||||
private @MonotonicNonNull Effects effects;
|
private @MonotonicNonNull Effects effects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,6 +83,47 @@ public class EditedMediaItem {
|
|||||||
return this;
|
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}.
|
* Sets the {@link Effects} to apply to the {@link MediaItem}.
|
||||||
*
|
*
|
||||||
@ -100,21 +145,28 @@ public class EditedMediaItem {
|
|||||||
new Effects(
|
new Effects(
|
||||||
/* audioProcessors= */ ImmutableList.of(), /* videoEffects= */ ImmutableList.of());
|
/* 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 MediaItem mediaItem;
|
||||||
/* package */ final boolean removeAudio;
|
/* package */ final boolean removeAudio;
|
||||||
/* package */ final boolean removeVideo;
|
/* package */ final boolean removeVideo;
|
||||||
|
/* package */ final boolean flattenForSlowMotion;
|
||||||
/* package */ final Effects effects;
|
/* package */ final Effects effects;
|
||||||
|
|
||||||
private EditedMediaItem(
|
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");
|
checkState(!removeAudio || !removeVideo, "Audio and video cannot both be removed");
|
||||||
this.mediaItem = mediaItem;
|
this.mediaItem = mediaItem;
|
||||||
this.removeAudio = removeAudio;
|
this.removeAudio = removeAudio;
|
||||||
this.removeVideo = removeVideo;
|
this.removeVideo = removeVideo;
|
||||||
|
this.flattenForSlowMotion = flattenForSlowMotion;
|
||||||
this.effects = effects;
|
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
|
* 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.
|
* 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
|
* @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
|
* applied in the order of the list.
|
||||||
* TransformationRequest.Builder#setFlattenForSlowMotion(boolean) slow-motion flattening}.
|
|
||||||
* @param frameProcessorFactory The {@link FrameProcessor.Factory} for the {@link FrameProcessor}
|
* @param frameProcessorFactory The {@link FrameProcessor.Factory} for the {@link FrameProcessor}
|
||||||
* to use when applying the {@code videoEffects} to the video frames.
|
* 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.RenderersFactory;
|
||||||
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
|
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
|
||||||
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
import androidx.media3.exoplayer.metadata.MetadataOutput;
|
||||||
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||||
import androidx.media3.exoplayer.source.MediaSource;
|
import androidx.media3.exoplayer.source.MediaSource;
|
||||||
import androidx.media3.exoplayer.text.TextOutput;
|
import androidx.media3.exoplayer.text.TextOutput;
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||||
import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
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.common.collect.ImmutableMap;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
|
||||||
@ -62,34 +65,50 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
|
|||||||
public static final class Factory implements AssetLoader.Factory {
|
public static final class Factory implements AssetLoader.Factory {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final MediaSource.Factory mediaSourceFactory;
|
|
||||||
private final Codec.DecoderFactory decoderFactory;
|
private final Codec.DecoderFactory decoderFactory;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
@Nullable private final MediaSource.Factory mediaSourceFactory;
|
||||||
|
|
||||||
private boolean removeAudio;
|
private boolean removeAudio;
|
||||||
private boolean removeVideo;
|
private boolean removeVideo;
|
||||||
private boolean flattenVideoForSlowMotion;
|
private boolean flattenVideoForSlowMotion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance.
|
* Creates an instance using a {@link DefaultMediaSourceFactory}.
|
||||||
*
|
*
|
||||||
* @param context The {@link Context}.
|
* @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
|
* @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
|
||||||
* necessary).
|
* necessary).
|
||||||
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
|
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
|
||||||
* testing.
|
* testing.
|
||||||
*/
|
*/
|
||||||
public Factory(
|
public Factory(Context context, Codec.DecoderFactory decoderFactory, Clock clock) {
|
||||||
Context context,
|
|
||||||
MediaSource.Factory mediaSourceFactory,
|
|
||||||
Codec.DecoderFactory decoderFactory,
|
|
||||||
Clock clock) {
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.mediaSourceFactory = mediaSourceFactory;
|
|
||||||
this.decoderFactory = decoderFactory;
|
this.decoderFactory = decoderFactory;
|
||||||
this.clock = clock;
|
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
|
@Override
|
||||||
@ -115,6 +134,14 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AssetLoader createAssetLoader(MediaItem mediaItem, Looper looper, Listener listener) {
|
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(
|
return new ExoPlayerAssetLoader(
|
||||||
context,
|
context,
|
||||||
mediaItem,
|
mediaItem,
|
||||||
|
@ -26,8 +26,6 @@ import androidx.media3.common.C;
|
|||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
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 com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -110,7 +108,6 @@ public final class TransformationRequest {
|
|||||||
/** A builder for {@link TransformationRequest} instances. */
|
/** A builder for {@link TransformationRequest} instances. */
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
|
|
||||||
private boolean flattenForSlowMotion;
|
|
||||||
private int outputHeight;
|
private int outputHeight;
|
||||||
@Nullable private String audioMimeType;
|
@Nullable private String audioMimeType;
|
||||||
@Nullable private String videoMimeType;
|
@Nullable private String videoMimeType;
|
||||||
@ -127,48 +124,12 @@ public final class TransformationRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Builder(TransformationRequest transformationRequest) {
|
private Builder(TransformationRequest transformationRequest) {
|
||||||
this.flattenForSlowMotion = transformationRequest.flattenForSlowMotion;
|
|
||||||
this.outputHeight = transformationRequest.outputHeight;
|
this.outputHeight = transformationRequest.outputHeight;
|
||||||
this.audioMimeType = transformationRequest.audioMimeType;
|
this.audioMimeType = transformationRequest.audioMimeType;
|
||||||
this.videoMimeType = transformationRequest.videoMimeType;
|
this.videoMimeType = transformationRequest.videoMimeType;
|
||||||
this.hdrMode = transformationRequest.hdrMode;
|
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.
|
* 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. */
|
/** Builds a {@link TransformationRequest} instance. */
|
||||||
public TransformationRequest build() {
|
public TransformationRequest build() {
|
||||||
return new TransformationRequest(
|
return new TransformationRequest(outputHeight, audioMimeType, videoMimeType, hdrMode);
|
||||||
flattenForSlowMotion, 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.
|
* 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;
|
public final @HdrMode int hdrMode;
|
||||||
|
|
||||||
private TransformationRequest(
|
private TransformationRequest(
|
||||||
boolean flattenForSlowMotion,
|
|
||||||
int outputHeight,
|
int outputHeight,
|
||||||
@Nullable String audioMimeType,
|
@Nullable String audioMimeType,
|
||||||
@Nullable String videoMimeType,
|
@Nullable String videoMimeType,
|
||||||
@HdrMode int hdrMode) {
|
@HdrMode int hdrMode) {
|
||||||
|
|
||||||
this.flattenForSlowMotion = flattenForSlowMotion;
|
|
||||||
this.outputHeight = outputHeight;
|
this.outputHeight = outputHeight;
|
||||||
this.audioMimeType = audioMimeType;
|
this.audioMimeType = audioMimeType;
|
||||||
this.videoMimeType = videoMimeType;
|
this.videoMimeType = videoMimeType;
|
||||||
@ -351,8 +302,7 @@ public final class TransformationRequest {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TransformationRequest that = (TransformationRequest) o;
|
TransformationRequest that = (TransformationRequest) o;
|
||||||
return flattenForSlowMotion == that.flattenForSlowMotion
|
return outputHeight == that.outputHeight
|
||||||
&& outputHeight == that.outputHeight
|
|
||||||
&& Util.areEqual(audioMimeType, that.audioMimeType)
|
&& Util.areEqual(audioMimeType, that.audioMimeType)
|
||||||
&& Util.areEqual(videoMimeType, that.videoMimeType)
|
&& Util.areEqual(videoMimeType, that.videoMimeType)
|
||||||
&& hdrMode == that.hdrMode;
|
&& hdrMode == that.hdrMode;
|
||||||
@ -360,8 +310,7 @@ public final class TransformationRequest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = (flattenForSlowMotion ? 1 : 0);
|
int result = outputHeight;
|
||||||
result = 31 * result + outputHeight;
|
|
||||||
result = 31 * result + (audioMimeType != null ? audioMimeType.hashCode() : 0);
|
result = 31 * result + (audioMimeType != null ? audioMimeType.hashCode() : 0);
|
||||||
result = 31 * result + (videoMimeType != null ? videoMimeType.hashCode() : 0);
|
result = 31 * result + (videoMimeType != null ? videoMimeType.hashCode() : 0);
|
||||||
result = 31 * result + hdrMode;
|
result = 31 * result + hdrMode;
|
||||||
|
@ -42,9 +42,6 @@ import androidx.media3.common.util.UnstableApi;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.effect.GlEffectsFrameProcessor;
|
import androidx.media3.effect.GlEffectsFrameProcessor;
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
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.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
@ -52,6 +49,7 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A transformer to transform media inputs.
|
* A transformer to transform media inputs.
|
||||||
@ -86,9 +84,10 @@ public final class Transformer {
|
|||||||
private ImmutableList<Effect> videoEffects;
|
private ImmutableList<Effect> videoEffects;
|
||||||
private boolean removeAudio;
|
private boolean removeAudio;
|
||||||
private boolean removeVideo;
|
private boolean removeVideo;
|
||||||
|
private boolean flattenForSlowMotion;
|
||||||
private boolean generateSilentAudio;
|
private boolean generateSilentAudio;
|
||||||
private ListenerSet<Transformer.Listener> listeners;
|
private ListenerSet<Transformer.Listener> listeners;
|
||||||
@Nullable private AssetLoader.Factory assetLoaderFactory;
|
private AssetLoader.@MonotonicNonNull Factory assetLoaderFactory;
|
||||||
private FrameProcessor.Factory frameProcessorFactory;
|
private FrameProcessor.Factory frameProcessorFactory;
|
||||||
private Codec.EncoderFactory encoderFactory;
|
private Codec.EncoderFactory encoderFactory;
|
||||||
private Muxer.Factory muxerFactory;
|
private Muxer.Factory muxerFactory;
|
||||||
@ -199,14 +198,14 @@ public final class Transformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link TransformationRequest.Builder#setFlattenForSlowMotion(boolean)}
|
* @deprecated Use {@link EditedMediaItem.Builder#setFlattenForSlowMotion(boolean)} to flatten
|
||||||
* instead.
|
* the {@link EditedMediaItem} passed to {@link #startTransformation(EditedMediaItem,
|
||||||
|
* String)} or {@link #startTransformation(EditedMediaItem, ParcelFileDescriptor)} instead.
|
||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) {
|
public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) {
|
||||||
transformationRequest =
|
this.flattenForSlowMotion = flattenForSlowMotion;
|
||||||
transformationRequest.buildUpon().setFlattenForSlowMotion(flattenForSlowMotion).build();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,15 +418,8 @@ public final class Transformer {
|
|||||||
checkSampleMimeType(transformationRequest.videoMimeType);
|
checkSampleMimeType(transformationRequest.videoMimeType);
|
||||||
}
|
}
|
||||||
if (assetLoaderFactory == null) {
|
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 =
|
assetLoaderFactory =
|
||||||
new DefaultAssetLoaderFactory(context, mediaSourceFactory, decoderFactory, clock);
|
new DefaultAssetLoaderFactory(context, new DefaultDecoderFactory(context), clock);
|
||||||
}
|
}
|
||||||
return new Transformer(
|
return new Transformer(
|
||||||
context,
|
context,
|
||||||
@ -436,6 +428,7 @@ public final class Transformer {
|
|||||||
videoEffects,
|
videoEffects,
|
||||||
removeAudio,
|
removeAudio,
|
||||||
removeVideo,
|
removeVideo,
|
||||||
|
flattenForSlowMotion,
|
||||||
generateSilentAudio,
|
generateSilentAudio,
|
||||||
listeners,
|
listeners,
|
||||||
assetLoaderFactory,
|
assetLoaderFactory,
|
||||||
@ -556,6 +549,7 @@ public final class Transformer {
|
|||||||
private final ImmutableList<Effect> videoEffects;
|
private final ImmutableList<Effect> videoEffects;
|
||||||
private final boolean removeAudio;
|
private final boolean removeAudio;
|
||||||
private final boolean removeVideo;
|
private final boolean removeVideo;
|
||||||
|
private final boolean flattenForSlowMotion;
|
||||||
private final boolean generateSilentAudio;
|
private final boolean generateSilentAudio;
|
||||||
private final ListenerSet<Transformer.Listener> listeners;
|
private final ListenerSet<Transformer.Listener> listeners;
|
||||||
private final AssetLoader.Factory assetLoaderFactory;
|
private final AssetLoader.Factory assetLoaderFactory;
|
||||||
@ -575,6 +569,7 @@ public final class Transformer {
|
|||||||
ImmutableList<Effect> videoEffects,
|
ImmutableList<Effect> videoEffects,
|
||||||
boolean removeAudio,
|
boolean removeAudio,
|
||||||
boolean removeVideo,
|
boolean removeVideo,
|
||||||
|
boolean flattenForSlowMotion,
|
||||||
boolean generateSilentAudio,
|
boolean generateSilentAudio,
|
||||||
ListenerSet<Listener> listeners,
|
ListenerSet<Listener> listeners,
|
||||||
AssetLoader.Factory assetLoaderFactory,
|
AssetLoader.Factory assetLoaderFactory,
|
||||||
@ -591,6 +586,7 @@ public final class Transformer {
|
|||||||
this.videoEffects = videoEffects;
|
this.videoEffects = videoEffects;
|
||||||
this.removeAudio = removeAudio;
|
this.removeAudio = removeAudio;
|
||||||
this.removeVideo = removeVideo;
|
this.removeVideo = removeVideo;
|
||||||
|
this.flattenForSlowMotion = flattenForSlowMotion;
|
||||||
this.generateSilentAudio = generateSilentAudio;
|
this.generateSilentAudio = generateSilentAudio;
|
||||||
this.listeners = listeners;
|
this.listeners = listeners;
|
||||||
this.assetLoaderFactory = assetLoaderFactory;
|
this.assetLoaderFactory = assetLoaderFactory;
|
||||||
@ -712,10 +708,16 @@ public final class Transformer {
|
|||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void startTransformation(MediaItem mediaItem, String path) {
|
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 =
|
EditedMediaItem editedMediaItem =
|
||||||
new EditedMediaItem.Builder(mediaItem)
|
new EditedMediaItem.Builder(mediaItem)
|
||||||
.setRemoveAudio(removeAudio)
|
.setRemoveAudio(removeAudio)
|
||||||
.setRemoveVideo(removeVideo)
|
.setRemoveVideo(removeVideo)
|
||||||
|
.setFlattenForSlowMotion(flattenForSlowMotion)
|
||||||
.setEffects(new Effects(audioProcessors, videoEffects, frameProcessorFactory))
|
.setEffects(new Effects(audioProcessors, videoEffects, frameProcessorFactory))
|
||||||
.build();
|
.build();
|
||||||
startTransformationInternal(editedMediaItem, path, /* parcelFileDescriptor= */ null);
|
startTransformationInternal(editedMediaItem, path, /* parcelFileDescriptor= */ null);
|
||||||
@ -727,10 +729,16 @@ public final class Transformer {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
@RequiresApi(26)
|
@RequiresApi(26)
|
||||||
public void startTransformation(MediaItem mediaItem, ParcelFileDescriptor parcelFileDescriptor) {
|
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 =
|
EditedMediaItem editedMediaItem =
|
||||||
new EditedMediaItem.Builder(mediaItem)
|
new EditedMediaItem.Builder(mediaItem)
|
||||||
.setRemoveAudio(removeAudio)
|
.setRemoveAudio(removeAudio)
|
||||||
.setRemoveVideo(removeVideo)
|
.setRemoveVideo(removeVideo)
|
||||||
|
.setFlattenForSlowMotion(flattenForSlowMotion)
|
||||||
.setEffects(new Effects(audioProcessors, videoEffects, frameProcessorFactory))
|
.setEffects(new Effects(audioProcessors, videoEffects, frameProcessorFactory))
|
||||||
.build();
|
.build();
|
||||||
startTransformationInternal(editedMediaItem, /* path= */ null, parcelFileDescriptor);
|
startTransformationInternal(editedMediaItem, /* path= */ null, parcelFileDescriptor);
|
||||||
@ -740,22 +748,16 @@ public final class Transformer {
|
|||||||
EditedMediaItem editedMediaItem,
|
EditedMediaItem editedMediaItem,
|
||||||
@Nullable String path,
|
@Nullable String path,
|
||||||
@Nullable ParcelFileDescriptor parcelFileDescriptor) {
|
@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();
|
verifyApplicationThread();
|
||||||
if (transformerInternal != null) {
|
if (transformerInternal != null) {
|
||||||
throw new IllegalStateException("There is already a transformation in progress.");
|
throw new IllegalStateException("There is already a transformation in progress.");
|
||||||
}
|
}
|
||||||
TransformerInternalListener transformerInternalListener =
|
TransformerInternalListener transformerInternalListener =
|
||||||
new TransformerInternalListener(mediaItem);
|
new TransformerInternalListener(editedMediaItem.mediaItem);
|
||||||
HandlerWrapper applicationHandler = clock.createHandler(looper, /* callback= */ null);
|
HandlerWrapper applicationHandler = clock.createHandler(looper, /* callback= */ null);
|
||||||
FallbackListener fallbackListener =
|
FallbackListener fallbackListener =
|
||||||
new FallbackListener(mediaItem, listeners, applicationHandler, transformationRequest);
|
new FallbackListener(
|
||||||
|
editedMediaItem.mediaItem, listeners, applicationHandler, transformationRequest);
|
||||||
transformerInternal =
|
transformerInternal =
|
||||||
new TransformerInternal(
|
new TransformerInternal(
|
||||||
context,
|
context,
|
||||||
|
@ -35,7 +35,6 @@ import androidx.media3.common.C;
|
|||||||
import androidx.media3.common.DebugViewProvider;
|
import androidx.media3.common.DebugViewProvider;
|
||||||
import androidx.media3.common.Effect;
|
import androidx.media3.common.Effect;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MediaItem;
|
|
||||||
import androidx.media3.common.Metadata;
|
import androidx.media3.common.Metadata;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
@ -99,7 +98,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private final HandlerThread internalHandlerThread;
|
private final HandlerThread internalHandlerThread;
|
||||||
private final HandlerWrapper internalHandler;
|
private final HandlerWrapper internalHandler;
|
||||||
private final AssetLoader assetLoader;
|
private final AssetLoader assetLoader;
|
||||||
private final Effects effects;
|
|
||||||
private final List<SamplePipeline> samplePipelines;
|
private final List<SamplePipeline> samplePipelines;
|
||||||
private final MuxerWrapper muxerWrapper;
|
private final MuxerWrapper muxerWrapper;
|
||||||
private final ConditionVariable transformerConditionVariable;
|
private final ConditionVariable transformerConditionVariable;
|
||||||
@ -138,15 +136,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
internalHandlerThread = new HandlerThread("Transformer:Internal");
|
internalHandlerThread = new HandlerThread("Transformer:Internal");
|
||||||
internalHandlerThread.start();
|
internalHandlerThread.start();
|
||||||
Looper internalLooper = internalHandlerThread.getLooper();
|
Looper internalLooper = internalHandlerThread.getLooper();
|
||||||
MediaItem mediaItem = editedMediaItem.mediaItem;
|
ComponentListener componentListener = new ComponentListener(editedMediaItem, fallbackListener);
|
||||||
ComponentListener componentListener = new ComponentListener(mediaItem, fallbackListener);
|
|
||||||
assetLoader =
|
assetLoader =
|
||||||
assetLoaderFactory
|
assetLoaderFactory
|
||||||
.setRemoveAudio(editedMediaItem.removeAudio)
|
.setRemoveAudio(editedMediaItem.removeAudio)
|
||||||
.setRemoveVideo(editedMediaItem.removeVideo)
|
.setRemoveVideo(editedMediaItem.removeVideo)
|
||||||
.setFlattenVideoForSlowMotion(transformationRequest.flattenForSlowMotion)
|
.setFlattenVideoForSlowMotion(editedMediaItem.flattenForSlowMotion)
|
||||||
.createAssetLoader(mediaItem, internalLooper, componentListener);
|
.createAssetLoader(editedMediaItem.mediaItem, internalLooper, componentListener);
|
||||||
effects = editedMediaItem.effects;
|
|
||||||
samplePipelines = new ArrayList<>();
|
samplePipelines = new ArrayList<>();
|
||||||
muxerWrapper =
|
muxerWrapper =
|
||||||
new MuxerWrapper(outputPath, outputParcelFileDescriptor, muxerFactory, componentListener);
|
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 class ComponentListener implements AssetLoader.Listener, MuxerWrapper.Listener {
|
||||||
|
|
||||||
private final MediaItem mediaItem;
|
private final EditedMediaItem editedMediaItem;
|
||||||
private final FallbackListener fallbackListener;
|
private final FallbackListener fallbackListener;
|
||||||
private final AtomicInteger trackCount;
|
private final AtomicInteger trackCount;
|
||||||
|
|
||||||
@ -332,8 +328,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
private volatile long durationUs;
|
private volatile long durationUs;
|
||||||
|
|
||||||
public ComponentListener(MediaItem mediaItem, FallbackListener fallbackListener) {
|
public ComponentListener(EditedMediaItem editedMediaItem, FallbackListener fallbackListener) {
|
||||||
this.mediaItem = mediaItem;
|
this.editedMediaItem = editedMediaItem;
|
||||||
this.fallbackListener = fallbackListener;
|
this.fallbackListener = fallbackListener;
|
||||||
trackCount = new AtomicInteger();
|
trackCount = new AtomicInteger();
|
||||||
durationUs = C.TIME_UNSET;
|
durationUs = C.TIME_UNSET;
|
||||||
@ -479,7 +475,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
streamStartPositionUs,
|
streamStartPositionUs,
|
||||||
streamOffsetUs,
|
streamOffsetUs,
|
||||||
transformationRequest,
|
transformationRequest,
|
||||||
effects.audioProcessors,
|
editedMediaItem.flattenForSlowMotion,
|
||||||
|
editedMediaItem.effects.audioProcessors,
|
||||||
generateSilentAudio ? durationUs : C.TIME_UNSET,
|
generateSilentAudio ? durationUs : C.TIME_UNSET,
|
||||||
encoderFactory,
|
encoderFactory,
|
||||||
muxerWrapper,
|
muxerWrapper,
|
||||||
@ -491,8 +488,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
streamStartPositionUs,
|
streamStartPositionUs,
|
||||||
streamOffsetUs,
|
streamOffsetUs,
|
||||||
transformationRequest,
|
transformationRequest,
|
||||||
effects.videoEffects,
|
editedMediaItem.effects.videoEffects,
|
||||||
effects.frameProcessorFactory,
|
editedMediaItem.effects.frameProcessorFactory,
|
||||||
encoderFactory,
|
encoderFactory,
|
||||||
muxerWrapper,
|
muxerWrapper,
|
||||||
/* errorConsumer= */ this::onTransformationError,
|
/* errorConsumer= */ this::onTransformationError,
|
||||||
@ -520,10 +517,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
|
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (transformationRequest.flattenForSlowMotion && isSlowMotion(inputFormat)) {
|
if (editedMediaItem.flattenForSlowMotion && isSlowMotion(inputFormat)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!effects.audioProcessors.isEmpty()) {
|
if (!editedMediaItem.effects.audioProcessors.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (generateSilentAudio) {
|
if (generateSilentAudio) {
|
||||||
@ -549,7 +546,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private boolean shouldTranscodeVideo(
|
private boolean shouldTranscodeVideo(
|
||||||
Format inputFormat, long streamStartPositionUs, long streamOffsetUs) {
|
Format inputFormat, long streamStartPositionUs, long streamOffsetUs) {
|
||||||
if ((streamStartPositionUs - streamOffsetUs) != 0
|
if ((streamStartPositionUs - streamOffsetUs) != 0
|
||||||
&& !mediaItem.clippingConfiguration.startsAtKeyFrame) {
|
&& !editedMediaItem.mediaItem.clippingConfiguration.startsAtKeyFrame) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (encoderFactory.videoNeedsEncoding()) {
|
if (encoderFactory.videoNeedsEncoding()) {
|
||||||
@ -571,8 +568,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(b/265927935): consider generalizing this logic.
|
// TODO(b/265927935): consider generalizing this logic.
|
||||||
for (int i = 0; i < effects.videoEffects.size(); i++) {
|
for (int i = 0; i < editedMediaItem.effects.videoEffects.size(); i++) {
|
||||||
Effect videoEffect = effects.videoEffects.get(i);
|
Effect videoEffect = editedMediaItem.effects.videoEffects.get(i);
|
||||||
if (videoEffect instanceof Presentation) {
|
if (videoEffect instanceof Presentation) {
|
||||||
Presentation presentation = (Presentation) videoEffect;
|
Presentation presentation = (Presentation) videoEffect;
|
||||||
// The decoder rotates encoded frames for display by inputFormat.rotationDegrees.
|
// The decoder rotates encoded frames for display by inputFormat.rotationDegrees.
|
||||||
|
@ -38,4 +38,19 @@ public final class EditedMediaItemBuilderTest {
|
|||||||
.setRemoveVideo(true)
|
.setRemoveVideo(true)
|
||||||
.build());
|
.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.MediaItem;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
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.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@ -118,10 +116,9 @@ public class ExoPlayerAssetLoaderTest {
|
|||||||
private static AssetLoader getAssetLoader(
|
private static AssetLoader getAssetLoader(
|
||||||
Looper looper, AssetLoader.Listener listener, Clock clock) {
|
Looper looper, AssetLoader.Listener listener, Clock clock) {
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(context);
|
|
||||||
Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
|
Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
|
||||||
MediaItem mediaItem = MediaItem.fromUri("asset:///media/mp4/sample.mp4");
|
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)
|
.setRemoveAudio(false)
|
||||||
.setRemoveVideo(false)
|
.setRemoveVideo(false)
|
||||||
.setFlattenVideoForSlowMotion(false)
|
.setFlattenVideoForSlowMotion(false)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package androidx.media3.transformer;
|
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 static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
@ -35,9 +36,10 @@ public class TransformationRequestTest {
|
|||||||
|
|
||||||
private static TransformationRequest createTestTransformationRequest() {
|
private static TransformationRequest createTestTransformationRequest() {
|
||||||
return new TransformationRequest.Builder()
|
return new TransformationRequest.Builder()
|
||||||
.setFlattenForSlowMotion(true)
|
.setResolution(720)
|
||||||
.setAudioMimeType(MimeTypes.AUDIO_AAC)
|
.setAudioMimeType(MimeTypes.AUDIO_AAC)
|
||||||
.setVideoMimeType(MimeTypes.VIDEO_H264)
|
.setVideoMimeType(MimeTypes.VIDEO_H264)
|
||||||
|
.setHdrMode(HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -502,13 +502,10 @@ public final class TransformerEndToEndTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception {
|
public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception {
|
||||||
Transformer transformer =
|
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
||||||
createTransformerBuilder(/* enableFallback= */ false)
|
|
||||||
.setTransformationRequest(
|
|
||||||
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
|
|
||||||
.build();
|
|
||||||
EditedMediaItem editedMediaItem =
|
EditedMediaItem editedMediaItem =
|
||||||
new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION))
|
new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION))
|
||||||
|
.setFlattenForSlowMotion(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
transformer.startTransformation(editedMediaItem, outputPath);
|
transformer.startTransformation(editedMediaItem, outputPath);
|
||||||
@ -645,7 +642,7 @@ public final class TransformerEndToEndTest {
|
|||||||
context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10));
|
context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10));
|
||||||
Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
|
Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
|
||||||
AssetLoader.Factory assetLoaderFactory =
|
AssetLoader.Factory assetLoaderFactory =
|
||||||
new ExoPlayerAssetLoader.Factory(context, mediaSourceFactory, decoderFactory, clock);
|
new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory);
|
||||||
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1);
|
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1);
|
||||||
Transformer transformer =
|
Transformer transformer =
|
||||||
createTransformerBuilder(/* enableFallback= */ false)
|
createTransformerBuilder(/* enableFallback= */ false)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user