Pass GlObjectsProvider to methods

By passing this class where it's needed, implementations don't need to store it
in a field (reducing boilerplate) and it's clearer that it can't be unset when
needed.

PiperOrigin-RevId: 542823522
This commit is contained in:
andrewlewis 2023-06-23 11:35:26 +00:00 committed by Tofunmi Adigun-Hameed
parent c2d8051662
commit 90c8f642af
19 changed files with 147 additions and 186 deletions

View File

@ -161,10 +161,8 @@ import java.util.concurrent.Future;
}
@Override
public void setGlObjectsProvider(GlObjectsProvider glObjectsProvider) {}
@Override
public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) {
public void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
AppTextureFrame appTextureFrame =
new AppTextureFrame(
inputTexture.getTexId(), inputTexture.getWidth(), inputTexture.getHeight());

View File

@ -22,7 +22,6 @@ import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import androidx.annotation.IntRange;
import androidx.annotation.RequiresApi;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.GlUtil.GlException;
import androidx.media3.common.util.UnstableApi;
@ -30,45 +29,6 @@ import androidx.media3.common.util.UnstableApi;
/** Provider to customize the creation and maintenance of GL objects. */
@UnstableApi
public interface GlObjectsProvider {
/**
* @deprecated Please use {@code DefaultGlObjectsProvider} in {@code androidx.media3.effect}.
*/
@Deprecated
GlObjectsProvider DEFAULT =
new GlObjectsProvider() {
@Override
@RequiresApi(17)
public EGLContext createEglContext(
EGLDisplay eglDisplay, int openGlVersion, int[] configAttributes) throws GlException {
return GlUtil.createEglContext(
EGL14.EGL_NO_CONTEXT, eglDisplay, openGlVersion, configAttributes);
}
@Override
@RequiresApi(17)
public EGLSurface createEglSurface(
EGLDisplay eglDisplay,
Object surface,
@C.ColorTransfer int colorTransfer,
boolean isEncoderInputSurface)
throws GlException {
return GlUtil.createEglSurface(eglDisplay, surface, colorTransfer, isEncoderInputSurface);
}
@Override
@RequiresApi(17)
public EGLSurface createFocusedPlaceholderEglSurface(
EGLContext eglContext, EGLDisplay eglDisplay) throws GlException {
return GlUtil.createFocusedPlaceholderEglSurface(eglContext, eglDisplay);
}
@Override
public GlTextureInfo createBuffersForTexture(int texId, int width, int height)
throws GlException {
int fboId = GlUtil.createFboForTexture(texId);
return new GlTextureInfo(texId, fboId, /* rboId= */ C.INDEX_UNSET, width, height);
}
};
/**
* Creates a new {@link EGLContext} for the specified {@link EGLDisplay}.

View File

@ -412,10 +412,8 @@ public final class DefaultVideoFrameProcessorVideoFrameRenderingTest {
public void setErrorListener(Executor executor, ErrorListener errorListener) {}
@Override
public void setGlObjectsProvider(GlObjectsProvider glObjectsProvider) {}
@Override
public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) {
public void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
// No input is queued in these tests. The BlankFrameProducer is used to produce frames.
throw new UnsupportedOperationException();
}

View File

@ -15,8 +15,6 @@
*/
package androidx.media3.effect;
import static androidx.media3.common.util.Assertions.checkState;
import androidx.annotation.CallSuper;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
@ -49,7 +47,6 @@ public abstract class BaseGlShaderProgram implements GlShaderProgram {
private OutputListener outputListener;
private ErrorListener errorListener;
private Executor errorListenerExecutor;
private boolean frameProcessingStarted;
/**
* Creates a {@code BaseGlShaderProgram} instance.
@ -119,20 +116,12 @@ public abstract class BaseGlShaderProgram implements GlShaderProgram {
}
@Override
public void setGlObjectsProvider(GlObjectsProvider glObjectsProvider) {
checkState(
!frameProcessingStarted,
"The GlObjectsProvider cannot be set after frame processing has started.");
outputTexturePool.setGlObjectsProvider(glObjectsProvider);
}
@Override
public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) {
public void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
try {
Size outputTextureSize = configure(inputTexture.getWidth(), inputTexture.getHeight());
outputTexturePool.ensureConfigured(
outputTextureSize.getWidth(), outputTextureSize.getHeight());
frameProcessingStarted = true;
glObjectsProvider, outputTextureSize.getWidth(), outputTextureSize.getHeight());
// Focus on the next free buffer.
GlTextureInfo outputTexture = outputTexturePool.useTexture();
@ -152,21 +141,18 @@ public abstract class BaseGlShaderProgram implements GlShaderProgram {
@Override
public void releaseOutputFrame(GlTextureInfo outputTexture) {
frameProcessingStarted = true;
outputTexturePool.freeTexture(outputTexture);
inputListener.onReadyToAcceptInputFrame();
}
@Override
public void signalEndOfCurrentInputStream() {
frameProcessingStarted = true;
outputListener.onCurrentOutputStreamEnded();
}
@Override
@CallSuper
public void flush() {
frameProcessingStarted = true;
outputTexturePool.freeAllTextures();
inputListener.onFlush();
for (int i = 0; i < outputTexturePool.capacity(); i++) {
@ -177,7 +163,6 @@ public abstract class BaseGlShaderProgram implements GlShaderProgram {
@Override
@CallSuper
public void release() throws VideoFrameProcessingException {
frameProcessingStarted = true;
try {
outputTexturePool.deleteAllTextures();
} catch (GlUtil.GlException e) {

View File

@ -25,6 +25,7 @@ import android.opengl.GLUtils;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.FrameInfo;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.GlUtil;
@ -47,6 +48,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
"Unsupported Image Configuration: No more than 8 bits of precision should be used for each"
+ " RGB channel.";
private final GlObjectsProvider glObjectsProvider;
private final GlShaderProgram shaderProgram;
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
// The queue holds all bitmaps with one or more frames pending to be sent downstream.
@ -62,14 +64,17 @@ 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) {
this.glObjectsProvider = glObjectsProvider;
this.shaderProgram = shaderProgram;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
pendingBitmaps = new LinkedBlockingQueue<>();
@ -187,7 +192,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
framesToQueueForCurrentBitmap--;
downstreamShaderProgramCapacity--;
shaderProgram.queueInputFrame(
checkNotNull(currentGlTextureInfo), round(currentPresentationTimeUs));
glObjectsProvider, checkNotNull(currentGlTextureInfo), round(currentPresentationTimeUs));
currentPresentationTimeUs += currentBitmapInfo.frameDurationUs;
if (framesToQueueForCurrentBitmap == 0) {

View File

@ -15,6 +15,7 @@
*/
package androidx.media3.effect;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.effect.GlShaderProgram.InputListener;
import androidx.media3.effect.GlShaderProgram.OutputListener;
@ -35,6 +36,7 @@ import androidx.media3.effect.GlShaderProgram.OutputListener;
/**
* Creates a new instance.
*
* @param glObjectsProvider The {@link GlObjectsProvider} for using EGL and GLES.
* @param producingGlShaderProgram The {@link GlShaderProgram} for which this listener will be set
* as {@link OutputListener}.
* @param consumingGlShaderProgram The {@link GlShaderProgram} for which this listener will be set
@ -45,12 +47,14 @@ import androidx.media3.effect.GlShaderProgram.OutputListener;
* releasing the {@link VideoFrameProcessingTaskExecutor}.
*/
public ChainingGlShaderProgramListener(
GlObjectsProvider glObjectsProvider,
GlShaderProgram producingGlShaderProgram,
GlShaderProgram consumingGlShaderProgram,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
this.producingGlShaderProgram = producingGlShaderProgram;
frameConsumptionManager =
new FrameConsumptionManager(consumingGlShaderProgram, videoFrameProcessingTaskExecutor);
new FrameConsumptionManager(
glObjectsProvider, consumingGlShaderProgram, videoFrameProcessingTaskExecutor);
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
}

View File

@ -44,7 +44,6 @@ import androidx.media3.common.util.Size;
*/
/* package */ final class DefaultFrameDroppingShaderProgram extends FrameCacheGlShaderProgram {
private final GlObjectsProvider glObjectsProvider;
private final boolean useHdr;
private final long targetFrameDeltaUs;
@ -69,25 +68,25 @@ import androidx.media3.common.util.Size;
this.targetFrameDeltaUs = (long) (C.MICROS_PER_SECOND / targetFrameRate);
lastQueuedPresentationTimeUs = C.TIME_UNSET;
previousPresentationTimeUs = C.TIME_UNSET;
glObjectsProvider = new DefaultGlObjectsProvider(/* sharedEglContext= */ null);
}
@Override
public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) {
public void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
framesReceived++;
if (framesReceived == 1) {
copyTextureToPreviousFrame(inputTexture, presentationTimeUs);
queuePreviousFrame();
copyTextureToPreviousFrame(glObjectsProvider, inputTexture, presentationTimeUs);
queuePreviousFrame(glObjectsProvider);
getInputListener().onInputFrameProcessed(inputTexture);
getInputListener().onReadyToAcceptInputFrame();
return;
}
if (shouldQueuePreviousFrame(presentationTimeUs)) {
queuePreviousFrame();
queuePreviousFrame(glObjectsProvider);
}
copyTextureToPreviousFrame(inputTexture, presentationTimeUs);
copyTextureToPreviousFrame(glObjectsProvider, inputTexture, presentationTimeUs);
getInputListener().onInputFrameProcessed(inputTexture);
getInputListener().onReadyToAcceptInputFrame();
}
@ -129,7 +128,8 @@ import androidx.media3.common.util.Size;
framesReceived = 0;
}
private void copyTextureToPreviousFrame(GlTextureInfo newTexture, long presentationTimeUs) {
private void copyTextureToPreviousFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo newTexture, long presentationTimeUs) {
try {
if (previousTexture == null) {
int texId = GlUtil.createTexture(newTexture.getWidth(), newTexture.getHeight(), useHdr);
@ -171,12 +171,12 @@ import androidx.media3.common.util.Size;
< abs(currentFrameTimeDeltaUs - targetFrameDeltaUs);
}
private void queuePreviousFrame() {
private void queuePreviousFrame(GlObjectsProvider glObjectsProvider) {
try {
GlTextureInfo previousTexture = checkNotNull(this.previousTexture);
Size outputTextureSize = configure(previousTexture.getWidth(), previousTexture.getHeight());
outputTexturePool.ensureConfigured(
outputTextureSize.getWidth(), outputTextureSize.getHeight());
glObjectsProvider, outputTextureSize.getWidth(), outputTextureSize.getHeight());
// Focus on the next free buffer.
GlTextureInfo outputTexture = outputTexturePool.useTexture();

View File

@ -39,10 +39,15 @@ public final class DefaultGlObjectsProvider implements GlObjectsProvider {
private final EGLContext sharedEglContext;
/** Creates an instance with no shared EGL context. */
public DefaultGlObjectsProvider() {
this(/* sharedEglContext= */ null);
}
/**
* Creates an instance.
* Creates an instance with the specified shared EGL context.
*
* @param sharedEglContext The {@link EGLContext} with which to share data.
* @param sharedEglContext The context with which to share data, or {@code null} if none.
*/
public DefaultGlObjectsProvider(@Nullable EGLContext sharedEglContext) {
this.sharedEglContext = sharedEglContext != null ? sharedEglContext : EGL14.EGL_NO_CONTEXT;

View File

@ -102,7 +102,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
/** Creates an instance. */
public Builder() {
enableColorTransfers = true;
glObjectsProvider = GlObjectsProvider.DEFAULT;
glObjectsProvider = new DefaultGlObjectsProvider();
}
/**
@ -119,7 +119,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
/**
* Sets the {@link GlObjectsProvider}.
*
* <p>The default value is {@link GlObjectsProvider#DEFAULT}.
* <p>The default value is a {@link DefaultGlObjectsProvider}.
*/
@CanIgnoreReturnValue
public Builder setGlObjectsProvider(GlObjectsProvider glObjectsProvider) {
@ -285,6 +285,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private static final long RELEASE_WAIT_TIME_MS = 500;
private final Context context;
private final GlObjectsProvider glObjectsProvider;
private final EGLDisplay eglDisplay;
private final EGLContext eglContext;
private final InputSwitcher inputSwitcher;
@ -303,7 +304,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private final List<Effect> activeEffects;
private final Object lock;
private final ColorInfo outputColorInfo;
private final GlObjectsProvider glObjectsProvider;
// CountDownLatch to wait for the current input stream to finish processing.
private volatile @MonotonicNonNull CountDownLatch latch;
@ -313,6 +313,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private DefaultVideoFrameProcessor(
Context context,
GlObjectsProvider glObjectsProvider,
EGLDisplay eglDisplay,
EGLContext eglContext,
InputSwitcher inputSwitcher,
@ -322,9 +323,9 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
ImmutableList<GlShaderProgram> intermediateGlShaderPrograms,
FinalShaderProgramWrapper finalShaderProgramWrapper,
boolean renderFramesAutomatically,
ColorInfo outputColorInfo,
GlObjectsProvider glObjectsProvider) {
ColorInfo outputColorInfo) {
this.context = context;
this.glObjectsProvider = glObjectsProvider;
this.eglDisplay = eglDisplay;
this.eglContext = eglContext;
this.inputSwitcher = inputSwitcher;
@ -335,7 +336,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
this.activeEffects = new ArrayList<>();
this.lock = new Object();
this.outputColorInfo = outputColorInfo;
this.glObjectsProvider = glObjectsProvider;
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
finalShaderProgramWrapper.setOnInputStreamProcessedListener(
() -> {
@ -461,8 +461,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
inputSwitcher.setDownstreamShaderProgram(
getFirst(
intermediateGlShaderPrograms, /* defaultValue= */ finalShaderProgramWrapper));
setGlObjectProviderOnShaderPrograms(intermediateGlShaderPrograms, glObjectsProvider);
chainShaderProgramsWithListeners(
glObjectsProvider,
intermediateGlShaderPrograms,
finalShaderProgramWrapper,
videoFrameProcessingTaskExecutor,
@ -521,7 +521,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
!renderFramesAutomatically,
"Calling this method is not allowed when renderFramesAutomatically is enabled");
videoFrameProcessingTaskExecutor.submitWithHighPriority(
() -> finalShaderProgramWrapper.renderOutputFrame(renderTimeNs));
() -> finalShaderProgramWrapper.renderOutputFrame(glObjectsProvider, renderTimeNs));
}
@Override
@ -658,7 +658,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
videoFrameProcessingTaskExecutor,
videoFrameProcessorListenerExecutor,
listener,
glObjectsProvider,
textureOutputListener,
textureOutputCapacity);
@ -681,8 +680,8 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
inputSwitcher.setDownstreamShaderProgram(
getFirst(intermediateGlShaderPrograms, /* defaultValue= */ finalShaderProgramWrapper));
setGlObjectProviderOnShaderPrograms(intermediateGlShaderPrograms, glObjectsProvider);
chainShaderProgramsWithListeners(
glObjectsProvider,
intermediateGlShaderPrograms,
finalShaderProgramWrapper,
videoFrameProcessingTaskExecutor,
@ -691,6 +690,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
return new DefaultVideoFrameProcessor(
context,
glObjectsProvider,
eglDisplay,
eglContext,
inputSwitcher,
@ -700,8 +700,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
intermediateGlShaderPrograms,
finalShaderProgramWrapper,
renderFramesAutomatically,
outputColorInfo,
glObjectsProvider);
outputColorInfo);
}
/**
@ -766,20 +765,12 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
return shaderProgramListBuilder.build();
}
/** Sets the {@link GlObjectsProvider} on all of the {@linkplain GlShaderProgram}s provided. */
private static void setGlObjectProviderOnShaderPrograms(
List<GlShaderProgram> shaderPrograms, GlObjectsProvider glObjectsProvider) {
for (int i = 0; i < shaderPrograms.size(); i++) {
GlShaderProgram shaderProgram = shaderPrograms.get(i);
shaderProgram.setGlObjectsProvider(glObjectsProvider);
}
}
/**
* Chains the given {@link GlShaderProgram} instances using {@link
* ChainingGlShaderProgramListener} instances.
*/
private static void chainShaderProgramsWithListeners(
GlObjectsProvider glObjectsProvider,
List<GlShaderProgram> shaderPrograms,
FinalShaderProgramWrapper finalShaderProgramWrapper,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
@ -792,7 +783,10 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
GlShaderProgram consumingGlShaderProgram = shaderProgramsToChain.get(i + 1);
ChainingGlShaderProgramListener chainingGlShaderProgramListener =
new ChainingGlShaderProgramListener(
producingGlShaderProgram, consumingGlShaderProgram, videoFrameProcessingTaskExecutor);
glObjectsProvider,
producingGlShaderProgram,
consumingGlShaderProgram,
videoFrameProcessingTaskExecutor);
producingGlShaderProgram.setOutputListener(chainingGlShaderProgramListener);
producingGlShaderProgram.setErrorListener(
videoFrameProcessorListenerExecutor, videoFrameProcessorListener::onError);

View File

@ -23,6 +23,7 @@ import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.FrameInfo;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.GlUtil;
@ -55,6 +56,7 @@ import java.util.concurrent.atomic.AtomicInteger;
private static final long SURFACE_TEXTURE_TIMEOUT_MS =
Util.DEVICE.contains("emulator") ? 10_000 : 500;
private final GlObjectsProvider glObjectsProvider;
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
private final ExternalShaderProgram externalShaderProgram;
private final int externalTexId;
@ -90,6 +92,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Creates a new instance.
*
* @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}.
@ -98,9 +101,11 @@ import java.util.concurrent.atomic.AtomicInteger;
// The onFrameAvailableListener will not be invoked until the constructor returns.
@SuppressWarnings("nullness:method.invocation.invalid")
public ExternalTextureManager(
GlObjectsProvider glObjectsProvider,
ExternalShaderProgram externalShaderProgram,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor)
throws VideoFrameProcessingException {
this.glObjectsProvider = glObjectsProvider;
this.externalShaderProgram = externalShaderProgram;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
try {
@ -314,6 +319,7 @@ import java.util.concurrent.atomic.AtomicInteger;
// Correct the presentation time so that GlShaderPrograms don't see the stream offset.
long presentationTimeUs = (frameTimeNs / 1000) + offsetToAddUs;
externalShaderProgram.queueInputFrame(
glObjectsProvider,
new GlTextureInfo(
externalTexId,
/* fboId= */ C.INDEX_UNSET,

View File

@ -101,12 +101,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private int outputHeight;
@Nullable private DefaultShaderProgram defaultShaderProgram;
@Nullable private SurfaceViewWrapper debugSurfaceViewWrapper;
private GlObjectsProvider glObjectsProvider;
private InputListener inputListener;
private @MonotonicNonNull Size outputSizeBeforeSurfaceTransformation;
@Nullable private SurfaceView debugSurfaceView;
@Nullable private OnInputStreamProcessedListener onInputStreamProcessedListener;
private boolean frameProcessingStarted;
private boolean matrixTransformationsChanged;
@GuardedBy("this")
@ -132,7 +130,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Executor videoFrameProcessorListenerExecutor,
VideoFrameProcessor.Listener videoFrameProcessorListener,
GlObjectsProvider glObjectsProvider,
@Nullable DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener,
int textureOutputCapacity) {
this.context = context;
@ -147,7 +144,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
this.videoFrameProcessorListenerExecutor = videoFrameProcessorListenerExecutor;
this.videoFrameProcessorListener = videoFrameProcessorListener;
this.glObjectsProvider = glObjectsProvider;
this.textureOutputListener = textureOutputListener;
inputListener = new InputListener() {};
@ -158,15 +154,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputTextureTimestamps = new ArrayDeque<>(textureOutputCapacity);
}
@Override
public void setGlObjectsProvider(GlObjectsProvider glObjectsProvider) {
checkState(
!frameProcessingStarted,
"The GlObjectsProvider cannot be set after frame processing has started.");
this.glObjectsProvider = glObjectsProvider;
outputTexturePool.setGlObjectsProvider(glObjectsProvider);
}
@Override
public void setInputListener(InputListener inputListener) {
this.inputListener = inputListener;
@ -192,27 +179,33 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public void signalEndOfCurrentInputStream() {
frameProcessingStarted = true;
checkNotNull(onInputStreamProcessedListener).onInputStreamProcessed();
}
// Methods that must be called on the GL thread.
@Override
public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) {
frameProcessingStarted = true;
public void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
videoFrameProcessorListenerExecutor.execute(
() -> videoFrameProcessorListener.onOutputFrameAvailableForRendering(presentationTimeUs));
if (textureOutputListener == null) {
if (renderFramesAutomatically) {
renderFrame(
inputTexture, presentationTimeUs, /* renderTimeNs= */ presentationTimeUs * 1000);
glObjectsProvider,
inputTexture,
presentationTimeUs,
/* renderTimeNs= */ presentationTimeUs * 1000);
} else {
availableFrames.add(Pair.create(inputTexture, presentationTimeUs));
}
} else {
checkState(outputTexturePool.freeTextureCount() > 0);
renderFrame(inputTexture, presentationTimeUs, /* renderTimeNs= */ presentationTimeUs * 1000);
renderFrame(
glObjectsProvider,
inputTexture,
presentationTimeUs,
/* renderTimeNs= */ presentationTimeUs * 1000);
}
maybeOnReadyToAcceptInputFrame();
}
@ -254,7 +247,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public void flush() {
frameProcessingStarted = true;
// Drops all frames that aren't rendered yet.
availableFrames.clear();
if (defaultShaderProgram != null) {
@ -277,14 +269,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
public void renderOutputFrame(long renderTimeNs) {
public void renderOutputFrame(GlObjectsProvider glObjectsProvider, long renderTimeNs) {
if (textureOutputListener != null) {
return;
}
frameProcessingStarted = true;
checkState(!renderFramesAutomatically);
Pair<GlTextureInfo, Long> oldestAvailableFrame = availableFrames.remove();
renderFrame(
glObjectsProvider,
/* inputTexture= */ oldestAvailableFrame.first,
/* presentationTimeUs= */ oldestAvailableFrame.second,
renderTimeNs);
@ -326,10 +318,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
private synchronized void renderFrame(
GlTextureInfo inputTexture, long presentationTimeUs, long renderTimeNs) {
GlObjectsProvider glObjectsProvider,
GlTextureInfo inputTexture,
long presentationTimeUs,
long renderTimeNs) {
try {
if (renderTimeNs == VideoFrameProcessor.DROP_OUTPUT_FRAME
|| !ensureConfigured(inputTexture.getWidth(), inputTexture.getHeight())) {
|| !ensureConfigured(
glObjectsProvider, inputTexture.getWidth(), inputTexture.getHeight())) {
inputListener.onInputFrameProcessed(inputTexture);
return; // Drop frames when requested, or there is no output surface and output texture.
}
@ -345,7 +341,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
VideoFrameProcessingException.from(e, presentationTimeUs)));
}
if (debugSurfaceViewWrapper != null && defaultShaderProgram != null) {
renderFrameToDebugSurface(inputTexture, presentationTimeUs);
renderFrameToDebugSurface(glObjectsProvider, inputTexture, presentationTimeUs);
}
inputListener.onInputFrameProcessed(inputTexture);
@ -399,7 +395,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
*
* <p>Returns {@code false} if {@code outputSurfaceInfo} is unset.
*/
private synchronized boolean ensureConfigured(int inputWidth, int inputHeight)
private synchronized boolean ensureConfigured(
GlObjectsProvider glObjectsProvider, int inputWidth, int inputHeight)
throws VideoFrameProcessingException, GlUtil.GlException {
// Clear extra or outdated resources.
boolean inputSizeChanged =
@ -455,7 +452,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* isEncoderInputSurface= */ renderFramesAutomatically);
}
if (textureOutputListener != null) {
outputTexturePool.ensureConfigured(outputWidth, outputHeight);
outputTexturePool.ensureConfigured(glObjectsProvider, outputWidth, outputHeight);
}
@Nullable
@ -522,7 +519,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return defaultShaderProgram;
}
private void renderFrameToDebugSurface(GlTextureInfo inputTexture, long presentationTimeUs) {
private void renderFrameToDebugSurface(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
DefaultShaderProgram defaultShaderProgram = checkNotNull(this.defaultShaderProgram);
SurfaceViewWrapper debugSurfaceViewWrapper = checkNotNull(this.debugSurfaceViewWrapper);
try {

View File

@ -19,6 +19,7 @@ import android.util.Pair;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.VideoFrameProcessor;
import java.util.ArrayDeque;
@ -32,6 +33,8 @@ import java.util.Queue;
*/
/* package */ final class FrameConsumptionManager implements GlShaderProgram.InputListener {
private final GlObjectsProvider glObjectsProvider;
private final GlShaderProgram consumingGlShaderProgram;
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
@ -44,12 +47,15 @@ import java.util.Queue;
/**
* Creates a new instance.
*
* @param glObjectsProvider The {@link GlObjectsProvider} for using EGL and GLES.
* @param consumingGlShaderProgram The {@link GlShaderProgram} that frames are queued to.
* @param videoFrameProcessingTaskExecutor The {@link VideoFrameProcessingTaskExecutor}.
*/
public FrameConsumptionManager(
GlObjectsProvider glObjectsProvider,
GlShaderProgram consumingGlShaderProgram,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
this.glObjectsProvider = glObjectsProvider;
this.consumingGlShaderProgram = consumingGlShaderProgram;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
availableFrames = new ArrayDeque<>();
@ -66,6 +72,7 @@ import java.util.Queue;
videoFrameProcessingTaskExecutor.submit(
() ->
consumingGlShaderProgram.queueInputFrame(
glObjectsProvider,
/* inputTexture= */ pendingFrame.first,
/* presentationTimeUs= */ pendingFrame.second));
@Nullable Pair<GlTextureInfo, Long> nextPendingFrame = availableFrames.peek();
@ -82,15 +89,15 @@ import java.util.Queue;
availableFrames.clear();
}
public synchronized void queueInputFrame(GlTextureInfo texture, long presentationTimeUs) {
public synchronized void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) {
if (consumingGlShaderProgramInputCapacity > 0) {
videoFrameProcessingTaskExecutor.submit(
() ->
consumingGlShaderProgram.queueInputFrame(
/* inputTexture= */ texture, presentationTimeUs));
glObjectsProvider, inputTexture, presentationTimeUs));
consumingGlShaderProgramInputCapacity--;
} else {
availableFrames.add(Pair.create(texture, presentationTimeUs));
availableFrames.add(Pair.create(inputTexture, presentationTimeUs));
}
}

View File

@ -25,9 +25,9 @@ import java.util.concurrent.Executor;
* Processes frames from one OpenGL 2D texture to another.
*
* <p>The {@code GlShaderProgram} consumes input frames it accepts via {@link
* #queueInputFrame(GlTextureInfo, long)} and surrenders each texture back to the caller via its
* {@linkplain InputListener#onInputFrameProcessed(GlTextureInfo) listener} once the texture's
* contents have been processed.
* #queueInputFrame(GlObjectsProvider, GlTextureInfo, long)} and surrenders each texture back to the
* caller via its {@linkplain InputListener#onInputFrameProcessed(GlTextureInfo) listener} once the
* texture's contents have been processed.
*
* <p>The {@code GlShaderProgram} produces output frames asynchronously and notifies its owner when
* they are available via its {@linkplain OutputListener#onOutputFrameAvailable(GlTextureInfo, long)
@ -57,8 +57,8 @@ public interface GlShaderProgram {
/**
* Called when the {@link GlShaderProgram} is ready to accept another input frame.
*
* <p>For each time this method is called, {@link #queueInputFrame(GlTextureInfo, long)} can be
* called once.
* <p>For each time this method is called, {@link #queueInputFrame(GlObjectsProvider,
* GlTextureInfo, long)} can be called once.
*/
default void onReadyToAcceptInputFrame() {}
@ -69,7 +69,7 @@ public interface GlShaderProgram {
* #onReadyToAcceptInputFrame ready to accept another input frame} when this method is called.
*
* @param inputTexture The {@link GlTextureInfo} that was used to {@linkplain
* #queueInputFrame(GlTextureInfo, long) queue} the input frame.
* #queueInputFrame(GlObjectsProvider, GlTextureInfo, long) queue} the input frame.
*/
default void onInputFrameProcessed(GlTextureInfo inputTexture) {}
@ -149,13 +149,6 @@ public interface GlShaderProgram {
*/
void setErrorListener(Executor executor, ErrorListener errorListener);
/**
* Sets the {@link GlObjectsProvider}.
*
* <p>This method should not be called after any of the frame processing methods.
*/
void setGlObjectsProvider(GlObjectsProvider glObjectsProvider);
/**
* Processes an input frame if possible.
*
@ -166,10 +159,12 @@ public interface GlShaderProgram {
* <p>This method must only be called when the {@code GlShaderProgram} can {@linkplain
* InputListener#onReadyToAcceptInputFrame() accept an input frame}.
*
* @param glObjectsProvider The {@link GlObjectsProvider} for using EGL and GLES.
* @param inputTexture A {@link GlTextureInfo} describing the texture containing the input frame.
* @param presentationTimeUs The presentation timestamp of the input frame, in microseconds.
*/
void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs);
void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs);
/**
* Notifies the {@code GlShaderProgram} that the frame on the given output texture is no longer

View File

@ -92,9 +92,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
inputColorInfo,
outputColorInfo,
enableColorTransfers);
samplingShaderProgram.setGlObjectsProvider(glObjectsProvider);
textureManager =
new ExternalTextureManager(samplingShaderProgram, videoFrameProcessingTaskExecutor);
new ExternalTextureManager(
glObjectsProvider, samplingShaderProgram, videoFrameProcessingTaskExecutor);
inputs.put(inputType, new Input(textureManager, samplingShaderProgram));
break;
case VideoFrameProcessor.INPUT_TYPE_BITMAP:
@ -107,9 +107,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputColorInfo,
enableColorTransfers,
inputType);
samplingShaderProgram.setGlObjectsProvider(glObjectsProvider);
textureManager =
new BitmapTextureManager(samplingShaderProgram, videoFrameProcessingTaskExecutor);
new BitmapTextureManager(
glObjectsProvider, samplingShaderProgram, videoFrameProcessingTaskExecutor);
inputs.put(inputType, new Input(textureManager, samplingShaderProgram));
break;
case VideoFrameProcessor.INPUT_TYPE_TEXTURE_ID:
@ -122,9 +122,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputColorInfo,
enableColorTransfers,
inputType);
samplingShaderProgram.setGlObjectsProvider(glObjectsProvider);
textureManager =
new TexIdTextureManager(samplingShaderProgram, videoFrameProcessingTaskExecutor);
new TexIdTextureManager(
glObjectsProvider, samplingShaderProgram, videoFrameProcessingTaskExecutor);
inputs.put(inputType, new Input(textureManager, samplingShaderProgram));
break;
default:
@ -155,6 +155,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (inputType == newInputType) {
input.setChainingListener(
new GatedChainingListenerWrapper(
glObjectsProvider,
input.samplingGlShaderProgram,
this.downstreamShaderProgram,
videoFrameProcessingTaskExecutor));
@ -235,15 +236,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
implements GlShaderProgram.OutputListener, GlShaderProgram.InputListener {
private final ChainingGlShaderProgramListener chainingGlShaderProgramListener;
private boolean isActive = false;
private boolean isActive;
public GatedChainingListenerWrapper(
GlObjectsProvider glObjectsProvider,
GlShaderProgram producingGlShaderProgram,
GlShaderProgram consumingGlShaderProgram,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
this.chainingGlShaderProgramListener =
new ChainingGlShaderProgramListener(
producingGlShaderProgram, consumingGlShaderProgram, videoFrameProcessingTaskExecutor);
glObjectsProvider,
producingGlShaderProgram,
consumingGlShaderProgram,
videoFrameProcessingTaskExecutor);
}
@Override

View File

@ -20,6 +20,7 @@ import static androidx.media3.common.util.Assertions.checkArgument;
import static java.lang.Math.round;
import android.content.Context;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.VideoFrameProcessingException;
@ -54,10 +55,11 @@ import androidx.media3.common.VideoFrameProcessingException;
}
@Override
public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) {
public void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
framesReceived++;
if (framesReceived % n == 0) {
super.queueInputFrame(inputTexture, presentationTimeUs);
super.queueInputFrame(glObjectsProvider, inputTexture, presentationTimeUs);
} else {
getInputListener().onInputFrameProcessed(inputTexture);
getInputListener().onReadyToAcceptInputFrame();

View File

@ -21,13 +21,14 @@ import android.opengl.GLES10;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.FrameInfo;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.OnInputFrameProcessedListener;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Forwards a video frames made available via {@linkplain GLES10#GL_TEXTURE_2D traditional GLES
* texture} to a {@link GlShaderProgram} for consumption.
* Forwards frames made available via {@linkplain GLES10#GL_TEXTURE_2D traditional GLES textures} to
* a {@link GlShaderProgram} for consumption.
*
* <p>Public methods in this class can be called from any thread.
*/
@ -41,16 +42,19 @@ 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 texIdTextureManager}
* will be set as the {@link GlShaderProgram.InputListener}.
* @param videoFrameProcessingTaskExecutor The {@link VideoFrameProcessingTaskExecutor}.
*/
public TexIdTextureManager(
GlObjectsProvider glObjectsProvider,
GlShaderProgram shaderProgram,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor) {
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
frameConsumptionManager =
new FrameConsumptionManager(shaderProgram, videoFrameProcessingTaskExecutor);
new FrameConsumptionManager(
glObjectsProvider, shaderProgram, videoFrameProcessingTaskExecutor);
}
@Override

View File

@ -32,8 +32,6 @@ import java.util.Queue;
private final int capacity;
private final boolean useHighPrecisionColorComponents;
private GlObjectsProvider glObjectsProvider;
/**
* Creates a {@code TexturePool} instance.
*
@ -47,14 +45,6 @@ import java.util.Queue;
freeTextures = new ArrayDeque<>(capacity);
inUseTextures = new ArrayDeque<>(capacity);
glObjectsProvider = new DefaultGlObjectsProvider(/* sharedEglContext= */ null);
}
/** Sets the {@link GlObjectsProvider}. */
public void setGlObjectsProvider(GlObjectsProvider glObjectsProvider) {
checkState(!isConfigured());
this.glObjectsProvider = glObjectsProvider;
}
/** Returns whether the instance has been {@linkplain #ensureConfigured configured}. */
@ -80,15 +70,16 @@ import java.util.Queue;
*
* <p>Reconfigures backing textures as needed.
*/
public void ensureConfigured(int width, int height) throws GlUtil.GlException {
public void ensureConfigured(GlObjectsProvider glObjectsProvider, int width, int height)
throws GlUtil.GlException {
if (!isConfigured()) {
createTextures(width, height);
createTextures(glObjectsProvider, width, height);
return;
}
GlTextureInfo texture = getIteratorToAllTextures().next();
if (texture.getWidth() != width || texture.getHeight() != height) {
deleteAllTextures();
createTextures(width, height);
createTextures(glObjectsProvider, width, height);
}
}
@ -147,7 +138,8 @@ import java.util.Queue;
inUseTextures.clear();
}
private void createTextures(int width, int height) throws GlUtil.GlException {
private void createTextures(GlObjectsProvider glObjectsProvider, int width, int height)
throws GlUtil.GlException {
checkState(freeTextures.isEmpty());
checkState(inUseTextures.isEmpty());
for (int i = 0; i < capacity; i++) {

View File

@ -72,21 +72,16 @@ import java.util.concurrent.Executor;
}
@Override
public void setGlObjectsProvider(GlObjectsProvider glObjectsProvider) {
copyGlShaderProgram.setGlObjectsProvider(glObjectsProvider);
wrappedGlShaderProgram.setGlObjectsProvider(glObjectsProvider);
}
@Override
public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) {
public void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
// TODO(b/277726418) Properly report shader program capacity when switching from wrapped shader
// program to copying shader program.
if (presentationTimeUs >= startTimeUs && presentationTimeUs <= endTimeUs) {
pendingWrappedGlShaderProgramFrames++;
wrappedGlShaderProgram.queueInputFrame(inputTexture, presentationTimeUs);
wrappedGlShaderProgram.queueInputFrame(glObjectsProvider, inputTexture, presentationTimeUs);
} else {
pendingCopyGlShaderProgramFrames++;
copyGlShaderProgram.queueInputFrame(inputTexture, presentationTimeUs);
copyGlShaderProgram.queueInputFrame(glObjectsProvider, inputTexture, presentationTimeUs);
}
}

View File

@ -19,6 +19,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import androidx.media3.common.C;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.Util;
@ -37,10 +38,12 @@ public final class ChainingGlShaderProgramListenerTest {
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor =
new VideoFrameProcessingTaskExecutor(
Util.newSingleThreadExecutor("Test"), mockFrameProcessorListener);
private final GlObjectsProvider mockGlObjectsProvider = mock(GlObjectsProvider.class);
private final GlShaderProgram mockProducingGlShaderProgram = mock(GlShaderProgram.class);
private final GlShaderProgram mockConsumingGlShaderProgram = mock(GlShaderProgram.class);
private final ChainingGlShaderProgramListener chainingGlShaderProgramListener =
new ChainingGlShaderProgramListener(
mockGlObjectsProvider,
mockProducingGlShaderProgram,
mockConsumingGlShaderProgram,
videoFrameProcessingTaskExecutor);
@ -83,7 +86,8 @@ public final class ChainingGlShaderProgramListenerTest {
chainingGlShaderProgramListener.onOutputFrameAvailable(texture, presentationTimeUs);
Thread.sleep(EXECUTOR_WAIT_TIME_MS);
verify(mockConsumingGlShaderProgram).queueInputFrame(texture, presentationTimeUs);
verify(mockConsumingGlShaderProgram)
.queueInputFrame(mockGlObjectsProvider, texture, presentationTimeUs);
}
@Test
@ -102,7 +106,8 @@ public final class ChainingGlShaderProgramListenerTest {
chainingGlShaderProgramListener.onReadyToAcceptInputFrame();
Thread.sleep(EXECUTOR_WAIT_TIME_MS);
verify(mockConsumingGlShaderProgram).queueInputFrame(texture, presentationTimeUs);
verify(mockConsumingGlShaderProgram)
.queueInputFrame(mockGlObjectsProvider, texture, presentationTimeUs);
}
@Test
@ -131,8 +136,10 @@ public final class ChainingGlShaderProgramListenerTest {
chainingGlShaderProgramListener.onReadyToAcceptInputFrame();
Thread.sleep(EXECUTOR_WAIT_TIME_MS);
verify(mockConsumingGlShaderProgram).queueInputFrame(firstTexture, firstPresentationTimeUs);
verify(mockConsumingGlShaderProgram).queueInputFrame(secondTexture, secondPresentationTimeUs);
verify(mockConsumingGlShaderProgram)
.queueInputFrame(mockGlObjectsProvider, firstTexture, firstPresentationTimeUs);
verify(mockConsumingGlShaderProgram)
.queueInputFrame(mockGlObjectsProvider, secondTexture, secondPresentationTimeUs);
}
@Test