diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7a8201de89..a234c0a0d6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -62,6 +62,8 @@ * Change default SDR color working space from linear colors to electrical BT 709 SDR video. Also provides third option to retain the original colorspace. + * Allow defining indeterminate z-order of EditedMediaItemSequences + ([#1055](https://github.com/androidx/media/pull/1055)). * Muxers: * IMA extension: * Promote API that is required for apps to play diff --git a/libraries/common/src/main/java/androidx/media3/common/VideoGraph.java b/libraries/common/src/main/java/androidx/media3/common/VideoGraph.java index 496e16f7d4..6f0e7f042a 100644 --- a/libraries/common/src/main/java/androidx/media3/common/VideoGraph.java +++ b/libraries/common/src/main/java/androidx/media3/common/VideoGraph.java @@ -16,6 +16,7 @@ package androidx.media3.common; +import androidx.annotation.IntRange; import androidx.annotation.Nullable; import androidx.media3.common.util.UnstableApi; @@ -73,19 +74,22 @@ public interface VideoGraph { *

A underlying processing {@link VideoFrameProcessor} is created every time this method is * called. * + *

All inputs must be registered before rendering frames to the underlying {@link + * #getProcessor(int) VideoFrameProcessor}. + * *

If the method throws, the caller must call {@link #release}. * - * @return The id of the registered input, which can be used to get the underlying {@link - * VideoFrameProcessor} via {@link #getProcessor(int)}. + * @param inputIndex The index of the input which could be used to order the inputs. The index + * must start from 0. */ - int registerInput() throws VideoFrameProcessingException; + void registerInput(@IntRange(from = 0) int inputIndex) throws VideoFrameProcessingException; /** * Returns the {@link VideoFrameProcessor} that handles the processing for an input registered via - * {@link #registerInput()}. If the {@code inputId} is not {@linkplain #registerInput() + * {@link #registerInput(int)}. If the {@code inputIndex} is not {@linkplain #registerInput(int) * registered} before, this method will throw an {@link IllegalStateException}. */ - VideoFrameProcessor getProcessor(int inputId); + VideoFrameProcessor getProcessor(int inputIndex); /** * Sets the output surface and supporting information. diff --git a/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java b/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java index 591e7d8e2a..7c84c3ed6b 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java @@ -18,6 +18,7 @@ package androidx.media3.effect; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkStateNotNull; +import static androidx.media3.common.util.Util.contains; import static java.lang.Math.abs; import static java.lang.Math.max; @@ -26,6 +27,7 @@ import android.opengl.EGLContext; import android.opengl.EGLDisplay; import android.opengl.EGLSurface; import android.opengl.GLES20; +import android.util.SparseArray; import androidx.annotation.GuardedBy; import androidx.annotation.IntRange; import androidx.annotation.Nullable; @@ -45,7 +47,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.io.IOException; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Queue; @@ -78,7 +79,8 @@ public final class DefaultVideoCompositor implements VideoCompositor { private static final String THREAD_NAME = "Effect:DefaultVideoCompositor:GlThread"; private static final String TAG = "DefaultVideoCompositor"; - private static final int PRIMARY_INPUT_ID = 0; + // TODO: b/338579287: Use the first registered index instead of a constant value. + private static final int PRIMARY_INPUT_INDEX = 0; private final VideoCompositor.Listener listener; private final GlTextureProducer.Listener textureOutputListener; @@ -88,7 +90,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor; @GuardedBy("this") - private final List inputSources; + private final SparseArray inputSources; @GuardedBy("this") private boolean allInputsEnded; // Whether all inputSources have signaled end of input. @@ -124,7 +126,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { this.settings = settings; this.compositorGlProgram = new CompositorGlProgram(context); - inputSources = new ArrayList<>(); + inputSources = new SparseArray<>(); outputTexturePool = new TexturePool(/* useHighPrecisionColorComponents= */ false, textureOutputCapacity); outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity); @@ -142,25 +144,26 @@ public final class DefaultVideoCompositor implements VideoCompositor { } @Override - public synchronized int registerInputSource() { - inputSources.add(new InputSource()); - return inputSources.size() - 1; + public synchronized void registerInputSource(@IntRange(from = 0) int inputIndex) { + checkState(!contains(inputSources, inputIndex)); + inputSources.put(inputIndex, new InputSource()); } @Override - public synchronized void signalEndOfInputSource(int inputId) { - inputSources.get(inputId).isInputEnded = true; + public synchronized void signalEndOfInputSource(int inputIndex) { + checkState(contains(inputSources, inputIndex)); + inputSources.get(inputIndex).isInputEnded = true; boolean allInputsEnded = true; for (int i = 0; i < inputSources.size(); i++) { - if (!inputSources.get(i).isInputEnded) { + if (!inputSources.valueAt(i).isInputEnded) { allInputsEnded = false; break; } } this.allInputsEnded = allInputsEnded; - if (inputSources.get(PRIMARY_INPUT_ID).frameInfos.isEmpty()) { - if (inputId == PRIMARY_INPUT_ID) { + if (inputSources.get(PRIMARY_INPUT_INDEX).frameInfos.isEmpty()) { + if (inputIndex == PRIMARY_INPUT_INDEX) { releaseExcessFramesInAllSecondaryStreams(); } if (allInputsEnded) { @@ -168,7 +171,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { return; } } - if (inputId != PRIMARY_INPUT_ID && inputSources.get(inputId).frameInfos.size() == 1) { + if (inputIndex != PRIMARY_INPUT_INDEX && inputSources.get(inputIndex).frameInfos.size() == 1) { // When a secondary stream ends input, composite if there was only one pending frame in the // stream. videoFrameProcessingTaskExecutor.submit(this::maybeComposite); @@ -177,12 +180,13 @@ public final class DefaultVideoCompositor implements VideoCompositor { @Override public synchronized void queueInputTexture( - int inputId, + int inputIndex, GlTextureProducer textureProducer, GlTextureInfo inputTexture, ColorInfo colorInfo, long presentationTimeUs) { - InputSource inputSource = inputSources.get(inputId); + checkState(contains(inputSources, inputIndex)); + InputSource inputSource = inputSources.get(inputIndex); checkState(!inputSource.isInputEnded); checkStateNotNull(!ColorInfo.isTransferHdr(colorInfo), "HDR input is not supported."); if (configuredColorInfo == null) { @@ -196,10 +200,10 @@ public final class DefaultVideoCompositor implements VideoCompositor { textureProducer, inputTexture, presentationTimeUs, - settings.getOverlaySettings(inputId, presentationTimeUs)); + settings.getOverlaySettings(inputIndex, presentationTimeUs)); inputSource.frameInfos.add(inputFrameInfo); - if (inputId == PRIMARY_INPUT_ID) { + if (inputIndex == PRIMARY_INPUT_INDEX) { releaseExcessFramesInAllSecondaryStreams(); } else { releaseExcessFramesInSecondaryStream(inputSource); @@ -225,11 +229,11 @@ public final class DefaultVideoCompositor implements VideoCompositor { } private synchronized void releaseExcessFramesInAllSecondaryStreams() { - for (int i = 0; i < inputSources.size(); i++) { - if (i == PRIMARY_INPUT_ID) { + for (int inputIndex = 0; inputIndex < inputSources.size(); inputIndex++) { + if (inputIndex == PRIMARY_INPUT_INDEX) { continue; } - releaseExcessFramesInSecondaryStream(inputSources.get(i)); + releaseExcessFramesInSecondaryStream(inputSources.valueAt(inputIndex)); } } @@ -241,7 +245,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { * began. */ private synchronized void releaseExcessFramesInSecondaryStream(InputSource secondaryInputSource) { - InputSource primaryInputSource = inputSources.get(PRIMARY_INPUT_ID); + InputSource primaryInputSource = inputSources.get(PRIMARY_INPUT_INDEX); // If the primary stream output is ended, all secondary frames can be released. if (primaryInputSource.frameInfos.isEmpty() && primaryInputSource.isInputEnded) { releaseFrames( @@ -292,7 +296,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { return; } - InputFrameInfo primaryInputFrame = framesToComposite.get(PRIMARY_INPUT_ID); + InputFrameInfo primaryInputFrame = framesToComposite.get(PRIMARY_INPUT_INDEX); ImmutableList.Builder inputSizes = new ImmutableList.Builder<>(); for (int i = 0; i < framesToComposite.size(); i++) { @@ -313,7 +317,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { textureOutputListener.onTextureRendered( /* textureProducer= */ this, outputTexture, outputPresentationTimestampUs, syncObject); - InputSource primaryInputSource = inputSources.get(PRIMARY_INPUT_ID); + InputSource primaryInputSource = inputSources.get(PRIMARY_INPUT_INDEX); releaseFrames(primaryInputSource, /* numberOfFramesToRelease= */ 1); releaseExcessFramesInAllSecondaryStreams(); @@ -333,18 +337,18 @@ public final class DefaultVideoCompositor implements VideoCompositor { if (outputTexturePool.freeTextureCount() == 0) { return ImmutableList.of(); } - for (int inputId = 0; inputId < inputSources.size(); inputId++) { - if (inputSources.get(inputId).frameInfos.isEmpty()) { + for (int i = 0; i < inputSources.size(); i++) { + if (inputSources.valueAt(i).frameInfos.isEmpty()) { return ImmutableList.of(); } } ImmutableList.Builder framesToComposite = new ImmutableList.Builder<>(); InputFrameInfo primaryFrameToComposite = - inputSources.get(PRIMARY_INPUT_ID).frameInfos.element(); + inputSources.get(PRIMARY_INPUT_INDEX).frameInfos.element(); framesToComposite.add(primaryFrameToComposite); - for (int inputId = 0; inputId < inputSources.size(); inputId++) { - if (inputId == PRIMARY_INPUT_ID) { + for (int i = 0; i < inputSources.size(); i++) { + if (i == PRIMARY_INPUT_INDEX) { continue; } // Select the secondary streams' frame that would be composited next. The frame selected is @@ -353,7 +357,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { // 2. Two or more frames, and at least one frame has timestamp greater than the target // timestamp. // The smaller timestamp is taken if two timestamps have the same distance from the primary. - InputSource secondaryInputSource = inputSources.get(inputId); + InputSource secondaryInputSource = inputSources.valueAt(i); if (secondaryInputSource.frameInfos.size() == 1 && !secondaryInputSource.isInputEnded) { return ImmutableList.of(); } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/MultipleInputVideoGraph.java b/libraries/effect/src/main/java/androidx/media3/effect/MultipleInputVideoGraph.java index e503ca997d..491572da96 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/MultipleInputVideoGraph.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/MultipleInputVideoGraph.java @@ -33,6 +33,7 @@ import android.opengl.EGLContext; import android.opengl.EGLDisplay; import android.opengl.EGLSurface; import android.util.SparseArray; +import androidx.annotation.IntRange; import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.ColorInfo; @@ -75,7 +76,7 @@ public abstract class MultipleInputVideoGraph implements VideoGraph { private final Executor listenerExecutor; private final VideoCompositorSettings videoCompositorSettings; private final List compositionEffects; - private final List preProcessors; + private final SparseArray preProcessors; private final ExecutorService sharedExecutorService; @@ -114,7 +115,7 @@ public abstract class MultipleInputVideoGraph implements VideoGraph { this.compositionEffects = new ArrayList<>(compositionEffects); this.initialTimestampOffsetUs = initialTimestampOffsetUs; lastRenderedPresentationTimeUs = C.TIME_UNSET; - preProcessors = new ArrayList<>(); + preProcessors = new SparseArray<>(); sharedExecutorService = newSingleThreadScheduledExecutor(SHARED_EXECUTOR_NAME); glObjectsProvider = new SingleContextGlObjectsProvider(); // TODO - b/289986435: Support injecting VideoFrameProcessor.Factory. @@ -136,7 +137,7 @@ public abstract class MultipleInputVideoGraph implements VideoGraph { @Override public void initialize() throws VideoFrameProcessingException { checkState( - preProcessors.isEmpty() + preProcessors.size() == 0 && videoCompositor == null && compositionVideoFrameProcessor == null && !released); @@ -211,10 +212,10 @@ public abstract class MultipleInputVideoGraph implements VideoGraph { } @Override - public int registerInput() throws VideoFrameProcessingException { - checkStateNotNull(videoCompositor); - - int videoCompositorInputId = videoCompositor.registerInputSource(); + public void registerInput(@IntRange(from = 0) int inputIndex) + throws VideoFrameProcessingException { + checkState(!contains(preProcessors, inputIndex)); + checkNotNull(videoCompositor).registerInputSource(inputIndex); // Creating a new VideoFrameProcessor for the input. VideoFrameProcessor preProcessor = videoFrameProcessorFactory @@ -223,7 +224,7 @@ public abstract class MultipleInputVideoGraph implements VideoGraph { // Texture output to compositor. (textureProducer, texture, presentationTimeUs, syncObject) -> queuePreProcessingOutputToCompositor( - videoCompositorInputId, textureProducer, texture, presentationTimeUs), + inputIndex, textureProducer, texture, presentationTimeUs), PRE_COMPOSITOR_TEXTURE_OUTPUT_CAPACITY) .build() .create( @@ -254,17 +255,16 @@ public abstract class MultipleInputVideoGraph implements VideoGraph { @Override public void onEnded() { - onPreProcessingVideoFrameProcessorEnded(videoCompositorInputId); + onPreProcessingVideoFrameProcessorEnded(inputIndex); } }); - preProcessors.add(preProcessor); - return videoCompositorInputId; + preProcessors.put(inputIndex, preProcessor); } @Override - public VideoFrameProcessor getProcessor(int inputId) { - checkState(inputId < preProcessors.size()); - return preProcessors.get(inputId); + public VideoFrameProcessor getProcessor(int inputIndex) { + checkState(contains(preProcessors, inputIndex)); + return preProcessors.get(inputIndex); } @Override @@ -285,7 +285,7 @@ public abstract class MultipleInputVideoGraph implements VideoGraph { // Needs to release the frame processors before their internal executor services are released. for (int i = 0; i < preProcessors.size(); i++) { - preProcessors.get(i).release(); + preProcessors.get(preProcessors.keyAt(i)).release(); } preProcessors.clear(); diff --git a/libraries/effect/src/main/java/androidx/media3/effect/PreviewingSingleInputVideoGraph.java b/libraries/effect/src/main/java/androidx/media3/effect/PreviewingSingleInputVideoGraph.java index d6edae887e..8998831524 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/PreviewingSingleInputVideoGraph.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/PreviewingSingleInputVideoGraph.java @@ -109,6 +109,6 @@ public final class PreviewingSingleInputVideoGraph extends SingleInputVideoGraph @Override public void renderOutputFrame(long renderTimeNs) { - getProcessor(SINGLE_INPUT_INDEX).renderOutputFrame(renderTimeNs); + getProcessor(getInputIndex()).renderOutputFrame(renderTimeNs); } } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/SingleInputVideoGraph.java b/libraries/effect/src/main/java/androidx/media3/effect/SingleInputVideoGraph.java index 33a4536481..9328eab27f 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/SingleInputVideoGraph.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/SingleInputVideoGraph.java @@ -16,11 +16,13 @@ package androidx.media3.effect; +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 android.content.Context; import androidx.annotation.Nullable; +import androidx.media3.common.C; import androidx.media3.common.ColorInfo; import androidx.media3.common.DebugViewProvider; import androidx.media3.common.Effect; @@ -38,9 +40,6 @@ import java.util.concurrent.Executor; @UnstableApi public abstract class SingleInputVideoGraph implements VideoGraph { - /** The ID {@link #registerInput()} returns. */ - public static final int SINGLE_INPUT_INDEX = 0; - private final Context context; private final VideoFrameProcessor.Factory videoFrameProcessorFactory; private final ColorInfo outputColorInfo; @@ -56,6 +55,7 @@ public abstract class SingleInputVideoGraph implements VideoGraph { private boolean isEnded; private boolean released; private volatile boolean hasProducedFrameWithTimestampZero; + private int inputIndex; /** * Creates an instance. @@ -86,6 +86,7 @@ public abstract class SingleInputVideoGraph implements VideoGraph { this.renderFramesAutomatically = renderFramesAutomatically; this.presentation = presentation; this.initialTimestampOffsetUs = initialTimestampOffsetUs; + this.inputIndex = C.INDEX_UNSET; } /** @@ -99,9 +100,11 @@ public abstract class SingleInputVideoGraph implements VideoGraph { } @Override - public int registerInput() throws VideoFrameProcessingException { + public void registerInput(int inputIndex) throws VideoFrameProcessingException { checkStateNotNull(videoFrameProcessor == null && !released); + checkState(this.inputIndex == C.INDEX_UNSET); + this.inputIndex = inputIndex; videoFrameProcessor = videoFrameProcessorFactory.create( context, @@ -159,11 +162,11 @@ public abstract class SingleInputVideoGraph implements VideoGraph { if (outputSurfaceInfo != null) { videoFrameProcessor.setOutputSurfaceInfo(outputSurfaceInfo); } - return SINGLE_INPUT_INDEX; } @Override - public VideoFrameProcessor getProcessor(int inputId) { + public VideoFrameProcessor getProcessor(int inputIndex) { + checkArgument(this.inputIndex != C.INDEX_UNSET && this.inputIndex == inputIndex); return checkStateNotNull(videoFrameProcessor); } @@ -193,6 +196,10 @@ public abstract class SingleInputVideoGraph implements VideoGraph { released = true; } + protected int getInputIndex() { + return inputIndex; + } + protected long getInitialTimestampOffsetUs() { return initialTimestampOffsetUs; } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java b/libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java index 5ec3e68047..ef43babf1f 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java @@ -15,6 +15,7 @@ */ package androidx.media3.effect; +import androidx.annotation.IntRange; import androidx.media3.common.ColorInfo; import androidx.media3.common.GlTextureInfo; import androidx.media3.common.VideoFrameProcessingException; @@ -46,29 +47,35 @@ public interface VideoCompositor extends GlTextureProducer { } /** - * Registers a new input source, and returns a unique {@code inputId} corresponding to this - * source, to be used in {@link #queueInputTexture}. + * Registers a new input source. + * + * @param inputIndex The index of the input source which could be used to determine the order of + * the input sources. The same index should to be used in {@link #queueInputTexture}. The + * index must start from 0. All inputs must be registered before {@linkplain + * #queueInputTexture(int, GlTextureProducer, GlTextureInfo, ColorInfo, long) queueing} + * textures. */ - int registerInputSource(); + void registerInputSource(@IntRange(from = 0) int inputIndex); /** * Signals that no more frames will come from the upstream {@link GlTextureProducer.Listener}. * - * @param inputId The identifier for an input source, returned from {@link #registerInputSource}. + * @param inputIndex The index of the input source. */ - void signalEndOfInputSource(int inputId); + void signalEndOfInputSource(int inputIndex); /** * Queues an input texture to be composited. * - * @param inputId The identifier for an input source, returned from {@link #registerInputSource}. + * @param inputIndex The index of the input source, the same index used when {@linkplain + * #registerInputSource(int) registering the input source}. * @param textureProducer The source from where the {@code inputTexture} is produced. * @param inputTexture The {@link GlTextureInfo} to composite. * @param colorInfo The {@link ColorInfo} of {@code inputTexture}. * @param presentationTimeUs The presentation time of {@code inputTexture}, in microseconds. */ void queueInputTexture( - int inputId, + int inputIndex, GlTextureProducer textureProducer, GlTextureInfo inputTexture, ColorInfo colorInfo, diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java index 7ba81a4389..5e468015d4 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java @@ -419,7 +419,6 @@ public final class CompositingVideoSinkProvider outputColorInfo = inputColorInfo.buildUpon().setColorTransfer(C.COLOR_TRANSFER_ST2084).build(); } - int videoGraphInputId; try { videoGraph = previewingVideoGraphFactory.create( @@ -435,12 +434,12 @@ public final class CompositingVideoSinkProvider Size size = currentSurfaceAndSize.second; maybeSetOutputSurfaceInfo(surface, size.getWidth(), size.getHeight()); } - videoGraphInputId = videoGraph.registerInput(); + videoGraph.registerInput(/* inputIndex= */ 0); } catch (VideoFrameProcessingException e) { throw new VideoSink.VideoSinkException(e, sourceFormat); } state = STATE_INITIALIZED; - return videoGraph.getProcessor(videoGraphInputId); + return videoGraph.getProcessor(/* inputIndex= */ 0); } private boolean isInitialized() { diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/DefaultVideoCompositorPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/DefaultVideoCompositorPixelTest.java index 2d584fdf30..76094d0144 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/DefaultVideoCompositorPixelTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/DefaultVideoCompositorPixelTest.java @@ -744,7 +744,8 @@ public final class DefaultVideoCompositorPixelTest { textureBitmapReader, videoCompositor, sharedExecutorService, - glObjectsProvider) + glObjectsProvider, + /* inputIndex= */ i) .setEffects(effectsToApply.build()) .build(); inputVideoFrameProcessorTestRunners.add(vfpTestRunner); @@ -855,8 +856,9 @@ public final class DefaultVideoCompositorPixelTest { TextureBitmapReader textureBitmapReader, VideoCompositor videoCompositor, @Nullable ExecutorService executorService, - GlObjectsProvider glObjectsProvider) { - int inputId = videoCompositor.registerInputSource(); + GlObjectsProvider glObjectsProvider, + int inputIndex) { + videoCompositor.registerInputSource(inputIndex); DefaultVideoFrameProcessor.Factory.Builder defaultVideoFrameProcessorFactoryBuilder = new DefaultVideoFrameProcessor.Factory.Builder() .setGlObjectsProvider(glObjectsProvider) @@ -870,7 +872,7 @@ public final class DefaultVideoCompositorPixelTest { textureBitmapReader.readBitmapUnpremultipliedAlpha( outputTexture, presentationTimeUs); videoCompositor.queueInputTexture( - inputId, + inputIndex, outputTextureProducer, outputTexture, ColorInfo.SRGB_BT709_FULL, @@ -884,7 +886,7 @@ public final class DefaultVideoCompositorPixelTest { .setTestId(testId) .setVideoFrameProcessorFactory(defaultVideoFrameProcessorFactoryBuilder.build()) .setBitmapReader(textureBitmapReader) - .setOnEndedListener(() -> videoCompositor.signalEndOfInputSource(inputId)); + .setOnEndedListener(() -> videoCompositor.signalEndOfInputSource(inputIndex)); } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSampleExporter.java b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSampleExporter.java index 6d434bd74c..9b053c3f3b 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSampleExporter.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSampleExporter.java @@ -97,7 +97,7 @@ import org.checkerframework.dataflow.qual.Pure; } @Override - public AudioGraphInput getInput(EditedMediaItem editedMediaItem, Format format) + public AudioGraphInput getInput(EditedMediaItem editedMediaItem, Format format, int inputIndex) throws ExportException { if (!returnedFirstInput) { // First input initialized in constructor because output AudioFormat is needed. diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/EncodedSampleExporter.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EncodedSampleExporter.java index 03097e1119..d7d3e65f87 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/EncodedSampleExporter.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EncodedSampleExporter.java @@ -129,7 +129,7 @@ import java.util.concurrent.atomic.AtomicLong; } @Override - public GraphInput getInput(EditedMediaItem item, Format format) { + public GraphInput getInput(EditedMediaItem item, Format format, int inputIndex) { return this; } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SampleExporter.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SampleExporter.java index b430639161..cafc939a5d 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SampleExporter.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SampleExporter.java @@ -64,10 +64,11 @@ import java.util.List; * * @param editedMediaItem The initial {@link EditedMediaItem} of the input. * @param format The initial {@link Format} of the input. + * @param inputIndex The index of the input. * @throws ExportException If an error occurs getting the input. */ - public abstract GraphInput getInput(EditedMediaItem editedMediaItem, Format format) - throws ExportException; + public abstract GraphInput getInput( + EditedMediaItem editedMediaItem, Format format, int inputIndex) throws ExportException; /** * Processes the input data and returns whether it may be possible to process more data by calling 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 1adf5c1de6..f840801b26 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java @@ -637,7 +637,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } GraphInput sampleExporterInput = - sampleExporter.getInput(firstEditedMediaItem, assetLoaderOutputFormat); + sampleExporter.getInput(firstEditedMediaItem, assetLoaderOutputFormat, sequenceIndex); OnMediaItemChangedListener onMediaItemChangedListener = (editedMediaItem, durationUs, decodedFormat, isLast) -> { onMediaItemChanged(trackType, durationUs, isLast); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerMultipleInputVideoGraph.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerMultipleInputVideoGraph.java index 27a122bdda..f206525bab 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerMultipleInputVideoGraph.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerMultipleInputVideoGraph.java @@ -79,9 +79,9 @@ import java.util.concurrent.Executor; } @Override - public GraphInput createInput() throws VideoFrameProcessingException { - int inputId = registerInput(); + public GraphInput createInput(int inputIndex) throws VideoFrameProcessingException { + registerInput(inputIndex); return new VideoFrameProcessingWrapper( - getProcessor(inputId), /* presentation= */ null, getInitialTimestampOffsetUs()); + getProcessor(inputIndex), /* presentation= */ null, getInitialTimestampOffsetUs()); } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerSingleInputVideoGraph.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerSingleInputVideoGraph.java index 5118e7fe47..571dd81ae6 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerSingleInputVideoGraph.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerSingleInputVideoGraph.java @@ -106,12 +106,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } @Override - public GraphInput createInput() throws VideoFrameProcessingException { + public GraphInput createInput(int inputIndex) throws VideoFrameProcessingException { checkState(videoFrameProcessingWrapper == null); - int inputId = registerInput(); + registerInput(inputIndex); videoFrameProcessingWrapper = new VideoFrameProcessingWrapper( - getProcessor(inputId), getPresentation(), getInitialTimestampOffsetUs()); + getProcessor(inputIndex), getPresentation(), getInitialTimestampOffsetUs()); return videoFrameProcessingWrapper; } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoGraph.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoGraph.java index eb9e836e45..016da4c01a 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoGraph.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoGraph.java @@ -69,6 +69,8 @@ import java.util.concurrent.Executor; *

This method must called exactly once for every input stream. * *

If the method throws any {@link Exception}, the caller must call {@link #release}. + * + * @param inputIndex The index of the input, which could be used to order the inputs. */ - GraphInput createInput() throws VideoFrameProcessingException; + GraphInput createInput(int inputIndex) throws VideoFrameProcessingException; } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java index 929d4727db..02a557e627 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java @@ -33,6 +33,7 @@ import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.util.Pair; import android.view.Surface; +import androidx.annotation.IntRange; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.media3.common.C; @@ -153,10 +154,10 @@ import org.checkerframework.dataflow.qual.Pure; } @Override - public GraphInput getInput(EditedMediaItem editedMediaItem, Format format) + public GraphInput getInput(EditedMediaItem editedMediaItem, Format format, int inputIndex) throws ExportException { try { - return videoGraph.createInput(); + return videoGraph.createInput(inputIndex); } catch (VideoFrameProcessingException e) { throw ExportException.createForVideoFrameProcessingException(e); } @@ -540,18 +541,19 @@ import org.checkerframework.dataflow.qual.Pure; } @Override - public int registerInput() throws VideoFrameProcessingException { - return videoGraph.registerInput(); + public void registerInput(@IntRange(from = 0) int inputIndex) + throws VideoFrameProcessingException { + videoGraph.registerInput(inputIndex); } @Override - public VideoFrameProcessor getProcessor(int inputId) { - return videoGraph.getProcessor(inputId); + public VideoFrameProcessor getProcessor(int inputIndex) { + return videoGraph.getProcessor(inputIndex); } @Override - public GraphInput createInput() throws VideoFrameProcessingException { - return videoGraph.createInput(); + public GraphInput createInput(int inputIndex) throws VideoFrameProcessingException { + return videoGraph.createInput(inputIndex); } @Override