Handle looping sequences in TransformerInternal

PiperOrigin-RevId: 518895943
This commit is contained in:
kimvde 2023-03-23 17:19:08 +00:00 committed by Tianyi Feng
parent 67f8fbf3bd
commit 1f6b000219

View File

@ -28,6 +28,7 @@ import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED
import static androidx.media3.transformer.TransformerUtil.areVideoEffectsAllNoOp; import static androidx.media3.transformer.TransformerUtil.areVideoEffectsAllNoOp;
import static androidx.media3.transformer.TransformerUtil.containsSlowMotionData; import static androidx.media3.transformer.TransformerUtil.containsSlowMotionData;
import static androidx.media3.transformer.TransformerUtil.getProcessedTrackType; import static androidx.media3.transformer.TransformerUtil.getProcessedTrackType;
import static java.lang.Math.max;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context; import android.content.Context;
@ -95,6 +96,8 @@ 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 Composition composition;
private final boolean compositionHasLoopingSequence;
private final CapturingEncoderFactory encoderFactory; private final CapturingEncoderFactory encoderFactory;
private final Listener listener; private final Listener listener;
private final HandlerWrapper applicationHandler; private final HandlerWrapper applicationHandler;
@ -107,11 +110,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final AtomicBoolean outputHasAudio; private final AtomicBoolean outputHasAudio;
private final AtomicBoolean outputHasVideo; private final AtomicBoolean outputHasVideo;
private final List<SamplePipeline> samplePipelines; private final List<SamplePipeline> samplePipelines;
private final Object setMaxSequenceDurationUsLock;
private final MuxerWrapper muxerWrapper; private final MuxerWrapper muxerWrapper;
private final ConditionVariable transformerConditionVariable; private final ConditionVariable transformerConditionVariable;
private final ExportResult.Builder exportResultBuilder; private final ExportResult.Builder exportResultBuilder;
private boolean isDrainingPipelines; private boolean isDrainingPipelines;
private long currentMaxSequenceDurationUs;
private int nonLoopingSequencesWithNonFinalDuration;
private @Transformer.ProgressState int progressState; private @Transformer.ProgressState int progressState;
private @MonotonicNonNull RuntimeException cancelException; private @MonotonicNonNull RuntimeException cancelException;
@ -133,6 +139,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
DebugViewProvider debugViewProvider, DebugViewProvider debugViewProvider,
Clock clock) { Clock clock) {
this.context = context; this.context = context;
this.composition = composition;
this.encoderFactory = new CapturingEncoderFactory(encoderFactory); this.encoderFactory = new CapturingEncoderFactory(encoderFactory);
this.listener = listener; this.listener = listener;
this.applicationHandler = applicationHandler; this.applicationHandler = applicationHandler;
@ -149,20 +156,29 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
transformationRequest, transformationRequest,
fallbackListener, fallbackListener,
debugViewProvider); debugViewProvider);
EditedMediaItemSequence sequence = composition.sequences.get(i);
sequenceAssetLoaders.add( sequenceAssetLoaders.add(
new SequenceAssetLoader( new SequenceAssetLoader(
composition.sequences.get(i), sequence,
composition.forceAudioTrack, composition.forceAudioTrack,
assetLoaderFactory, assetLoaderFactory,
internalLooper, internalLooper,
sequenceAssetLoaderListener, sequenceAssetLoaderListener,
clock)); clock));
if (!sequence.isLooping) {
// All sequences have a non-final duration at this point, as the AssetLoaders haven't
// started loading yet.
nonLoopingSequencesWithNonFinalDuration++;
} }
}
compositionHasLoopingSequence =
nonLoopingSequencesWithNonFinalDuration != composition.sequences.size();
trackCountsToReport = new AtomicInteger(composition.sequences.size()); trackCountsToReport = new AtomicInteger(composition.sequences.size());
tracksToAdd = new AtomicInteger(); tracksToAdd = new AtomicInteger();
outputHasAudio = new AtomicBoolean(); outputHasAudio = new AtomicBoolean();
outputHasVideo = new AtomicBoolean(); outputHasVideo = new AtomicBoolean();
samplePipelines = new ArrayList<>(); samplePipelines = new ArrayList<>();
setMaxSequenceDurationUsLock = new Object();
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.
@ -400,16 +416,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private void updateProgressInternal(ProgressHolder progressHolder) { private void updateProgressInternal(ProgressHolder progressHolder) {
int progressSum = 0; int progressSum = 0;
int progressCount = 0;
ProgressHolder individualProgressHolder = new ProgressHolder(); ProgressHolder individualProgressHolder = new ProgressHolder();
for (int i = 0; i < sequenceAssetLoaders.size(); i++) { for (int i = 0; i < sequenceAssetLoaders.size(); i++) {
if (composition.sequences.get(i).isLooping) {
// Looping sequence progress is always unavailable. Skip it.
continue;
}
progressState = sequenceAssetLoaders.get(i).getProgress(individualProgressHolder); progressState = sequenceAssetLoaders.get(i).getProgress(individualProgressHolder);
if (progressState != PROGRESS_STATE_AVAILABLE) { if (progressState != PROGRESS_STATE_AVAILABLE) {
transformerConditionVariable.open(); transformerConditionVariable.open();
return; return;
} }
progressSum += individualProgressHolder.progress; progressSum += individualProgressHolder.progress;
progressCount++;
} }
progressHolder.progress = progressSum / sequenceAssetLoaders.size(); progressHolder.progress = progressSum / progressCount;
transformerConditionVariable.open(); transformerConditionVariable.open();
} }
@ -423,6 +445,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final DebugViewProvider debugViewProvider; private final DebugViewProvider debugViewProvider;
private final Map<Integer, AddedTrackInfo> addedTrackInfoByTrackType; private final Map<Integer, AddedTrackInfo> addedTrackInfoByTrackType;
private long currentSequenceDurationUs;
public SequenceAssetLoaderListener( public SequenceAssetLoaderListener(
int sequenceIndex, int sequenceIndex,
Composition composition, Composition composition,
@ -495,9 +519,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
AddedTrackInfo trackInfo = checkStateNotNull(addedTrackInfoByTrackType.get(trackType)); AddedTrackInfo trackInfo = checkStateNotNull(addedTrackInfoByTrackType.get(trackType));
SamplePipeline samplePipeline = getSamplePipeline(assetLoaderOutputFormat, trackInfo); SamplePipeline samplePipeline = getSamplePipeline(assetLoaderOutputFormat, trackInfo);
OnMediaItemChangedListener onMediaItemChangedListener =
(editedMediaItem, durationUs, trackFormat, isLast) -> {
onMediaItemChanged(trackType, durationUs, isLast);
samplePipeline.onMediaItemChanged(editedMediaItem, durationUs, trackFormat, isLast);
};
sequenceAssetLoaders sequenceAssetLoaders
.get(sequenceIndex) .get(sequenceIndex)
.addOnMediaItemChangedListener(samplePipeline, trackType); .addOnMediaItemChangedListener(onMediaItemChangedListener, trackType);
internalHandler.obtainMessage(MSG_REGISTER_SAMPLE_PIPELINE, samplePipeline).sendToTarget(); internalHandler.obtainMessage(MSG_REGISTER_SAMPLE_PIPELINE, samplePipeline).sendToTarget();
return samplePipeline; return samplePipeline;
} }
@ -559,6 +589,46 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
fallbackListener); fallbackListener);
} }
/**
* Updates the maximum sequence duration and passes it to the SequenceAssetLoaders if needed.
*/
private void onMediaItemChanged(@C.TrackType int trackType, long durationUs, boolean isLast) {
if (!compositionHasLoopingSequence) {
// The code in this method handles looping sequences. Skip it if there are none.
return;
}
if (addedTrackInfoByTrackType.size() > 1 && trackType == C.TRACK_TYPE_VIDEO) {
// Make sure this method is only executed once per MediaItem (and not per track).
return;
}
if (composition.sequences.get(sequenceIndex).isLooping) {
return;
}
checkState(
durationUs != C.TIME_UNSET,
"MediaItem duration required for sequence looping could not be extracted.");
currentSequenceDurationUs += durationUs;
// onMediaItemChanged can be executed concurrently from different sequences.
synchronized (setMaxSequenceDurationUsLock) {
if (isLast) {
// The total sequence duration is known when the last MediaItem is loaded.
nonLoopingSequencesWithNonFinalDuration--;
}
boolean isMaxSequenceDurationUsFinal = nonLoopingSequencesWithNonFinalDuration == 0;
if (currentSequenceDurationUs > currentMaxSequenceDurationUs
|| isMaxSequenceDurationUsFinal) {
currentMaxSequenceDurationUs =
max(currentSequenceDurationUs, currentMaxSequenceDurationUs);
for (int i = 0; i < sequenceAssetLoaders.size(); i++) {
sequenceAssetLoaders
.get(i)
.setMaxSequenceDurationUs(
currentMaxSequenceDurationUs, isMaxSequenceDurationUsFinal);
}
}
}
}
private final class AddedTrackInfo { private final class AddedTrackInfo {
public final Format firstAssetLoaderInputFormat; public final Format firstAssetLoaderInputFormat;
public final long streamStartPositionUs; public final long streamStartPositionUs;