Mark methods needing to be called on GL thread

Also remove @WorkerThread annotations, as static checks associated with
this annotation aren't useful in this part of the codebase because
almost no methods are called on the main thread.

This change should be a no-op.

PiperOrigin-RevId: 512060367
This commit is contained in:
andrewlewis 2023-02-24 14:56:54 +00:00 committed by tonihei
parent 51f8d103dc
commit 6566237153
5 changed files with 271 additions and 279 deletions

View file

@ -17,7 +17,6 @@
package com.google.android.exoplayer2.effect;
import android.content.Context;
import androidx.annotation.WorkerThread;
import com.google.android.exoplayer2.util.GlUtil;
import com.google.android.exoplayer2.util.VideoFrameProcessingException;
@ -39,9 +38,7 @@ public interface ColorLut extends GlEffect {
/** Releases the OpenGL texture of the LUT. */
void release() throws GlUtil.GlException;
/** This method must be executed on the same thread as other GL commands. */
@Override
@WorkerThread
default SingleFrameGlShaderProgram toGlShaderProgram(Context context, boolean useHdr)
throws VideoFrameProcessingException {
return new ColorLutShaderProgram(context, /* colorLut= */ this, useHdr);

View file

@ -31,7 +31,6 @@ import android.opengl.GLES30;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.DebugViewProvider;
import com.google.android.exoplayer2.util.Effect;
@ -151,206 +150,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
}
}
/**
* Creates the OpenGL context, surfaces, textures, and frame buffers, initializes {@link
* GlShaderProgram} instances corresponding to the {@link GlEffect} instances, and returns a new
* {@code DefaultVideoFrameProcessor}.
*
* <p>All {@link Effect} instances must be {@link GlEffect} instances.
*
* <p>This method must be executed using the {@code singleThreadExecutorService}, as later OpenGL
* commands will be called on that thread.
*/
@WorkerThread
private static DefaultVideoFrameProcessor createOpenGlObjectsAndFrameProcessor(
Context context,
List<Effect> effects,
DebugViewProvider debugViewProvider,
ColorInfo inputColorInfo,
ColorInfo outputColorInfo,
boolean isInputTextureExternal,
boolean releaseFramesAutomatically,
ExecutorService singleThreadExecutorService,
Executor executor,
Listener listener)
throws GlUtil.GlException, VideoFrameProcessingException {
checkState(Thread.currentThread().getName().equals(THREAD_NAME));
// TODO(b/237674316): Delay initialization of things requiring the colorInfo, to
// configure based on the color info from the decoder output media format instead.
EGLDisplay eglDisplay = GlUtil.createEglDisplay();
int[] configAttributes =
ColorInfo.isTransferHdr(outputColorInfo)
? GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_1010102
: GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_8888;
int openGlVersion =
ColorInfo.isTransferHdr(inputColorInfo) || ColorInfo.isTransferHdr(outputColorInfo) ? 3 : 2;
EGLContext eglContext = GlUtil.createEglContext(eglDisplay, openGlVersion, configAttributes);
GlUtil.createFocusedPlaceholderEglSurface(eglContext, eglDisplay, configAttributes);
// Not releaseFramesAutomatically means outputting to a display surface. HDR display surfaces
// require the BT2020 PQ GL extension.
if (!releaseFramesAutomatically && ColorInfo.isTransferHdr(outputColorInfo)) {
// Display hardware supports PQ only.
checkArgument(outputColorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084);
if (Util.SDK_INT < 33 || !GlUtil.isBt2020PqExtensionSupported()) {
GlUtil.destroyEglContext(eglDisplay, eglContext);
// On API<33, the system cannot display PQ content correctly regardless of whether BT2020 PQ
// GL extension is supported.
throw new VideoFrameProcessingException("BT.2020 PQ OpenGL output isn't supported.");
}
}
ImmutableList<GlShaderProgram> shaderPrograms =
getGlShaderProgramsForGlEffects(
context,
effects,
eglDisplay,
eglContext,
debugViewProvider,
inputColorInfo,
outputColorInfo,
isInputTextureExternal,
releaseFramesAutomatically,
executor,
listener);
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor =
new VideoFrameProcessingTaskExecutor(singleThreadExecutorService, listener);
chainShaderProgramsWithListeners(
shaderPrograms, videoFrameProcessingTaskExecutor, listener, executor);
return new DefaultVideoFrameProcessor(
eglDisplay,
eglContext,
isInputTextureExternal,
videoFrameProcessingTaskExecutor,
shaderPrograms,
releaseFramesAutomatically);
}
/**
* Combines consecutive {@link GlMatrixTransformation} and {@link RgbMatrix} instances into a
* single {@link DefaultShaderProgram} and converts all other {@link GlEffect} instances to
* separate {@link GlShaderProgram} instances.
*
* <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
* first is an {@link ExternalShaderProgram} and the last is a {@link
* FinalShaderProgramWrapper}.
*/
private static ImmutableList<GlShaderProgram> getGlShaderProgramsForGlEffects(
Context context,
List<Effect> effects,
EGLDisplay eglDisplay,
EGLContext eglContext,
DebugViewProvider debugViewProvider,
ColorInfo inputColorInfo,
ColorInfo outputColorInfo,
boolean isInputTextureExternal,
boolean releaseFramesAutomatically,
Executor executor,
Listener listener)
throws VideoFrameProcessingException {
ImmutableList.Builder<GlShaderProgram> shaderProgramListBuilder = new ImmutableList.Builder<>();
ImmutableList.Builder<GlMatrixTransformation> matrixTransformationListBuilder =
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++) {
Effect effect = effects.get(i);
checkArgument(
effect instanceof GlEffect, "DefaultVideoFrameProcessor only supports GlEffects");
GlEffect glEffect = (GlEffect) effect;
// The following logic may change the order of the RgbMatrix and GlMatrixTransformation
// effects. This does not influence the output since RgbMatrix only changes the individual
// pixels and does not take any location in account, which the GlMatrixTransformation
// may change.
if (glEffect instanceof GlMatrixTransformation) {
matrixTransformationListBuilder.add((GlMatrixTransformation) glEffect);
continue;
}
if (glEffect instanceof RgbMatrix) {
rgbMatrixListBuilder.add((RgbMatrix) glEffect);
continue;
}
ImmutableList<GlMatrixTransformation> matrixTransformations =
matrixTransformationListBuilder.build();
ImmutableList<RgbMatrix> rgbMatrices = rgbMatrixListBuilder.build();
boolean isOutputTransferHdr = ColorInfo.isTransferHdr(outputColorInfo);
if (!matrixTransformations.isEmpty() || !rgbMatrices.isEmpty() || sampleFromInputTexture) {
DefaultShaderProgram defaultShaderProgram;
if (sampleFromInputTexture) {
if (isInputTextureExternal) {
defaultShaderProgram =
DefaultShaderProgram.createWithExternalSampler(
context, matrixTransformations, rgbMatrices, inputColorInfo, linearColorInfo);
} else {
defaultShaderProgram =
DefaultShaderProgram.createWithInternalSampler(
context, matrixTransformations, rgbMatrices, inputColorInfo, linearColorInfo);
}
} else {
defaultShaderProgram =
DefaultShaderProgram.create(
context, matrixTransformations, rgbMatrices, isOutputTransferHdr);
}
shaderProgramListBuilder.add(defaultShaderProgram);
matrixTransformationListBuilder = new ImmutableList.Builder<>();
rgbMatrixListBuilder = new ImmutableList.Builder<>();
sampleFromInputTexture = false;
}
shaderProgramListBuilder.add(glEffect.toGlShaderProgram(context, isOutputTransferHdr));
}
shaderProgramListBuilder.add(
new FinalShaderProgramWrapper(
context,
eglDisplay,
eglContext,
matrixTransformationListBuilder.build(),
rgbMatrixListBuilder.build(),
debugViewProvider,
/* inputColorInfo= */ sampleFromInputTexture ? inputColorInfo : linearColorInfo,
outputColorInfo,
sampleFromInputTexture,
isInputTextureExternal,
releaseFramesAutomatically,
executor,
listener));
return shaderProgramListBuilder.build();
}
/**
* Chains the given {@link GlShaderProgram} instances using {@link
* ChainingGlShaderProgramListener} instances.
*/
private static void chainShaderProgramsWithListeners(
ImmutableList<GlShaderProgram> shaderPrograms,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Listener videoFrameProcessorListener,
Executor videoFrameProcessorListenerExecutor) {
for (int i = 0; i < shaderPrograms.size() - 1; i++) {
GlShaderProgram producingGlShaderProgram = shaderPrograms.get(i);
GlShaderProgram consumingGlShaderProgram = shaderPrograms.get(i + 1);
ChainingGlShaderProgramListener chainingGlShaderProgramListener =
new ChainingGlShaderProgramListener(
producingGlShaderProgram, consumingGlShaderProgram, videoFrameProcessingTaskExecutor);
producingGlShaderProgram.setOutputListener(chainingGlShaderProgramListener);
producingGlShaderProgram.setErrorListener(
videoFrameProcessorListenerExecutor, videoFrameProcessorListener::onError);
consumingGlShaderProgram.setInputListener(chainingGlShaderProgramListener);
}
}
private static final String TAG = "DefaultFrameProcessor";
private static final String THREAD_NAME = "Effect:GlThread";
private static final long RELEASE_WAIT_TIME_MS = 100;
@ -543,12 +343,210 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
}
}
// Methods that must be called on the GL thread.
/**
* Creates the OpenGL context, surfaces, textures, and frame buffers, initializes {@link
* GlShaderProgram} instances corresponding to the {@link GlEffect} instances, and returns a new
* {@code DefaultVideoFrameProcessor}.
*
* <p>All {@link Effect} instances must be {@link GlEffect} instances.
*
* <p>This method must be executed using the {@code singleThreadExecutorService}, as later OpenGL
* commands will be called on that thread.
*/
private static DefaultVideoFrameProcessor createOpenGlObjectsAndFrameProcessor(
Context context,
List<Effect> effects,
DebugViewProvider debugViewProvider,
ColorInfo inputColorInfo,
ColorInfo outputColorInfo,
boolean isInputTextureExternal,
boolean releaseFramesAutomatically,
ExecutorService singleThreadExecutorService,
Executor executor,
Listener listener)
throws GlUtil.GlException, VideoFrameProcessingException {
checkState(Thread.currentThread().getName().equals(THREAD_NAME));
// TODO(b/237674316): Delay initialization of things requiring the colorInfo, to
// configure based on the color info from the decoder output media format instead.
EGLDisplay eglDisplay = GlUtil.createEglDisplay();
int[] configAttributes =
ColorInfo.isTransferHdr(outputColorInfo)
? GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_1010102
: GlUtil.EGL_CONFIG_ATTRIBUTES_RGBA_8888;
int openGlVersion =
ColorInfo.isTransferHdr(inputColorInfo) || ColorInfo.isTransferHdr(outputColorInfo) ? 3 : 2;
EGLContext eglContext = GlUtil.createEglContext(eglDisplay, openGlVersion, configAttributes);
GlUtil.createFocusedPlaceholderEglSurface(eglContext, eglDisplay, configAttributes);
// Not releaseFramesAutomatically means outputting to a display surface. HDR display surfaces
// require the BT2020 PQ GL extension.
if (!releaseFramesAutomatically && ColorInfo.isTransferHdr(outputColorInfo)) {
// Display hardware supports PQ only.
checkArgument(outputColorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084);
if (Util.SDK_INT < 33 || !GlUtil.isBt2020PqExtensionSupported()) {
GlUtil.destroyEglContext(eglDisplay, eglContext);
// On API<33, the system cannot display PQ content correctly regardless of whether BT2020 PQ
// GL extension is supported.
throw new VideoFrameProcessingException("BT.2020 PQ OpenGL output isn't supported.");
}
}
ImmutableList<GlShaderProgram> shaderPrograms =
getGlShaderProgramsForGlEffects(
context,
effects,
eglDisplay,
eglContext,
debugViewProvider,
inputColorInfo,
outputColorInfo,
isInputTextureExternal,
releaseFramesAutomatically,
executor,
listener);
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor =
new VideoFrameProcessingTaskExecutor(singleThreadExecutorService, listener);
chainShaderProgramsWithListeners(
shaderPrograms, videoFrameProcessingTaskExecutor, listener, executor);
return new DefaultVideoFrameProcessor(
eglDisplay,
eglContext,
isInputTextureExternal,
videoFrameProcessingTaskExecutor,
shaderPrograms,
releaseFramesAutomatically);
}
/**
* Combines consecutive {@link GlMatrixTransformation} and {@link RgbMatrix} instances into a
* single {@link DefaultShaderProgram} and converts all other {@link GlEffect} instances to
* separate {@link GlShaderProgram} instances.
*
* <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
* first is an {@link ExternalShaderProgram} and the last is a {@link
* FinalShaderProgramWrapper}.
*/
private static ImmutableList<GlShaderProgram> getGlShaderProgramsForGlEffects(
Context context,
List<Effect> effects,
EGLDisplay eglDisplay,
EGLContext eglContext,
DebugViewProvider debugViewProvider,
ColorInfo inputColorInfo,
ColorInfo outputColorInfo,
boolean isInputTextureExternal,
boolean releaseFramesAutomatically,
Executor executor,
Listener listener)
throws VideoFrameProcessingException {
ImmutableList.Builder<GlShaderProgram> shaderProgramListBuilder = new ImmutableList.Builder<>();
ImmutableList.Builder<GlMatrixTransformation> matrixTransformationListBuilder =
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++) {
Effect effect = effects.get(i);
checkArgument(
effect instanceof GlEffect, "DefaultVideoFrameProcessor only supports GlEffects");
GlEffect glEffect = (GlEffect) effect;
// The following logic may change the order of the RgbMatrix and GlMatrixTransformation
// effects. This does not influence the output since RgbMatrix only changes the individual
// pixels and does not take any location in account, which the GlMatrixTransformation
// may change.
if (glEffect instanceof GlMatrixTransformation) {
matrixTransformationListBuilder.add((GlMatrixTransformation) glEffect);
continue;
}
if (glEffect instanceof RgbMatrix) {
rgbMatrixListBuilder.add((RgbMatrix) glEffect);
continue;
}
ImmutableList<GlMatrixTransformation> matrixTransformations =
matrixTransformationListBuilder.build();
ImmutableList<RgbMatrix> rgbMatrices = rgbMatrixListBuilder.build();
boolean isOutputTransferHdr = ColorInfo.isTransferHdr(outputColorInfo);
if (!matrixTransformations.isEmpty() || !rgbMatrices.isEmpty() || sampleFromInputTexture) {
DefaultShaderProgram defaultShaderProgram;
if (sampleFromInputTexture) {
if (isInputTextureExternal) {
defaultShaderProgram =
DefaultShaderProgram.createWithExternalSampler(
context, matrixTransformations, rgbMatrices, inputColorInfo, linearColorInfo);
} else {
defaultShaderProgram =
DefaultShaderProgram.createWithInternalSampler(
context, matrixTransformations, rgbMatrices, inputColorInfo, linearColorInfo);
}
} else {
defaultShaderProgram =
DefaultShaderProgram.create(
context, matrixTransformations, rgbMatrices, isOutputTransferHdr);
}
shaderProgramListBuilder.add(defaultShaderProgram);
matrixTransformationListBuilder = new ImmutableList.Builder<>();
rgbMatrixListBuilder = new ImmutableList.Builder<>();
sampleFromInputTexture = false;
}
shaderProgramListBuilder.add(glEffect.toGlShaderProgram(context, isOutputTransferHdr));
}
shaderProgramListBuilder.add(
new FinalShaderProgramWrapper(
context,
eglDisplay,
eglContext,
matrixTransformationListBuilder.build(),
rgbMatrixListBuilder.build(),
debugViewProvider,
/* inputColorInfo= */ sampleFromInputTexture ? inputColorInfo : linearColorInfo,
outputColorInfo,
sampleFromInputTexture,
isInputTextureExternal,
releaseFramesAutomatically,
executor,
listener));
return shaderProgramListBuilder.build();
}
/**
* Chains the given {@link GlShaderProgram} instances using {@link
* ChainingGlShaderProgramListener} instances.
*/
private static void chainShaderProgramsWithListeners(
ImmutableList<GlShaderProgram> shaderPrograms,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Listener videoFrameProcessorListener,
Executor videoFrameProcessorListenerExecutor) {
for (int i = 0; i < shaderPrograms.size() - 1; i++) {
GlShaderProgram producingGlShaderProgram = shaderPrograms.get(i);
GlShaderProgram consumingGlShaderProgram = shaderPrograms.get(i + 1);
ChainingGlShaderProgramListener chainingGlShaderProgramListener =
new ChainingGlShaderProgramListener(
producingGlShaderProgram, consumingGlShaderProgram, videoFrameProcessingTaskExecutor);
producingGlShaderProgram.setOutputListener(chainingGlShaderProgramListener);
producingGlShaderProgram.setErrorListener(
videoFrameProcessorListenerExecutor, videoFrameProcessorListener::onError);
consumingGlShaderProgram.setInputListener(chainingGlShaderProgramListener);
}
}
/**
* Releases the {@link GlShaderProgram} instances and destroys the OpenGL context.
*
* <p>This method must be called on the {@linkplain #THREAD_NAME background thread}.
*/
@WorkerThread
private void releaseShaderProgramsAndDestroyGlContext() {
try {
for (int i = 0; i < allShaderPrograms.size(); i++) {

View file

@ -20,7 +20,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.graphics.SurfaceTexture;
import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.effect.GlShaderProgram.InputListener;
import com.google.android.exoplayer2.util.FrameInfo;
@ -186,7 +185,15 @@ import java.util.concurrent.atomic.AtomicInteger;
surface.release();
}
@WorkerThread
private void maybeExecuteAfterFlushTask() {
if (onFlushCompleteTask == null || numberOfFramesToDropOnBecomingAvailable > 0) {
return;
}
videoFrameProcessingTaskExecutor.submitWithHighPriority(onFlushCompleteTask);
}
// Methods that must be called on the GL thread.
private void flush() {
// A frame that is registered before flush may arrive after flush.
numberOfFramesToDropOnBecomingAvailable = pendingFrames.size() - availableFrameCount;
@ -200,14 +207,6 @@ import java.util.concurrent.atomic.AtomicInteger;
maybeExecuteAfterFlushTask();
}
private void maybeExecuteAfterFlushTask() {
if (onFlushCompleteTask == null || numberOfFramesToDropOnBecomingAvailable > 0) {
return;
}
videoFrameProcessingTaskExecutor.submitWithHighPriority(onFlushCompleteTask);
}
@WorkerThread
private void maybeQueueFrameToExternalShaderProgram() {
if (externalShaderProgramInputCapacity.get() == 0
|| availableFrameCount == 0

View file

@ -32,7 +32,6 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.DebugViewProvider;
import com.google.android.exoplayer2.util.GlUtil;
@ -152,6 +151,30 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
throw new UnsupportedOperationException();
}
@Override
public void signalEndOfCurrentInputStream() {
checkState(!streamOffsetUsQueue.isEmpty(), "No input stream to end.");
streamOffsetUsQueue.remove();
if (streamOffsetUsQueue.isEmpty()) {
videoFrameProcessorListenerExecutor.execute(videoFrameProcessorListener::onEnded);
}
}
/**
* Signals that there will be another input stream after all previously appended input streams
* have {@linkplain #signalEndOfCurrentInputStream() ended}.
*
* <p>This method does not need to be called on the GL thread, but the caller must ensure that
* stream offsets are appended in the correct order.
*
* @param streamOffsetUs The presentation timestamp offset, in microseconds.
*/
public void appendStream(long streamOffsetUs) {
streamOffsetUsQueue.add(streamOffsetUs);
}
// Methods that must be called on the GL thread.
@Override
public void queueInputFrame(TextureInfo inputTexture, long presentationTimeUs) {
long streamOffsetUs =
@ -174,7 +197,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
throw new UnsupportedOperationException();
}
@WorkerThread
public void releaseOutputFrame(long releaseTimeNs) {
checkState(!releaseFramesAutomatically);
Pair<TextureInfo, Long> oldestAvailableFrame = availableFrames.remove();
@ -184,15 +206,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
releaseTimeNs);
}
@Override
public void signalEndOfCurrentInputStream() {
checkState(!streamOffsetUsQueue.isEmpty(), "No input stream to end.");
streamOffsetUsQueue.remove();
if (streamOffsetUsQueue.isEmpty()) {
videoFrameProcessorListenerExecutor.execute(videoFrameProcessorListener::onEnded);
}
}
@Override
public void flush() {
// Drops all frames that aren't released yet.
@ -204,19 +217,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
inputListener.onReadyToAcceptInputFrame();
}
@Override
@WorkerThread
public synchronized void release() throws VideoFrameProcessingException {
if (defaultShaderProgram != null) {
defaultShaderProgram.release();
}
try {
GlUtil.destroyEglSurface(eglDisplay, outputEglSurface);
} catch (GlUtil.GlException e) {
throw new VideoFrameProcessingException(e);
}
}
@Override
public void setTextureTransformMatrix(float[] textureTransformMatrix) {
System.arraycopy(
@ -231,17 +231,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
/**
* Signals that there will be another input stream after all previously appended input streams
* have {@linkplain #signalEndOfCurrentInputStream() ended}.
*
* <p>This method does not need to be called on the GL thread, but the caller must ensure that
* stream offsets are appended in the correct order.
*
* @param streamOffsetUs The presentation timestamp offset, in microseconds.
*/
public void appendStream(long streamOffsetUs) {
streamOffsetUsQueue.add(streamOffsetUs);
@Override
public synchronized void release() throws VideoFrameProcessingException {
if (defaultShaderProgram != null) {
defaultShaderProgram.release();
}
try {
GlUtil.destroyEglSurface(eglDisplay, outputEglSurface);
} catch (GlUtil.GlException e) {
throw new VideoFrameProcessingException(e);
}
}
/**
@ -495,31 +494,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
height = surfaceView.getHeight();
}
/**
* Focuses the wrapped surface view's surface as an {@link EGLSurface}, renders using {@code
* renderingTask} and swaps buffers, if the view's holder has a valid surface. Does nothing
* otherwise.
*/
@WorkerThread
public synchronized void maybeRenderToSurfaceView(VideoFrameProcessingTask renderingTask)
throws GlUtil.GlException, VideoFrameProcessingException {
if (surface == null) {
return;
}
if (eglSurface == null) {
eglSurface =
GlUtil.createEglSurface(
eglDisplay, surface, outputColorTransfer, /* isEncoderInputSurface= */ false);
}
EGLSurface eglSurface = this.eglSurface;
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, width, height);
renderingTask.run();
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
// Prevents white flashing on the debug SurfaceView when frames are rendered too fast.
GLES20.glFinish();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {}
@ -542,5 +516,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
width = C.LENGTH_UNSET;
height = C.LENGTH_UNSET;
}
/**
* Focuses the wrapped surface view's surface as an {@link EGLSurface}, renders using {@code
* renderingTask} and swaps buffers, if the view's holder has a valid surface. Does nothing
* otherwise.
*
* <p>Must be called on the GL thread.
*/
public synchronized void maybeRenderToSurfaceView(VideoFrameProcessingTask renderingTask)
throws GlUtil.GlException, VideoFrameProcessingException {
if (surface == null) {
return;
}
if (eglSurface == null) {
eglSurface =
GlUtil.createEglSurface(
eglDisplay, surface, outputColorTransfer, /* isEncoderInputSurface= */ false);
}
EGLSurface eglSurface = this.eglSurface;
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, width, height);
renderingTask.run();
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
// Prevents white flashing on the debug SurfaceView when frames are rendered too fast.
GLES20.glFinish();
}
}
}

View file

@ -21,7 +21,6 @@ import static java.lang.Math.floor;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import androidx.annotation.WorkerThread;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.GlUtil;
import com.google.android.exoplayer2.util.VideoFrameProcessingException;
@ -34,7 +33,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* Forwards a video frame produced from a {@link Bitmap} to a {@link GlShaderProgram} for
* consumption.
*
* <p>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 InternalTextureManager implements GlShaderProgram.InputListener {
private final GlShaderProgram shaderProgram;
@ -99,7 +98,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
});
}
@WorkerThread
// Methods that must be called on the GL thread.
private void setupBitmap(Bitmap bitmap, long durationUs, float frameRate, boolean useHdr)
throws VideoFrameProcessingException {
this.useHdr = useHdr;
@ -113,7 +113,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
maybeQueueToShaderProgram();
}
@WorkerThread
private void maybeQueueToShaderProgram() throws VideoFrameProcessingException {
if (pendingBitmaps.isEmpty() || downstreamShaderProgramCapacity == 0) {
return;
@ -156,7 +155,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
}
@WorkerThread
private void maybeSignalEndOfOutput() {
if (framesToQueueForCurrentBitmap == 0
&& pendingBitmaps.isEmpty()