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:
parent
408b4449ff
commit
d2d5174f09
@ -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;
|
||||||
|
if (!trackCountReported) {
|
||||||
int trackCount = nonEndedTracks.get() + (addForcedAudioTrack ? 1 : 0);
|
int trackCount = nonEndedTracks.get() + (addForcedAudioTrack ? 1 : 0);
|
||||||
compositeAssetLoaderListener.onTrackCount(trackCount);
|
compositeAssetLoaderListener.onTrackCount(trackCount);
|
||||||
|
trackCountReported = true;
|
||||||
|
}
|
||||||
sampleConsumer =
|
sampleConsumer =
|
||||||
new SampleConsumerWrapper(
|
new SampleConsumerWrapper(
|
||||||
compositeAssetLoaderListener.onTrackAdded(
|
compositeAssetLoaderListener.onTrackAdded(
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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,
|
|
||||||
composition.transmuxVideo,
|
|
||||||
fallbackListener);
|
|
||||||
compositeAssetLoader =
|
|
||||||
new CompositeAssetLoader(
|
new CompositeAssetLoader(
|
||||||
sequence,
|
composition.sequences.get(i),
|
||||||
composition.forceAudioTrack,
|
composition.forceAudioTrack,
|
||||||
assetLoaderFactory,
|
assetLoaderFactory,
|
||||||
internalLooper,
|
internalLooper,
|
||||||
componentListener,
|
compositeAssetLoaderListener,
|
||||||
clock);
|
clock));
|
||||||
|
}
|
||||||
|
totalInputTrackCount = new AtomicInteger();
|
||||||
|
unreportedInputTrackCounts = new AtomicInteger(composition.sequences.size());
|
||||||
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,27 +316,40 @@ 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 {
|
compositeAssetLoaders.get(i).release();
|
||||||
compositeAssetLoader.release();
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
for (int i = 0; i < samplePipelines.size(); i++) {
|
|
||||||
samplePipelines.get(i).release();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
muxerWrapper.release(forCancellation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Muxer.MuxerException e) {
|
|
||||||
releaseExportException = ExportException.createForMuxer(e, ERROR_CODE_MUXING_FAILED);
|
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
if (releaseExportException == null) {
|
||||||
releaseExportException = ExportException.createForUnexpected(e);
|
releaseExportException = ExportException.createForUnexpected(e);
|
||||||
// cancelException is not reported through a listener. It is thrown in cancel(), as this
|
// cancelException is not reported through a listener. It is thrown in cancel(), as this
|
||||||
// method is blocking.
|
// method is blocking.
|
||||||
cancelException = e;
|
cancelException = e;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
if (releaseExportException == null) {
|
||||||
|
releaseExportException = ExportException.createForMuxer(e, ERROR_CODE_MUXING_FAILED);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
if (releaseExportException == null) {
|
||||||
|
releaseExportException = ExportException.createForUnexpected(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.
|
||||||
internalHandler.post(internalHandlerThread::quitSafely);
|
internalHandler.post(internalHandlerThread::quitSafely);
|
||||||
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user