diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java b/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java index 700b620486..77fa6620ff 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java @@ -77,7 +77,7 @@ public interface AssetLoader { void onDurationUs(long durationUs); /** - * Called when the number of tracks output by the asset loader is known. + * Called when the number of tracks being output is known. * *

Can be called from any thread. */ @@ -91,28 +91,41 @@ public interface AssetLoader { * *

Must be called once per {@linkplain #onTrackCount(int) declared} track. * - *

Must be called from the thread that will be used to call the returned {@link - * SampleConsumer}'s methods. This thread must be the same for all the tracks added, and is - * generally different from the one used to access the {@link AssetLoader} methods. + *

Must be called from the thread that will be used to call {@link #onOutputFormat(Format)}. * - * @param format The {@link Format} of the input media (prior to video slow motion flattening or - * to decoding). + * @param inputFormat The {@link Format} of samples that will be input to the {@link + * AssetLoader} (prior to video slow motion flattening or to decoding). * @param supportedOutputTypes The output {@linkplain SupportedOutputTypes types} supported by - * this asset loader for the track added. At least one output type must be supported. + * this {@link AssetLoader} for the track added. At least one output type must be supported. * @param streamStartPositionUs The start position of the stream (offset by {@code * streamOffsetUs}), in microseconds. * @param streamOffsetUs The offset that will be added to the timestamps to make sure they are * non-negative, in microseconds. - * @return The {@link SampleConsumer} describing the type of sample data expected, and to which - * to pass this data. - * @throws ExportException If an error occurs configuring the {@link SampleConsumer}. + * @return Whether the {@link AssetLoader} needs to provide decoded data to the {@link + * SampleConsumer}. */ - SampleConsumer onTrackAdded( - Format format, + boolean onTrackAdded( + Format inputFormat, @SupportedOutputTypes int supportedOutputTypes, long streamStartPositionUs, - long streamOffsetUs) - throws ExportException; + long streamOffsetUs); + + /** + * Called when the {@link Format} of samples that will be output by the {@link AssetLoader} is + * known. + * + *

Must be called once per {@linkplain #onTrackCount declared} track, and only after that + * track has been {@link #onTrackAdded added}. + * + *

Must be called from the thread that will be used to call the returned {@link + * SampleConsumer}'s methods. This thread must be the same for all formats output, and is + * generally different from the one used to access the {@link AssetLoader} methods. + * + * @param format The {@link Format} of samples that will be output. + * @return The {@link SampleConsumer} of samples of the given {@link Format}. + * @throws ExportException If an error occurs configuring the {@link SampleConsumer}. + */ + SampleConsumer onOutputFormat(Format format) throws ExportException; /** * Called if an error occurs in the asset loader. In this case, the asset loader will be diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java index 719d1eab2f..cab903f78c 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java @@ -60,7 +60,8 @@ import org.checkerframework.dataflow.qual.Pure; // TODO(b/260618558): Move silent audio generation upstream of this component. public AudioSamplePipeline( - Format firstInputFormat, + Format firstAssetLoaderInputFormat, + Format firstPipelineInputFormat, long streamOffsetUs, TransformationRequest transformationRequest, boolean flattenForSlowMotion, @@ -69,9 +70,9 @@ import org.checkerframework.dataflow.qual.Pure; MuxerWrapper muxerWrapper, FallbackListener fallbackListener) throws ExportException { - super(firstInputFormat, /* streamStartPositionUs= */ streamOffsetUs, muxerWrapper); + super(firstPipelineInputFormat, /* streamStartPositionUs= */ streamOffsetUs, muxerWrapper); - silentAudioGenerator = new SilentAudioGenerator(firstInputFormat); + silentAudioGenerator = new SilentAudioGenerator(firstPipelineInputFormat); availableInputBuffers = new ConcurrentLinkedDeque<>(); ByteBuffer emptyBuffer = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder()); for (int i = 0; i < MAX_INPUT_BUFFER_COUNT; i++) { @@ -84,12 +85,12 @@ import org.checkerframework.dataflow.qual.Pure; encoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED); encoderOutputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED); - if (flattenForSlowMotion && firstInputFormat.metadata != null) { + if (flattenForSlowMotion && firstAssetLoaderInputFormat.metadata != null) { audioProcessors = new ImmutableList.Builder() .add( new SpeedChangingAudioProcessor( - new SegmentSpeedProvider(firstInputFormat.metadata))) + new SegmentSpeedProvider(firstAssetLoaderInputFormat.metadata))) .addAll(audioProcessors) .build(); } @@ -98,10 +99,10 @@ import org.checkerframework.dataflow.qual.Pure; // TODO(b/267301878): Once decoder format propagated, remove setting default PCM encoding. AudioFormat pipelineInputAudioFormat = new AudioFormat( - firstInputFormat.sampleRate, - firstInputFormat.channelCount, - firstInputFormat.pcmEncoding != Format.NO_VALUE - ? firstInputFormat.pcmEncoding + firstPipelineInputFormat.sampleRate, + firstPipelineInputFormat.channelCount, + firstPipelineInputFormat.pcmEncoding != Format.NO_VALUE + ? firstPipelineInputFormat.pcmEncoding : DEFAULT_PCM_ENCODING); try { @@ -118,7 +119,7 @@ import org.checkerframework.dataflow.qual.Pure; .setSampleMimeType( transformationRequest.audioMimeType != null ? transformationRequest.audioMimeType - : checkNotNull(firstInputFormat.sampleMimeType)) + : checkNotNull(firstAssetLoaderInputFormat.sampleMimeType)) .setSampleRate(encoderInputAudioFormat.sampleRate) .setChannelCount(encoderInputAudioFormat.channelCount) .setPcmEncoding(encoderInputAudioFormat.encoding) diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositeAssetLoader.java b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositeAssetLoader.java index 5eb1a5ea17..fc51ea6688 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositeAssetLoader.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositeAssetLoader.java @@ -49,6 +49,13 @@ import java.util.concurrent.atomic.AtomicInteger; */ /* package */ final class CompositeAssetLoader implements AssetLoader, AssetLoader.Listener { + private static final Format FORCE_AUDIO_TRACK_FORMAT = + new Format.Builder() + .setSampleMimeType(MimeTypes.AUDIO_AAC) + .setSampleRate(44100) + .setChannelCount(2) + .build(); + private final List editedMediaItems; private final AtomicInteger currentMediaItemIndex; private final boolean forceAudioTrack; @@ -76,6 +83,8 @@ import java.util.concurrent.atomic.AtomicInteger; private AssetLoader currentAssetLoader; private boolean trackCountReported; private int processedInputsSize; + private boolean decodeAudio; + private boolean decodeVideo; private volatile long currentDurationUs; @@ -186,42 +195,65 @@ import java.util.concurrent.atomic.AtomicInteger; } @Override - public SampleConsumer onTrackAdded( - Format format, + public boolean onTrackAdded( + Format inputFormat, @SupportedOutputTypes int supportedOutputTypes, long streamStartPositionUs, - long streamOffsetUs) - throws ExportException { + long streamOffsetUs) { + boolean isAudio = getProcessedTrackType(inputFormat.sampleMimeType) == C.TRACK_TYPE_AUDIO; + + if (currentMediaItemIndex.get() != 0) { + return isAudio ? decodeAudio : decodeVideo; + } + + boolean addForcedAudioTrack = forceAudioTrack && nonEndedTracks.get() == 1 && !isAudio; + + if (!trackCountReported) { + int trackCount = nonEndedTracks.get() + (addForcedAudioTrack ? 1 : 0); + compositeAssetLoaderListener.onTrackCount(trackCount); + trackCountReported = true; + } + + boolean decodeOutput = + compositeAssetLoaderListener.onTrackAdded( + inputFormat, supportedOutputTypes, streamStartPositionUs, streamOffsetUs); + + if (isAudio) { + decodeAudio = decodeOutput; + } else { + decodeVideo = decodeOutput; + } + + if (addForcedAudioTrack) { + compositeAssetLoaderListener.onTrackAdded( + FORCE_AUDIO_TRACK_FORMAT, + SUPPORTED_OUTPUT_TYPE_DECODED, + streamStartPositionUs, + streamOffsetUs); + } + + return decodeOutput; + } + + @Override + public SampleConsumer onOutputFormat(Format format) throws ExportException { @C.TrackType int trackType = getProcessedTrackType(format.sampleMimeType); SampleConsumer sampleConsumer; if (currentMediaItemIndex.get() == 0) { - boolean addForcedAudioTrack = - forceAudioTrack && nonEndedTracks.get() == 1 && trackType == C.TRACK_TYPE_VIDEO; - if (!trackCountReported) { - int trackCount = nonEndedTracks.get() + (addForcedAudioTrack ? 1 : 0); - compositeAssetLoaderListener.onTrackCount(trackCount); - trackCountReported = true; - } sampleConsumer = - new SampleConsumerWrapper( - compositeAssetLoaderListener.onTrackAdded( - format, supportedOutputTypes, streamStartPositionUs, streamOffsetUs)); + new SampleConsumerWrapper(compositeAssetLoaderListener.onOutputFormat(format)); sampleConsumersByTrackType.put(trackType, sampleConsumer); - if (addForcedAudioTrack) { - Format firstAudioFormat = - new Format.Builder() - .setSampleMimeType(MimeTypes.AUDIO_AAC) - .setSampleRate(44100) - .setChannelCount(2) - .build(); - SampleConsumer audioSampleConsumer = + + if (forceAudioTrack && nonEndedTracks.get() == 1 && trackType == C.TRACK_TYPE_VIDEO) { + sampleConsumersByTrackType.put( + C.TRACK_TYPE_AUDIO, new SampleConsumerWrapper( - compositeAssetLoaderListener.onTrackAdded( - firstAudioFormat, - SUPPORTED_OUTPUT_TYPE_DECODED, - streamStartPositionUs, - streamOffsetUs)); - sampleConsumersByTrackType.put(C.TRACK_TYPE_AUDIO, audioSampleConsumer); + compositeAssetLoaderListener.onOutputFormat( + FORCE_AUDIO_TRACK_FORMAT + .buildUpon() + .setSampleMimeType(MimeTypes.AUDIO_RAW) + .setPcmEncoding(C.ENCODING_PCM_16BIT) + .build()))); } } else { // TODO(b/270533049): Remove the check below when implementing blank video frames generation. @@ -288,13 +320,6 @@ import java.util.concurrent.atomic.AtomicInteger; this.sampleConsumer = sampleConsumer; } - @Override - public boolean expectsDecodedData() { - // TODO(b/252537210): handle the case where the first media item doesn't need to be encoded - // but a following one does. - return sampleConsumer.expectsDecodedData(); - } - @Nullable @Override public DecoderInputBuffer getInputBuffer() { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/EncodedSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EncodedSamplePipeline.java index 9663e390a1..9c922c0b81 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EncodedSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EncodedSamplePipeline.java @@ -71,11 +71,6 @@ import java.util.concurrent.atomic.AtomicLong; nextMediaItemOffsetUs.addAndGet(durationUs); } - @Override - public boolean expectsDecodedData() { - return false; - } - @Override @Nullable public DecoderInputBuffer getInputBuffer() { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderAudioRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderAudioRenderer.java index 2502b8b571..64b65cd537 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderAudioRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderAudioRenderer.java @@ -56,14 +56,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; * @throws ExportException If an error occurs in the decoder. */ @Override - @RequiresNonNull("sampleConsumer") + @RequiresNonNull({"sampleConsumer", "decoder"}) protected boolean feedConsumerFromDecoder() throws ExportException { @Nullable DecoderInputBuffer sampleConsumerInputBuffer = sampleConsumer.getInputBuffer(); if (sampleConsumerInputBuffer == null) { return false; } - Codec decoder = checkNotNull(this.decoder); if (decoder.isEnded()) { checkNotNull(sampleConsumerInputBuffer.data).limit(0); sampleConsumerInputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java index a55fcef32b..9933e77a47 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java @@ -17,10 +17,12 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.decoder.DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED; import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT; import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED; import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED; +import static androidx.media3.transformer.TransformerUtil.getProcessedTrackType; import androidx.annotation.Nullable; import androidx.media3.common.C; @@ -32,6 +34,7 @@ import androidx.media3.exoplayer.FormatHolder; import androidx.media3.exoplayer.MediaClock; import androidx.media3.exoplayer.RendererCapabilities; import androidx.media3.exoplayer.source.SampleStream.ReadDataResult; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; @@ -42,6 +45,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; protected @MonotonicNonNull SampleConsumer sampleConsumer; protected @MonotonicNonNull Codec decoder; protected boolean isEnded; + private @MonotonicNonNull Format inputFormat; private final TransformerMediaClock mediaClock; private final AssetLoader.Listener assetLoaderListener; @@ -92,15 +96,25 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; @Override public void render(long positionUs, long elapsedRealtimeUs) { try { - if (!isRunning || isEnded() || !ensureConfigured()) { + if (!isRunning || isEnded() || !hasReadInputFormat()) { return; } - if (sampleConsumer.expectsDecodedData()) { - while (feedConsumerFromDecoder() || feedDecoderFromInput()) {} + if (decoder != null) { + boolean progressMade; + do { + progressMade = false; + if (ensureSampleConsumerInitialized()) { + progressMade = feedConsumerFromDecoder(); + } + progressMade |= feedDecoderFromInput(); + } while (progressMade); } else { - while (feedConsumerFromInput()) {} + if (ensureSampleConsumerInitialized()) { + while (feedConsumerFromInput()) {} + } } + } catch (ExportException e) { isRunning = false; assetLoaderListener.onError(e); @@ -144,7 +158,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; protected void onInputFormatRead(Format inputFormat) {} /** Initializes {@link #decoder} with an appropriate {@linkplain Codec decoder}. */ - @RequiresNonNull("sampleConsumer") + @EnsuresNonNull("decoder") protected abstract void initDecoder(Format inputFormat) throws ExportException; /** @@ -166,12 +180,22 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; * @return Whether it may be possible to read more data immediately by calling this method again. * @throws ExportException If an error occurs in the decoder. */ - @RequiresNonNull("sampleConsumer") + @RequiresNonNull({"sampleConsumer", "decoder"}) protected abstract boolean feedConsumerFromDecoder() throws ExportException; - @EnsuresNonNullIf(expression = "sampleConsumer", result = true) - private boolean ensureConfigured() throws ExportException { - if (sampleConsumer != null) { + /** + * Attempts to read the input {@link Format} from the source, if not read. + * + *

After reading the format, {@link AssetLoader.Listener#onTrackAdded} is notified, and, if + * needed, the decoder is {@linkplain #initDecoder(Format) initialized}. + * + * @return Whether the input {@link Format} is available. + * @throws ExportException If an error occurs {@linkplain #initDecoder initializing} the + * {@linkplain Codec decoder}. + */ + @EnsuresNonNullIf(expression = "inputFormat", result = true) + private boolean hasReadInputFormat() throws ExportException { + if (inputFormat != null) { return true; } @@ -181,16 +205,58 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; if (result != C.RESULT_FORMAT_READ) { return false; } - Format inputFormat = overrideFormat(checkNotNull(formatHolder.format)); - @AssetLoader.SupportedOutputTypes - int supportedOutputTypes = SUPPORTED_OUTPUT_TYPE_ENCODED | SUPPORTED_OUTPUT_TYPE_DECODED; - sampleConsumer = - assetLoaderListener.onTrackAdded( - inputFormat, supportedOutputTypes, streamStartPositionUs, streamOffsetUs); + inputFormat = overrideFormat(checkNotNull(formatHolder.format)); onInputFormatRead(inputFormat); - if (sampleConsumer.expectsDecodedData()) { - initDecoder(inputFormat); + + boolean decodeOutput = + assetLoaderListener.onTrackAdded( + inputFormat, + SUPPORTED_OUTPUT_TYPE_DECODED | SUPPORTED_OUTPUT_TYPE_ENCODED, + streamStartPositionUs, + streamOffsetUs); + if (decodeOutput) { + if (getProcessedTrackType(inputFormat.sampleMimeType) == C.TRACK_TYPE_AUDIO) { + initDecoder(inputFormat); + } else { + // TODO(b/237674316): Move surface creation out of video sampleConsumer. Init decoder and + // get decoder output Format before init sampleConsumer. + checkState(ensureSampleConsumerInitialized()); + initDecoder(inputFormat); + } } + + return true; + } + + /** + * Attempts to initialize the {@link SampleConsumer}, if not initialized. + * + * @return Whether the {@link SampleConsumer} is initialized. + * @throws ExportException If the {@linkplain Codec decoder} errors getting it's {@linkplain + * Codec#getOutputFormat() output format}. + * @throws ExportException If the {@link AssetLoader.Listener} errors providing a {@link + * SampleConsumer}. + */ + @RequiresNonNull("inputFormat") + @EnsuresNonNullIf(expression = "sampleConsumer", result = true) + private boolean ensureSampleConsumerInitialized() throws ExportException { + if (sampleConsumer != null) { + return true; + } + + if (decoder != null + && getProcessedTrackType(inputFormat.sampleMimeType) == C.TRACK_TYPE_AUDIO) { + @Nullable Format decoderOutputFormat = decoder.getOutputFormat(); + if (decoderOutputFormat == null) { + return false; + } + sampleConsumer = assetLoaderListener.onOutputFormat(decoderOutputFormat); + } else { + // TODO(b/237674316): Move surface creation out of video sampleConsumer. Init decoder and get + // decoderOutput Format before init sampleConsumer. + sampleConsumer = assetLoaderListener.onOutputFormat(inputFormat); + } + return true; } @@ -200,8 +266,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; * @return Whether it may be possible to read more data immediately by calling this method again. * @throws ExportException If an error occurs in the decoder. */ + @RequiresNonNull("decoder") private boolean feedDecoderFromInput() throws ExportException { - Codec decoder = checkNotNull(this.decoder); if (!decoder.maybeDequeueInputBuffer(decoderInputBuffer)) { return false; } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java index 45961e1239..5f9e7e2624 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java @@ -16,6 +16,7 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.common.util.Assertions.checkStateNotNull; import android.media.MediaCodec; import androidx.annotation.Nullable; @@ -75,8 +76,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } @Override - @RequiresNonNull("sampleConsumer") protected void initDecoder(Format inputFormat) throws ExportException { + // TODO(b/237674316): Move surface creation out of sampleConsumer. Init decoder before + // sampleConsumer. + checkStateNotNull(sampleConsumer); boolean isDecoderToneMappingRequired = ColorInfo.isTransferHdr(inputFormat.colorInfo) && !ColorInfo.isTransferHdr(sampleConsumer.getExpectedInputColorInfo()); @@ -116,9 +119,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } @Override - @RequiresNonNull("sampleConsumer") + @RequiresNonNull({"sampleConsumer", "decoder"}) protected boolean feedConsumerFromDecoder() throws ExportException { - Codec decoder = checkNotNull(this.decoder); if (decoder.isEnded()) { sampleConsumer.signalEndOfVideoInput(); isEnded = true; diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ImageAssetLoader.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ImageAssetLoader.java index 9c3df6b172..d1e52881d7 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ImageAssetLoader.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ImageAssetLoader.java @@ -88,12 +88,13 @@ public final class ImageAssetLoader implements AssetLoader { .setWidth(bitmap.getWidth()) .setSampleMimeType(MIME_TYPE_IMAGE_ALL) .build(); - SampleConsumer sampleConsumer = - listener.onTrackAdded( - format, - SUPPORTED_OUTPUT_TYPE_DECODED, - /* streamStartPositionUs= */ 0, - /* streamOffsetUs= */ 0); + listener.onTrackAdded( + format, + SUPPORTED_OUTPUT_TYPE_DECODED, + /* streamStartPositionUs= */ 0, + /* streamOffsetUs= */ 0); + SampleConsumer sampleConsumer = listener.onOutputFormat(format); + checkState(editedMediaItem.durationUs != C.TIME_UNSET); checkState(editedMediaItem.frameRate != C.RATE_UNSET_INT); // TODO(b/262693274): consider using listener.onDurationUs() or the MediaItem change diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/OnMediaItemChangedListener.java b/libraries/transformer/src/main/java/androidx/media3/transformer/OnMediaItemChangedListener.java index 33c2b35d1c..b1650664b8 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/OnMediaItemChangedListener.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/OnMediaItemChangedListener.java @@ -28,8 +28,9 @@ import androidx.media3.common.MediaItem; * * @param editedMediaItem The {@link MediaItem} with the transformations to apply to it. * @param durationUs The duration of the {@link MediaItem}, in microseconds. - * @param trackFormat The {@link Format} of the {@link MediaItem} track corresponding to the - * {@link SamplePipeline}, or {@code null} if no such track was extracted. + * @param trackFormat The {@link Format} extracted (and possibly decoded) from the {@link + * MediaItem} track, which represents the samples input to the {@link SamplePipeline}. {@code + * null} if no such track was extracted. * @param isLast Whether the {@link MediaItem} is the last one passed to the {@link * SamplePipeline}. */ diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SampleConsumer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SampleConsumer.java index f46dfa5512..20f38cd98c 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SampleConsumer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SampleConsumer.java @@ -26,14 +26,6 @@ import androidx.media3.decoder.DecoderInputBuffer; @UnstableApi public interface SampleConsumer { - /** - * Returns whether the consumer should be fed with decoded sample data. If false, encoded sample - * data should be fed. - */ - boolean expectsDecodedData(); - - // Methods to pass compressed input or raw audio input. - /** * Returns a buffer if the consumer is ready to accept input, and {@code null} otherwise. * diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java index 906914e856..52a663ee88 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java @@ -56,12 +56,7 @@ import java.util.List; Format firstInputFormat, long streamStartPositionUs, MuxerWrapper muxerWrapper) { this.streamStartPositionUs = streamStartPositionUs; this.muxerWrapper = muxerWrapper; - this.outputTrackType = getProcessedTrackType(firstInputFormat.sampleMimeType); - } - - @Override - public boolean expectsDecodedData() { - return true; + outputTrackType = getProcessedTrackType(firstInputFormat.sampleMimeType); } /** diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java index 89a781b9f5..eb92c8d064 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java @@ -18,12 +18,15 @@ 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.checkStateNotNull; import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED; 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_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.TransformerUtil.areVideoEffectsAllNoOp; +import static androidx.media3.transformer.TransformerUtil.containsSlowMotionData; import static androidx.media3.transformer.TransformerUtil.getProcessedTrackType; import static java.lang.annotation.ElementType.TYPE_USE; @@ -37,21 +40,20 @@ import androidx.media3.common.C; import androidx.media3.common.DebugViewProvider; import androidx.media3.common.Effect; import androidx.media3.common.Format; -import androidx.media3.common.Metadata; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Clock; import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.HandlerWrapper; -import androidx.media3.effect.GlEffect; import androidx.media3.effect.Presentation; -import androidx.media3.extractor.metadata.mp4.SlowMotionData; import com.google.common.collect.ImmutableList; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -404,6 +406,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final TransformationRequest transformationRequest; private final FallbackListener fallbackListener; private final DebugViewProvider debugViewProvider; + private final Map addedTrackInfoByTrackType; public CompositeAssetLoaderListener( int sequenceIndex, @@ -417,6 +420,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; this.transformationRequest = transformationRequest; this.fallbackListener = fallbackListener; this.debugViewProvider = debugViewProvider; + addedTrackInfoByTrackType = new HashMap<>(); } @Override @@ -440,20 +444,30 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } @Override - public SampleConsumer onTrackAdded( - Format firstInputFormat, + public boolean onTrackAdded( + Format firstAssetLoaderInputFormat, @AssetLoader.SupportedOutputTypes int supportedOutputTypes, long streamStartPositionUs, - long streamOffsetUs) - throws ExportException { - SamplePipeline samplePipeline = - getSamplePipeline( - firstInputFormat, - shouldTranscode( - firstInputFormat, supportedOutputTypes, streamStartPositionUs, streamOffsetUs), + long streamOffsetUs) { + AddedTrackInfo trackInfo = + new AddedTrackInfo( + firstAssetLoaderInputFormat, + supportedOutputTypes, streamStartPositionUs, streamOffsetUs); - @C.TrackType int trackType = getProcessedTrackType(firstInputFormat.sampleMimeType); + + addedTrackInfoByTrackType.put( + getProcessedTrackType(firstAssetLoaderInputFormat.sampleMimeType), trackInfo); + + return trackInfo.shouldTranscode; + } + + @Override + public SampleConsumer onOutputFormat(Format assetLoaderOutputFormat) throws ExportException { + @C.TrackType int trackType = getProcessedTrackType(assetLoaderOutputFormat.sampleMimeType); + AddedTrackInfo trackInfo = checkStateNotNull(addedTrackInfoByTrackType.get(trackType)); + SamplePipeline samplePipeline = getSamplePipeline(assetLoaderOutputFormat, trackInfo); + compositeAssetLoaders .get(sequenceIndex) .addOnMediaItemChangedListener(samplePipeline, trackType); @@ -469,17 +483,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; // Private methods. private SamplePipeline getSamplePipeline( - Format firstInputFormat, - boolean shouldTranscode, - long streamStartPositionUs, - long streamOffsetUs) - throws ExportException { - if (shouldTranscode) { + Format firstAssetLoaderOutputFormat, AddedTrackInfo addedTrackInfo) throws ExportException { + if (addedTrackInfo.shouldTranscode) { EditedMediaItem firstEditedMediaItem = editedMediaItems.get(0); - if (MimeTypes.isAudio(firstInputFormat.sampleMimeType)) { + if (MimeTypes.isAudio(firstAssetLoaderOutputFormat.sampleMimeType)) { return new AudioSamplePipeline( - firstInputFormat, - streamOffsetUs, + addedTrackInfo.firstAssetLoaderInputFormat, + /* firstPipelineInputFormat= */ firstAssetLoaderOutputFormat, + addedTrackInfo.streamOffsetUs, transformationRequest, firstEditedMediaItem.flattenForSlowMotion, firstEditedMediaItem.effects.audioProcessors, @@ -493,11 +504,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; compositionVideoEffects.isEmpty() ? null : (Presentation) compositionVideoEffects.get(0); + + // TODO(b/267301878): Pass firstAssetLoaderOutputFormat once surface creation not in VSP. return new VideoSamplePipeline( context, - firstInputFormat, - streamStartPositionUs, - streamOffsetUs, + addedTrackInfo.firstAssetLoaderInputFormat, + addedTrackInfo.streamStartPositionUs, + addedTrackInfo.streamOffsetUs, transformationRequest, firstEditedMediaItem.effects.videoEffects, compositionPresentation, @@ -509,135 +522,123 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; debugViewProvider); } } + return new EncodedSamplePipeline( - firstInputFormat, - streamStartPositionUs, + firstAssetLoaderOutputFormat, + addedTrackInfo.streamStartPositionUs, transformationRequest, muxerWrapper, fallbackListener); } - private boolean shouldTranscode( - Format inputFormat, - @AssetLoader.SupportedOutputTypes int supportedOutputTypes, - long streamStartPositionUs, - long streamOffsetUs) { - boolean assetLoaderCanOutputDecoded = - (supportedOutputTypes & SUPPORTED_OUTPUT_TYPE_DECODED) != 0; - boolean assetLoaderCanOutputEncoded = - (supportedOutputTypes & SUPPORTED_OUTPUT_TYPE_ENCODED) != 0; - checkArgument(assetLoaderCanOutputDecoded || assetLoaderCanOutputEncoded); + private final class AddedTrackInfo { + public final Format firstAssetLoaderInputFormat; + public final long streamStartPositionUs; + public final long streamOffsetUs; + public final boolean shouldTranscode; - @C.TrackType int trackType = getProcessedTrackType(inputFormat.sampleMimeType); - - boolean shouldTranscode = false; - if (!assetLoaderCanOutputEncoded) { - shouldTranscode = true; - } else if (trackType == C.TRACK_TYPE_AUDIO) { - shouldTranscode = shouldTranscodeAudio(inputFormat); - } else if (trackType == C.TRACK_TYPE_VIDEO) { - shouldTranscode = shouldTranscodeVideo(inputFormat, streamStartPositionUs, streamOffsetUs); + public AddedTrackInfo( + Format firstAssetLoaderInputFormat, + @AssetLoader.SupportedOutputTypes int supportedOutputTypes, + long streamStartPositionUs, + long streamOffsetUs) { + this.firstAssetLoaderInputFormat = firstAssetLoaderInputFormat; + this.streamStartPositionUs = streamStartPositionUs; + this.streamOffsetUs = streamOffsetUs; + shouldTranscode = + shouldTranscode( + firstAssetLoaderInputFormat, + supportedOutputTypes, + streamStartPositionUs, + streamOffsetUs); } - checkState(!shouldTranscode || assetLoaderCanOutputDecoded); + private boolean shouldTranscode( + Format inputFormat, + @AssetLoader.SupportedOutputTypes int supportedOutputTypes, + long streamStartPositionUs, + long streamOffsetUs) { + boolean assetLoaderCanOutputDecoded = + (supportedOutputTypes & SUPPORTED_OUTPUT_TYPE_DECODED) != 0; + boolean assetLoaderCanOutputEncoded = + (supportedOutputTypes & SUPPORTED_OUTPUT_TYPE_ENCODED) != 0; + checkArgument(assetLoaderCanOutputDecoded || assetLoaderCanOutputEncoded); - return shouldTranscode; - } + @C.TrackType int trackType = getProcessedTrackType(inputFormat.sampleMimeType); - private boolean shouldTranscodeAudio(Format inputFormat) { - if (editedMediaItems.size() > 1 && !composition.transmuxAudio) { - return true; - } - if (encoderFactory.audioNeedsEncoding()) { - return true; - } - if (transformationRequest.audioMimeType != null - && !transformationRequest.audioMimeType.equals(inputFormat.sampleMimeType)) { - return true; - } - if (transformationRequest.audioMimeType == null - && !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) { - return true; - } - EditedMediaItem firstEditedMediaItem = editedMediaItems.get(0); - if (firstEditedMediaItem.flattenForSlowMotion && isSlowMotion(inputFormat)) { - return true; - } - if (!firstEditedMediaItem.effects.audioProcessors.isEmpty()) { - return true; + boolean shouldTranscode = false; + if (!assetLoaderCanOutputEncoded) { + shouldTranscode = true; + } else if (trackType == C.TRACK_TYPE_AUDIO) { + shouldTranscode = shouldTranscodeAudio(inputFormat); + } else if (trackType == C.TRACK_TYPE_VIDEO) { + shouldTranscode = + shouldTranscodeVideo(inputFormat, streamStartPositionUs, streamOffsetUs); + } + + checkState(!shouldTranscode || assetLoaderCanOutputDecoded); + + return shouldTranscode; } - return false; - } - - private boolean isSlowMotion(Format format) { - @Nullable Metadata metadata = format.metadata; - if (metadata == null) { - return false; - } - for (int i = 0; i < metadata.length(); i++) { - if (metadata.get(i) instanceof SlowMotionData) { + private boolean shouldTranscodeAudio(Format inputFormat) { + if (editedMediaItems.size() > 1 && !composition.transmuxAudio) { return true; } - } - return false; - } - - private boolean shouldTranscodeVideo( - Format inputFormat, long streamStartPositionUs, long streamOffsetUs) { - if (editedMediaItems.size() > 1 && !composition.transmuxVideo) { - return true; - } - EditedMediaItem firstEditedMediaItem = editedMediaItems.get(0); - if ((streamStartPositionUs - streamOffsetUs) != 0 - && !firstEditedMediaItem.mediaItem.clippingConfiguration.startsAtKeyFrame) { - return true; - } - if (encoderFactory.videoNeedsEncoding()) { - return true; - } - if (transformationRequest.hdrMode != TransformationRequest.HDR_MODE_KEEP_HDR) { - return true; - } - if (transformationRequest.videoMimeType != null - && !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) { - return true; - } - if (transformationRequest.videoMimeType == null - && !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) { - return true; - } - if (inputFormat.pixelWidthHeightRatio != 1f) { - return true; - } - if (!areVideoEffectsAllNoOp(firstEditedMediaItem.effects.videoEffects, inputFormat)) { - return true; - } - return false; - } - - /** - * Returns whether all {@code videoEffects} are {@linkplain GlEffect#isNoOp(int, int) no-ops}, - * given an input {@link Format}. - */ - private boolean areVideoEffectsAllNoOp(ImmutableList videoEffects, Format inputFormat) { - int decodedWidth = - (inputFormat.rotationDegrees % 180 == 0) ? inputFormat.width : inputFormat.height; - int decodedHeight = - (inputFormat.rotationDegrees % 180 == 0) ? inputFormat.height : inputFormat.width; - for (int i = 0; i < videoEffects.size(); i++) { - Effect videoEffect = videoEffects.get(i); - if (!(videoEffect instanceof GlEffect)) { - // We cannot confirm whether Effect instances that are not GlEffect instances are - // no-ops. - return false; + if (encoderFactory.audioNeedsEncoding()) { + return true; } - GlEffect glEffect = (GlEffect) videoEffect; - if (!glEffect.isNoOp(decodedWidth, decodedHeight)) { - return false; + if (transformationRequest.audioMimeType != null + && !transformationRequest.audioMimeType.equals(inputFormat.sampleMimeType)) { + return true; } + if (transformationRequest.audioMimeType == null + && !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) { + return true; + } + EditedMediaItem firstEditedMediaItem = editedMediaItems.get(0); + if (firstEditedMediaItem.flattenForSlowMotion && containsSlowMotionData(inputFormat)) { + return true; + } + if (!firstEditedMediaItem.effects.audioProcessors.isEmpty()) { + return true; + } + + return false; + } + + private boolean shouldTranscodeVideo( + Format inputFormat, long streamStartPositionUs, long streamOffsetUs) { + if (editedMediaItems.size() > 1 && !composition.transmuxVideo) { + return true; + } + EditedMediaItem firstEditedMediaItem = editedMediaItems.get(0); + if ((streamStartPositionUs - streamOffsetUs) != 0 + && !firstEditedMediaItem.mediaItem.clippingConfiguration.startsAtKeyFrame) { + return true; + } + if (encoderFactory.videoNeedsEncoding()) { + return true; + } + if (transformationRequest.hdrMode != TransformationRequest.HDR_MODE_KEEP_HDR) { + return true; + } + if (transformationRequest.videoMimeType != null + && !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) { + return true; + } + if (transformationRequest.videoMimeType == null + && !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) { + return true; + } + if (inputFormat.pixelWidthHeightRatio != 1f) { + return true; + } + if (!areVideoEffectsAllNoOp(firstEditedMediaItem.effects.videoEffects, inputFormat)) { + return true; + } + return false; } - return true; } } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerUtil.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerUtil.java index 73d75a8fa8..a2b85fd9a5 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerUtil.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerUtil.java @@ -18,7 +18,13 @@ package androidx.media3.transformer; import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.Effect; +import androidx.media3.common.Format; +import androidx.media3.common.Metadata; import androidx.media3.common.MimeTypes; +import androidx.media3.effect.GlEffect; +import androidx.media3.extractor.metadata.mp4.SlowMotionData; +import com.google.common.collect.ImmutableList; /** Utility methods for Transformer. */ /* package */ final class TransformerUtil { @@ -37,4 +43,45 @@ import androidx.media3.common.MimeTypes; @C.TrackType int trackType = MimeTypes.getTrackType(mimeType); return trackType == C.TRACK_TYPE_IMAGE ? C.TRACK_TYPE_VIDEO : trackType; } + + /** + * Returns whether the collection of {@code videoEffects} would be a {@linkplain + * GlEffect#isNoOp(int, int) no-op}, if queued samples of this {@link Format}. + */ + public static boolean areVideoEffectsAllNoOp( + ImmutableList videoEffects, Format inputFormat) { + int decodedWidth = + (inputFormat.rotationDegrees % 180 == 0) ? inputFormat.width : inputFormat.height; + int decodedHeight = + (inputFormat.rotationDegrees % 180 == 0) ? inputFormat.height : inputFormat.width; + for (int i = 0; i < videoEffects.size(); i++) { + Effect videoEffect = videoEffects.get(i); + if (!(videoEffect instanceof GlEffect)) { + // We cannot confirm whether Effect instances that are not GlEffect instances are + // no-ops. + return false; + } + GlEffect glEffect = (GlEffect) videoEffect; + if (!glEffect.isNoOp(decodedWidth, decodedHeight)) { + return false; + } + } + return true; + } + + /** + * Returns whether the {@link Format} contains {@linkplain SlowMotionData slow motion metadata}. + */ + public static boolean containsSlowMotionData(Format format) { + @Nullable Metadata metadata = format.metadata; + if (metadata == null) { + return false; + } + for (int i = 0; i < metadata.length(); i++) { + if (metadata.get(i) instanceof SlowMotionData) { + return true; + } + } + return false; + } } diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java index f0d64ac2d5..dca3cbd9bc 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java @@ -16,6 +16,7 @@ package androidx.media3.transformer; import static androidx.media3.test.utils.robolectric.RobolectricUtil.runLooperUntil; +import static androidx.media3.transformer.TransformerUtil.getProcessedTrackType; import static com.google.common.truth.Truth.assertThat; import android.content.Context; @@ -23,6 +24,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import androidx.annotation.Nullable; +import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MediaItem; import androidx.media3.common.util.Clock; @@ -46,12 +48,16 @@ public class ExoPlayerAssetLoaderTest { assetLoaderThread.start(); Looper assetLoaderLooper = assetLoaderThread.getLooper(); AtomicReference exceptionRef = new AtomicReference<>(); - AtomicBoolean isTrackAdded = new AtomicBoolean(); + AtomicBoolean isAudioOutputFormatSet = new AtomicBoolean(); + AtomicBoolean isVideoOutputFormatSet = new AtomicBoolean(); + AssetLoader.Listener listener = new AssetLoader.Listener() { private volatile boolean isDurationSet; private volatile boolean isTrackCountSet; + private volatile boolean isAudioTrackAdded; + private volatile boolean isVideoTrackAdded; @Override public void onDurationUs(long durationUs) { @@ -68,8 +74,8 @@ public class ExoPlayerAssetLoaderTest { } @Override - public SampleConsumer onTrackAdded( - Format format, + public boolean onTrackAdded( + Format inputFormat, @AssetLoader.SupportedOutputTypes int supportedOutputTypes, long streamStartPositionUs, long streamOffsetUs) { @@ -80,7 +86,32 @@ public class ExoPlayerAssetLoaderTest { exceptionRef.set( new IllegalStateException("onTrackAdded() called before onTrackCount()")); } - isTrackAdded.set(true); + sleep(); + @C.TrackType int trackType = getProcessedTrackType(inputFormat.sampleMimeType); + if (trackType == C.TRACK_TYPE_AUDIO) { + isAudioTrackAdded = true; + } else if (trackType == C.TRACK_TYPE_VIDEO) { + isVideoTrackAdded = true; + } + return false; + } + + @Override + public SampleConsumer onOutputFormat(Format format) { + @C.TrackType int trackType = getProcessedTrackType(format.sampleMimeType); + boolean isAudio = trackType == C.TRACK_TYPE_AUDIO; + boolean isVideo = trackType == C.TRACK_TYPE_VIDEO; + + boolean isTrackAdded = (isAudio && isAudioTrackAdded) || (isVideo && isVideoTrackAdded); + if (!isTrackAdded) { + exceptionRef.set( + new IllegalStateException("onOutputFormat() called before onTrackAdded()")); + } + if (isAudio) { + isAudioOutputFormatSet.set(true); + } else if (isVideo) { + isVideoOutputFormatSet.set(true); + } return new FakeSampleConsumer(); } @@ -107,7 +138,8 @@ public class ExoPlayerAssetLoaderTest { Looper.myLooper(), () -> { ShadowSystemClock.advanceBy(Duration.ofMillis(10)); - return isTrackAdded.get() || exceptionRef.get() != null; + return (isAudioOutputFormatSet.get() && isVideoOutputFormatSet.get()) + || exceptionRef.get() != null; }); assertThat(exceptionRef.get()).isNull(); @@ -126,11 +158,6 @@ public class ExoPlayerAssetLoaderTest { private static final class FakeSampleConsumer implements SampleConsumer { - @Override - public boolean expectsDecodedData() { - return false; - } - @Nullable @Override public DecoderInputBuffer getInputBuffer() { diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java index b5faf5b2b0..9dada9986a 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java @@ -1000,7 +1000,7 @@ public final class TransformerEndToEndTest { transformer.start(mediaItem, outputPath); runLooperUntil(transformer.getApplicationLooper(), () -> sampleConsumerRef.get() != null); - assertThat(sampleConsumerRef.get().expectsDecodedData()).isTrue(); + assertThat(sampleConsumerRef.get()).isNotInstanceOf(EncodedSamplePipeline.class); } @Test @@ -1488,12 +1488,10 @@ public final class TransformerEndToEndTest { .setChannelCount(2) .build(); try { - SampleConsumer sampleConsumer = - listener.onTrackAdded( - format, - supportedOutputTypes, - /* streamStartPositionUs= */ 0, - /* streamOffsetUs= */ 0); + listener.onTrackAdded( + format, supportedOutputTypes, /* streamStartPositionUs= */ 0, /* streamOffsetUs= */ 0); + + SampleConsumer sampleConsumer = listener.onOutputFormat(format); if (sampleConsumerRef != null) { sampleConsumerRef.set(sampleConsumer); }