From 177f1f33d0dc67c2a7b65466a8d16cf926006113 Mon Sep 17 00:00:00 2001 From: Patrik Aradi Date: Wed, 31 Jan 2024 09:55:40 +0800 Subject: [PATCH] Fix indeterminate z-order of EditedMediaItemSequences by passing sequenceIndex when registeringInput --- .../androidx/media3/common/VideoGraph.java | 6 +++-- .../media3/effect/DefaultVideoCompositor.java | 26 +++++++++++-------- .../effect/MultipleInputVideoGraph.java | 15 ++++++++--- .../media3/effect/SingleInputVideoGraph.java | 4 +-- .../media3/effect/VideoCompositor.java | 6 +++-- .../video/CompositingVideoSinkProvider.java | 2 ++ .../DefaultVideoCompositorPixelTest.java | 2 +- .../transformer/AudioSampleExporter.java | 2 +- .../transformer/EncodedSampleExporter.java | 2 +- .../media3/transformer/SampleExporter.java | 4 +-- .../transformer/TransformerInternal.java | 2 +- .../TransformerMultipleInputVideoGraph.java | 5 ++-- .../TransformerSingleInputVideoGraph.java | 11 +++++--- .../transformer/TransformerVideoGraph.java | 4 ++- .../transformer/VideoSampleExporter.java | 16 +++++++----- 15 files changed, 66 insertions(+), 41 deletions(-) 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..3221caa6be 100644 --- a/libraries/common/src/main/java/androidx/media3/common/VideoGraph.java +++ b/libraries/common/src/main/java/androidx/media3/common/VideoGraph.java @@ -75,14 +75,16 @@ public interface VideoGraph { * *

If the method throws, the caller must call {@link #release}. * + * @param sequenceIndex The sequence index of the input which can aid ordering of the inputs. + * * @return The id of the registered input, which can be used to get the underlying {@link * VideoFrameProcessor} via {@link #getProcessor(int)}. */ - int registerInput() throws VideoFrameProcessingException; + int registerInput(int sequenceIndex) 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 inputId} is not {@linkplain #registerInput(int) * registered} before, this method will throw an {@link IllegalStateException}. */ VideoFrameProcessor getProcessor(int inputId); 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..70f0f76a71 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/DefaultVideoCompositor.java @@ -26,6 +26,8 @@ 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; @@ -41,16 +43,18 @@ import androidx.media3.common.util.LongArrayQueue; import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; + import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + import java.io.IOException; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Queue; import java.util.concurrent.ExecutorService; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * A basic {@link VideoCompositor} implementation that takes in frames from input sources' streams @@ -88,7 +92,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 +128,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,9 +146,9 @@ public final class DefaultVideoCompositor implements VideoCompositor { } @Override - public synchronized int registerInputSource() { - inputSources.add(new InputSource()); - return inputSources.size() - 1; + public synchronized int registerInputSource(int sequenceId) { + inputSources.put(sequenceId, new InputSource()); + return sequenceId; } @Override @@ -152,7 +156,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { inputSources.get(inputId).isInputEnded = true; boolean allInputsEnded = true; for (int i = 0; i < inputSources.size(); i++) { - if (!inputSources.get(i).isInputEnded) { + if (!inputSources.get(inputSources.keyAt(i)).isInputEnded) { allInputsEnded = false; break; } @@ -229,7 +233,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { if (i == PRIMARY_INPUT_ID) { continue; } - releaseExcessFramesInSecondaryStream(inputSources.get(i)); + releaseExcessFramesInSecondaryStream(inputSources.get(inputSources.keyAt(i))); } } @@ -334,7 +338,7 @@ public final class DefaultVideoCompositor implements VideoCompositor { return ImmutableList.of(); } for (int inputId = 0; inputId < inputSources.size(); inputId++) { - if (inputSources.get(inputId).frameInfos.isEmpty()) { + if (inputSources.get(inputSources.keyAt(inputId)).frameInfos.isEmpty()) { return ImmutableList.of(); } } @@ -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.get(inputSources.keyAt(inputId)); 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..6dac599b0b 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/MultipleInputVideoGraph.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/MultipleInputVideoGraph.java @@ -46,6 +46,7 @@ import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoGraph; import androidx.media3.common.util.GlUtil; +import androidx.media3.common.util.NullableType; import androidx.media3.common.util.UnstableApi; import com.google.common.util.concurrent.MoreExecutors; import java.util.ArrayDeque; @@ -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 List<@NullableType VideoFrameProcessor> preProcessors; private final ExecutorService sharedExecutorService; @@ -211,10 +212,11 @@ public abstract class MultipleInputVideoGraph implements VideoGraph { } @Override - public int registerInput() throws VideoFrameProcessingException { + public int registerInput(int forceId) throws VideoFrameProcessingException { checkStateNotNull(videoCompositor); - int videoCompositorInputId = videoCompositor.registerInputSource(); + int videoCompositorInputId; + videoCompositorInputId = videoCompositor.registerInputSource(forceId); // Creating a new VideoFrameProcessor for the input. VideoFrameProcessor preProcessor = videoFrameProcessorFactory @@ -257,7 +259,12 @@ public abstract class MultipleInputVideoGraph implements VideoGraph { onPreProcessingVideoFrameProcessorEnded(videoCompositorInputId); } }); - preProcessors.add(preProcessor); + + while (preProcessors.size() <= videoCompositorInputId) { + //noinspection DataFlowIssue + preProcessors.add(null); + } + preProcessors.set(videoCompositorInputId, preProcessor); return videoCompositorInputId; } 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..17f1293562 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/SingleInputVideoGraph.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/SingleInputVideoGraph.java @@ -38,7 +38,7 @@ import java.util.concurrent.Executor; @UnstableApi public abstract class SingleInputVideoGraph implements VideoGraph { - /** The ID {@link #registerInput()} returns. */ + /** The ID {@link #registerInput(int)} returns. */ public static final int SINGLE_INPUT_INDEX = 0; private final Context context; @@ -99,7 +99,7 @@ public abstract class SingleInputVideoGraph implements VideoGraph { } @Override - public int registerInput() throws VideoFrameProcessingException { + public int registerInput(int sequenceIndex) throws VideoFrameProcessingException { checkStateNotNull(videoFrameProcessor == null && !released); videoFrameProcessor = 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..13ed072bb7 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/VideoCompositor.java @@ -48,9 +48,11 @@ 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}. + * + * @param sequenceId The sequence ID of the input source, which is can be used to determine the + * order of the input sources. */ - int registerInputSource(); - + int registerInputSource(int sequenceId); /** * Signals that no more frames will come from the upstream {@link GlTextureProducer.Listener}. * 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..f9da1c3789 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 @@ -550,6 +550,8 @@ public final class CompositingVideoSinkProvider // reduces decoder timeouts, and consider restoring. videoFrameProcessorMaxPendingFrameCount = Util.getMaxPendingFramesCountForMediaCodecDecoders(context); + int videoGraphInputId = videoGraph.registerInput(0); + videoFrameProcessor = videoGraph.getProcessor(videoGraphInputId); videoEffects = new ArrayList<>(); finalBufferPresentationTimeUs = C.TIME_UNSET; 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..bf85f8a757 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/DefaultVideoCompositorPixelTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/DefaultVideoCompositorPixelTest.java @@ -856,7 +856,7 @@ public final class DefaultVideoCompositorPixelTest { VideoCompositor videoCompositor, @Nullable ExecutorService executorService, GlObjectsProvider glObjectsProvider) { - int inputId = videoCompositor.registerInputSource(); + int inputId = videoCompositor.registerInputSource(0); DefaultVideoFrameProcessor.Factory.Builder defaultVideoFrameProcessorFactoryBuilder = new DefaultVideoFrameProcessor.Factory.Builder() .setGlObjectsProvider(glObjectsProvider) 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..951f8165f8 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 sequenceIndex) 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..30e1f81207 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 sequenceIndex) { 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..276caef733 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,10 @@ import java.util.List; * * @param editedMediaItem The initial {@link EditedMediaItem} of the input. * @param format The initial {@link Format} of the input. + * @param sequenceIndex The sequence 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 sequenceIndex) 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..d40df2a4ca 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..a334379efb 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerMultipleInputVideoGraph.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerMultipleInputVideoGraph.java @@ -22,6 +22,7 @@ import androidx.media3.common.DebugViewProvider; import androidx.media3.common.Effect; import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoGraph; +import androidx.media3.common.util.Log; import androidx.media3.effect.MultipleInputVideoGraph; import androidx.media3.effect.VideoCompositorSettings; import java.util.List; @@ -79,8 +80,8 @@ import java.util.concurrent.Executor; } @Override - public GraphInput createInput() throws VideoFrameProcessingException { - int inputId = registerInput(); + public GraphInput createInput(int sequenceIndex) throws VideoFrameProcessingException { + int inputId = registerInput(sequenceIndex); return new VideoFrameProcessingWrapper( getProcessor(inputId), /* 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..588ccc0601 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,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } @Override - public GraphInput createInput() throws VideoFrameProcessingException { + public GraphInput createInput(int sequenceIndex) throws VideoFrameProcessingException { checkState(videoFrameProcessingWrapper == null); - int inputId = registerInput(); + int inputId = registerInput(sequenceIndex); videoFrameProcessingWrapper = - new VideoFrameProcessingWrapper( - getProcessor(inputId), getPresentation(), getInitialTimestampOffsetUs()); + new VideoFrameProcessingWrapper( + getProcessor(inputId), + getInputColorInfo(), + 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..5883e8ab6f 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 sequenceIndex The sequence index of the input, which can aid ordering of the inputs. */ - GraphInput createInput() throws VideoFrameProcessingException; + GraphInput createInput(int sequenceIndex) 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..0556bdf65f 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSampleExporter.java @@ -50,6 +50,7 @@ import androidx.media3.common.util.Util; import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.effect.DebugTraceUtil; import androidx.media3.effect.VideoCompositorSettings; +import androidx.media3.exoplayer.mediacodec.MediaCodecUtil; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.MoreExecutors; import java.nio.ByteBuffer; @@ -58,6 +59,8 @@ import java.util.Objects; import org.checkerframework.checker.initialization.qual.Initialized; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.dataflow.qual.Pure; +import java.nio.ByteBuffer; +import java.util.List; /** Processes, encodes and muxes raw video frames. */ /* package */ final class VideoSampleExporter extends SampleExporter { @@ -153,10 +156,9 @@ import org.checkerframework.dataflow.qual.Pure; } @Override - public GraphInput getInput(EditedMediaItem editedMediaItem, Format format) - throws ExportException { + public GraphInput getInput(EditedMediaItem editedMediaItem, Format format, int sequenceIndex) throws ExportException { try { - return videoGraph.createInput(); + return videoGraph.createInput(sequenceIndex); } catch (VideoFrameProcessingException e) { throw ExportException.createForVideoFrameProcessingException(e); } @@ -540,8 +542,8 @@ import org.checkerframework.dataflow.qual.Pure; } @Override - public int registerInput() throws VideoFrameProcessingException { - return videoGraph.registerInput(); + public int registerInput(int sequenceIndex) throws VideoFrameProcessingException { + return videoGraph.registerInput(sequenceIndex); } @Override @@ -550,8 +552,8 @@ import org.checkerframework.dataflow.qual.Pure; } @Override - public GraphInput createInput() throws VideoFrameProcessingException { - return videoGraph.createInput(); + public GraphInput createInput(int sequenceIndex) throws VideoFrameProcessingException { + return videoGraph.createInput(sequenceIndex); } @Override