Fix indeterminate z-order of EditedMediaItemSequences by passing sequenceIndex when registeringInput

This commit is contained in:
Patrik Aradi 2024-01-31 09:55:40 +08:00 committed by Luyuan Chen
parent bef3d518d2
commit 177f1f33d0
15 changed files with 66 additions and 41 deletions

View File

@ -75,14 +75,16 @@ public interface VideoGraph {
* *
* <p>If the method throws, the caller must call {@link #release}. * <p>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 * @return The id of the registered input, which can be used to get the underlying {@link
* VideoFrameProcessor} via {@link #getProcessor(int)}. * 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 * 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}. * registered} before, this method will throw an {@link IllegalStateException}.
*/ */
VideoFrameProcessor getProcessor(int inputId); VideoFrameProcessor getProcessor(int inputId);

View File

@ -26,6 +26,8 @@ import android.opengl.EGLContext;
import android.opengl.EGLDisplay; import android.opengl.EGLDisplay;
import android.opengl.EGLSurface; import android.opengl.EGLSurface;
import android.opengl.GLES20; import android.opengl.GLES20;
import android.util.SparseArray;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; 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.Size;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ExecutorService; 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 * 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; private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
@GuardedBy("this") @GuardedBy("this")
private final List<InputSource> inputSources; private final SparseArray<InputSource> inputSources;
@GuardedBy("this") @GuardedBy("this")
private boolean allInputsEnded; // Whether all inputSources have signaled end of input. 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.settings = settings;
this.compositorGlProgram = new CompositorGlProgram(context); this.compositorGlProgram = new CompositorGlProgram(context);
inputSources = new ArrayList<>(); inputSources = new SparseArray<>();
outputTexturePool = outputTexturePool =
new TexturePool(/* useHighPrecisionColorComponents= */ false, textureOutputCapacity); new TexturePool(/* useHighPrecisionColorComponents= */ false, textureOutputCapacity);
outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity); outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity);
@ -142,9 +146,9 @@ public final class DefaultVideoCompositor implements VideoCompositor {
} }
@Override @Override
public synchronized int registerInputSource() { public synchronized int registerInputSource(int sequenceId) {
inputSources.add(new InputSource()); inputSources.put(sequenceId, new InputSource());
return inputSources.size() - 1; return sequenceId;
} }
@Override @Override
@ -152,7 +156,7 @@ public final class DefaultVideoCompositor implements VideoCompositor {
inputSources.get(inputId).isInputEnded = true; inputSources.get(inputId).isInputEnded = true;
boolean allInputsEnded = true; boolean allInputsEnded = true;
for (int i = 0; i < inputSources.size(); i++) { for (int i = 0; i < inputSources.size(); i++) {
if (!inputSources.get(i).isInputEnded) { if (!inputSources.get(inputSources.keyAt(i)).isInputEnded) {
allInputsEnded = false; allInputsEnded = false;
break; break;
} }
@ -229,7 +233,7 @@ public final class DefaultVideoCompositor implements VideoCompositor {
if (i == PRIMARY_INPUT_ID) { if (i == PRIMARY_INPUT_ID) {
continue; continue;
} }
releaseExcessFramesInSecondaryStream(inputSources.get(i)); releaseExcessFramesInSecondaryStream(inputSources.get(inputSources.keyAt(i)));
} }
} }
@ -334,7 +338,7 @@ public final class DefaultVideoCompositor implements VideoCompositor {
return ImmutableList.of(); return ImmutableList.of();
} }
for (int inputId = 0; inputId < inputSources.size(); inputId++) { 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(); 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 // 2. Two or more frames, and at least one frame has timestamp greater than the target
// timestamp. // timestamp.
// The smaller timestamp is taken if two timestamps have the same distance from the primary. // 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) { if (secondaryInputSource.frameInfos.size() == 1 && !secondaryInputSource.isInputEnded) {
return ImmutableList.of(); return ImmutableList.of();
} }

View File

@ -46,6 +46,7 @@ import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.VideoGraph; import androidx.media3.common.VideoGraph;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -75,7 +76,7 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
private final Executor listenerExecutor; private final Executor listenerExecutor;
private final VideoCompositorSettings videoCompositorSettings; private final VideoCompositorSettings videoCompositorSettings;
private final List<Effect> compositionEffects; private final List<Effect> compositionEffects;
private final List<VideoFrameProcessor> preProcessors; private final List<@NullableType VideoFrameProcessor> preProcessors;
private final ExecutorService sharedExecutorService; private final ExecutorService sharedExecutorService;
@ -211,10 +212,11 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
} }
@Override @Override
public int registerInput() throws VideoFrameProcessingException { public int registerInput(int forceId) throws VideoFrameProcessingException {
checkStateNotNull(videoCompositor); checkStateNotNull(videoCompositor);
int videoCompositorInputId = videoCompositor.registerInputSource(); int videoCompositorInputId;
videoCompositorInputId = videoCompositor.registerInputSource(forceId);
// Creating a new VideoFrameProcessor for the input. // Creating a new VideoFrameProcessor for the input.
VideoFrameProcessor preProcessor = VideoFrameProcessor preProcessor =
videoFrameProcessorFactory videoFrameProcessorFactory
@ -257,7 +259,12 @@ public abstract class MultipleInputVideoGraph implements VideoGraph {
onPreProcessingVideoFrameProcessorEnded(videoCompositorInputId); onPreProcessingVideoFrameProcessorEnded(videoCompositorInputId);
} }
}); });
preProcessors.add(preProcessor);
while (preProcessors.size() <= videoCompositorInputId) {
//noinspection DataFlowIssue
preProcessors.add(null);
}
preProcessors.set(videoCompositorInputId, preProcessor);
return videoCompositorInputId; return videoCompositorInputId;
} }

