Effect: Move inputColorInfo usage to registerInputStream.

To prepare to move `inputColorInfo` from `VFP.Factory.create` to
`VFP.registerInputStream`, move all usage of `inputColorInfo` to be *after*
`registerInputStream`.

To do this, defer creation of `externalShaderProgram` instances, which require
`inputColorInfo`. However, we must still initialize `InputSwitcher` and OpenGL
ES 3.0 contexts in the VFP create, to create and request the input surface from
ExternalTextureManager.

PiperOrigin-RevId: 590937251
This commit is contained in:
huangdarwin 2023-12-14 07:45:58 -08:00 committed by Copybara-Service
parent 01578780a6
commit 97e9ed3f7b
6 changed files with 176 additions and 136 deletions

View File

@ -45,7 +45,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+ " RGB channel.";
private final GlObjectsProvider glObjectsProvider;
private final GlShaderProgram shaderProgram;
private @MonotonicNonNull GlShaderProgram shaderProgram;
// The queue holds all bitmaps with one or more frames pending to be sent downstream.
private final Queue<BitmapFrameSequenceInfo> pendingBitmaps;
@ -63,21 +63,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* Creates a new instance.
*
* @param glObjectsProvider The {@link GlObjectsProvider} for using EGL and GLES.
* @param shaderProgram The {@link GlShaderProgram} for which this {@code BitmapTextureManager}
* will be set as the {@link GlShaderProgram.InputListener}.
* @param videoFrameProcessingTaskExecutor The {@link VideoFrameProcessingTaskExecutor} that the
* methods of this class run on.
*/
public BitmapTextureManager(
GlObjectsProvider glObjectsProvider,
GlShaderProgram shaderProgram,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
super(videoFrameProcessingTaskExecutor);
this.glObjectsProvider = glObjectsProvider;
this.shaderProgram = shaderProgram;
pendingBitmaps = new LinkedBlockingQueue<>();
}
@Override
public void setSamplingGlShaderProgram(GlShaderProgram samplingGlShaderProgram) {
this.shaderProgram = samplingGlShaderProgram;
}
@Override
public void onReadyToAcceptInputFrame() {
videoFrameProcessingTaskExecutor.submit(
@ -111,7 +112,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
videoFrameProcessingTaskExecutor.submit(
() -> {
if (pendingBitmaps.isEmpty()) {
shaderProgram.signalEndOfCurrentInputStream();
checkNotNull(shaderProgram).signalEndOfCurrentInputStream();
DebugTraceUtil.logEvent(
DebugTraceUtil.EVENT_BITMAP_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
} else {
@ -166,7 +167,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
downstreamShaderProgramCapacity--;
shaderProgram.queueInputFrame(
checkNotNull(shaderProgram)
.queueInputFrame(
glObjectsProvider, checkNotNull(currentGlTextureInfo), currentPresentationTimeUs);
DebugTraceUtil.logEvent(
DebugTraceUtil.EVENT_VFP_QUEUE_BITMAP,
@ -181,7 +183,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
finishedBitmapInfo.bitmap.recycle();
if (pendingBitmaps.isEmpty() && currentInputStreamEnded) {
// Only signal end of stream after all pending bitmaps are processed.
shaderProgram.signalEndOfCurrentInputStream();
checkNotNull(shaderProgram).signalEndOfCurrentInputStream();
DebugTraceUtil.logEvent(
DebugTraceUtil.EVENT_BITMAP_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
currentInputStreamEnded = false;

View File

@ -254,26 +254,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
throws VideoFrameProcessingException {
// TODO(b/261188041) Add tests to verify the Listener is invoked on the given Executor.
checkArgument(inputColorInfo.isDataSpaceValid());
checkArgument(inputColorInfo.colorTransfer != C.COLOR_TRANSFER_LINEAR);
checkArgument(outputColorInfo.isDataSpaceValid());
checkArgument(outputColorInfo.colorTransfer != C.COLOR_TRANSFER_LINEAR);
if (ColorInfo.isTransferHdr(inputColorInfo) || ColorInfo.isTransferHdr(outputColorInfo)) {
checkArgument(enableColorTransfers);
}
if (inputColorInfo.colorSpace != outputColorInfo.colorSpace
|| ColorInfo.isTransferHdr(inputColorInfo) != ColorInfo.isTransferHdr(outputColorInfo)) {
// OpenGL tone mapping is only implemented for BT2020 to BT709 and HDR to SDR (Gamma 2.2).
// Gamma 2.2 is used instead of SMPTE 170M for SDR, despite MediaFormat's
// COLOR_TRANSFER_SDR_VIDEO being defined as SMPTE 170M. This is to match
// other known tone-mapping behavior within the Android ecosystem.
checkArgument(inputColorInfo.colorSpace == C.COLOR_SPACE_BT2020);
checkArgument(outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020);
checkArgument(ColorInfo.isTransferHdr(inputColorInfo));
checkArgument(outputColorInfo.colorTransfer == C.COLOR_TRANSFER_GAMMA_2_2);
}
boolean shouldShutdownExecutorService = executorService == null;
ExecutorService instanceExecutorService =
executorService == null ? Util.newSingleThreadExecutor(THREAD_NAME) : executorService;
@ -340,6 +320,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private final List<Effect> activeEffects;
private final Object lock;
private final boolean enableColorTransfers;
private final ColorInfo firstInputColorInfo;
private final ColorInfo outputColorInfo;
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
@ -356,6 +338,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
Executor listenerExecutor,
FinalShaderProgramWrapper finalShaderProgramWrapper,
boolean renderFramesAutomatically,
boolean enableColorTransfers,
ColorInfo firstInputColorInfo,
ColorInfo outputColorInfo) {
this.context = context;
this.glObjectsProvider = glObjectsProvider;
@ -368,6 +352,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
this.renderFramesAutomatically = renderFramesAutomatically;
this.activeEffects = new ArrayList<>();
this.lock = new Object();
this.enableColorTransfers = enableColorTransfers;
this.firstInputColorInfo = firstInputColorInfo;
this.outputColorInfo = outputColorInfo;
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
this.intermediateGlShaderPrograms = new ArrayList<>();
@ -477,7 +463,10 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
synchronized (lock) {
// An input stream is pending until its effects are configured.
InputStreamInfo pendingInputStreamInfo = new InputStreamInfo(inputType, effects, frameInfo);
// TODO: b/307952514 - Move inputColorInfo's API from Factory.create into registerInputStream,
// and from StreamInfo into FrameInfo.
InputStreamInfo pendingInputStreamInfo =
new InputStreamInfo(inputType, effects, this.firstInputColorInfo, frameInfo);
if (!registeredFirstInputStream) {
registeredFirstInputStream = true;
inputStreamRegisteredCondition.close();
@ -640,11 +629,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
: GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_8888;
EGLContext eglContext =
createFocusedEglContextWithFallback(glObjectsProvider, eglDisplay, configAttributes);
if ((ColorInfo.isTransferHdr(inputColorInfo) || ColorInfo.isTransferHdr(outputColorInfo))
&& GlUtil.getContextMajorVersion() != 3) {
throw new VideoFrameProcessingException(
"OpenGL ES 3.0 context support is required for HDR input or output.");
}
// Not renderFramesAutomatically means outputting to a display surface. HDR display surfaces
// require the BT2020 PQ GL extension.
@ -689,16 +673,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
textureOutputListener,
textureOutputCapacity);
inputSwitcher.registerInput(inputColorInfo, INPUT_TYPE_SURFACE);
if (!ColorInfo.isTransferHdr(inputColorInfo)) {
// HDR bitmap input is not supported. Bitmaps are always sRGB/Full range/BT.709.
inputSwitcher.registerInput(ColorInfo.SRGB_BT709_FULL, INPUT_TYPE_BITMAP);
}
if (inputColorInfo.colorTransfer != C.COLOR_TRANSFER_SRGB) {
// Image and textureId concatenation not supported.
inputSwitcher.registerInput(inputColorInfo, INPUT_TYPE_TEXTURE_ID);
}
return new DefaultVideoFrameProcessor(
context,
glObjectsProvider,
@ -710,6 +684,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
videoFrameProcessorListenerExecutor,
finalShaderProgramWrapper,
renderFramesAutomatically,
enableColorTransfers,
/* firstInputColorInfo= */ inputColorInfo,
outputColorInfo);
}
@ -826,6 +802,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
*/
private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure)
throws VideoFrameProcessingException {
checkColors(firstInputColorInfo, outputColorInfo, enableColorTransfers);
if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) {
if (!intermediateGlShaderPrograms.isEmpty()) {
for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) {
@ -853,7 +830,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
activeEffects.addAll(inputStreamInfo.effects);
}
inputSwitcher.switchToInput(inputStreamInfo.inputType, inputStreamInfo.frameInfo);
inputSwitcher.switchToInput(
inputStreamInfo.inputType, inputStreamInfo.frameInfo, inputStreamInfo.colorInfo);
inputStreamRegisteredCondition.open();
listenerExecutor.execute(
() ->
@ -861,6 +839,42 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
inputStreamInfo.inputType, inputStreamInfo.effects, inputStreamInfo.frameInfo));
}
/** Checks that color configuration is valid for {@link DefaultVideoFrameProcessor}. */
private static void checkColors(
ColorInfo inputColorInfo, ColorInfo outputColorInfo, boolean enableColorTransfers)
throws VideoFrameProcessingException {
if ((ColorInfo.isTransferHdr(inputColorInfo) || ColorInfo.isTransferHdr(outputColorInfo))) {
checkArgument(enableColorTransfers);
long glVersion;
try {
glVersion = GlUtil.getContextMajorVersion();
} catch (GlUtil.GlException e) {
throw VideoFrameProcessingException.from(e);
}
if (glVersion != 3) {
throw new VideoFrameProcessingException(
"OpenGL ES 3.0 context support is required for HDR input or output.");
}
}
checkArgument(inputColorInfo.isDataSpaceValid());
checkArgument(inputColorInfo.colorTransfer != C.COLOR_TRANSFER_LINEAR);
checkArgument(outputColorInfo.isDataSpaceValid());
checkArgument(outputColorInfo.colorTransfer != C.COLOR_TRANSFER_LINEAR);
if (inputColorInfo.colorSpace != outputColorInfo.colorSpace
|| ColorInfo.isTransferHdr(inputColorInfo) != ColorInfo.isTransferHdr(outputColorInfo)) {
// OpenGL tone mapping is only implemented for BT2020 to BT709 and HDR to SDR (Gamma 2.2).
// Gamma 2.2 is used instead of SMPTE 170M for SDR, despite MediaFormat's
// COLOR_TRANSFER_SDR_VIDEO being defined as SMPTE 170M. This is to match
// other known tone-mapping behavior within the Android ecosystem.
checkArgument(inputColorInfo.colorSpace == C.COLOR_SPACE_BT2020);
checkArgument(outputColorInfo.colorSpace != C.COLOR_SPACE_BT2020);
checkArgument(ColorInfo.isTransferHdr(inputColorInfo));
checkArgument(outputColorInfo.colorTransfer == C.COLOR_TRANSFER_GAMMA_2_2);
}
}
/**
* Releases the {@link GlShaderProgram} instances and destroys the OpenGL context.
*
@ -925,11 +939,14 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private static final class InputStreamInfo {
public final @InputType int inputType;
public final List<Effect> effects;
public final ColorInfo colorInfo;
public final FrameInfo frameInfo;
public InputStreamInfo(@InputType int inputType, List<Effect> effects, FrameInfo frameInfo) {
public InputStreamInfo(
@InputType int inputType, List<Effect> effects, ColorInfo colorInfo, FrameInfo frameInfo) {
this.inputType = inputType;
this.effects = effects;
this.colorInfo = colorInfo;
this.frameInfo = frameInfo;
}
}

View File

@ -15,6 +15,8 @@
*/
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.isRunningOnEmulator;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@ -30,12 +32,12 @@ import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.effect.GlShaderProgram.InputListener;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Forwards externally produced frames that become available via a {@link SurfaceTexture} to an
@ -58,7 +60,7 @@ import java.util.concurrent.atomic.AtomicInteger;
private static final long SURFACE_TEXTURE_TIMEOUT_MS = isRunningOnEmulator() ? 10_000 : 500;
private final GlObjectsProvider glObjectsProvider;
private final ExternalShaderProgram externalShaderProgram;
private @MonotonicNonNull ExternalShaderProgram externalShaderProgram;
private final int externalTexId;
private final Surface surface;
private final SurfaceTexture surfaceTexture;
@ -82,8 +84,6 @@ import java.util.concurrent.atomic.AtomicInteger;
* Creates a new instance. The caller's thread must have a current GL context.
*
* @param glObjectsProvider The {@link GlObjectsProvider} for using EGL and GLES.
* @param externalShaderProgram The {@link ExternalShaderProgram} for which this {@code
* ExternalTextureManager} will be set as the {@link InputListener}.
* @param videoFrameProcessingTaskExecutor The {@link VideoFrameProcessingTaskExecutor}.
* @throws VideoFrameProcessingException If a problem occurs while creating the external texture.
*/
@ -91,12 +91,10 @@ import java.util.concurrent.atomic.AtomicInteger;
@SuppressWarnings("nullness:method.invocation.invalid")
public ExternalTextureManager(
GlObjectsProvider glObjectsProvider,
ExternalShaderProgram externalShaderProgram,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor)
throws VideoFrameProcessingException {
super(videoFrameProcessingTaskExecutor);
this.glObjectsProvider = glObjectsProvider;
this.externalShaderProgram = externalShaderProgram;
try {
externalTexId = GlUtil.createExternalTexture();
} catch (GlUtil.GlException e) {
@ -134,6 +132,17 @@ import java.util.concurrent.atomic.AtomicInteger;
surface = new Surface(surfaceTexture);
}
/**
* {@inheritDoc}
*
* <p>{@code glShaderProgram} must be an {@link ExternalShaderProgram}.
*/
@Override
public void setSamplingGlShaderProgram(GlShaderProgram samplingGlShaderProgram) {
checkState(samplingGlShaderProgram instanceof ExternalShaderProgram);
this.externalShaderProgram = (ExternalShaderProgram) samplingGlShaderProgram;
}
@Override
public void setDefaultBufferSize(int width, int height) {
surfaceTexture.setDefaultBufferSize(width, height);
@ -161,7 +170,7 @@ import java.util.concurrent.atomic.AtomicInteger;
if (currentInputStreamEnded && pendingFrames.isEmpty()) {
// Reset because there could be further input streams after the current one ends.
currentInputStreamEnded = false;
externalShaderProgram.signalEndOfCurrentInputStream();
checkNotNull(externalShaderProgram).signalEndOfCurrentInputStream();
DebugTraceUtil.logEvent(
DebugTraceUtil.EVENT_EXTERNAL_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
cancelForceSignalEndOfStreamTimer();
@ -200,7 +209,7 @@ import java.util.concurrent.atomic.AtomicInteger;
videoFrameProcessingTaskExecutor.submit(
() -> {
if (pendingFrames.isEmpty() && currentFrame == null) {
externalShaderProgram.signalEndOfCurrentInputStream();
checkNotNull(externalShaderProgram).signalEndOfCurrentInputStream();
DebugTraceUtil.logEvent(
DebugTraceUtil.EVENT_EXTERNAL_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
cancelForceSignalEndOfStreamTimer();
@ -294,12 +303,13 @@ import java.util.concurrent.atomic.AtomicInteger;
FrameInfo currentFrame = checkStateNotNull(this.currentFrame);
externalShaderProgramInputCapacity.decrementAndGet();
surfaceTexture.getTransformMatrix(textureTransformMatrix);
externalShaderProgram.setTextureTransformMatrix(textureTransformMatrix);
checkNotNull(externalShaderProgram).setTextureTransformMatrix(textureTransformMatrix);
long frameTimeNs = surfaceTexture.getTimestamp();
long offsetToAddUs = currentFrame.offsetToAddUs;
// Correct presentationTimeUs so that GlShaderPrograms don't see the stream offset.
long presentationTimeUs = (frameTimeNs / 1000) + offsetToAddUs;
externalShaderProgram.queueInputFrame(
checkNotNull(externalShaderProgram)
.queueInputFrame(
glObjectsProvider,
new GlTextureInfo(
externalTexId,

View File

@ -28,6 +28,7 @@ import static androidx.media3.common.util.Util.contains;
import android.content.Context;
import android.util.SparseArray;
import android.view.Surface;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.FrameInfo;
import androidx.media3.common.GlObjectsProvider;
@ -38,6 +39,7 @@ import androidx.media3.common.VideoFrameProcessor;
import com.google.common.collect.ImmutableList;
import java.util.concurrent.Executor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A switcher to switch between {@linkplain TextureManager texture managers} of different
@ -63,7 +65,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Executor errorListenerExecutor,
GlShaderProgram.ErrorListener samplingShaderProgramErrorListener,
boolean enableColorTransfers) {
boolean enableColorTransfers)
throws VideoFrameProcessingException {
this.context = context;
this.outputColorInfo = outputColorInfo;
this.glObjectsProvider = glObjectsProvider;
@ -72,30 +75,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.samplingShaderProgramErrorListener = samplingShaderProgramErrorListener;
this.inputs = new SparseArray<>();
this.enableColorTransfers = enableColorTransfers;
// TODO(b/274109008): Investigate lazy instantiating the texture managers.
inputs.put(
INPUT_TYPE_SURFACE,
new Input(new ExternalTextureManager(glObjectsProvider, videoFrameProcessingTaskExecutor)));
inputs.put(
INPUT_TYPE_BITMAP,
new Input(new BitmapTextureManager(glObjectsProvider, videoFrameProcessingTaskExecutor)));
inputs.put(
INPUT_TYPE_TEXTURE_ID,
new Input(new TexIdTextureManager(glObjectsProvider, videoFrameProcessingTaskExecutor)));
}
/**
* Registers for a new {@link VideoFrameProcessor.InputType input}.
*
* <p>Can be called multiple times on the same {@link VideoFrameProcessor.InputType inputType},
* with the new inputs overwriting the old ones. For example, a new instance of {@link
* ExternalTextureManager} is created following each call to this method with {@link
* VideoFrameProcessor#INPUT_TYPE_SURFACE}. Effectively, the {@code inputSwitcher} keeps exactly
* one {@link TextureManager} per {@linkplain VideoFrameProcessor.InputType input type}.
*
* <p>Creates an {@link TextureManager} and an appropriate {@linkplain DefaultShaderProgram
* sampler} to sample from the input.
*
* @param inputColorInfo The {@link ColorInfo} for the input frames.
* @param inputType The {@linkplain VideoFrameProcessor.InputType type} of the input being
* registered.
*/
public void registerInput(ColorInfo inputColorInfo, @VideoFrameProcessor.InputType int inputType)
private DefaultShaderProgram createSamplingShaderProgram(
ColorInfo inputColorInfo, @VideoFrameProcessor.InputType int inputType)
throws VideoFrameProcessingException {
// TODO(b/274109008): Investigate lazy instantiating the texture managers.
DefaultShaderProgram samplingShaderProgram;
TextureManager textureManager;
// TODO(b/274109008): Refactor DefaultShaderProgram to create a class just for sampling.
DefaultShaderProgram samplingShaderProgram;
switch (inputType) {
case INPUT_TYPE_SURFACE:
samplingShaderProgram =
@ -106,29 +103,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
inputColorInfo,
outputColorInfo,
enableColorTransfers);
samplingShaderProgram.setErrorListener(
errorListenerExecutor, samplingShaderProgramErrorListener);
textureManager =
new ExternalTextureManager(
glObjectsProvider, samplingShaderProgram, videoFrameProcessingTaskExecutor);
break;
case INPUT_TYPE_BITMAP:
// HDR bitmap input is not supported. Bitmaps are always sRGB/Full range/BT.709.
checkState(!ColorInfo.isTransferHdr(inputColorInfo));
ColorInfo bitmapColorInfo = ColorInfo.SRGB_BT709_FULL;
samplingShaderProgram =
DefaultShaderProgram.createWithInternalSampler(
context,
/* matrixTransformations= */ ImmutableList.of(),
/* rgbMatrices= */ ImmutableList.of(),
inputColorInfo,
bitmapColorInfo,
outputColorInfo,
enableColorTransfers,
inputType);
samplingShaderProgram.setErrorListener(
errorListenerExecutor, samplingShaderProgramErrorListener);
textureManager =
new BitmapTextureManager(
glObjectsProvider, samplingShaderProgram, videoFrameProcessingTaskExecutor);
break;
case INPUT_TYPE_TEXTURE_ID:
// Image and textureId concatenation not supported.
checkState(inputColorInfo.colorTransfer != C.COLOR_TRANSFER_SRGB);
samplingShaderProgram =
DefaultShaderProgram.createWithInternalSampler(
context,
@ -138,16 +130,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputColorInfo,
enableColorTransfers,
inputType);
samplingShaderProgram.setErrorListener(
errorListenerExecutor, samplingShaderProgramErrorListener);
textureManager =
new TexIdTextureManager(
glObjectsProvider, samplingShaderProgram, videoFrameProcessingTaskExecutor);
break;
default:
throw new VideoFrameProcessingException("Unsupported input type " + inputType);
}
inputs.put(inputType, new Input(textureManager, samplingShaderProgram));
samplingShaderProgram.setErrorListener(
errorListenerExecutor, samplingShaderProgramErrorListener);
return samplingShaderProgram;
}
/** Sets the {@link GlShaderProgram} that {@code InputSwitcher} outputs to. */
@ -158,14 +147,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Switches to a new source of input.
*
* <p>Must be called after the corresponding {@code newInputType} is {@linkplain #registerInput
* registered}.
* <p>The first time this is called for each {@link VideoFrameProcessor.InputType}, a sampling
* {@link GlShaderProgram} is created for the {@code newInputType}.
*
* @param newInputType The new {@link VideoFrameProcessor.InputType} to switch to.
* @param inputFrameInfo The {@link FrameInfo} associated with the new input.
* @param inputColorInfo The {@link ColorInfo} associated with the new input.
*/
public void switchToInput(
@VideoFrameProcessor.InputType int newInputType, FrameInfo inputFrameInfo) {
@VideoFrameProcessor.InputType int newInputType,
FrameInfo inputFrameInfo,
ColorInfo inputColorInfo)
throws VideoFrameProcessingException {
checkStateNotNull(downstreamShaderProgram);
checkState(contains(inputs, newInputType), "Input type not registered: " + newInputType);
@ -173,10 +166,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@VideoFrameProcessor.InputType int inputType = inputs.keyAt(i);
Input input = inputs.get(inputType);
if (inputType == newInputType) {
if (input.getSamplingGlShaderProgram() == null) {
// TODO: b/307952514 - When switchToInput is called, and the inputColorInfo doesn't match
// the prior inputColorInfo, recreate and reinitialize the input.samplingGlShaderProgram.
input.setSamplingGlShaderProgram(
createSamplingShaderProgram(inputColorInfo, newInputType));
}
input.setChainingListener(
new GatedChainingListenerWrapper(
glObjectsProvider,
input.samplingGlShaderProgram,
checkNotNull(input.getSamplingGlShaderProgram()),
this.downstreamShaderProgram,
videoFrameProcessingTaskExecutor));
input.setActive(true);
@ -217,31 +216,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
*
* @return The input {@link Surface}, regardless if the current input is {@linkplain
* #switchToInput set} to {@link VideoFrameProcessor#INPUT_TYPE_SURFACE}.
* @throws IllegalStateException If {@link VideoFrameProcessor#INPUT_TYPE_SURFACE} is not
* {@linkplain #registerInput registered}.
*/
public Surface getInputSurface() {
checkState(contains(inputs, INPUT_TYPE_SURFACE));
return inputs.get(INPUT_TYPE_SURFACE).textureManager.getInputSurface();
}
/**
* See {@link DefaultVideoFrameProcessor#setInputDefaultBufferSize}.
*
* @throws IllegalStateException If {@link VideoFrameProcessor#INPUT_TYPE_SURFACE} is not
* {@linkplain #registerInput registered}.
*/
/** See {@link DefaultVideoFrameProcessor#setInputDefaultBufferSize}. */
public void setInputDefaultBufferSize(int width, int height) {
checkState(contains(inputs, INPUT_TYPE_SURFACE));
inputs.get(INPUT_TYPE_SURFACE).textureManager.setDefaultBufferSize(width, height);
}
/**
* Sets the {@link OnInputFrameProcessedListener}.
*
* @throws IllegalStateException If {@link VideoFrameProcessor#INPUT_TYPE_TEXTURE_ID} is not
* {@linkplain #registerInput registered}.
*/
/** Sets the {@link OnInputFrameProcessedListener}. */
public void setOnInputFrameProcessedListener(OnInputFrameProcessedListener listener) {
checkState(contains(inputs, INPUT_TYPE_TEXTURE_ID));
inputs.get(INPUT_TYPE_TEXTURE_ID).textureManager.setOnInputFrameProcessedListener(listener);
@ -262,19 +249,27 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
*/
private static final class Input {
public final TextureManager textureManager;
public final GlShaderProgram samplingGlShaderProgram;
private @MonotonicNonNull ExternalShaderProgram samplingGlShaderProgram;
private @MonotonicNonNull GatedChainingListenerWrapper gatedChainingListenerWrapper;
public Input(TextureManager textureManager, GlShaderProgram samplingGlShaderProgram) {
public Input(TextureManager textureManager) {
this.textureManager = textureManager;
}
public void setSamplingGlShaderProgram(ExternalShaderProgram samplingGlShaderProgram) {
this.samplingGlShaderProgram = samplingGlShaderProgram;
textureManager.setSamplingGlShaderProgram(samplingGlShaderProgram);
samplingGlShaderProgram.setInputListener(textureManager);
}
public void setChainingListener(GatedChainingListenerWrapper gatedChainingListenerWrapper) {
this.gatedChainingListenerWrapper = gatedChainingListenerWrapper;
samplingGlShaderProgram.setOutputListener(gatedChainingListenerWrapper);
checkNotNull(samplingGlShaderProgram).setOutputListener(gatedChainingListenerWrapper);
}
public @Nullable ExternalShaderProgram getSamplingGlShaderProgram() {
return samplingGlShaderProgram;
}
public void setActive(boolean active) {
@ -286,9 +281,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public void release() throws VideoFrameProcessingException {
textureManager.release();
if (samplingGlShaderProgram != null) {
samplingGlShaderProgram.release();
}
}
}
/**
* Wraps a {@link ChainingGlShaderProgramListener}, with the ability to turn off the event

View File

@ -31,31 +31,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* a {@link GlShaderProgram} for consumption.
*/
/* package */ final class TexIdTextureManager extends TextureManager {
private final FrameConsumptionManager frameConsumptionManager;
private @MonotonicNonNull FrameConsumptionManager frameConsumptionManager;
private @MonotonicNonNull OnInputFrameProcessedListener frameProcessedListener;
private @MonotonicNonNull FrameInfo inputFrameInfo;
private final GlObjectsProvider glObjectsProvider;
/**
* Creates a new instance.
*
* @param glObjectsProvider The {@link GlObjectsProvider} for using EGL and GLES.
* @param shaderProgram The {@link GlShaderProgram} for which this {@code texIdTextureManager}
* will be set as the {@link GlShaderProgram.InputListener}.
* @param videoFrameProcessingTaskExecutor The {@link VideoFrameProcessingTaskExecutor}.
*/
public TexIdTextureManager(
GlObjectsProvider glObjectsProvider,
GlShaderProgram shaderProgram,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
super(videoFrameProcessingTaskExecutor);
frameConsumptionManager =
new FrameConsumptionManager(
glObjectsProvider, shaderProgram, videoFrameProcessingTaskExecutor);
this.glObjectsProvider = glObjectsProvider;
}
@Override
public void onReadyToAcceptInputFrame() {
checkNotNull(frameConsumptionManager);
videoFrameProcessingTaskExecutor.submit(frameConsumptionManager::onReadyToAcceptInputFrame);
}
@ -67,6 +64,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
.onInputFrameProcessed(inputTexture.texId, GlUtil.createGlSyncFence()));
}
@Override
public void setSamplingGlShaderProgram(GlShaderProgram samplingGlShaderProgram) {
frameConsumptionManager =
new FrameConsumptionManager(
glObjectsProvider, samplingGlShaderProgram, videoFrameProcessingTaskExecutor);
}
@Override
public void queueInputTexture(int inputTexId, long presentationTimeUs) {
FrameInfo frameInfo = checkNotNull(this.inputFrameInfo);
@ -80,7 +84,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* rboId= */ C.INDEX_UNSET,
frameInfo.width,
frameInfo.height);
frameConsumptionManager.queueInputFrame(inputTexture, presentationTimeUs);
checkNotNull(frameConsumptionManager).queueInputFrame(inputTexture, presentationTimeUs);
DebugTraceUtil.logEvent(
DebugTraceUtil.EVENT_VFP_QUEUE_TEXTURE,
presentationTimeUs,
@ -102,14 +106,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public int getPendingFrameCount() {
return frameConsumptionManager.getPendingFrameCount();
return checkNotNull(frameConsumptionManager).getPendingFrameCount();
}
@Override
public void signalEndOfCurrentInputStream() {
videoFrameProcessingTaskExecutor.submit(
() -> {
frameConsumptionManager.signalEndOfCurrentStream();
checkNotNull(frameConsumptionManager).signalEndOfCurrentStream();
DebugTraceUtil.logEvent(
DebugTraceUtil.EVENT_TEX_ID_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
});
@ -124,7 +128,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
protected synchronized void flush() {
frameConsumptionManager.onFlush();
checkNotNull(frameConsumptionManager).onFlush();
super.flush();
}
}

View File

@ -64,6 +64,16 @@ import androidx.media3.common.util.TimestampIterator;
throw new UnsupportedOperationException();
}
/**
* Sets the {@link GlShaderProgram} that consumes the {@link TextureManager}'s output.
*
* <p>Must be called before any method that queues input or {@link
* #signalEndOfCurrentInputStream()}.
*
* <p>This must only be called once.
*/
public abstract void setSamplingGlShaderProgram(GlShaderProgram samplingGlShaderProgram);
/**
* Provides an input {@link Bitmap} to put into the video frames.
*