Handle multiple sequences in a Composition

This lays the groundwork for full multi-asset, and more particularly for
adding looping background audio.

PiperOrigin-RevId: 512887888
This commit is contained in:
kimvde 2023-02-28 11:53:43 +00:00 committed by tonihei
parent 408b4449ff
commit d2d5174f09
5 changed files with 187 additions and 138 deletions

View File

@ -73,6 +73,7 @@ import java.util.concurrent.atomic.AtomicInteger;
private final AtomicInteger nonEndedTracks; private final AtomicInteger nonEndedTracks;
private AssetLoader currentAssetLoader; private AssetLoader currentAssetLoader;
private boolean trackCountReported;
private int processedInputsSize; private int processedInputsSize;
private volatile long currentDurationUs; private volatile long currentDurationUs;
@ -197,8 +198,11 @@ import java.util.concurrent.atomic.AtomicInteger;
if (currentMediaItemIndex.get() == 0) { if (currentMediaItemIndex.get() == 0) {
boolean addForcedAudioTrack = boolean addForcedAudioTrack =
forceAudioTrack && nonEndedTracks.get() == 1 && trackType == C.TRACK_TYPE_VIDEO; forceAudioTrack && nonEndedTracks.get() == 1 && trackType == C.TRACK_TYPE_VIDEO;
int trackCount = nonEndedTracks.get() + (addForcedAudioTrack ? 1 : 0); if (!trackCountReported) {
compositeAssetLoaderListener.onTrackCount(trackCount); int trackCount = nonEndedTracks.get() + (addForcedAudioTrack ? 1 : 0);
compositeAssetLoaderListener.onTrackCount(trackCount);
trackCountReported = true;
}
sampleConsumer = sampleConsumer =
new SampleConsumerWrapper( new SampleConsumerWrapper(
compositeAssetLoaderListener.onTrackAdded( compositeAssetLoaderListener.onTrackAdded(

View File

@ -23,6 +23,7 @@ import androidx.media3.common.C;
import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.ListenerSet; import androidx.media3.common.util.ListenerSet;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Listener for fallback {@link TransformationRequest TransformationRequests} from the audio and * Listener for fallback {@link TransformationRequest TransformationRequests} from the audio and
@ -31,12 +32,12 @@ import androidx.media3.common.util.Util;
/* package */ final class FallbackListener { /* package */ final class FallbackListener {
private final Composition composition; private final Composition composition;
private final TransformationRequest originalTransformationRequest;
private final ListenerSet<Transformer.Listener> transformerListeners; private final ListenerSet<Transformer.Listener> transformerListeners;
private final HandlerWrapper transformerListenerHandler; private final HandlerWrapper transformerListenerHandler;
private final TransformationRequest originalTransformationRequest;
private final AtomicInteger trackCount;
private TransformationRequest fallbackTransformationRequest; private TransformationRequest fallbackTransformationRequest;
private int trackCount;
/** /**
* Creates a new instance. * Creates a new instance.
@ -58,6 +59,7 @@ import androidx.media3.common.util.Util;
this.transformerListenerHandler = transformerListenerHandler; this.transformerListenerHandler = transformerListenerHandler;
this.originalTransformationRequest = originalTransformationRequest; this.originalTransformationRequest = originalTransformationRequest;
this.fallbackTransformationRequest = originalTransformationRequest; this.fallbackTransformationRequest = originalTransformationRequest;
trackCount = new AtomicInteger();
} }
/** /**
@ -65,9 +67,11 @@ import androidx.media3.common.util.Util;
* *
* <p>The track count must be set before a transformation request is {@linkplain * <p>The track count must be set before a transformation request is {@linkplain
* #onTransformationRequestFinalized(TransformationRequest) finalized}. * #onTransformationRequestFinalized(TransformationRequest) finalized}.
*
* <p>Can be called from any thread.
*/ */
public void setTrackCount(@IntRange(from = 1) int trackCount) { public void setTrackCount(@IntRange(from = 1) int trackCount) {
this.trackCount = trackCount; this.trackCount.set(trackCount);
} }
/** /**
@ -86,7 +90,7 @@ import androidx.media3.common.util.Util;
* #setTrackCount(int)}. * #setTrackCount(int)}.
*/ */
public void onTransformationRequestFinalized(TransformationRequest transformationRequest) { public void onTransformationRequestFinalized(TransformationRequest transformationRequest) {
checkState(trackCount-- > 0); checkState(trackCount.getAndDecrement() > 0);
TransformationRequest.Builder fallbackRequestBuilder = TransformationRequest.Builder fallbackRequestBuilder =
fallbackTransformationRequest.buildUpon(); fallbackTransformationRequest.buildUpon();
@ -107,7 +111,8 @@ import androidx.media3.common.util.Util;
TransformationRequest newFallbackTransformationRequest = fallbackRequestBuilder.build(); TransformationRequest newFallbackTransformationRequest = fallbackRequestBuilder.build();
fallbackTransformationRequest = newFallbackTransformationRequest; fallbackTransformationRequest = newFallbackTransformationRequest;
if (trackCount == 0 && !originalTransformationRequest.equals(fallbackTransformationRequest)) { if (trackCount.get() == 0
&& !originalTransformationRequest.equals(fallbackTransformationRequest)) {
transformerListenerHandler.post( transformerListenerHandler.post(
() -> () ->
transformerListeners.sendEvent( transformerListeners.sendEvent(

View File

@ -69,7 +69,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final SparseArray<TrackInfo> trackTypeToInfo; private final SparseArray<TrackInfo> trackTypeToInfo;
private final ScheduledExecutorService abortScheduledExecutorService; private final ScheduledExecutorService abortScheduledExecutorService;
private int trackCount;
private boolean isReady; private boolean isReady;
private boolean isEnded; private boolean isEnded;
private @C.TrackType int previousTrackType; private @C.TrackType int previousTrackType;
@ -79,6 +78,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private boolean isAborted; private boolean isAborted;
private @MonotonicNonNull Muxer muxer; private @MonotonicNonNull Muxer muxer;
private volatile int trackCount;
public MuxerWrapper(String outputPath, Muxer.Factory muxerFactory, Listener listener) { public MuxerWrapper(String outputPath, Muxer.Factory muxerFactory, Listener listener) {
this.outputPath = outputPath; this.outputPath = outputPath;
this.muxerFactory = muxerFactory; this.muxerFactory = muxerFactory;
@ -95,6 +96,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* <p>The track count must be set before any track format is {@linkplain #addTrackFormat(Format) * <p>The track count must be set before any track format is {@linkplain #addTrackFormat(Format)
* added}. * added}.
* *
* <p>Can be called from any thread.
*
* @throws IllegalStateException If a track format was {@linkplain #addTrackFormat(Format) added} * @throws IllegalStateException If a track format was {@linkplain #addTrackFormat(Format) added}
* before calling this method. * before calling this method.
*/ */
@ -135,6 +138,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* the track. * the track.
*/ */
public void addTrackFormat(Format format) throws Muxer.MuxerException { public void addTrackFormat(Format format) throws Muxer.MuxerException {
int trackCount = this.trackCount;
checkState(trackCount > 0, "The track count should be set before the formats are added."); checkState(trackCount > 0, "The track count should be set before the formats are added.");
checkState(trackTypeToInfo.size() < trackCount, "All track formats have already been added."); checkState(trackTypeToInfo.size() < trackCount, "All track formats have already been added.");
@Nullable String sampleMimeType = format.sampleMimeType; @Nullable String sampleMimeType = format.sampleMimeType;

View File

@ -39,8 +39,9 @@ import java.util.List;
* *
* <p>The {@link SampleConsumer} and {@link OnMediaItemChangedListener} methods must be called from * <p>The {@link SampleConsumer} and {@link OnMediaItemChangedListener} methods must be called from
* the same thread. This thread can change when the {@link * the same thread. This thread can change when the {@link
* OnMediaItemChangedListener#onMediaItemChanged(EditedMediaItem, Format, long) MediaItem} changes, * OnMediaItemChangedListener#onMediaItemChanged(EditedMediaItem, long, Format, boolean) MediaItem}
* and can be different from the thread used to call the other {@code SamplePipeline} methods. * changes, and can be different from the thread used to call the other {@code SamplePipeline}
* methods.
*/ */
/* package */ abstract class SamplePipeline implements SampleConsumer, OnMediaItemChangedListener { /* package */ abstract class SamplePipeline implements SampleConsumer, OnMediaItemChangedListener {

View File

@ -22,6 +22,7 @@ import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECO
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED; import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED;
import static androidx.media3.transformer.ExportException.ERROR_CODE_FAILED_RUNTIME_CHECK; import static androidx.media3.transformer.ExportException.ERROR_CODE_FAILED_RUNTIME_CHECK;
import static androidx.media3.transformer.ExportException.ERROR_CODE_MUXING_FAILED; import static androidx.media3.transformer.ExportException.ERROR_CODE_MUXING_FAILED;
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_AVAILABLE;
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED; import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
@ -53,7 +54,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* package */ final class TransformerInternal { /* package */ final class TransformerInternal implements MuxerWrapper.Listener {
public interface Listener { public interface Listener {
@ -88,15 +89,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private static final int DRAIN_PIPELINES_DELAY_MS = 10; private static final int DRAIN_PIPELINES_DELAY_MS = 10;
private final Context context; private final Context context;
private final TransformationRequest transformationRequest;
private final CapturingEncoderFactory encoderFactory; private final CapturingEncoderFactory encoderFactory;
private final Listener listener; private final Listener listener;
private final HandlerWrapper applicationHandler; private final HandlerWrapper applicationHandler;
private final DebugViewProvider debugViewProvider;
private final Clock clock; private final Clock clock;
private final HandlerThread internalHandlerThread; private final HandlerThread internalHandlerThread;
private final HandlerWrapper internalHandler; private final HandlerWrapper internalHandler;
private final CompositeAssetLoader compositeAssetLoader; private final List<CompositeAssetLoader> compositeAssetLoaders;
private final AtomicInteger totalInputTrackCount;
private final AtomicInteger unreportedInputTrackCounts;
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;
@ -108,6 +109,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private volatile boolean released; private volatile boolean released;
// Warning suppression is needed to assign the MuxerWrapper with "this" as listener.
@SuppressWarnings("assignment.type.incompatible")
public TransformerInternal( public TransformerInternal(
Context context, Context context,
Composition composition, Composition composition,
@ -122,37 +125,34 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
DebugViewProvider debugViewProvider, DebugViewProvider debugViewProvider,
Clock clock) { Clock clock) {
this.context = context; this.context = context;
this.transformationRequest = transformationRequest;
this.encoderFactory = new CapturingEncoderFactory(encoderFactory); this.encoderFactory = new CapturingEncoderFactory(encoderFactory);
this.listener = listener; this.listener = listener;
this.applicationHandler = applicationHandler; this.applicationHandler = applicationHandler;
this.debugViewProvider = debugViewProvider;
this.clock = clock; this.clock = clock;
internalHandlerThread = new HandlerThread("Transformer:Internal"); internalHandlerThread = new HandlerThread("Transformer:Internal");
internalHandlerThread.start(); internalHandlerThread.start();
compositeAssetLoaders = new ArrayList<>();
Looper internalLooper = internalHandlerThread.getLooper(); Looper internalLooper = internalHandlerThread.getLooper();
EditedMediaItemSequence sequence = composition.sequences.get(0); for (int i = 0; i < composition.sequences.size(); i++) {
ImmutableList<Effect> compositionVideoEffects = composition.effects.videoEffects; CompositeAssetLoaderListener compositeAssetLoaderListener =
@Nullable new CompositeAssetLoaderListener(
Presentation presentation = /* sequenceIndex= */ i,
compositionVideoEffects.isEmpty() ? null : (Presentation) compositionVideoEffects.get(0); composition,
ComponentListener componentListener = transformationRequest,
new ComponentListener( fallbackListener,
sequence, debugViewProvider);
presentation, compositeAssetLoaders.add(
composition.transmuxAudio, new CompositeAssetLoader(
composition.transmuxVideo, composition.sequences.get(i),
fallbackListener); composition.forceAudioTrack,
compositeAssetLoader = assetLoaderFactory,
new CompositeAssetLoader( internalLooper,
sequence, compositeAssetLoaderListener,
composition.forceAudioTrack, clock));
assetLoaderFactory, }
internalLooper, totalInputTrackCount = new AtomicInteger();
componentListener, unreportedInputTrackCounts = new AtomicInteger(composition.sequences.size());
clock);
samplePipelines = new ArrayList<>(); samplePipelines = new ArrayList<>();
muxerWrapper = new MuxerWrapper(outputPath, muxerFactory, componentListener);
transformerConditionVariable = new ConditionVariable(); transformerConditionVariable = new ConditionVariable();
exportResultBuilder = new ExportResult.Builder(); exportResultBuilder = new ExportResult.Builder();
// It's safe to use "this" because we don't send a message before exiting the constructor. // It's safe to use "this" because we don't send a message before exiting the constructor.
@ -160,6 +160,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
HandlerWrapper internalHandler = HandlerWrapper internalHandler =
clock.createHandler(internalLooper, /* callback= */ this::handleMessage); clock.createHandler(internalLooper, /* callback= */ this::handleMessage);
this.internalHandler = internalHandler; this.internalHandler = internalHandler;
// It's safe to use "this" because we don't mux any data before exiting the constructor.
@SuppressWarnings("nullness:argument.type.incompatible")
MuxerWrapper muxerWrapper = new MuxerWrapper(outputPath, muxerFactory, /* listener= */ this);
this.muxerWrapper = muxerWrapper;
} }
public void start() { public void start() {
@ -192,6 +196,51 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
} }
// MuxerWrapper.Listener implementation
@Override
public void onTrackEnded(
@C.TrackType int trackType, Format format, int averageBitrate, int sampleCount) {
if (trackType == C.TRACK_TYPE_AUDIO) {
exportResultBuilder.setAverageAudioBitrate(averageBitrate).setPcmEncoding(format.pcmEncoding);
if (format.channelCount != Format.NO_VALUE) {
exportResultBuilder.setChannelCount(format.channelCount);
}
if (format.sampleRate != Format.NO_VALUE) {
exportResultBuilder.setSampleRate(format.sampleRate);
}
} else if (trackType == C.TRACK_TYPE_VIDEO) {
exportResultBuilder
.setAverageVideoBitrate(averageBitrate)
.setColorInfo(format.colorInfo)
.setVideoFrameCount(sampleCount);
if (format.height != Format.NO_VALUE) {
exportResultBuilder.setHeight(format.height);
}
if (format.width != Format.NO_VALUE) {
exportResultBuilder.setWidth(format.width);
}
}
}
@Override
public void onEnded(long durationMs, long fileSizeBytes) {
exportResultBuilder.setDurationMs(durationMs).setFileSizeBytes(fileSizeBytes);
internalHandler
.obtainMessage(MSG_END, END_REASON_COMPLETED, /* unused */ 0, /* exportException */ null)
.sendToTarget();
}
@Override
public void onError(ExportException exportException) {
internalHandler
.obtainMessage(MSG_END, END_REASON_ERROR, /* unused */ 0, exportException)
.sendToTarget();
}
// Private methods.
private boolean handleMessage(Message msg) { private boolean handleMessage(Message msg) {
// Some messages cannot be ignored when resources have been released. End messages must be // Some messages cannot be ignored when resources have been released. End messages must be
// handled to report release timeouts and to unblock the transformer condition variable in case // handled to report release timeouts and to unblock the transformer condition variable in case
@ -229,7 +278,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void startInternal() { private void startInternal() {
compositeAssetLoader.start(); for (int i = 0; i < compositeAssetLoaders.size(); i++) {
compositeAssetLoaders.get(i).start();
}
} }
private void registerSamplePipelineInternal(SamplePipeline samplePipeline) { private void registerSamplePipelineInternal(SamplePipeline samplePipeline) {
@ -251,10 +302,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void endInternal(@EndReason int endReason, @Nullable ExportException exportException) { private void endInternal(@EndReason int endReason, @Nullable ExportException exportException) {
ImmutableList<ExportResult.ProcessedInput> processedInputs = ImmutableList.Builder<ExportResult.ProcessedInput> processedInputsBuilder =
compositeAssetLoader.getProcessedInputs(); new ImmutableList.Builder<>();
for (int i = 0; i < compositeAssetLoaders.size(); i++) {
processedInputsBuilder.addAll(compositeAssetLoaders.get(i).getProcessedInputs());
}
exportResultBuilder exportResultBuilder
.setProcessedInputs(processedInputs) .setProcessedInputs(processedInputsBuilder.build())
.setAudioEncoderName(encoderFactory.getAudioEncoderName()) .setAudioEncoderName(encoderFactory.getAudioEncoderName())
.setVideoEncoderName(encoderFactory.getVideoEncoderName()); .setVideoEncoderName(encoderFactory.getVideoEncoderName());
@ -262,26 +316,39 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable ExportException releaseExportException = null; @Nullable ExportException releaseExportException = null;
if (!released) { if (!released) {
released = true; released = true;
for (int i = 0; i < compositeAssetLoaders.size(); i++) {
try {
try { try {
compositeAssetLoader.release(); compositeAssetLoaders.get(i).release();
} finally { } catch (RuntimeException e) {
try { if (releaseExportException == null) {
for (int i = 0; i < samplePipelines.size(); i++) { releaseExportException = ExportException.createForUnexpected(e);
samplePipelines.get(i).release(); // cancelException is not reported through a listener. It is thrown in cancel(), as this
} // method is blocking.
} finally { cancelException = e;
muxerWrapper.release(forCancellation);
} }
} }
}
for (int i = 0; i < samplePipelines.size(); i++) {
try {
samplePipelines.get(i).release();
} catch (RuntimeException e) {
if (releaseExportException == null) {
releaseExportException = ExportException.createForUnexpected(e);
cancelException = e;
}
}
}
try {
muxerWrapper.release(forCancellation);
} catch (Muxer.MuxerException e) { } catch (Muxer.MuxerException e) {
releaseExportException = ExportException.createForMuxer(e, ERROR_CODE_MUXING_FAILED); if (releaseExportException == null) {
releaseExportException = ExportException.createForMuxer(e, ERROR_CODE_MUXING_FAILED);
}
} catch (RuntimeException e) { } catch (RuntimeException e) {
releaseExportException = ExportException.createForUnexpected(e); if (releaseExportException == null) {
// cancelException is not reported through a listener. It is thrown in cancel(), as this releaseExportException = ExportException.createForUnexpected(e);
// method is blocking. cancelException = e;
cancelException = e; }
} }
// Quit thread lazily so that all events that got triggered when releasing the AssetLoader are // Quit thread lazily so that all events that got triggered when releasing the AssetLoader are
// still delivered. // still delivered.
@ -314,49 +381,43 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void updateProgressInternal(ProgressHolder progressHolder) { private void updateProgressInternal(ProgressHolder progressHolder) {
progressState = compositeAssetLoader.getProgress(progressHolder); int progressSum = 0;
ProgressHolder individualProgressHolder = new ProgressHolder();
for (int i = 0; i < compositeAssetLoaders.size(); i++) {
progressState = compositeAssetLoaders.get(i).getProgress(individualProgressHolder);
if (progressState != PROGRESS_STATE_AVAILABLE) {
transformerConditionVariable.open();
return;
}
progressSum += individualProgressHolder.progress;
}
progressHolder.progress = progressSum / compositeAssetLoaders.size();
transformerConditionVariable.open(); transformerConditionVariable.open();
} }
private class ComponentListener implements AssetLoader.Listener, MuxerWrapper.Listener { private final class CompositeAssetLoaderListener implements AssetLoader.Listener {
// The first EditedMediaItem in the sequence determines which SamplePipeline to use. private final int sequenceIndex;
private final EditedMediaItem firstEditedMediaItem; private final ImmutableList<EditedMediaItem> editedMediaItems;
@Nullable private final Presentation compositionPresentation; private final Composition composition;
private final int mediaItemCount; private final TransformationRequest transformationRequest;
private final boolean transmuxAudio;
private final boolean transmuxVideo;
private final FallbackListener fallbackListener; private final FallbackListener fallbackListener;
private final AtomicInteger trackCount; private final DebugViewProvider debugViewProvider;
private boolean trackAdded; public CompositeAssetLoaderListener(
int sequenceIndex,
public ComponentListener( Composition composition,
EditedMediaItemSequence sequence, TransformationRequest transformationRequest,
@Nullable Presentation compositionPresentation, FallbackListener fallbackListener,
boolean transmuxAudio, DebugViewProvider debugViewProvider) {
boolean transmuxVideo, this.sequenceIndex = sequenceIndex;
FallbackListener fallbackListener) { editedMediaItems = composition.sequences.get(sequenceIndex).editedMediaItems;
firstEditedMediaItem = sequence.editedMediaItems.get(0); this.composition = composition;
this.compositionPresentation = compositionPresentation; this.transformationRequest = transformationRequest;
mediaItemCount = sequence.editedMediaItems.size();
this.transmuxAudio = transmuxAudio;
this.transmuxVideo = transmuxVideo;
this.fallbackListener = fallbackListener; this.fallbackListener = fallbackListener;
trackCount = new AtomicInteger(); this.debugViewProvider = debugViewProvider;
} }
// AssetLoader.Listener and MuxerWrapper.Listener implementation.
@Override
public void onError(ExportException exportException) {
internalHandler
.obtainMessage(MSG_END, END_REASON_ERROR, /* unused */ 0, exportException)
.sendToTarget();
}
// AssetLoader.Listener implementation.
@Override @Override
public void onDurationUs(long durationUs) {} public void onDurationUs(long durationUs) {}
@ -369,7 +430,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
ERROR_CODE_FAILED_RUNTIME_CHECK)); ERROR_CODE_FAILED_RUNTIME_CHECK));
return; return;
} }
this.trackCount.set(trackCount); totalInputTrackCount.addAndGet(trackCount);
unreportedInputTrackCounts.decrementAndGet();
if (unreportedInputTrackCounts.get() == 0) {
muxerWrapper.setTrackCount(totalInputTrackCount.get());
fallbackListener.setTrackCount(totalInputTrackCount.get());
}
} }
@Override @Override
@ -379,14 +445,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
long streamStartPositionUs, long streamStartPositionUs,
long streamOffsetUs) long streamOffsetUs)
throws ExportException { throws ExportException {
if (!trackAdded) {
// Call setTrackCount() methods here so that they are called from the same thread as the
// MuxerWrapper and FallbackListener methods called when building the sample pipelines.
muxerWrapper.setTrackCount(trackCount.get());
fallbackListener.setTrackCount(trackCount.get());
trackAdded = true;
}
SamplePipeline samplePipeline = SamplePipeline samplePipeline =
getSamplePipeline( getSamplePipeline(
firstInputFormat, firstInputFormat,
@ -399,48 +457,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
MimeTypes.isAudio(firstInputFormat.sampleMimeType) MimeTypes.isAudio(firstInputFormat.sampleMimeType)
? C.TRACK_TYPE_AUDIO ? C.TRACK_TYPE_AUDIO
: C.TRACK_TYPE_VIDEO; : C.TRACK_TYPE_VIDEO;
compositeAssetLoader.addOnMediaItemChangedListener(samplePipeline, trackType); compositeAssetLoaders
.get(sequenceIndex)
.addOnMediaItemChangedListener(samplePipeline, trackType);
internalHandler.obtainMessage(MSG_REGISTER_SAMPLE_PIPELINE, samplePipeline).sendToTarget(); internalHandler.obtainMessage(MSG_REGISTER_SAMPLE_PIPELINE, samplePipeline).sendToTarget();
return samplePipeline; return samplePipeline;
} }
// MuxerWrapper.Listener implementation.
@Override @Override
public void onTrackEnded( public void onError(ExportException exportException) {
@C.TrackType int trackType, Format format, int averageBitrate, int sampleCount) { TransformerInternal.this.onError(exportException);
if (trackType == C.TRACK_TYPE_AUDIO) {
exportResultBuilder
.setAverageAudioBitrate(averageBitrate)
.setPcmEncoding(format.pcmEncoding);
if (format.channelCount != Format.NO_VALUE) {
exportResultBuilder.setChannelCount(format.channelCount);
}
if (format.sampleRate != Format.NO_VALUE) {
exportResultBuilder.setSampleRate(format.sampleRate);
}
} else if (trackType == C.TRACK_TYPE_VIDEO) {
exportResultBuilder
.setAverageVideoBitrate(averageBitrate)
.setColorInfo(format.colorInfo)
.setVideoFrameCount(sampleCount);
if (format.height != Format.NO_VALUE) {
exportResultBuilder.setHeight(format.height);
}
if (format.width != Format.NO_VALUE) {
exportResultBuilder.setWidth(format.width);
}
}
}
@Override
public void onEnded(long durationMs, long fileSizeBytes) {
exportResultBuilder.setDurationMs(durationMs).setFileSizeBytes(fileSizeBytes);
internalHandler
.obtainMessage(MSG_END, END_REASON_COMPLETED, /* unused */ 0, /* exportException */ null)
.sendToTarget();
} }
// Private methods. // Private methods.
@ -452,6 +478,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
long streamOffsetUs) long streamOffsetUs)
throws ExportException { throws ExportException {
if (shouldTranscode) { if (shouldTranscode) {
EditedMediaItem firstEditedMediaItem = editedMediaItems.get(0);
if (MimeTypes.isAudio(firstInputFormat.sampleMimeType)) { if (MimeTypes.isAudio(firstInputFormat.sampleMimeType)) {
return new AudioSamplePipeline( return new AudioSamplePipeline(
firstInputFormat, firstInputFormat,
@ -463,6 +490,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
muxerWrapper, muxerWrapper,
fallbackListener); fallbackListener);
} else { // MIME type is video or image. } else { // MIME type is video or image.
ImmutableList<Effect> compositionVideoEffects = composition.effects.videoEffects;
@Nullable
Presentation compositionPresentation =
compositionVideoEffects.isEmpty()
? null
: (Presentation) compositionVideoEffects.get(0);
return new VideoSamplePipeline( return new VideoSamplePipeline(
context, context,
firstInputFormat, firstInputFormat,
@ -513,7 +546,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private boolean shouldTranscodeAudio(Format inputFormat) { private boolean shouldTranscodeAudio(Format inputFormat) {
if (mediaItemCount > 1 && !transmuxAudio) { if (editedMediaItems.size() > 1 && !composition.transmuxAudio) {
return true; return true;
} }
if (encoderFactory.audioNeedsEncoding()) { if (encoderFactory.audioNeedsEncoding()) {
@ -527,6 +560,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) { && !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
return true; return true;
} }
EditedMediaItem firstEditedMediaItem = editedMediaItems.get(0);
if (firstEditedMediaItem.flattenForSlowMotion && isSlowMotion(inputFormat)) { if (firstEditedMediaItem.flattenForSlowMotion && isSlowMotion(inputFormat)) {
return true; return true;
} }
@ -552,9 +586,10 @@ 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 (mediaItemCount > 1 && !transmuxVideo) { if (editedMediaItems.size() > 1 && !composition.transmuxVideo) {
return true; return true;
} }
EditedMediaItem firstEditedMediaItem = editedMediaItems.get(0);
if ((streamStartPositionUs - streamOffsetUs) != 0 if ((streamStartPositionUs - streamOffsetUs) != 0
&& !firstEditedMediaItem.mediaItem.clippingConfiguration.startsAtKeyFrame) { && !firstEditedMediaItem.mediaItem.clippingConfiguration.startsAtKeyFrame) {
return true; return true;