View File

@ -38,7 +38,7 @@ import java.util.concurrent.Executor;
@UnstableApi @UnstableApi
public abstract class SingleInputVideoGraph implements VideoGraph { 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; public static final int SINGLE_INPUT_INDEX = 0;
private final Context context; private final Context context;
@ -99,7 +99,7 @@ public abstract class SingleInputVideoGraph implements VideoGraph {
} }
@Override @Override
public int registerInput() throws VideoFrameProcessingException { public int registerInput(int sequenceIndex) throws VideoFrameProcessingException {
checkStateNotNull(videoFrameProcessor == null && !released); checkStateNotNull(videoFrameProcessor == null && !released);
videoFrameProcessor = videoFrameProcessor =

View File

@ -48,9 +48,11 @@ public interface VideoCompositor extends GlTextureProducer {
/** /**
* Registers a new input source, and returns a unique {@code inputId} corresponding to this * Registers a new input source, and returns a unique {@code inputId} corresponding to this
* source, to be used in {@link #queueInputTexture}. * 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}. * Signals that no more frames will come from the upstream {@link GlTextureProducer.Listener}.
* *

View File

@ -550,6 +550,8 @@ public final class CompositingVideoSinkProvider
// reduces decoder timeouts, and consider restoring. // reduces decoder timeouts, and consider restoring.
videoFrameProcessorMaxPendingFrameCount = videoFrameProcessorMaxPendingFrameCount =
Util.getMaxPendingFramesCountForMediaCodecDecoders(context); Util.getMaxPendingFramesCountForMediaCodecDecoders(context);
int videoGraphInputId = videoGraph.registerInput(0);
videoFrameProcessor = videoGraph.getProcessor(videoGraphInputId);
videoEffects = new ArrayList<>(); videoEffects = new ArrayList<>();
finalBufferPresentationTimeUs = C.TIME_UNSET; finalBufferPresentationTimeUs = C.TIME_UNSET;

View File

@ -856,7 +856,7 @@ public final class DefaultVideoCompositorPixelTest {
VideoCompositor videoCompositor, VideoCompositor videoCompositor,
@Nullable ExecutorService executorService, @Nullable ExecutorService executorService,
GlObjectsProvider glObjectsProvider) { GlObjectsProvider glObjectsProvider) {
int inputId = videoCompositor.registerInputSource(); int inputId = videoCompositor.registerInputSource(0);
DefaultVideoFrameProcessor.Factory.Builder defaultVideoFrameProcessorFactoryBuilder = DefaultVideoFrameProcessor.Factory.Builder defaultVideoFrameProcessorFactoryBuilder =
new DefaultVideoFrameProcessor.Factory.Builder() new DefaultVideoFrameProcessor.Factory.Builder()
.setGlObjectsProvider(glObjectsProvider) .setGlObjectsProvider(glObjectsProvider)

View File

@ -97,7 +97,7 @@ import org.checkerframework.dataflow.qual.Pure;
} }
@Override @Override
public AudioGraphInput getInput(EditedMediaItem editedMediaItem, Format format) public AudioGraphInput getInput(EditedMediaItem editedMediaItem, Format format, int sequenceIndex)
throws ExportException { throws ExportException {
if (!returnedFirstInput) { if (!returnedFirstInput) {
// First input initialized in constructor because output AudioFormat is needed. // First input initialized in constructor because output AudioFormat is needed.

View File

@ -129,7 +129,7 @@ import java.util.concurrent.atomic.AtomicLong;
} }
@Override @Override
public GraphInput getInput(EditedMediaItem item, Format format) { public GraphInput getInput(EditedMediaItem item, Format format, int sequenceIndex) {
return this; return this;
} }

View File

@ -64,10 +64,10 @@ import java.util.List;
* *
* @param editedMediaItem The initial {@link EditedMediaItem} of the input. * @param editedMediaItem The initial {@link EditedMediaItem} of the input.
* @param format The initial {@link Format} 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. * @throws ExportException If an error occurs getting the input.
*/ */
public abstract GraphInput getInput(EditedMediaItem editedMediaItem, Format format) public abstract GraphInput getInput(EditedMediaItem editedMediaItem, Format format, int sequenceIndex) throws ExportException;
throws ExportException;
/** /**
* Processes the input data and returns whether it may be possible to process more data by calling * Processes the input data and returns whether it may be possible to process more data by calling

View File

@ -637,7 +637,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
GraphInput sampleExporterInput = GraphInput sampleExporterInput =
sampleExporter.getInput(firstEditedMediaItem, assetLoaderOutputFormat); sampleExporter.getInput(firstEditedMediaItem, assetLoaderOutputFormat, sequenceIndex);
OnMediaItemChangedListener onMediaItemChangedListener = OnMediaItemChangedListener onMediaItemChangedListener =
(editedMediaItem, durationUs, decodedFormat, isLast) -> { (editedMediaItem, durationUs, decodedFormat, isLast) -> {
onMediaItemChanged(trackType, durationUs, isLast); onMediaItemChanged(trackType, durationUs, isLast);

View File

@ -22,6 +22,7 @@ import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoGraph; import androidx.media3.common.VideoGraph;
import androidx.media3.common.util.Log;
import androidx.media3.effect.MultipleInputVideoGraph; import androidx.media3.effect.MultipleInputVideoGraph;
import androidx.media3.effect.VideoCompositorSettings; import androidx.media3.effect.VideoCompositorSettings;
import java.util.List; import java.util.List;
@ -79,8 +80,8 @@ import java.util.concurrent.Executor;
} }
@Override @Override
public GraphInput createInput() throws VideoFrameProcessingException { public GraphInput createInput(int sequenceIndex) throws VideoFrameProcessingException {
int inputId = registerInput(); int inputId = registerInput(sequenceIndex);
return new VideoFrameProcessingWrapper( return new VideoFrameProcessingWrapper(
getProcessor(inputId), /* presentation= */ null, getInitialTimestampOffsetUs()); getProcessor(inputId), /* presentation= */ null, getInitialTimestampOffsetUs());
} }

