mirror of
https://github.com/androidx/media.git
synced 2025-05-12 18:19:50 +08:00
Handle when some MediaItems require transcoding but others don't
- For single-asset, the behavior stays the same. Transcode if and only if it's necessary, - For constrained multi-asset, always transcode, except if the setter to transmux is set. This is to avoid failing if a MediaItem that doesn't require transcoding is followed by a MediaItem that does require transcoding. PiperOrigin-RevId: 508097798
This commit is contained in:
parent
287cc3a570
commit
07ff48165a
@ -83,6 +83,7 @@ public final class Transformer {
|
|||||||
private boolean removeAudio;
|
private boolean removeAudio;
|
||||||
private boolean removeVideo;
|
private boolean removeVideo;
|
||||||
private boolean flattenForSlowMotion;
|
private boolean flattenForSlowMotion;
|
||||||
|
private boolean transmux;
|
||||||
private boolean generateSilentAudio;
|
private boolean generateSilentAudio;
|
||||||
private ListenerSet<Transformer.Listener> listeners;
|
private ListenerSet<Transformer.Listener> listeners;
|
||||||
private AssetLoader.@MonotonicNonNull Factory assetLoaderFactory;
|
private AssetLoader.@MonotonicNonNull Factory assetLoaderFactory;
|
||||||
@ -204,6 +205,28 @@ public final class Transformer {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether to transmux the {@linkplain MediaItem media items} in the input {@link
|
||||||
|
* Composition}.
|
||||||
|
*
|
||||||
|
* <p>The default value is {@code false}.
|
||||||
|
*
|
||||||
|
* <p>If the input {@link Composition} contains one {@link MediaItem}, the value set is ignored.
|
||||||
|
* The {@link MediaItem} will only be transcoded if necessary.
|
||||||
|
*
|
||||||
|
* <p>If the input {@link Composition} contains multiple {@linkplain MediaItem media items},
|
||||||
|
* they are all transmuxed if {@code transmux} is {@code true} and exporting the first {@link
|
||||||
|
* MediaItem} doesn't require transcoding. Otherwise, they are all transcoded.
|
||||||
|
*
|
||||||
|
* @param transmux Whether to transmux.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setTransmux(boolean transmux) {
|
||||||
|
this.transmux = transmux;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #addListener(Listener)}, {@link #removeListener(Listener)} or {@link
|
* @deprecated Use {@link #addListener(Listener)}, {@link #removeListener(Listener)} or {@link
|
||||||
* #removeAllListeners()} instead.
|
* #removeAllListeners()} instead.
|
||||||
@ -421,6 +444,7 @@ public final class Transformer {
|
|||||||
removeAudio,
|
removeAudio,
|
||||||
removeVideo,
|
removeVideo,
|
||||||
flattenForSlowMotion,
|
flattenForSlowMotion,
|
||||||
|
transmux,
|
||||||
generateSilentAudio,
|
generateSilentAudio,
|
||||||
listeners,
|
listeners,
|
||||||
assetLoaderFactory,
|
assetLoaderFactory,
|
||||||
@ -578,6 +602,7 @@ public final class Transformer {
|
|||||||
private final boolean removeAudio;
|
private final boolean removeAudio;
|
||||||
private final boolean removeVideo;
|
private final boolean removeVideo;
|
||||||
private final boolean flattenForSlowMotion;
|
private final boolean flattenForSlowMotion;
|
||||||
|
private final boolean transmux;
|
||||||
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;
|
||||||
@ -598,6 +623,7 @@ public final class Transformer {
|
|||||||
boolean removeAudio,
|
boolean removeAudio,
|
||||||
boolean removeVideo,
|
boolean removeVideo,
|
||||||
boolean flattenForSlowMotion,
|
boolean flattenForSlowMotion,
|
||||||
|
boolean transmux,
|
||||||
boolean generateSilentAudio,
|
boolean generateSilentAudio,
|
||||||
ListenerSet<Listener> listeners,
|
ListenerSet<Listener> listeners,
|
||||||
AssetLoader.Factory assetLoaderFactory,
|
AssetLoader.Factory assetLoaderFactory,
|
||||||
@ -615,6 +641,7 @@ public final class Transformer {
|
|||||||
this.removeAudio = removeAudio;
|
this.removeAudio = removeAudio;
|
||||||
this.removeVideo = removeVideo;
|
this.removeVideo = removeVideo;
|
||||||
this.flattenForSlowMotion = flattenForSlowMotion;
|
this.flattenForSlowMotion = flattenForSlowMotion;
|
||||||
|
this.transmux = transmux;
|
||||||
this.generateSilentAudio = generateSilentAudio;
|
this.generateSilentAudio = generateSilentAudio;
|
||||||
this.listeners = listeners;
|
this.listeners = listeners;
|
||||||
this.assetLoaderFactory = assetLoaderFactory;
|
this.assetLoaderFactory = assetLoaderFactory;
|
||||||
@ -692,7 +719,6 @@ public final class Transformer {
|
|||||||
* flattening}).
|
* flattening}).
|
||||||
* <li>have identical {@link EditedMediaItem#effects Effects} applied.
|
* <li>have identical {@link EditedMediaItem#effects Effects} applied.
|
||||||
* <li>not represent an image.
|
* <li>not represent an image.
|
||||||
* <li>be such that either all or none requires transcoding.
|
|
||||||
* </ul>
|
* </ul>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
@ -735,6 +761,7 @@ public final class Transformer {
|
|||||||
composition,
|
composition,
|
||||||
path,
|
path,
|
||||||
transformationRequest,
|
transformationRequest,
|
||||||
|
transmux,
|
||||||
generateSilentAudio,
|
generateSilentAudio,
|
||||||
assetLoaderFactory,
|
assetLoaderFactory,
|
||||||
encoderFactory,
|
encoderFactory,
|
||||||
|
@ -112,6 +112,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
Composition composition,
|
Composition composition,
|
||||||
String outputPath,
|
String outputPath,
|
||||||
TransformationRequest transformationRequest,
|
TransformationRequest transformationRequest,
|
||||||
|
boolean transmux,
|
||||||
boolean generateSilentAudio,
|
boolean generateSilentAudio,
|
||||||
AssetLoader.Factory assetLoaderFactory,
|
AssetLoader.Factory assetLoaderFactory,
|
||||||
Codec.EncoderFactory encoderFactory,
|
Codec.EncoderFactory encoderFactory,
|
||||||
@ -133,7 +134,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
internalHandlerThread.start();
|
internalHandlerThread.start();
|
||||||
Looper internalLooper = internalHandlerThread.getLooper();
|
Looper internalLooper = internalHandlerThread.getLooper();
|
||||||
EditedMediaItemSequence sequence = composition.sequences.get(0);
|
EditedMediaItemSequence sequence = composition.sequences.get(0);
|
||||||
ComponentListener componentListener = new ComponentListener(sequence, fallbackListener);
|
ComponentListener componentListener =
|
||||||
|
new ComponentListener(sequence, transmux, fallbackListener);
|
||||||
compositeAssetLoader =
|
compositeAssetLoader =
|
||||||
new CompositeAssetLoader(
|
new CompositeAssetLoader(
|
||||||
sequence, assetLoaderFactory, internalLooper, componentListener, clock);
|
sequence, assetLoaderFactory, internalLooper, componentListener, clock);
|
||||||
@ -314,6 +316,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
// The first EditedMediaItem in the sequence determines which SamplePipeline to use.
|
// The first EditedMediaItem in the sequence determines which SamplePipeline to use.
|
||||||
private final EditedMediaItem firstEditedMediaItem;
|
private final EditedMediaItem firstEditedMediaItem;
|
||||||
|
private final int mediaItemCount;
|
||||||
|
private final boolean transmux;
|
||||||
private final FallbackListener fallbackListener;
|
private final FallbackListener fallbackListener;
|
||||||
private final AtomicInteger trackCount;
|
private final AtomicInteger trackCount;
|
||||||
|
|
||||||
@ -321,8 +325,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
private volatile long durationUs;
|
private volatile long durationUs;
|
||||||
|
|
||||||
public ComponentListener(EditedMediaItemSequence sequence, FallbackListener fallbackListener) {
|
public ComponentListener(
|
||||||
|
EditedMediaItemSequence sequence, boolean transmux, FallbackListener fallbackListener) {
|
||||||
firstEditedMediaItem = sequence.editedMediaItems.get(0);
|
firstEditedMediaItem = sequence.editedMediaItems.get(0);
|
||||||
|
mediaItemCount = sequence.editedMediaItems.size();
|
||||||
|
this.transmux = transmux;
|
||||||
this.fallbackListener = fallbackListener;
|
this.fallbackListener = fallbackListener;
|
||||||
trackCount = new AtomicInteger();
|
trackCount = new AtomicInteger();
|
||||||
durationUs = C.TIME_UNSET;
|
durationUs = C.TIME_UNSET;
|
||||||
@ -457,10 +464,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
throws TransformationException {
|
throws TransformationException {
|
||||||
checkState(supportedOutputTypes != 0);
|
checkState(supportedOutputTypes != 0);
|
||||||
boolean isAudio = MimeTypes.isAudio(firstInputFormat.sampleMimeType);
|
boolean isAudio = MimeTypes.isAudio(firstInputFormat.sampleMimeType);
|
||||||
boolean shouldTranscode =
|
boolean shouldTranscode;
|
||||||
|
if (mediaItemCount > 1 && !transmux) {
|
||||||
|
shouldTranscode = true;
|
||||||
|
} else {
|
||||||
|
shouldTranscode =
|
||||||
isAudio
|
isAudio
|
||||||
? shouldTranscodeAudio(firstInputFormat)
|
? shouldTranscodeAudio(firstInputFormat)
|
||||||
: shouldTranscodeVideo(firstInputFormat, streamStartPositionUs, streamOffsetUs);
|
: shouldTranscodeVideo(firstInputFormat, streamStartPositionUs, streamOffsetUs);
|
||||||
|
}
|
||||||
boolean assetLoaderNeverDecodes = (supportedOutputTypes & SUPPORTED_OUTPUT_TYPE_DECODED) == 0;
|
boolean assetLoaderNeverDecodes = (supportedOutputTypes & SUPPORTED_OUTPUT_TYPE_DECODED) == 0;
|
||||||
checkState(!shouldTranscode || !assetLoaderNeverDecodes);
|
checkState(!shouldTranscode || !assetLoaderNeverDecodes);
|
||||||
boolean assetLoaderAlwaysDecodes =
|
boolean assetLoaderAlwaysDecodes =
|
||||||
|
@ -379,7 +379,8 @@ public final class TransformerEndToEndTest {
|
|||||||
@Test
|
@Test
|
||||||
public void startTransformation_concatenateMediaItemsWithSameFormat_completesSuccessfully()
|
public void startTransformation_concatenateMediaItemsWithSameFormat_completesSuccessfully()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
|
Transformer transformer =
|
||||||
|
createTransformerBuilder(/* enableFallback= */ false).setTransmux(true).build();
|
||||||
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||||
EditedMediaItem editedMediaItem =
|
EditedMediaItem editedMediaItem =
|
||||||
new EditedMediaItem.Builder(mediaItem).setEffects(Effects.EMPTY).build();
|
new EditedMediaItem.Builder(mediaItem).setEffects(Effects.EMPTY).build();
|
||||||
@ -423,6 +424,51 @@ public final class TransformerEndToEndTest {
|
|||||||
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silence_skipped_concatenated"));
|
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silence_skipped_concatenated"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void startTransformation_singleMediaItemAndTransmux_ignoresTransmux() throws Exception {
|
||||||
|
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
|
||||||
|
sonicAudioProcessor.setOutputSampleRateHz(48000);
|
||||||
|
Transformer transformer =
|
||||||
|
createTransformerBuilder(/* enableFallback= */ false).setTransmux(true).build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||||
|
ImmutableList<AudioProcessor> audioProcessors = ImmutableList.of(sonicAudioProcessor);
|
||||||
|
Effects effects = new Effects(audioProcessors, /* videoEffects= */ ImmutableList.of());
|
||||||
|
EditedMediaItem editedMediaItem =
|
||||||
|
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
|
||||||
|
|
||||||
|
transformer.start(editedMediaItem, outputPath);
|
||||||
|
TransformerTestRunner.runLooper(transformer);
|
||||||
|
|
||||||
|
DumpFileAsserts.assertOutput(
|
||||||
|
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".48000hz"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void startTransformation_multipleMediaItemsWithEffectsAndTransmux_ignoresTransmux()
|
||||||
|
throws Exception {
|
||||||
|
Transformer transformer =
|
||||||
|
createTransformerBuilder(/* enableFallback= */ false).setTransmux(true).build();
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
|
||||||
|
AudioProcessor audioProcessor = new SilenceSkippingAudioProcessor();
|
||||||
|
Effects effects =
|
||||||
|
new Effects(ImmutableList.of(audioProcessor), /* videoEffects= */ ImmutableList.of());
|
||||||
|
EditedMediaItem editedMediaItem =
|
||||||
|
new EditedMediaItem.Builder(mediaItem).setEffects(effects).setRemoveVideo(true).build();
|
||||||
|
EditedMediaItemSequence editedMediaItemSequence =
|
||||||
|
new EditedMediaItemSequence(ImmutableList.of(editedMediaItem, editedMediaItem));
|
||||||
|
Composition composition =
|
||||||
|
new Composition(ImmutableList.of(editedMediaItemSequence), Effects.EMPTY);
|
||||||
|
|
||||||
|
transformer.start(composition, outputPath);
|
||||||
|
TransformerTestRunner.runLooper(transformer);
|
||||||
|
|
||||||
|
// The inputs should be transcoded even though transmuxing has been requested. This is because
|
||||||
|
// audio effects have been added to the first MediaItem in the sequence, so the transcoding
|
||||||
|
// audio sample pipeline should be picked to apply these effects.
|
||||||
|
DumpFileAsserts.assertOutput(
|
||||||
|
context, testMuxer, getDumpFileName(FILE_AUDIO_VIDEO + ".silence_skipped_concatenated"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void startTransformation_withMultipleListeners_callsEachOnCompletion() throws Exception {
|
public void startTransformation_withMultipleListeners_callsEachOnCompletion() throws Exception {
|
||||||
Transformer.Listener mockListener1 = mock(Transformer.Listener.class);
|
Transformer.Listener mockListener1 = mock(Transformer.Listener.class);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user