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:
parent
01578780a6
commit
97e9ed3f7b
@ -45,7 +45,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
+ " RGB channel.";
|
+ " RGB channel.";
|
||||||
|
|
||||||
private final GlObjectsProvider glObjectsProvider;
|
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.
|
// The queue holds all bitmaps with one or more frames pending to be sent downstream.
|
||||||
private final Queue<BitmapFrameSequenceInfo> pendingBitmaps;
|
private final Queue<BitmapFrameSequenceInfo> pendingBitmaps;
|
||||||
|
|
||||||
@ -63,21 +63,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
*
|
*
|
||||||
* @param glObjectsProvider The {@link GlObjectsProvider} for using EGL and GLES.
|
* @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
|
* @param videoFrameProcessingTaskExecutor The {@link VideoFrameProcessingTaskExecutor} that the
|
||||||
* methods of this class run on.
|
* methods of this class run on.
|
||||||
*/
|
*/
|
||||||
public BitmapTextureManager(
|
public BitmapTextureManager(
|
||||||
GlObjectsProvider glObjectsProvider,
|
GlObjectsProvider glObjectsProvider,
|
||||||
GlShaderProgram shaderProgram,
|
|
||||||
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
|
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
|
||||||
super(videoFrameProcessingTaskExecutor);
|
super(videoFrameProcessingTaskExecutor);
|
||||||
this.glObjectsProvider = glObjectsProvider;
|
this.glObjectsProvider = glObjectsProvider;
|
||||||
this.shaderProgram = shaderProgram;
|
|
||||||
pendingBitmaps = new LinkedBlockingQueue<>();
|
pendingBitmaps = new LinkedBlockingQueue<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSamplingGlShaderProgram(GlShaderProgram samplingGlShaderProgram) {
|
||||||
|
this.shaderProgram = samplingGlShaderProgram;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReadyToAcceptInputFrame() {
|
public void onReadyToAcceptInputFrame() {
|
||||||
videoFrameProcessingTaskExecutor.submit(
|
videoFrameProcessingTaskExecutor.submit(
|
||||||
@ -111,7 +112,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
videoFrameProcessingTaskExecutor.submit(
|
videoFrameProcessingTaskExecutor.submit(
|
||||||
() -> {
|
() -> {
|
||||||
if (pendingBitmaps.isEmpty()) {
|
if (pendingBitmaps.isEmpty()) {
|
||||||
shaderProgram.signalEndOfCurrentInputStream();
|
checkNotNull(shaderProgram).signalEndOfCurrentInputStream();
|
||||||
DebugTraceUtil.logEvent(
|
DebugTraceUtil.logEvent(
|
||||||
DebugTraceUtil.EVENT_BITMAP_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
DebugTraceUtil.EVENT_BITMAP_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
||||||
} else {
|
} else {
|
||||||
@ -166,7 +167,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
downstreamShaderProgramCapacity--;
|
downstreamShaderProgramCapacity--;
|
||||||
shaderProgram.queueInputFrame(
|
checkNotNull(shaderProgram)
|
||||||
|
.queueInputFrame(
|
||||||
glObjectsProvider, checkNotNull(currentGlTextureInfo), currentPresentationTimeUs);
|
glObjectsProvider, checkNotNull(currentGlTextureInfo), currentPresentationTimeUs);
|
||||||
DebugTraceUtil.logEvent(
|
DebugTraceUtil.logEvent(
|
||||||
DebugTraceUtil.EVENT_VFP_QUEUE_BITMAP,
|
DebugTraceUtil.EVENT_VFP_QUEUE_BITMAP,
|
||||||
@ -181,7 +183,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
finishedBitmapInfo.bitmap.recycle();
|
finishedBitmapInfo.bitmap.recycle();
|
||||||
if (pendingBitmaps.isEmpty() && currentInputStreamEnded) {
|
if (pendingBitmaps.isEmpty() && currentInputStreamEnded) {
|
||||||
// Only signal end of stream after all pending bitmaps are processed.
|
// Only signal end of stream after all pending bitmaps are processed.
|
||||||
shaderProgram.signalEndOfCurrentInputStream();
|
checkNotNull(shaderProgram).signalEndOfCurrentInputStream();
|
||||||
DebugTraceUtil.logEvent(
|
DebugTraceUtil.logEvent(
|
||||||
DebugTraceUtil.EVENT_BITMAP_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
DebugTraceUtil.EVENT_BITMAP_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
||||||
currentInputStreamEnded = false;
|
currentInputStreamEnded = false;
|
||||||
|
@ -254,26 +254,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
throws VideoFrameProcessingException {
|
throws VideoFrameProcessingException {
|
||||||
// TODO(b/261188041) Add tests to verify the Listener is invoked on the given Executor.
|
// 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;
|
boolean shouldShutdownExecutorService = executorService == null;
|
||||||
ExecutorService instanceExecutorService =
|
ExecutorService instanceExecutorService =
|
||||||
executorService == null ? Util.newSingleThreadExecutor(THREAD_NAME) : executorService;
|
executorService == null ? Util.newSingleThreadExecutor(THREAD_NAME) : executorService;
|
||||||
@ -340,6 +320,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
|
|
||||||
private final List<Effect> activeEffects;
|
private final List<Effect> activeEffects;
|
||||||
private final Object lock;
|
private final Object lock;
|
||||||
|
private final boolean enableColorTransfers;
|
||||||
|
private final ColorInfo firstInputColorInfo;
|
||||||
private final ColorInfo outputColorInfo;
|
private final ColorInfo outputColorInfo;
|
||||||
|
|
||||||
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
|
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
|
||||||
@ -356,6 +338,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
Executor listenerExecutor,
|
Executor listenerExecutor,
|
||||||
FinalShaderProgramWrapper finalShaderProgramWrapper,
|
FinalShaderProgramWrapper finalShaderProgramWrapper,
|
||||||
boolean renderFramesAutomatically,
|
boolean renderFramesAutomatically,
|
||||||
|
boolean enableColorTransfers,
|
||||||
|
ColorInfo firstInputColorInfo,
|
||||||
ColorInfo outputColorInfo) {
|
ColorInfo outputColorInfo) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.glObjectsProvider = glObjectsProvider;
|
this.glObjectsProvider = glObjectsProvider;
|
||||||
@ -368,6 +352,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
this.renderFramesAutomatically = renderFramesAutomatically;
|
this.renderFramesAutomatically = renderFramesAutomatically;
|
||||||
this.activeEffects = new ArrayList<>();
|
this.activeEffects = new ArrayList<>();
|
||||||
this.lock = new Object();
|
this.lock = new Object();
|
||||||
|
this.enableColorTransfers = enableColorTransfers;
|
||||||
|
this.firstInputColorInfo = firstInputColorInfo;
|
||||||
this.outputColorInfo = outputColorInfo;
|
this.outputColorInfo = outputColorInfo;
|
||||||
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
|
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
|
||||||
this.intermediateGlShaderPrograms = new ArrayList<>();
|
this.intermediateGlShaderPrograms = new ArrayList<>();
|
||||||
@ -477,7 +463,10 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
|
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
// An input stream is pending until its effects are configured.
|
// 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) {
|
if (!registeredFirstInputStream) {
|
||||||
registeredFirstInputStream = true;
|
registeredFirstInputStream = true;
|
||||||
inputStreamRegisteredCondition.close();
|
inputStreamRegisteredCondition.close();
|
||||||
@ -640,11 +629,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
: GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_8888;
|
: GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_8888;
|
||||||
EGLContext eglContext =
|
EGLContext eglContext =
|
||||||
createFocusedEglContextWithFallback(glObjectsProvider, eglDisplay, configAttributes);
|
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
|
// Not renderFramesAutomatically means outputting to a display surface. HDR display surfaces
|
||||||
// require the BT2020 PQ GL extension.
|
// require the BT2020 PQ GL extension.
|
||||||
@ -689,16 +673,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
textureOutputListener,
|
textureOutputListener,
|
||||||
textureOutputCapacity);
|
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(
|
return new DefaultVideoFrameProcessor(
|
||||||
context,
|
context,
|
||||||
glObjectsProvider,
|
glObjectsProvider,
|
||||||
@ -710,6 +684,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
videoFrameProcessorListenerExecutor,
|
videoFrameProcessorListenerExecutor,
|
||||||
finalShaderProgramWrapper,
|
finalShaderProgramWrapper,
|
||||||
renderFramesAutomatically,
|
renderFramesAutomatically,
|
||||||
|
enableColorTransfers,
|
||||||
|
/* firstInputColorInfo= */ inputColorInfo,
|
||||||
outputColorInfo);
|
outputColorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -826,6 +802,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
*/
|
*/
|
||||||
private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure)
|
private void configureEffects(InputStreamInfo inputStreamInfo, boolean forceReconfigure)
|
||||||
throws VideoFrameProcessingException {
|
throws VideoFrameProcessingException {
|
||||||
|
checkColors(firstInputColorInfo, outputColorInfo, enableColorTransfers);
|
||||||
if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) {
|
if (forceReconfigure || !activeEffects.equals(inputStreamInfo.effects)) {
|
||||||
if (!intermediateGlShaderPrograms.isEmpty()) {
|
if (!intermediateGlShaderPrograms.isEmpty()) {
|
||||||
for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) {
|
for (int i = 0; i < intermediateGlShaderPrograms.size(); i++) {
|
||||||
@ -853,7 +830,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
activeEffects.addAll(inputStreamInfo.effects);
|
activeEffects.addAll(inputStreamInfo.effects);
|
||||||
}
|
}
|
||||||
|
|
||||||
inputSwitcher.switchToInput(inputStreamInfo.inputType, inputStreamInfo.frameInfo);
|
inputSwitcher.switchToInput(
|
||||||
|
inputStreamInfo.inputType, inputStreamInfo.frameInfo, inputStreamInfo.colorInfo);
|
||||||
inputStreamRegisteredCondition.open();
|
inputStreamRegisteredCondition.open();
|
||||||
listenerExecutor.execute(
|
listenerExecutor.execute(
|
||||||
() ->
|
() ->
|
||||||
@ -861,6 +839,42 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
|||||||
inputStreamInfo.inputType, inputStreamInfo.effects, inputStreamInfo.frameInfo));
|
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.
|
* 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 {
|
private static final class InputStreamInfo {
|
||||||
public final @InputType int inputType;
|
public final @InputType int inputType;
|
||||||
public final List<Effect> effects;
|
public final List<Effect> effects;
|
||||||
|
public final ColorInfo colorInfo;
|
||||||
public final FrameInfo frameInfo;
|
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.inputType = inputType;
|
||||||
this.effects = effects;
|
this.effects = effects;
|
||||||
|
this.colorInfo = colorInfo;
|
||||||
this.frameInfo = frameInfo;
|
this.frameInfo = frameInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.effect;
|
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.Assertions.checkStateNotNull;
|
||||||
import static androidx.media3.common.util.Util.isRunningOnEmulator;
|
import static androidx.media3.common.util.Util.isRunningOnEmulator;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
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.GlUtil;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.effect.GlShaderProgram.InputListener;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
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
|
* 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 static final long SURFACE_TEXTURE_TIMEOUT_MS = isRunningOnEmulator() ? 10_000 : 500;
|
||||||
|
|
||||||
private final GlObjectsProvider glObjectsProvider;
|
private final GlObjectsProvider glObjectsProvider;
|
||||||
private final ExternalShaderProgram externalShaderProgram;
|
private @MonotonicNonNull ExternalShaderProgram externalShaderProgram;
|
||||||
private final int externalTexId;
|
private final int externalTexId;
|
||||||
private final Surface surface;
|
private final Surface surface;
|
||||||
private final SurfaceTexture surfaceTexture;
|
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.
|
* 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 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}.
|
* @param videoFrameProcessingTaskExecutor The {@link VideoFrameProcessingTaskExecutor}.
|
||||||
* @throws VideoFrameProcessingException If a problem occurs while creating the external texture.
|
* @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")
|
@SuppressWarnings("nullness:method.invocation.invalid")
|
||||||
public ExternalTextureManager(
|
public ExternalTextureManager(
|
||||||
GlObjectsProvider glObjectsProvider,
|
GlObjectsProvider glObjectsProvider,
|
||||||
ExternalShaderProgram externalShaderProgram,
|
|
||||||
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor)
|
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor)
|
||||||
throws VideoFrameProcessingException {
|
throws VideoFrameProcessingException {
|
||||||
super(videoFrameProcessingTaskExecutor);
|
super(videoFrameProcessingTaskExecutor);
|
||||||
this.glObjectsProvider = glObjectsProvider;
|
this.glObjectsProvider = glObjectsProvider;
|
||||||
this.externalShaderProgram = externalShaderProgram;
|
|
||||||
try {
|
try {
|
||||||
externalTexId = GlUtil.createExternalTexture();
|
externalTexId = GlUtil.createExternalTexture();
|
||||||
} catch (GlUtil.GlException e) {
|
} catch (GlUtil.GlException e) {
|
||||||
@ -134,6 +132,17 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
surface = new Surface(surfaceTexture);
|
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
|
@Override
|
||||||
public void setDefaultBufferSize(int width, int height) {
|
public void setDefaultBufferSize(int width, int height) {
|
||||||
surfaceTexture.setDefaultBufferSize(width, height);
|
surfaceTexture.setDefaultBufferSize(width, height);
|
||||||
@ -161,7 +170,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
if (currentInputStreamEnded && pendingFrames.isEmpty()) {
|
if (currentInputStreamEnded && pendingFrames.isEmpty()) {
|
||||||
// Reset because there could be further input streams after the current one ends.
|
// Reset because there could be further input streams after the current one ends.
|
||||||
currentInputStreamEnded = false;
|
currentInputStreamEnded = false;
|
||||||
externalShaderProgram.signalEndOfCurrentInputStream();
|
checkNotNull(externalShaderProgram).signalEndOfCurrentInputStream();
|
||||||
DebugTraceUtil.logEvent(
|
DebugTraceUtil.logEvent(
|
||||||
DebugTraceUtil.EVENT_EXTERNAL_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
DebugTraceUtil.EVENT_EXTERNAL_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
||||||
cancelForceSignalEndOfStreamTimer();
|
cancelForceSignalEndOfStreamTimer();
|
||||||
@ -200,7 +209,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
videoFrameProcessingTaskExecutor.submit(
|
videoFrameProcessingTaskExecutor.submit(
|
||||||
() -> {
|
() -> {
|
||||||
if (pendingFrames.isEmpty() && currentFrame == null) {
|
if (pendingFrames.isEmpty() && currentFrame == null) {
|
||||||
externalShaderProgram.signalEndOfCurrentInputStream();
|
checkNotNull(externalShaderProgram).signalEndOfCurrentInputStream();
|
||||||
DebugTraceUtil.logEvent(
|
DebugTraceUtil.logEvent(
|
||||||
DebugTraceUtil.EVENT_EXTERNAL_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
DebugTraceUtil.EVENT_EXTERNAL_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
||||||
cancelForceSignalEndOfStreamTimer();
|
cancelForceSignalEndOfStreamTimer();
|
||||||
@ -294,12 +303,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
FrameInfo currentFrame = checkStateNotNull(this.currentFrame);
|
FrameInfo currentFrame = checkStateNotNull(this.currentFrame);
|
||||||
externalShaderProgramInputCapacity.decrementAndGet();
|
externalShaderProgramInputCapacity.decrementAndGet();
|
||||||
surfaceTexture.getTransformMatrix(textureTransformMatrix);
|
surfaceTexture.getTransformMatrix(textureTransformMatrix);
|
||||||
externalShaderProgram.setTextureTransformMatrix(textureTransformMatrix);
|
checkNotNull(externalShaderProgram).setTextureTransformMatrix(textureTransformMatrix);
|
||||||
long frameTimeNs = surfaceTexture.getTimestamp();
|
long frameTimeNs = surfaceTexture.getTimestamp();
|
||||||
long offsetToAddUs = currentFrame.offsetToAddUs;
|
long offsetToAddUs = currentFrame.offsetToAddUs;
|
||||||
// Correct presentationTimeUs so that GlShaderPrograms don't see the stream offset.
|
// Correct presentationTimeUs so that GlShaderPrograms don't see the stream offset.
|
||||||
long presentationTimeUs = (frameTimeNs / 1000) + offsetToAddUs;
|
long presentationTimeUs = (frameTimeNs / 1000) + offsetToAddUs;
|
||||||
externalShaderProgram.queueInputFrame(
|
checkNotNull(externalShaderProgram)
|
||||||
|
.queueInputFrame(
|
||||||
glObjectsProvider,
|
glObjectsProvider,
|
||||||
new GlTextureInfo(
|
new GlTextureInfo(
|
||||||
externalTexId,
|
externalTexId,
|
||||||
|
@ -28,6 +28,7 @@ import static androidx.media3.common.util.Util.contains;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.ColorInfo;
|
import androidx.media3.common.ColorInfo;
|
||||||
import androidx.media3.common.FrameInfo;
|
import androidx.media3.common.FrameInfo;
|
||||||
import androidx.media3.common.GlObjectsProvider;
|
import androidx.media3.common.GlObjectsProvider;
|
||||||
@ -38,6 +39,7 @@ import androidx.media3.common.VideoFrameProcessor;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
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
|
* A switcher to switch between {@linkplain TextureManager texture managers} of different
|
||||||
@ -63,7 +65,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
|
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
|
||||||
Executor errorListenerExecutor,
|
Executor errorListenerExecutor,
|
||||||
GlShaderProgram.ErrorListener samplingShaderProgramErrorListener,
|
GlShaderProgram.ErrorListener samplingShaderProgramErrorListener,
|
||||||
boolean enableColorTransfers) {
|
boolean enableColorTransfers)
|
||||||
|
throws VideoFrameProcessingException {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.outputColorInfo = outputColorInfo;
|
this.outputColorInfo = outputColorInfo;
|
||||||
this.glObjectsProvider = glObjectsProvider;
|
this.glObjectsProvider = glObjectsProvider;
|
||||||
@ -72,30 +75,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
this.samplingShaderProgramErrorListener = samplingShaderProgramErrorListener;
|
this.samplingShaderProgramErrorListener = samplingShaderProgramErrorListener;
|
||||||
this.inputs = new SparseArray<>();
|
this.inputs = new SparseArray<>();
|
||||||
this.enableColorTransfers = enableColorTransfers;
|
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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private DefaultShaderProgram createSamplingShaderProgram(
|
||||||
* Registers for a new {@link VideoFrameProcessor.InputType input}.
|
ColorInfo inputColorInfo, @VideoFrameProcessor.InputType int inputType)
|
||||||
*
|
|
||||||
* <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)
|
|
||||||
throws VideoFrameProcessingException {
|
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.
|
// TODO(b/274109008): Refactor DefaultShaderProgram to create a class just for sampling.
|
||||||
|
DefaultShaderProgram samplingShaderProgram;
|
||||||
switch (inputType) {
|
switch (inputType) {
|
||||||
case INPUT_TYPE_SURFACE:
|
case INPUT_TYPE_SURFACE:
|
||||||
samplingShaderProgram =
|
samplingShaderProgram =
|
||||||
@ -106,29 +103,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
inputColorInfo,
|
inputColorInfo,
|
||||||
outputColorInfo,
|
outputColorInfo,
|
||||||
enableColorTransfers);
|
enableColorTransfers);
|
||||||
samplingShaderProgram.setErrorListener(
|
|
||||||
errorListenerExecutor, samplingShaderProgramErrorListener);
|
|
||||||
textureManager =
|
|
||||||
new ExternalTextureManager(
|
|
||||||
glObjectsProvider, samplingShaderProgram, videoFrameProcessingTaskExecutor);
|
|
||||||
break;
|
break;
|
||||||
case INPUT_TYPE_BITMAP:
|
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 =
|
samplingShaderProgram =
|
||||||
DefaultShaderProgram.createWithInternalSampler(
|
DefaultShaderProgram.createWithInternalSampler(
|
||||||
context,
|
context,
|
||||||
/* matrixTransformations= */ ImmutableList.of(),
|
/* matrixTransformations= */ ImmutableList.of(),
|
||||||
/* rgbMatrices= */ ImmutableList.of(),
|
/* rgbMatrices= */ ImmutableList.of(),
|
||||||
inputColorInfo,
|
bitmapColorInfo,
|
||||||
outputColorInfo,
|
outputColorInfo,
|
||||||
enableColorTransfers,
|
enableColorTransfers,
|
||||||
inputType);
|
inputType);
|
||||||
samplingShaderProgram.setErrorListener(
|
|
||||||
errorListenerExecutor, samplingShaderProgramErrorListener);
|
|
||||||
textureManager =
|
|
||||||
new BitmapTextureManager(
|
|
||||||
glObjectsProvider, samplingShaderProgram, videoFrameProcessingTaskExecutor);
|
|
||||||
break;
|
break;
|
||||||
case INPUT_TYPE_TEXTURE_ID:
|
case INPUT_TYPE_TEXTURE_ID:
|
||||||
|
// Image and textureId concatenation not supported.
|
||||||
|
checkState(inputColorInfo.colorTransfer != C.COLOR_TRANSFER_SRGB);
|
||||||
samplingShaderProgram =
|
samplingShaderProgram =
|
||||||
DefaultShaderProgram.createWithInternalSampler(
|
DefaultShaderProgram.createWithInternalSampler(
|
||||||
context,
|
context,
|
||||||
@ -138,16 +130,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
outputColorInfo,
|
outputColorInfo,
|
||||||
enableColorTransfers,
|
enableColorTransfers,
|
||||||
inputType);
|
inputType);
|
||||||
samplingShaderProgram.setErrorListener(
|
|
||||||
errorListenerExecutor, samplingShaderProgramErrorListener);
|
|
||||||
textureManager =
|
|
||||||
new TexIdTextureManager(
|
|
||||||
glObjectsProvider, samplingShaderProgram, videoFrameProcessingTaskExecutor);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new VideoFrameProcessingException("Unsupported input type " + inputType);
|
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. */
|
/** 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.
|
* Switches to a new source of input.
|
||||||
*
|
*
|
||||||
* <p>Must be called after the corresponding {@code newInputType} is {@linkplain #registerInput
|
* <p>The first time this is called for each {@link VideoFrameProcessor.InputType}, a sampling
|
||||||
* registered}.
|
* {@link GlShaderProgram} is created for the {@code newInputType}.
|
||||||
*
|
*
|
||||||
* @param newInputType The new {@link VideoFrameProcessor.InputType} to switch to.
|
* @param newInputType The new {@link VideoFrameProcessor.InputType} to switch to.
|
||||||
* @param inputFrameInfo The {@link FrameInfo} associated with the new input.
|
* @param inputFrameInfo The {@link FrameInfo} associated with the new input.
|
||||||
|
* @param inputColorInfo The {@link ColorInfo} associated with the new input.
|
||||||
*/
|
*/
|
||||||
public void switchToInput(
|
public void switchToInput(
|
||||||
@VideoFrameProcessor.InputType int newInputType, FrameInfo inputFrameInfo) {
|
@VideoFrameProcessor.InputType int newInputType,
|
||||||
|
FrameInfo inputFrameInfo,
|
||||||
|
ColorInfo inputColorInfo)
|
||||||
|
throws VideoFrameProcessingException {
|
||||||
checkStateNotNull(downstreamShaderProgram);
|
checkStateNotNull(downstreamShaderProgram);
|
||||||
checkState(contains(inputs, newInputType), "Input type not registered: " + newInputType);
|
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);
|
@VideoFrameProcessor.InputType int inputType = inputs.keyAt(i);
|
||||||
Input input = inputs.get(inputType);
|
Input input = inputs.get(inputType);
|
||||||
if (inputType == newInputType) {
|
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(
|
input.setChainingListener(
|
||||||
new GatedChainingListenerWrapper(
|
new GatedChainingListenerWrapper(
|
||||||
glObjectsProvider,
|
glObjectsProvider,
|
||||||
input.samplingGlShaderProgram,
|
checkNotNull(input.getSamplingGlShaderProgram()),
|
||||||
this.downstreamShaderProgram,
|
this.downstreamShaderProgram,
|
||||||
videoFrameProcessingTaskExecutor));
|
videoFrameProcessingTaskExecutor));
|
||||||
input.setActive(true);
|
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
|
* @return The input {@link Surface}, regardless if the current input is {@linkplain
|
||||||
* #switchToInput set} to {@link VideoFrameProcessor#INPUT_TYPE_SURFACE}.
|
* #switchToInput set} to {@link VideoFrameProcessor#INPUT_TYPE_SURFACE}.
|
||||||
* @throws IllegalStateException If {@link VideoFrameProcessor#INPUT_TYPE_SURFACE} is not
|
|
||||||
* {@linkplain #registerInput registered}.
|
|
||||||
*/
|
*/
|
||||||
public Surface getInputSurface() {
|
public Surface getInputSurface() {
|
||||||
checkState(contains(inputs, INPUT_TYPE_SURFACE));
|
checkState(contains(inputs, INPUT_TYPE_SURFACE));
|
||||||
return inputs.get(INPUT_TYPE_SURFACE).textureManager.getInputSurface();
|
return inputs.get(INPUT_TYPE_SURFACE).textureManager.getInputSurface();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** See {@link DefaultVideoFrameProcessor#setInputDefaultBufferSize}. */
|
||||||
* See {@link DefaultVideoFrameProcessor#setInputDefaultBufferSize}.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If {@link VideoFrameProcessor#INPUT_TYPE_SURFACE} is not
|
|
||||||
* {@linkplain #registerInput registered}.
|
|
||||||
*/
|
|
||||||
public void setInputDefaultBufferSize(int width, int height) {
|
public void setInputDefaultBufferSize(int width, int height) {
|
||||||
checkState(contains(inputs, INPUT_TYPE_SURFACE));
|
checkState(contains(inputs, INPUT_TYPE_SURFACE));
|
||||||
inputs.get(INPUT_TYPE_SURFACE).textureManager.setDefaultBufferSize(width, height);
|
inputs.get(INPUT_TYPE_SURFACE).textureManager.setDefaultBufferSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Sets the {@link OnInputFrameProcessedListener}. */
|
||||||
* Sets the {@link OnInputFrameProcessedListener}.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If {@link VideoFrameProcessor#INPUT_TYPE_TEXTURE_ID} is not
|
|
||||||
* {@linkplain #registerInput registered}.
|
|
||||||
*/
|
|
||||||
public void setOnInputFrameProcessedListener(OnInputFrameProcessedListener listener) {
|
public void setOnInputFrameProcessedListener(OnInputFrameProcessedListener listener) {
|
||||||
checkState(contains(inputs, INPUT_TYPE_TEXTURE_ID));
|
checkState(contains(inputs, INPUT_TYPE_TEXTURE_ID));
|
||||||
inputs.get(INPUT_TYPE_TEXTURE_ID).textureManager.setOnInputFrameProcessedListener(listener);
|
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 {
|
private static final class Input {
|
||||||
public final TextureManager textureManager;
|
public final TextureManager textureManager;
|
||||||
public final GlShaderProgram samplingGlShaderProgram;
|
|
||||||
|
|
||||||
|
private @MonotonicNonNull ExternalShaderProgram samplingGlShaderProgram;
|
||||||
private @MonotonicNonNull GatedChainingListenerWrapper gatedChainingListenerWrapper;
|
private @MonotonicNonNull GatedChainingListenerWrapper gatedChainingListenerWrapper;
|
||||||
|
|
||||||
public Input(TextureManager textureManager, GlShaderProgram samplingGlShaderProgram) {
|
public Input(TextureManager textureManager) {
|
||||||
this.textureManager = textureManager;
|
this.textureManager = textureManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSamplingGlShaderProgram(ExternalShaderProgram samplingGlShaderProgram) {
|
||||||
this.samplingGlShaderProgram = samplingGlShaderProgram;
|
this.samplingGlShaderProgram = samplingGlShaderProgram;
|
||||||
|
textureManager.setSamplingGlShaderProgram(samplingGlShaderProgram);
|
||||||
samplingGlShaderProgram.setInputListener(textureManager);
|
samplingGlShaderProgram.setInputListener(textureManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChainingListener(GatedChainingListenerWrapper gatedChainingListenerWrapper) {
|
public void setChainingListener(GatedChainingListenerWrapper gatedChainingListenerWrapper) {
|
||||||
this.gatedChainingListenerWrapper = gatedChainingListenerWrapper;
|
this.gatedChainingListenerWrapper = gatedChainingListenerWrapper;
|
||||||
samplingGlShaderProgram.setOutputListener(gatedChainingListenerWrapper);
|
checkNotNull(samplingGlShaderProgram).setOutputListener(gatedChainingListenerWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ExternalShaderProgram getSamplingGlShaderProgram() {
|
||||||
|
return samplingGlShaderProgram;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setActive(boolean active) {
|
public void setActive(boolean active) {
|
||||||
@ -286,9 +281,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
public void release() throws VideoFrameProcessingException {
|
public void release() throws VideoFrameProcessingException {
|
||||||
textureManager.release();
|
textureManager.release();
|
||||||
|
if (samplingGlShaderProgram != null) {
|
||||||
samplingGlShaderProgram.release();
|
samplingGlShaderProgram.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps a {@link ChainingGlShaderProgramListener}, with the ability to turn off the event
|
* Wraps a {@link ChainingGlShaderProgramListener}, with the ability to turn off the event
|
||||||
|
@ -31,31 +31,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* a {@link GlShaderProgram} for consumption.
|
* a {@link GlShaderProgram} for consumption.
|
||||||
*/
|
*/
|
||||||
/* package */ final class TexIdTextureManager extends TextureManager {
|
/* package */ final class TexIdTextureManager extends TextureManager {
|
||||||
private final FrameConsumptionManager frameConsumptionManager;
|
private @MonotonicNonNull FrameConsumptionManager frameConsumptionManager;
|
||||||
|
|
||||||
private @MonotonicNonNull OnInputFrameProcessedListener frameProcessedListener;
|
private @MonotonicNonNull OnInputFrameProcessedListener frameProcessedListener;
|
||||||
private @MonotonicNonNull FrameInfo inputFrameInfo;
|
private @MonotonicNonNull FrameInfo inputFrameInfo;
|
||||||
|
private final GlObjectsProvider glObjectsProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
*
|
*
|
||||||
* @param glObjectsProvider The {@link GlObjectsProvider} for using EGL and GLES.
|
* @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}.
|
* @param videoFrameProcessingTaskExecutor The {@link VideoFrameProcessingTaskExecutor}.
|
||||||
*/
|
*/
|
||||||
public TexIdTextureManager(
|
public TexIdTextureManager(
|
||||||
GlObjectsProvider glObjectsProvider,
|
GlObjectsProvider glObjectsProvider,
|
||||||
GlShaderProgram shaderProgram,
|
|
||||||
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
|
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
|
||||||
super(videoFrameProcessingTaskExecutor);
|
super(videoFrameProcessingTaskExecutor);
|
||||||
frameConsumptionManager =
|
this.glObjectsProvider = glObjectsProvider;
|
||||||
new FrameConsumptionManager(
|
|
||||||
glObjectsProvider, shaderProgram, videoFrameProcessingTaskExecutor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReadyToAcceptInputFrame() {
|
public void onReadyToAcceptInputFrame() {
|
||||||
|
checkNotNull(frameConsumptionManager);
|
||||||
videoFrameProcessingTaskExecutor.submit(frameConsumptionManager::onReadyToAcceptInputFrame);
|
videoFrameProcessingTaskExecutor.submit(frameConsumptionManager::onReadyToAcceptInputFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +64,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
.onInputFrameProcessed(inputTexture.texId, GlUtil.createGlSyncFence()));
|
.onInputFrameProcessed(inputTexture.texId, GlUtil.createGlSyncFence()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSamplingGlShaderProgram(GlShaderProgram samplingGlShaderProgram) {
|
||||||
|
frameConsumptionManager =
|
||||||
|
new FrameConsumptionManager(
|
||||||
|
glObjectsProvider, samplingGlShaderProgram, videoFrameProcessingTaskExecutor);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInputTexture(int inputTexId, long presentationTimeUs) {
|
public void queueInputTexture(int inputTexId, long presentationTimeUs) {
|
||||||
FrameInfo frameInfo = checkNotNull(this.inputFrameInfo);
|
FrameInfo frameInfo = checkNotNull(this.inputFrameInfo);
|
||||||
@ -80,7 +84,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
/* rboId= */ C.INDEX_UNSET,
|
/* rboId= */ C.INDEX_UNSET,
|
||||||
frameInfo.width,
|
frameInfo.width,
|
||||||
frameInfo.height);
|
frameInfo.height);
|
||||||
frameConsumptionManager.queueInputFrame(inputTexture, presentationTimeUs);
|
checkNotNull(frameConsumptionManager).queueInputFrame(inputTexture, presentationTimeUs);
|
||||||
DebugTraceUtil.logEvent(
|
DebugTraceUtil.logEvent(
|
||||||
DebugTraceUtil.EVENT_VFP_QUEUE_TEXTURE,
|
DebugTraceUtil.EVENT_VFP_QUEUE_TEXTURE,
|
||||||
presentationTimeUs,
|
presentationTimeUs,
|
||||||
@ -102,14 +106,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPendingFrameCount() {
|
public int getPendingFrameCount() {
|
||||||
return frameConsumptionManager.getPendingFrameCount();
|
return checkNotNull(frameConsumptionManager).getPendingFrameCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void signalEndOfCurrentInputStream() {
|
public void signalEndOfCurrentInputStream() {
|
||||||
videoFrameProcessingTaskExecutor.submit(
|
videoFrameProcessingTaskExecutor.submit(
|
||||||
() -> {
|
() -> {
|
||||||
frameConsumptionManager.signalEndOfCurrentStream();
|
checkNotNull(frameConsumptionManager).signalEndOfCurrentStream();
|
||||||
DebugTraceUtil.logEvent(
|
DebugTraceUtil.logEvent(
|
||||||
DebugTraceUtil.EVENT_TEX_ID_TEXTURE_MANAGER_SIGNAL_EOS, C.TIME_END_OF_SOURCE);
|
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
|
@Override
|
||||||
protected synchronized void flush() {
|
protected synchronized void flush() {
|
||||||
frameConsumptionManager.onFlush();
|
checkNotNull(frameConsumptionManager).onFlush();
|
||||||
super.flush();
|
super.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,16 @@ import androidx.media3.common.util.TimestampIterator;
|
|||||||
throw new UnsupportedOperationException();
|
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.
|
* Provides an input {@link Bitmap} to put into the video frames.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user