View File

@ -106,12 +106,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@Override @Override
public GraphInput createInput() throws VideoFrameProcessingException { public GraphInput createInput(int sequenceIndex) throws VideoFrameProcessingException {
checkState(videoFrameProcessingWrapper == null); checkState(videoFrameProcessingWrapper == null);
int inputId = registerInput(); int inputId = registerInput(sequenceIndex);
videoFrameProcessingWrapper = videoFrameProcessingWrapper =
new VideoFrameProcessingWrapper( new VideoFrameProcessingWrapper(
getProcessor(inputId), getPresentation(), getInitialTimestampOffsetUs()); getProcessor(inputId),
getInputColorInfo(),
getPresentation(),
getInitialTimestampOffsetUs());
return videoFrameProcessingWrapper; return videoFrameProcessingWrapper;
} }
} }

View File

@ -69,6 +69,8 @@ import java.util.concurrent.Executor;
* <p>This method must called exactly once for every input stream. * <p>This method must called exactly once for every input stream.
* *
* <p>If the method throws any {@link Exception}, the caller must call {@link #release}. * <p>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;
} }

View File

@ -50,6 +50,7 @@ import androidx.media3.common.util.Util;
import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.effect.DebugTraceUtil; import androidx.media3.effect.DebugTraceUtil;
import androidx.media3.effect.VideoCompositorSettings; import androidx.media3.effect.VideoCompositorSettings;
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -58,6 +59,8 @@ import java.util.Objects;
import org.checkerframework.checker.initialization.qual.Initialized; import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.dataflow.qual.Pure; import org.checkerframework.dataflow.qual.Pure;
import java.nio.ByteBuffer;
import java.util.List;
/** Processes, encodes and muxes raw video frames. */ /** Processes, encodes and muxes raw video frames. */
/* package */ final class VideoSampleExporter extends SampleExporter { /* package */ final class VideoSampleExporter extends SampleExporter {
@ -153,10 +156,9 @@ import org.checkerframework.dataflow.qual.Pure;
} }
@Override @Override
public GraphInput getInput(EditedMediaItem editedMediaItem, Format format) public GraphInput getInput(EditedMediaItem editedMediaItem, Format format, int sequenceIndex) throws ExportException {
throws ExportException {
try { try {
return videoGraph.createInput(); return videoGraph.createInput(sequenceIndex);
} catch (VideoFrameProcessingException e) { } catch (VideoFrameProcessingException e) {
throw ExportException.createForVideoFrameProcessingException(e); throw ExportException.createForVideoFrameProcessingException(e);
} }
@ -540,8 +542,8 @@ import org.checkerframework.dataflow.qual.Pure;
} }
@Override @Override
public int registerInput() throws VideoFrameProcessingException { public int registerInput(int sequenceIndex) throws VideoFrameProcessingException {
return videoGraph.registerInput(); return videoGraph.registerInput(sequenceIndex);
} }
@Override @Override
@ -550,8 +552,8 @@ import org.checkerframework.dataflow.qual.Pure;
} }
@Override @Override
public GraphInput createInput() throws VideoFrameProcessingException { public GraphInput createInput(int sequenceIndex) throws VideoFrameProcessingException {
return videoGraph.createInput(); return videoGraph.createInput(sequenceIndex);
} }
@Override @Override