Allow using different video effects per MediaItem

PiperOrigin-RevId: 539596345
This commit is contained in:
claincly 2023-06-12 10:07:23 +00:00 committed by Tofunmi Adigun-Hameed
parent 1c18503ad0
commit 5f43180a68
10 changed files with 210 additions and 114 deletions

View file

@ -204,9 +204,15 @@ public interface VideoFrameProcessor {
*
* <p>Call {@link #setInputFrameInfo} before this method if the {@link FrameInfo} of the new input
* stream differs from that of the current input stream.
*
* @param inputType The {@link InputType} of the new input stream.
* @param effects The list of {@link Effect effects} to apply to the new input stream. The list is
* ignored for the first input stream registered after {@linkplain Factory#create creating the
* VideoFrameProcessor}. The first input stream will use the effects passed in in {@link
* Factory#create}.
*/
// TODO(b/274109008) Merge this and setInputFrameInfo.
void registerInputStream(@InputType int inputType);
// TODO(b/286032822) Merge this and setInputFrameInfo.
void registerInputStream(@InputType int inputType, List<Effect> effects);
/**
* Sets information about the input frames.

View file

@ -347,7 +347,8 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
blankFrameProducer.configureGlObjects();
// A frame needs to be registered despite not queuing any external input to ensure
// that the video frame processor knows about the stream offset.
checkNotNull(defaultVideoFrameProcessor).registerInputStream(INPUT_TYPE_SURFACE);
checkNotNull(defaultVideoFrameProcessor)
.registerInputStream(INPUT_TYPE_SURFACE, /* effects= */ ImmutableList.of());
defaultVideoFrameProcessor.setInputFrameInfo(
new FrameInfo.Builder(WIDTH, HEIGHT).build());
blankFrameProducer.produceBlankFramesAndQueueEndOfStream(inputPresentationTimesUs);

View file

@ -20,7 +20,7 @@ import static androidx.media3.common.util.Assertions.checkArgument;
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 com.google.common.collect.Iterables.getLast;
import static com.google.common.collect.Iterables.getFirst;
import android.content.Context;
import android.graphics.Bitmap;
@ -52,6 +52,7 @@ import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -285,6 +286,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private static final String THREAD_NAME = "Effect:GlThread";
private static final long RELEASE_WAIT_TIME_MS = 500;
private final Context context;
private final EGLDisplay eglDisplay;
private final EGLContext eglContext;
private final InputSwitcher inputSwitcher;
@ -293,13 +295,17 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private final Executor listenerExecutor;
private final boolean renderFramesAutomatically;
private final FinalShaderProgramWrapper finalShaderProgramWrapper;
// Shader programs that apply Effects.
private final ImmutableList<GlShaderProgram> effectsShaderPrograms;
private final List<GlShaderProgram> intermediateGlShaderPrograms;
// A queue of input streams that have not been fully processed identified by their input types.
@GuardedBy("lock")
private final Queue<@InputType Integer> unprocessedInputStreams;
private final List<Effect> activeEffects;
private final Object lock;
private final ColorInfo outputColorInfo;
private final GlObjectsProvider glObjectsProvider;
// CountDownLatch to wait for the current input stream to finish processing.
private volatile @MonotonicNonNull CountDownLatch latch;
@ -308,14 +314,19 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private volatile boolean hasRefreshedNextInputFrameInfo;
private DefaultVideoFrameProcessor(
Context context,
EGLDisplay eglDisplay,
EGLContext eglContext,
InputSwitcher inputSwitcher,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
VideoFrameProcessor.Listener listener,
Listener listener,
Executor listenerExecutor,
ImmutableList<GlShaderProgram> effectsShaderPrograms,
boolean renderFramesAutomatically) {
ImmutableList<GlShaderProgram> intermediateGlShaderPrograms,
FinalShaderProgramWrapper finalShaderProgramWrapper,
boolean renderFramesAutomatically,
ColorInfo outputColorInfo,
GlObjectsProvider glObjectsProvider) {
this.context = context;
this.eglDisplay = eglDisplay;
this.eglContext = eglContext;
this.inputSwitcher = inputSwitcher;
@ -324,12 +335,11 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
this.listenerExecutor = listenerExecutor;
this.renderFramesAutomatically = renderFramesAutomatically;
this.unprocessedInputStreams = new ConcurrentLinkedQueue<>();
this.activeEffects = new ArrayList<>();
this.lock = new Object();
checkState(!effectsShaderPrograms.isEmpty());
checkState(getLast(effectsShaderPrograms) instanceof FinalShaderProgramWrapper);
finalShaderProgramWrapper = (FinalShaderProgramWrapper) getLast(effectsShaderPrograms);
this.outputColorInfo = outputColorInfo;
this.glObjectsProvider = glObjectsProvider;
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
finalShaderProgramWrapper.setOnInputStreamProcessedListener(
() -> {
synchronized (lock) {
@ -340,7 +350,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
return inputStreamEnded && unprocessedInputStreams.isEmpty();
}
});
this.effectsShaderPrograms = effectsShaderPrograms;
this.intermediateGlShaderPrograms = new ArrayList<>(intermediateGlShaderPrograms);
}
/** Returns the task executor that runs video frame processing tasks. */
@ -400,11 +410,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
}
@Override
public void registerInputStream(@InputType int inputType) {
public void registerInputStream(@InputType int inputType, List<Effect> effects) {
synchronized (lock) {
if (unprocessedInputStreams.isEmpty()) {
inputSwitcher.switchToInput(inputType);
unprocessedInputStreams.add(inputType);
activeEffects.clear();
activeEffects.addAll(effects);
return;
}
}
@ -418,10 +430,47 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
Thread.currentThread().interrupt();
listenerExecutor.execute(() -> listener.onError(VideoFrameProcessingException.from(e)));
}
inputSwitcher.switchToInput(inputType);
synchronized (lock) {
unprocessedInputStreams.add(inputType);
}
if (!activeEffects.equals(effects)) {
// TODO(b/269424561) Investigate non blocking re-configuration.
// Shader program recreation must be on GL thread. Currently the calling thread is blocked
// until all shader programs are recreated, so that DefaultVideoFrameProcessor doesn't receive
// a new frame from the new input stream prematurely.
videoFrameProcessingTaskExecutor.submitAndBlock(
() -> {
try {
for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) {
intermediateGlShaderPrograms.get(i).release();
}
intermediateGlShaderPrograms.clear();
intermediateGlShaderPrograms.addAll(
createGlShaderPrograms(
context, effects, outputColorInfo, finalShaderProgramWrapper));
} catch (VideoFrameProcessingException e) {
listenerExecutor.execute(() -> listener.onError(e));
return;
}
inputSwitcher.setDownstreamShaderProgram(
getFirst(
intermediateGlShaderPrograms, /* defaultValue= */ finalShaderProgramWrapper));
setGlObjectProviderOnShaderPrograms(intermediateGlShaderPrograms, glObjectsProvider);
chainShaderProgramsWithListeners(
intermediateGlShaderPrograms,
finalShaderProgramWrapper,
videoFrameProcessingTaskExecutor,
listener,
listenerExecutor);
activeEffects.clear();
activeEffects.addAll(effects);
});
}
inputSwitcher.switchToInput(inputType);
}
@Override
@ -506,7 +555,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
public void release() {
try {
videoFrameProcessingTaskExecutor.release(
/* releaseTask= */ this::releaseShaderProgramsAndDestroyGlContext, RELEASE_WAIT_TIME_MS);
/* releaseTask= */ this::releaseGlObjects, RELEASE_WAIT_TIME_MS);
} catch (InterruptedException unexpected) {
Thread.currentThread().interrupt();
throw new IllegalStateException(unexpected);
@ -555,7 +604,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
boolean enableColorTransfers,
boolean renderFramesAutomatically,
ExecutorService singleThreadExecutorService,
Executor executor,
Executor videoFrameProcessorListenerExecutor,
Listener listener,
GlObjectsProvider glObjectsProvider,
@Nullable TextureOutputListener textureOutputListener,
@ -602,10 +651,9 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
videoFrameProcessingTaskExecutor,
enableColorTransfers);
ImmutableList<GlShaderProgram> effectsShaderPrograms =
getGlShaderProgramsForGlEffects(
FinalShaderProgramWrapper finalShaderProgramWrapper =
new FinalShaderProgramWrapper(
context,
effects,
eglDisplay,
eglContext,
debugViewProvider,
@ -613,12 +661,18 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
enableColorTransfers,
renderFramesAutomatically,
videoFrameProcessingTaskExecutor,
executor,
videoFrameProcessorListenerExecutor,
listener,
glObjectsProvider,
textureOutputListener,
textureOutputCapacity);
// TODO(b/269424561): Move effect creation to registerInputStream().
// The GlShaderPrograms that should be inserted in between InputSwitcher and
// FinalShaderProgramWrapper.
ImmutableList<GlShaderProgram> intermediateGlShaderPrograms =
createGlShaderPrograms(context, effects, outputColorInfo, finalShaderProgramWrapper);
inputSwitcher.registerInput(inputColorInfo, INPUT_TYPE_SURFACE);
if (!ColorInfo.isTransferHdr(inputColorInfo)) {
// HDR bitmap input is not supported. Bitmaps are always sRGB/Full range/BT.709.
@ -628,48 +682,53 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
// Image and textureId concatenation not supported.
inputSwitcher.registerInput(inputColorInfo, INPUT_TYPE_TEXTURE_ID);
}
inputSwitcher.setDownstreamShaderProgram(effectsShaderPrograms.get(0));
setGlObjectProviderOnShaderPrograms(effectsShaderPrograms, glObjectsProvider);
inputSwitcher.setDownstreamShaderProgram(
getFirst(intermediateGlShaderPrograms, /* defaultValue= */ finalShaderProgramWrapper));
setGlObjectProviderOnShaderPrograms(intermediateGlShaderPrograms, glObjectsProvider);
chainShaderProgramsWithListeners(
effectsShaderPrograms, videoFrameProcessingTaskExecutor, listener, executor);
intermediateGlShaderPrograms,
finalShaderProgramWrapper,
videoFrameProcessingTaskExecutor,
listener,
videoFrameProcessorListenerExecutor);
return new DefaultVideoFrameProcessor(
context,
eglDisplay,
eglContext,
inputSwitcher,
videoFrameProcessingTaskExecutor,
listener,
executor,
effectsShaderPrograms,
renderFramesAutomatically);
videoFrameProcessorListenerExecutor,
intermediateGlShaderPrograms,
finalShaderProgramWrapper,
renderFramesAutomatically,
outputColorInfo,
glObjectsProvider);
}
/**
* Combines consecutive {@link GlMatrixTransformation} and {@link RgbMatrix} instances into a
* single {@link DefaultShaderProgram} and converts all other {@link GlEffect} instances to
* separate {@link GlShaderProgram} instances.
* Combines consecutive {@link GlMatrixTransformation GlMatrixTransformations} and {@link
* RgbMatrix RgbMatrices} instances into a single {@link DefaultShaderProgram} and converts all
* other {@link GlEffect} instances to separate {@link GlShaderProgram} instances.
*
* <p>All {@link Effect} instances must be {@link GlEffect} instances.
*
* @return A non-empty list of {@link GlShaderProgram} instances to apply in the given order. The
* last is a {@link FinalShaderProgramWrapper}.
* @param context The {@link Context}.
* @param effects The list of {@link GlEffect effects}.
* @param outputColorInfo The {@link ColorInfo} on {@code DefaultVideoFrameProcessor} output.
* @param finalShaderProgramWrapper The {@link FinalShaderProgramWrapper} to apply the {@link
* GlMatrixTransformation GlMatrixTransformations} and {@link RgbMatrix RgbMatrices} after all
* other {@link GlEffect GlEffects}.
* @return A non-empty list of {@link GlShaderProgram} instances to apply in the given order.
*/
private static ImmutableList<GlShaderProgram> getGlShaderProgramsForGlEffects(
private static ImmutableList<GlShaderProgram> createGlShaderPrograms(
Context context,
List<Effect> effects,
EGLDisplay eglDisplay,
EGLContext eglContext,
DebugViewProvider debugViewProvider,
ColorInfo outputColorInfo,
boolean enableColorTransfers,
boolean renderFramesAutomatically,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Executor executor,
Listener listener,
GlObjectsProvider glObjectsProvider,
@Nullable TextureOutputListener textureOutputListener,
int textureOutputCapacity)
FinalShaderProgramWrapper finalShaderProgramWrapper)
throws VideoFrameProcessingException {
ImmutableList.Builder<GlShaderProgram> shaderProgramListBuilder = new ImmutableList.Builder<>();
ImmutableList.Builder<GlMatrixTransformation> matrixTransformationListBuilder =
@ -707,29 +766,14 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
shaderProgramListBuilder.add(glEffect.toGlShaderProgram(context, isOutputTransferHdr));
}
shaderProgramListBuilder.add(
new FinalShaderProgramWrapper(
context,
eglDisplay,
eglContext,
matrixTransformationListBuilder.build(),
rgbMatrixListBuilder.build(),
debugViewProvider,
outputColorInfo,
enableColorTransfers,
renderFramesAutomatically,
videoFrameProcessingTaskExecutor,
executor,
listener,
glObjectsProvider,
textureOutputListener,
textureOutputCapacity));
finalShaderProgramWrapper.setMatrixTransformations(
matrixTransformationListBuilder.build(), rgbMatrixListBuilder.build());
return shaderProgramListBuilder.build();
}
/** Sets the {@link GlObjectsProvider} on all of the {@linkplain GlShaderProgram}s provided. */
private static void setGlObjectProviderOnShaderPrograms(
ImmutableList<GlShaderProgram> shaderPrograms, GlObjectsProvider glObjectsProvider) {
List<GlShaderProgram> shaderPrograms, GlObjectsProvider glObjectsProvider) {
for (int i = 0; i < shaderPrograms.size() - 1; i++) {
GlShaderProgram shaderProgram = shaderPrograms.get(i);
shaderProgram.setGlObjectsProvider(glObjectsProvider);
@ -741,13 +785,16 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
* ChainingGlShaderProgramListener} instances.
*/
private static void chainShaderProgramsWithListeners(
ImmutableList<GlShaderProgram> shaderPrograms,
List<GlShaderProgram> shaderPrograms,
FinalShaderProgramWrapper finalShaderProgramWrapper,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Listener videoFrameProcessorListener,
Executor videoFrameProcessorListenerExecutor) {
for (int i = 0; i < shaderPrograms.size() - 1; i++) {
GlShaderProgram producingGlShaderProgram = shaderPrograms.get(i);
GlShaderProgram consumingGlShaderProgram = shaderPrograms.get(i + 1);
ArrayList<GlShaderProgram> shaderProgramsToChain = new ArrayList<>(shaderPrograms);
shaderProgramsToChain.add(finalShaderProgramWrapper);
for (int i = 0; i < shaderProgramsToChain.size() - 1; i++) {
GlShaderProgram producingGlShaderProgram = shaderProgramsToChain.get(i);
GlShaderProgram consumingGlShaderProgram = shaderProgramsToChain.get(i + 1);
ChainingGlShaderProgramListener chainingGlShaderProgramListener =
new ChainingGlShaderProgramListener(
producingGlShaderProgram, consumingGlShaderProgram, videoFrameProcessingTaskExecutor);
@ -763,12 +810,12 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
*
* <p>This method must be called on the {@linkplain #THREAD_NAME background thread}.
*/
private void releaseShaderProgramsAndDestroyGlContext() {
private void releaseGlObjects() {
try {
try {
inputSwitcher.release();
for (int i = 0; i < effectsShaderPrograms.size(); i++) {
effectsShaderPrograms.get(i).release();
for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) {
intermediateGlShaderPrograms.get(i).release();
}
} catch (Exception e) {
Log.e(TAG, "Error releasing shader program", e);

View file

@ -45,6 +45,8 @@ import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
@ -77,8 +79,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private static final String TAG = "FinalShaderWrapper";
private final Context context;
private final ImmutableList<GlMatrixTransformation> matrixTransformations;
private final ImmutableList<RgbMatrix> rgbMatrices;
private final List<GlMatrixTransformation> matrixTransformations;
private final List<RgbMatrix> rgbMatrices;
private final EGLDisplay eglDisplay;
private final EGLContext eglContext;
private final DebugViewProvider debugViewProvider;
@ -105,6 +107,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable private SurfaceView debugSurfaceView;
@Nullable private OnInputStreamProcessedListener onInputStreamProcessedListener;
private boolean frameProcessingStarted;
private boolean matrixTransformationsChanged;
@GuardedBy("this")
private boolean outputSurfaceInfoChanged;
@ -122,8 +125,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Context context,
EGLDisplay eglDisplay,
EGLContext eglContext,
ImmutableList<GlMatrixTransformation> matrixTransformations,
ImmutableList<RgbMatrix> rgbMatrices,
DebugViewProvider debugViewProvider,
ColorInfo outputColorInfo,
boolean enableColorTransfers,
@ -135,8 +136,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Nullable DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener,
int textureOutputCapacity) {
this.context = context;
this.matrixTransformations = matrixTransformations;
this.rgbMatrices = rgbMatrices;
this.matrixTransformations = new ArrayList<>();
this.rgbMatrices = new ArrayList<>();
this.eglDisplay = eglDisplay;
this.eglContext = eglContext;
this.debugViewProvider = debugViewProvider;
@ -227,6 +228,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
throw new UnsupportedOperationException();
}
/**
* Sets the list of {@link GlMatrixTransformation GlMatrixTransformations} and list of {@link
* RgbMatrix RgbMatrices} to apply to the next {@linkplain #queueInputFrame queued} frame.
*
* <p>The new transformations will be applied to the next {@linkplain #queueInputFrame queued}
* frame.
*/
public void setMatrixTransformations(
List<GlMatrixTransformation> matrixTransformations, List<RgbMatrix> rgbMatrices) {
this.matrixTransformations.clear();
this.matrixTransformations.addAll(matrixTransformations);
this.rgbMatrices.clear();
this.rgbMatrices.addAll(rgbMatrices);
matrixTransformationsChanged = true;
}
public void releaseOutputFrame(long presentationTimeUs) {
videoFrameProcessingTaskExecutor.submit(() -> releaseOutputFrameInternal(presentationTimeUs));
}
@ -456,10 +473,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
this.debugSurfaceView = debugSurfaceView;
if (defaultShaderProgram != null && (outputSurfaceInfoChanged || inputSizeChanged)) {
if (defaultShaderProgram != null
&& (outputSurfaceInfoChanged || inputSizeChanged || matrixTransformationsChanged)) {
defaultShaderProgram.release();
defaultShaderProgram = null;
outputSurfaceInfoChanged = false;
matrixTransformationsChanged = false;
}
if (defaultShaderProgram == null) {

View file

@ -23,7 +23,6 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.content.Context;
import android.util.SparseArray;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
@ -47,7 +46,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private @MonotonicNonNull GlShaderProgram downstreamShaderProgram;
private @MonotonicNonNull TextureManager activeTextureManager;
private boolean inputEnded;
private int activeInputType;
public InputSwitcher(
Context context,
@ -61,7 +59,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
this.inputs = new SparseArray<>();
this.enableColorTransfers = enableColorTransfers;
activeInputType = C.INDEX_UNSET;
}
/**
@ -136,25 +133,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
/** Sets the {@link GlShaderProgram} that {@code InputSwitcher} outputs to. */
public void setDownstreamShaderProgram(GlShaderProgram downstreamShaderProgram) {
this.downstreamShaderProgram = downstreamShaderProgram;
for (int i = 0; i < inputs.size(); i++) {
@VideoFrameProcessor.InputType int inputType = inputs.keyAt(i);
Input input = inputs.get(inputType);
input.setChainingListener(
new GatedChainingListenerWrapper(
input.samplingGlShaderProgram,
this.downstreamShaderProgram,
videoFrameProcessingTaskExecutor));
}
}
/**
* Switches to a new source of input.
*
* <p>Blocks until the current input stream is processed.
*
* <p>Must be called after the corresponding {@code newInputType} is {@linkplain #registerInput
* registered}.
*
@ -164,14 +150,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
checkStateNotNull(downstreamShaderProgram);
checkState(inputs.indexOfKey(newInputType) >= 0, "Input type not registered: " + newInputType);
if (newInputType == activeInputType) {
activeTextureManager = inputs.get(activeInputType).textureManager;
}
for (int i = 0; i < inputs.size(); i++) {
@VideoFrameProcessor.InputType int inputType = inputs.keyAt(i);
Input input = inputs.get(inputType);
if (inputType == newInputType) {
input.setChainingListener(
new GatedChainingListenerWrapper(
input.samplingGlShaderProgram,
this.downstreamShaderProgram,
videoFrameProcessingTaskExecutor));
input.setActive(true);
downstreamShaderProgram.setInputListener(checkNotNull(input.gatedChainingListenerWrapper));
activeTextureManager = input.textureManager;
@ -179,7 +166,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
input.setActive(false);
}
}
activeInputType = newInputType;
}
/**
@ -240,7 +226,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
public void setActive(boolean active) {
checkStateNotNull(gatedChainingListenerWrapper);
if (gatedChainingListenerWrapper == null) {
return;
}
gatedChainingListenerWrapper.setActive(active);
}

View file

@ -21,6 +21,7 @@ import android.opengl.Matrix;
import androidx.media3.common.util.Size;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.List;
/** Utility functions for working with matrices, vertices, and polygons. */
/* package */ final class MatrixUtils {
@ -223,9 +224,7 @@ import java.util.Arrays;
* GlMatrixTransformations} to an input frame with the given size.
*/
public static Size configureAndGetOutputSize(
int inputWidth,
int inputHeight,
ImmutableList<GlMatrixTransformation> matrixTransformations) {
int inputWidth, int inputHeight, List<GlMatrixTransformation> matrixTransformations) {
checkArgument(inputWidth > 0, "inputWidth must be positive");
checkArgument(inputHeight > 0, "inputHeight must be positive");

View file

@ -23,7 +23,9 @@ import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.UnstableApi;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
@ -52,7 +54,7 @@ import java.util.concurrent.RejectedExecutionException;
private final Object lock;
@GuardedBy("lock")
private final ArrayDeque<VideoFrameProcessingTask> highPriorityTasks;
private final Queue<VideoFrameProcessingTask> highPriorityTasks;
@GuardedBy("lock")
private boolean shouldCancelTasks;
@ -89,6 +91,28 @@ import java.util.concurrent.RejectedExecutionException;
}
}
/**
* Submits the given {@link VideoFrameProcessingTask} to execute, and returns after the task is
* executed.
*/
public void submitAndBlock(VideoFrameProcessingTask task) {
synchronized (lock) {
if (shouldCancelTasks) {
return;
}
}
Future<?> future = wrapTaskAndSubmitToExecutorService(task, /* isFlushOrReleaseTask= */ false);
try {
future.get();
} catch (ExecutionException e) {
handleException(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
handleException(e);
}
}
/**
* Submits the given {@link VideoFrameProcessingTask} to be executed after the currently running
* task and all previously submitted high-priority tasks have completed.

View file

@ -2120,7 +2120,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
throw new IllegalStateException();
}
});
videoFrameProcessor.registerInputStream(VideoFrameProcessor.INPUT_TYPE_SURFACE);
videoFrameProcessor.registerInputStream(
VideoFrameProcessor.INPUT_TYPE_SURFACE, /* effects= */ ImmutableList.of());
this.initialStreamOffsetUs = initialStreamOffsetUs;
} catch (Exception e) {
throw renderer.createRendererException(

View file

@ -312,7 +312,7 @@ public final class VideoFrameProcessorTestRunner {
videoFrameProcessingEnded = true;
}
});
videoFrameProcessor.registerInputStream(inputType);
videoFrameProcessor.registerInputStream(inputType, /* effects= */ ImmutableList.of());
}
public void processFirstFrameAndEnd() throws Exception {
@ -327,7 +327,8 @@ public final class VideoFrameProcessorTestRunner {
mediaFormat.getInteger(MediaFormat.KEY_HEIGHT))
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
.build());
videoFrameProcessor.registerInputStream(INPUT_TYPE_SURFACE);
videoFrameProcessor.registerInputStream(
INPUT_TYPE_SURFACE, /* effects= */ ImmutableList.of());
videoFrameProcessor.registerInputFrame();
}
@ -347,7 +348,7 @@ public final class VideoFrameProcessorTestRunner {
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
.setOffsetToAddUs(offsetToAddUs)
.build());
videoFrameProcessor.registerInputStream(INPUT_TYPE_BITMAP);
videoFrameProcessor.registerInputStream(INPUT_TYPE_BITMAP, /* effects= */ ImmutableList.of());
videoFrameProcessor.queueInputBitmap(inputBitmap, durationUs, frameRate);
}
@ -356,7 +357,8 @@ public final class VideoFrameProcessorTestRunner {
new FrameInfo.Builder(inputTexture.getWidth(), inputTexture.getHeight())
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
.build());
videoFrameProcessor.registerInputStream(INPUT_TYPE_TEXTURE_ID);
videoFrameProcessor.registerInputStream(
INPUT_TYPE_TEXTURE_ID, /* effects= */ ImmutableList.of());
videoFrameProcessor.setOnInputFrameProcessedListener(
texId -> {
try {

View file

@ -59,7 +59,6 @@ import androidx.media3.exoplayer.mediacodec.MediaCodecUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -74,6 +73,7 @@ import org.checkerframework.dataflow.qual.Pure;
private final ColorInfo videoFrameProcessorInputColor;
private final EncoderWrapper encoderWrapper;
private final DecoderInputBuffer encoderOutputBuffer;
@Nullable final Presentation presentation;
private volatile boolean encoderExpectsTimestampZero;
/**
@ -149,15 +149,12 @@ import org.checkerframework.dataflow.qual.Pure;
videoFrameProcessorOutputColor = videoFrameProcessorInputColor;
}
List<Effect> effectsWithPresentation = new ArrayList<>(effects);
if (presentation != null) {
effectsWithPresentation.add(presentation);
}
this.presentation = presentation;
try {
videoFrameProcessor =
videoFrameProcessorFactory.create(
context,
effectsWithPresentation,
createEffectListWithPresentation(effects, presentation),
debugViewProvider,
videoFrameProcessorInputColor,
videoFrameProcessorOutputColor,
@ -216,7 +213,8 @@ import org.checkerframework.dataflow.qual.Pure;
if (trackFormat != null) {
Size decodedSize = getDecodedSize(trackFormat);
videoFrameProcessor.registerInputStream(
getInputType(checkNotNull(trackFormat.sampleMimeType)));
getInputType(checkNotNull(trackFormat.sampleMimeType)),
createEffectListWithPresentation(editedMediaItem.effects.videoEffects, presentation));
videoFrameProcessor.setInputFrameInfo(
new FrameInfo.Builder(decodedSize.getWidth(), decodedSize.getHeight())
.setPixelWidthHeightRatio(trackFormat.pixelWidthHeightRatio)
@ -335,6 +333,16 @@ import org.checkerframework.dataflow.qual.Pure;
return new Size(decodedWidth, decodedHeight);
}
private static ImmutableList<Effect> createEffectListWithPresentation(
List<Effect> effects, @Nullable Presentation presentation) {
if (presentation == null) {
return ImmutableList.copyOf(effects);
}
ImmutableList.Builder<Effect> effectsWithPresentationBuilder = new ImmutableList.Builder<>();
effectsWithPresentationBuilder.addAll(effects).add(presentation);
return effectsWithPresentationBuilder.build();
}
/**
* Wraps an {@linkplain Codec encoder} and provides its input {@link Surface}.
*