Commit e6c8136a by claincly Committed by Marc Baechinger

Add an input switcher to switch between input types.

Also make FinalShaderProgramWrapper always receive internal texture.

This means it does not sample from a input texture, and its input color is
always linear, hence the input type does not matter.

PiperOrigin-RevId: 527869045
parent ca4928cf
...@@ -36,7 +36,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -36,7 +36,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* *
* <p>Public methods in this class can be called from any thread. * <p>Public methods in this class can be called from any thread.
*/ */
/* package */ final class BitmapTextureManager implements InputHandler { /* package */ final class BitmapTextureManager implements TextureManager {
private final GlShaderProgram shaderProgram; private final GlShaderProgram shaderProgram;
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor; private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
// 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.
......
...@@ -249,11 +249,15 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -249,11 +249,15 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private final EGLDisplay eglDisplay; private final EGLDisplay eglDisplay;
private final EGLContext eglContext; private final EGLContext eglContext;
private final InputSwitcher inputSwitcher;
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor; private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
private final InputHandler inputHandler; // TODO(b/274109008) Use InputSwither to interact with texture manager.
// Owned and released by inputSwitcher.
private final TextureManager textureManager;
private final boolean renderFramesAutomatically; private final boolean renderFramesAutomatically;
private final FinalShaderProgramWrapper finalShaderProgramWrapper; private final FinalShaderProgramWrapper finalShaderProgramWrapper;
private final ImmutableList<GlShaderProgram> allShaderPrograms; // Shader programs that apply Effects.
private final ImmutableList<GlShaderProgram> effectsShaderPrograms;
// A queue of input streams that have not been fully processed identified by their input types. // A queue of input streams that have not been fully processed identified by their input types.
private final Queue<@InputType Integer> unprocessedInputStreams; private final Queue<@InputType Integer> unprocessedInputStreams;
...@@ -266,41 +270,23 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -266,41 +270,23 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private DefaultVideoFrameProcessor( private DefaultVideoFrameProcessor(
EGLDisplay eglDisplay, EGLDisplay eglDisplay,
EGLContext eglContext, EGLContext eglContext,
InputSwitcher inputSwitcher,
@InputType int inputType, @InputType int inputType,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor, VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
ImmutableList<GlShaderProgram> shaderPrograms, ImmutableList<GlShaderProgram> effectsShaderPrograms,
boolean renderFramesAutomatically) boolean renderFramesAutomatically) {
throws VideoFrameProcessingException {
this.eglDisplay = eglDisplay; this.eglDisplay = eglDisplay;
this.eglContext = eglContext; this.eglContext = eglContext;
this.inputSwitcher = inputSwitcher;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor; this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
this.renderFramesAutomatically = renderFramesAutomatically; this.renderFramesAutomatically = renderFramesAutomatically;
this.unprocessedInputStreams = new ConcurrentLinkedQueue<>(); this.unprocessedInputStreams = new ConcurrentLinkedQueue<>();
checkState(!shaderPrograms.isEmpty()); checkState(!effectsShaderPrograms.isEmpty());
checkState(getLast(shaderPrograms) instanceof FinalShaderProgramWrapper); checkState(getLast(effectsShaderPrograms) instanceof FinalShaderProgramWrapper);
GlShaderProgram inputShaderProgram = shaderPrograms.get(0); textureManager = inputSwitcher.switchToInput(inputType);
finalShaderProgramWrapper = (FinalShaderProgramWrapper) getLast(effectsShaderPrograms);
switch (inputType) {
case VideoFrameProcessor.INPUT_TYPE_SURFACE:
checkState(inputShaderProgram instanceof ExternalShaderProgram);
inputHandler =
new ExternalTextureManager(
(ExternalShaderProgram) inputShaderProgram, videoFrameProcessingTaskExecutor);
break;
case VideoFrameProcessor.INPUT_TYPE_BITMAP:
inputHandler =
new BitmapTextureManager(inputShaderProgram, videoFrameProcessingTaskExecutor);
break;
case VideoFrameProcessor.INPUT_TYPE_TEXTURE_ID: // fall through
default:
throw new VideoFrameProcessingException("Input type not supported yet");
}
inputShaderProgram.setInputListener(inputHandler);
finalShaderProgramWrapper = (FinalShaderProgramWrapper) getLast(shaderPrograms);
finalShaderProgramWrapper.setOnInputStreamProcessedListener( finalShaderProgramWrapper.setOnInputStreamProcessedListener(
() -> { () -> {
@InputType int currentInputType = unprocessedInputStreams.remove(); @InputType int currentInputType = unprocessedInputStreams.remove();
...@@ -317,7 +303,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -317,7 +303,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
} }
return inputStreamEnded && unprocessedInputStreams.isEmpty(); return inputStreamEnded && unprocessedInputStreams.isEmpty();
}); });
allShaderPrograms = shaderPrograms; this.effectsShaderPrograms = effectsShaderPrograms;
} }
/** Returns the task executor that runs video frame processing tasks. */ /** Returns the task executor that runs video frame processing tasks. */
...@@ -342,7 +328,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -342,7 +328,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
* @param height The default height for input buffers, in pixels. * @param height The default height for input buffers, in pixels.
*/ */
public void setInputDefaultBufferSize(int width, int height) { public void setInputDefaultBufferSize(int width, int height) {
inputHandler.setDefaultBufferSize(width, height); textureManager.setDefaultBufferSize(width, height);
} }
@Override @Override
...@@ -350,7 +336,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -350,7 +336,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
checkState( checkState(
hasRefreshedNextInputFrameInfo, hasRefreshedNextInputFrameInfo,
"setInputFrameInfo must be called before queueing another bitmap"); "setInputFrameInfo must be called before queueing another bitmap");
inputHandler.queueInputBitmap( textureManager.queueInputBitmap(
inputBitmap, inputBitmap,
durationUs, durationUs,
checkNotNull(nextInputFrameInfo).offsetToAddUs, checkNotNull(nextInputFrameInfo).offsetToAddUs,
...@@ -361,13 +347,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -361,13 +347,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
@Override @Override
public Surface getInputSurface() { public Surface getInputSurface() {
return inputHandler.getInputSurface(); return textureManager.getInputSurface();
} }
@Override @Override
public void registerInputStream(@InputType int inputType) { public void registerInputStream(@InputType int inputType) {
if (!unprocessedInputStreams.isEmpty()) { if (!unprocessedInputStreams.isEmpty()) {
inputHandler.signalEndOfCurrentInputStream(); textureManager.signalEndOfCurrentInputStream();
// Wait until the current video is processed before continuing to the next input. // Wait until the current video is processed before continuing to the next input.
if (checkNotNull(unprocessedInputStreams.peek()) == INPUT_TYPE_SURFACE) { if (checkNotNull(unprocessedInputStreams.peek()) == INPUT_TYPE_SURFACE) {
latch = new CountDownLatch(1); latch = new CountDownLatch(1);
...@@ -394,13 +380,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -394,13 +380,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
checkStateNotNull( checkStateNotNull(
nextInputFrameInfo, "setInputFrameInfo must be called before registering input frames"); nextInputFrameInfo, "setInputFrameInfo must be called before registering input frames");
inputHandler.registerInputFrame(nextInputFrameInfo); textureManager.registerInputFrame(nextInputFrameInfo);
hasRefreshedNextInputFrameInfo = false; hasRefreshedNextInputFrameInfo = false;
} }
@Override @Override
public int getPendingInputFrameCount() { public int getPendingInputFrameCount() {
return inputHandler.getPendingFrameCount(); return textureManager.getPendingFrameCount();
} }
@Override @Override
...@@ -421,8 +407,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -421,8 +407,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
public void signalEndOfInput() { public void signalEndOfInput() {
checkState(!inputStreamEnded); checkState(!inputStreamEnded);
inputStreamEnded = true; inputStreamEnded = true;
inputHandler.signalEndOfCurrentInputStream(); textureManager.signalEndOfCurrentInputStream();
inputHandler.signalEndOfInput(); inputSwitcher.signalEndOfInput();
} }
@Override @Override
...@@ -430,10 +416,10 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -430,10 +416,10 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
try { try {
videoFrameProcessingTaskExecutor.flush(); videoFrameProcessingTaskExecutor.flush();
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
inputHandler.setOnFlushCompleteListener(latch::countDown); textureManager.setOnFlushCompleteListener(latch::countDown);
videoFrameProcessingTaskExecutor.submit(finalShaderProgramWrapper::flush); videoFrameProcessingTaskExecutor.submit(finalShaderProgramWrapper::flush);
latch.await(); latch.await();
inputHandler.setOnFlushCompleteListener(null); textureManager.setOnFlushCompleteListener(null);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
...@@ -448,7 +434,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -448,7 +434,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new IllegalStateException(unexpected); throw new IllegalStateException(unexpected);
} }
inputHandler.release();
} }
/** /**
...@@ -524,35 +509,54 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -524,35 +509,54 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
throw new VideoFrameProcessingException("BT.2020 PQ OpenGL output isn't supported."); throw new VideoFrameProcessingException("BT.2020 PQ OpenGL output isn't supported.");
} }
} }
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor =
new VideoFrameProcessingTaskExecutor(singleThreadExecutorService, listener);
ColorInfo linearColorInfo =
outputColorInfo
.buildUpon()
.setColorTransfer(C.COLOR_TRANSFER_LINEAR)
.setHdrStaticInfo(null)
.build();
InputSwitcher inputSwitcher =
new InputSwitcher(
context,
inputColorInfo,
/* outputColorInfo= */ linearColorInfo,
glObjectsProvider,
videoFrameProcessingTaskExecutor,
enableColorTransfers);
ImmutableList<GlShaderProgram> shaderPrograms = ImmutableList<GlShaderProgram> effectsShaderPrograms =
getGlShaderProgramsForGlEffects( getGlShaderProgramsForGlEffects(
context, context,
effects, effects,
eglDisplay, eglDisplay,
eglContext, eglContext,
debugViewProvider, debugViewProvider,
inputColorInfo, /* inputColorInfo= */ linearColorInfo,
outputColorInfo, outputColorInfo,
enableColorTransfers, enableColorTransfers,
inputType,
renderFramesAutomatically, renderFramesAutomatically,
executor, executor,
listener, listener,
glObjectsProvider, glObjectsProvider,
textureOutputListener); textureOutputListener);
setGlObjectProviderOnShaderPrograms(shaderPrograms, glObjectsProvider);
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor = // TODO(b/274109008): Register both image and video input.
new VideoFrameProcessingTaskExecutor(singleThreadExecutorService, listener); inputSwitcher.registerInput(inputType);
inputSwitcher.setDownstreamShaderProgram(effectsShaderPrograms.get(0));
setGlObjectProviderOnShaderPrograms(effectsShaderPrograms, glObjectsProvider);
chainShaderProgramsWithListeners( chainShaderProgramsWithListeners(
shaderPrograms, videoFrameProcessingTaskExecutor, listener, executor); effectsShaderPrograms, videoFrameProcessingTaskExecutor, listener, executor);
return new DefaultVideoFrameProcessor( return new DefaultVideoFrameProcessor(
eglDisplay, eglDisplay,
eglContext, eglContext,
inputSwitcher,
inputType, inputType,
videoFrameProcessingTaskExecutor, videoFrameProcessingTaskExecutor,
shaderPrograms, effectsShaderPrograms,
renderFramesAutomatically); renderFramesAutomatically);
} }
...@@ -564,8 +568,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -564,8 +568,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
* <p>All {@link Effect} instances must be {@link GlEffect} 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 * @return A non-empty list of {@link GlShaderProgram} instances to apply in the given order. The
* first is an {@link ExternalShaderProgram} and the last is a {@link * last is a {@link FinalShaderProgramWrapper}.
* FinalShaderProgramWrapper}.
*/ */
private static ImmutableList<GlShaderProgram> getGlShaderProgramsForGlEffects( private static ImmutableList<GlShaderProgram> getGlShaderProgramsForGlEffects(
Context context, Context context,
...@@ -576,7 +579,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -576,7 +579,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
ColorInfo inputColorInfo, ColorInfo inputColorInfo,
ColorInfo outputColorInfo, ColorInfo outputColorInfo,
boolean enableColorTransfers, boolean enableColorTransfers,
@InputType int inputType,
boolean renderFramesAutomatically, boolean renderFramesAutomatically,
Executor executor, Executor executor,
Listener listener, Listener listener,
...@@ -587,13 +589,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -587,13 +589,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
ImmutableList.Builder<GlMatrixTransformation> matrixTransformationListBuilder = ImmutableList.Builder<GlMatrixTransformation> matrixTransformationListBuilder =
new ImmutableList.Builder<>(); new ImmutableList.Builder<>();
ImmutableList.Builder<RgbMatrix> rgbMatrixListBuilder = new ImmutableList.Builder<>(); ImmutableList.Builder<RgbMatrix> rgbMatrixListBuilder = new ImmutableList.Builder<>();
boolean sampleFromInputTexture = true;
ColorInfo linearColorInfo =
outputColorInfo
.buildUpon()
.setColorTransfer(C.COLOR_TRANSFER_LINEAR)
.setHdrStaticInfo(null)
.build();
for (int i = 0; i < effects.size(); i++) { for (int i = 0; i < effects.size(); i++) {
Effect effect = effects.get(i); Effect effect = effects.get(i);
checkArgument( checkArgument(
...@@ -615,38 +610,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -615,38 +610,13 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
matrixTransformationListBuilder.build(); matrixTransformationListBuilder.build();
ImmutableList<RgbMatrix> rgbMatrices = rgbMatrixListBuilder.build(); ImmutableList<RgbMatrix> rgbMatrices = rgbMatrixListBuilder.build();
boolean isOutputTransferHdr = ColorInfo.isTransferHdr(outputColorInfo); boolean isOutputTransferHdr = ColorInfo.isTransferHdr(outputColorInfo);
if (!matrixTransformations.isEmpty() || !rgbMatrices.isEmpty() || sampleFromInputTexture) { if (!matrixTransformations.isEmpty() || !rgbMatrices.isEmpty()) {
DefaultShaderProgram defaultShaderProgram; DefaultShaderProgram defaultShaderProgram =
if (sampleFromInputTexture) {
if (inputType == INPUT_TYPE_SURFACE) {
defaultShaderProgram =
DefaultShaderProgram.createWithExternalSampler(
context,
matrixTransformations,
rgbMatrices,
inputColorInfo,
linearColorInfo,
enableColorTransfers);
} else {
defaultShaderProgram =
DefaultShaderProgram.createWithInternalSampler(
context,
matrixTransformations,
rgbMatrices,
inputColorInfo,
linearColorInfo,
enableColorTransfers,
inputType);
}
} else {
defaultShaderProgram =
DefaultShaderProgram.create( DefaultShaderProgram.create(
context, matrixTransformations, rgbMatrices, isOutputTransferHdr); context, matrixTransformations, rgbMatrices, isOutputTransferHdr);
}
shaderProgramListBuilder.add(defaultShaderProgram); shaderProgramListBuilder.add(defaultShaderProgram);
matrixTransformationListBuilder = new ImmutableList.Builder<>(); matrixTransformationListBuilder = new ImmutableList.Builder<>();
rgbMatrixListBuilder = new ImmutableList.Builder<>(); rgbMatrixListBuilder = new ImmutableList.Builder<>();
sampleFromInputTexture = false;
} }
shaderProgramListBuilder.add(glEffect.toGlShaderProgram(context, isOutputTransferHdr)); shaderProgramListBuilder.add(glEffect.toGlShaderProgram(context, isOutputTransferHdr));
} }
...@@ -659,11 +629,9 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -659,11 +629,9 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
matrixTransformationListBuilder.build(), matrixTransformationListBuilder.build(),
rgbMatrixListBuilder.build(), rgbMatrixListBuilder.build(),
debugViewProvider, debugViewProvider,
/* inputColorInfo= */ sampleFromInputTexture ? inputColorInfo : linearColorInfo, inputColorInfo,
outputColorInfo, outputColorInfo,
enableColorTransfers, enableColorTransfers,
sampleFromInputTexture,
inputType,
renderFramesAutomatically, renderFramesAutomatically,
executor, executor,
listener, listener,
...@@ -710,13 +678,14 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor { ...@@ -710,13 +678,14 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
*/ */
private void releaseShaderProgramsAndDestroyGlContext() { private void releaseShaderProgramsAndDestroyGlContext() {
try { try {
for (int i = 0; i < allShaderPrograms.size(); i++) {
try { try {
allShaderPrograms.get(i).release(); inputSwitcher.release();
for (int i = 0; i < effectsShaderPrograms.size(); i++) {
effectsShaderPrograms.get(i).release();
}
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error releasing shader program", e); Log.e(TAG, "Error releasing shader program", e);
} }
}
} finally { } finally {
try { try {
GlUtil.destroyEglContext(eglDisplay, eglContext); GlUtil.destroyEglContext(eglDisplay, eglContext);
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.effect; package com.google.android.exoplayer2.effect;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.graphics.Bitmap; import android.graphics.Bitmap;
...@@ -35,7 +36,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -35,7 +36,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* 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
* {@link ExternalShaderProgram} for consumption. * {@link ExternalShaderProgram} for consumption.
*/ */
/* package */ final class ExternalTextureManager implements InputHandler { /* package */ final class ExternalTextureManager implements TextureManager {
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor; private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
private final ExternalShaderProgram externalShaderProgram; private final ExternalShaderProgram externalShaderProgram;
...@@ -167,6 +168,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -167,6 +168,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
@Override @Override
public void registerInputFrame(FrameInfo frame) { public void registerInputFrame(FrameInfo frame) {
checkState(!inputStreamEnded);
pendingFrames.add(frame); pendingFrames.add(frame);
} }
......
...@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.effect; ...@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.effect;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.VideoFrameProcessor.INPUT_TYPE_SURFACE;
import android.content.Context; import android.content.Context;
import android.opengl.EGL14; import android.opengl.EGL14;
...@@ -63,7 +62,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -63,7 +62,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* <p>This wrapper is used for the final {@link DefaultShaderProgram} instance in the chain of * <p>This wrapper is used for the final {@link DefaultShaderProgram} instance in the chain of
* {@link DefaultShaderProgram} instances used by {@link VideoFrameProcessor}. * {@link DefaultShaderProgram} instances used by {@link VideoFrameProcessor}.
*/ */
/* package */ final class FinalShaderProgramWrapper implements ExternalShaderProgram { /* package */ final class FinalShaderProgramWrapper implements GlShaderProgram {
/** Listener interface for the current input stream ending. */ /** Listener interface for the current input stream ending. */
interface OnInputStreamProcessedListener { interface OnInputStreamProcessedListener {
...@@ -82,15 +81,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -82,15 +81,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final EGLDisplay eglDisplay; private final EGLDisplay eglDisplay;
private final EGLContext eglContext; private final EGLContext eglContext;
private final DebugViewProvider debugViewProvider; private final DebugViewProvider debugViewProvider;
private final boolean sampleFromInputTexture;
private final @VideoFrameProcessor.InputType int inputType;
private final ColorInfo inputColorInfo;
private final ColorInfo outputColorInfo; private final ColorInfo outputColorInfo;
private final boolean enableColorTransfers; private final boolean enableColorTransfers;
private final boolean renderFramesAutomatically; private final boolean renderFramesAutomatically;
private final Executor videoFrameProcessorListenerExecutor; private final Executor videoFrameProcessorListenerExecutor;
private final VideoFrameProcessor.Listener videoFrameProcessorListener; private final VideoFrameProcessor.Listener videoFrameProcessorListener;
private final float[] textureTransformMatrix;
private final Queue<Pair<GlTextureInfo, Long>> availableFrames; private final Queue<Pair<GlTextureInfo, Long>> availableFrames;
@Nullable private final DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener; @Nullable private final DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener;
...@@ -127,8 +122,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -127,8 +122,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
ColorInfo inputColorInfo, ColorInfo inputColorInfo,
ColorInfo outputColorInfo, ColorInfo outputColorInfo,
boolean enableColorTransfers, boolean enableColorTransfers,
boolean sampleFromInputTexture,
@VideoFrameProcessor.InputType int inputType,
boolean renderFramesAutomatically, boolean renderFramesAutomatically,
Executor videoFrameProcessorListenerExecutor, Executor videoFrameProcessorListenerExecutor,
VideoFrameProcessor.Listener videoFrameProcessorListener, VideoFrameProcessor.Listener videoFrameProcessorListener,
...@@ -140,9 +133,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -140,9 +133,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.eglDisplay = eglDisplay; this.eglDisplay = eglDisplay;
this.eglContext = eglContext; this.eglContext = eglContext;
this.debugViewProvider = debugViewProvider; this.debugViewProvider = debugViewProvider;
this.sampleFromInputTexture = sampleFromInputTexture;
this.inputType = inputType;
this.inputColorInfo = inputColorInfo;
this.outputColorInfo = outputColorInfo; this.outputColorInfo = outputColorInfo;
this.enableColorTransfers = enableColorTransfers; this.enableColorTransfers = enableColorTransfers;
this.renderFramesAutomatically = renderFramesAutomatically; this.renderFramesAutomatically = renderFramesAutomatically;
...@@ -151,7 +141,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -151,7 +141,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.glObjectsProvider = glObjectsProvider; this.glObjectsProvider = glObjectsProvider;
this.textureOutputListener = textureOutputListener; this.textureOutputListener = textureOutputListener;
textureTransformMatrix = GlUtil.create4x4IdentityMatrix();
inputListener = new InputListener() {}; inputListener = new InputListener() {};
availableFrames = new ConcurrentLinkedQueue<>(); availableFrames = new ConcurrentLinkedQueue<>();
} }
...@@ -241,20 +230,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -241,20 +230,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@Override @Override
public void setTextureTransformMatrix(float[] textureTransformMatrix) {
System.arraycopy(
/* src= */ textureTransformMatrix,
/* srcPos= */ 0,
/* dest= */ this.textureTransformMatrix,
/* destPost= */ 0,
/* length= */ textureTransformMatrix.length);
if (defaultShaderProgram != null) {
defaultShaderProgram.setTextureTransformMatrix(textureTransformMatrix);
}
}
@Override
public synchronized void release() throws VideoFrameProcessingException { public synchronized void release() throws VideoFrameProcessingException {
if (defaultShaderProgram != null) { if (defaultShaderProgram != null) {
defaultShaderProgram.release(); defaultShaderProgram.release();
...@@ -479,28 +454,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -479,28 +454,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
DefaultShaderProgram defaultShaderProgram; DefaultShaderProgram defaultShaderProgram;
ImmutableList<GlMatrixTransformation> expandedMatrixTransformations = ImmutableList<GlMatrixTransformation> expandedMatrixTransformations =
matrixTransformationListBuilder.build(); matrixTransformationListBuilder.build();
if (sampleFromInputTexture) {
if (inputType == INPUT_TYPE_SURFACE) {
defaultShaderProgram =
DefaultShaderProgram.createWithExternalSampler(
context,
expandedMatrixTransformations,
rgbMatrices,
inputColorInfo,
outputColorInfo,
enableColorTransfers);
} else {
defaultShaderProgram =
DefaultShaderProgram.createWithInternalSampler(
context,
expandedMatrixTransformations,
rgbMatrices,
inputColorInfo,
outputColorInfo,
enableColorTransfers,
inputType);
}
} else {
defaultShaderProgram = defaultShaderProgram =
DefaultShaderProgram.createApplyingOetf( DefaultShaderProgram.createApplyingOetf(
context, context,
...@@ -508,9 +461,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -508,9 +461,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
rgbMatrices, rgbMatrices,
outputColorInfo, outputColorInfo,
enableColorTransfers); enableColorTransfers);
}
defaultShaderProgram.setTextureTransformMatrix(textureTransformMatrix);
Size outputSize = defaultShaderProgram.configure(inputWidth, inputHeight); Size outputSize = defaultShaderProgram.configure(inputWidth, inputHeight);
if (outputSurfaceInfo != null) { if (outputSurfaceInfo != null) {
SurfaceInfo outputSurfaceInfo = checkNotNull(this.outputSurfaceInfo); SurfaceInfo outputSurfaceInfo = checkNotNull(this.outputSurfaceInfo);
......
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.google.android.exoplayer2.effect;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.content.Context;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.GlObjectsProvider;
import com.google.android.exoplayer2.util.GlTextureInfo;
import com.google.android.exoplayer2.util.VideoFrameProcessingException;
import com.google.android.exoplayer2.util.VideoFrameProcessor;
import com.google.android.exoplayer2.video.ColorInfo;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A switcher to switch between {@linkplain TextureManager texture managers} of different
* {@linkplain VideoFrameProcessor.InputType input types}.
*/
/* package */ final class InputSwitcher {
private final Context context;
private final ColorInfo inputColorInfo;
private final ColorInfo outputColorInfo;
private final GlObjectsProvider glObjectsProvider;
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
private final SparseArray<Input> inputs;
private final boolean enableColorTransfers;
private @MonotonicNonNull GlShaderProgram downstreamShaderProgram;
private boolean inputEnded;
private int activeInputType;
public InputSwitcher(
Context context,
ColorInfo inputColorInfo,
ColorInfo outputColorInfo,
GlObjectsProvider glObjectsProvider,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
boolean enableColorTransfers) {
this.context = context;
this.inputColorInfo = inputColorInfo;
this.outputColorInfo = outputColorInfo;
this.glObjectsProvider = glObjectsProvider;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
this.inputs = new SparseArray<>();
this.enableColorTransfers = enableColorTransfers;
activeInputType = C.INDEX_UNSET;
}
/**
* 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.
*/
public void registerInput(@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.
switch (inputType) {
case VideoFrameProcessor.INPUT_TYPE_SURFACE:
samplingShaderProgram =
DefaultShaderProgram.createWithExternalSampler(
context,
/* matrixTransformations= */ ImmutableList.of(),
/* rgbMatrices= */ ImmutableList.of(),
inputColorInfo,
outputColorInfo,
enableColorTransfers);
samplingShaderProgram.setGlObjectsProvider(glObjectsProvider);
textureManager =
new ExternalTextureManager(samplingShaderProgram, videoFrameProcessingTaskExecutor);
inputs.put(inputType, new Input(textureManager, samplingShaderProgram));
break;
case VideoFrameProcessor.INPUT_TYPE_BITMAP:
samplingShaderProgram =
DefaultShaderProgram.createWithInternalSampler(
context,
/* matrixTransformations= */ ImmutableList.of(),
/* rgbMatrices= */ ImmutableList.of(),
inputColorInfo,
outputColorInfo,
enableColorTransfers,
inputType);
samplingShaderProgram.setGlObjectsProvider(glObjectsProvider);
textureManager =
new BitmapTextureManager(samplingShaderProgram, videoFrameProcessingTaskExecutor);
inputs.put(inputType, new Input(textureManager, samplingShaderProgram));
break;
case VideoFrameProcessor.INPUT_TYPE_TEXTURE_ID: // fall through
default:
throw new VideoFrameProcessingException("Unsupported input type " + inputType);
}
}
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}.
*
* @param newInputType The new {@link VideoFrameProcessor.InputType} to switch to.
* @return The {@link TextureManager} associated with the {@code newInputType}.
*/
public TextureManager switchToInput(@VideoFrameProcessor.InputType int newInputType) {
checkStateNotNull(downstreamShaderProgram);
checkState(inputs.indexOfKey(newInputType) >= 0, "Input type not registered: " + newInputType);
if (newInputType == activeInputType) {
return inputs.get(activeInputType).textureManager;
}
@Nullable TextureManager activeTextureManager = null;
for (int i = 0; i < inputs.size(); i++) {
@VideoFrameProcessor.InputType int inputType = inputs.keyAt(i);
Input input = inputs.get(inputType);
if (inputType == newInputType) {
input.setActive(true);
downstreamShaderProgram.setInputListener(checkNotNull(input.gatedChainingListenerWrapper));
activeTextureManager = input.textureManager;
} else {
input.setActive(false);
}
}
activeInputType = newInputType;
return checkNotNull(activeTextureManager);
}
/** Signals end of input to all {@linkplain #registerInput registered inputs}. */
public void signalEndOfInput() {
checkState(!inputEnded);
inputEnded = true;
for (int i = 0; i < inputs.size(); i++) {
@VideoFrameProcessor.InputType int inputType = inputs.keyAt(i);
inputs.get(inputType).signalEndOfInput();
}
}
/** Releases the resources. */
public void release() throws VideoFrameProcessingException {
for (int i = 0; i < inputs.size(); i++) {
inputs.get(inputs.keyAt(i)).release();
}
}
/**
* Wraps a {@link TextureManager} and an appropriate {@linkplain GlShaderProgram sampling shader
* program}.
*
* <p>The output is always an internal GL texture.
*/
private static final class Input {
public final TextureManager textureManager;
public final GlShaderProgram samplingGlShaderProgram;
private @MonotonicNonNull GatedChainingListenerWrapper gatedChainingListenerWrapper;
public Input(TextureManager textureManager, GlShaderProgram samplingGlShaderProgram) {
this.textureManager = textureManager;
this.samplingGlShaderProgram = samplingGlShaderProgram;
samplingGlShaderProgram.setInputListener(textureManager);
}
public void setChainingListener(GatedChainingListenerWrapper gatedChainingListenerWrapper) {
this.gatedChainingListenerWrapper = gatedChainingListenerWrapper;
samplingGlShaderProgram.setOutputListener(gatedChainingListenerWrapper);
}
public void setActive(boolean active) {
checkStateNotNull(gatedChainingListenerWrapper);
gatedChainingListenerWrapper.setActive(active);
}
public void signalEndOfInput() {
textureManager.signalEndOfInput();
}
public void release() throws VideoFrameProcessingException {
textureManager.release();
samplingGlShaderProgram.release();
}
}
/**
* Wraps a {@link ChainingGlShaderProgramListener}, with the ability to turn off the event
* listening.
*/
private static final class GatedChainingListenerWrapper
implements GlShaderProgram.OutputListener, GlShaderProgram.InputListener {
private final ChainingGlShaderProgramListener chainingGlShaderProgramListener;
private boolean isActive = false;
public GatedChainingListenerWrapper(
GlShaderProgram producingGlShaderProgram,
GlShaderProgram consumingGlShaderProgram,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
this.chainingGlShaderProgramListener =
new ChainingGlShaderProgramListener(
producingGlShaderProgram, consumingGlShaderProgram, videoFrameProcessingTaskExecutor);
}
@Override
public void onReadyToAcceptInputFrame() {
if (isActive) {
chainingGlShaderProgramListener.onReadyToAcceptInputFrame();
}
}
@Override
public void onInputFrameProcessed(GlTextureInfo inputTexture) {
if (isActive) {
chainingGlShaderProgramListener.onInputFrameProcessed(inputTexture);
}
}
@Override
public synchronized void onFlush() {
if (isActive) {
chainingGlShaderProgramListener.onFlush();
}
}
@Override
public synchronized void onOutputFrameAvailable(
GlTextureInfo outputTexture, long presentationTimeUs) {
if (isActive) {
chainingGlShaderProgramListener.onOutputFrameAvailable(outputTexture, presentationTimeUs);
}
}
@Override
public synchronized void onCurrentOutputStreamEnded() {
if (isActive) {
chainingGlShaderProgramListener.onCurrentOutputStreamEnded();
}
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
}
}
...@@ -25,7 +25,7 @@ import com.google.android.exoplayer2.util.FrameInfo; ...@@ -25,7 +25,7 @@ import com.google.android.exoplayer2.util.FrameInfo;
import com.google.android.exoplayer2.util.VideoFrameProcessor; import com.google.android.exoplayer2.util.VideoFrameProcessor;
/** A component that handles {@code DefaultVideoFrameProcessor}'s input. */ /** A component that handles {@code DefaultVideoFrameProcessor}'s input. */
/* package */ interface InputHandler extends GlShaderProgram.InputListener { /* package */ interface TextureManager extends GlShaderProgram.InputListener {
/** /**
* See {@link DefaultVideoFrameProcessor#setInputDefaultBufferSize}. * See {@link DefaultVideoFrameProcessor#setInputDefaultBufferSize}.
...@@ -61,7 +61,7 @@ import com.google.android.exoplayer2.util.VideoFrameProcessor; ...@@ -61,7 +61,7 @@ import com.google.android.exoplayer2.util.VideoFrameProcessor;
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/** Informs the {@code InputHandler} that a frame will be queued. */ /** Informs the {@code TextureManager} that a frame will be queued. */
default void registerInputFrame(FrameInfo frameInfo) { default void registerInputFrame(FrameInfo frameInfo) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment