diff --git a/library/effect/src/main/java/com/google/android/exoplayer2/effect/ColorLut.java b/library/effect/src/main/java/com/google/android/exoplayer2/effect/ColorLut.java
index 411307b35e..61dd6b1a9c 100644
--- a/library/effect/src/main/java/com/google/android/exoplayer2/effect/ColorLut.java
+++ b/library/effect/src/main/java/com/google/android/exoplayer2/effect/ColorLut.java
@@ -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);
diff --git a/library/effect/src/main/java/com/google/android/exoplayer2/effect/DefaultVideoFrameProcessor.java b/library/effect/src/main/java/com/google/android/exoplayer2/effect/DefaultVideoFrameProcessor.java
index 1cc06268fb..c3530e179b 100644
--- a/library/effect/src/main/java/com/google/android/exoplayer2/effect/DefaultVideoFrameProcessor.java
+++ b/library/effect/src/main/java/com/google/android/exoplayer2/effect/DefaultVideoFrameProcessor.java
@@ -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}.
- *
- *
All {@link Effect} instances must be {@link GlEffect} instances.
- *
- *
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 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 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.
- *
- * 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 getGlShaderProgramsForGlEffects(
- Context context,
- List effects,
- EGLDisplay eglDisplay,
- EGLContext eglContext,
- DebugViewProvider debugViewProvider,
- ColorInfo inputColorInfo,
- ColorInfo outputColorInfo,
- boolean isInputTextureExternal,
- boolean releaseFramesAutomatically,
- Executor executor,
- Listener listener)
- throws VideoFrameProcessingException {
- ImmutableList.Builder shaderProgramListBuilder = new ImmutableList.Builder<>();
- ImmutableList.Builder matrixTransformationListBuilder =
- new ImmutableList.Builder<>();
- ImmutableList.Builder 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 matrixTransformations =
- matrixTransformationListBuilder.build();
- ImmutableList 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 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}.
+ *
+ * All {@link Effect} instances must be {@link GlEffect} instances.
+ *
+ *
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 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 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.
+ *
+ * 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 getGlShaderProgramsForGlEffects(
+ Context context,
+ List effects,
+ EGLDisplay eglDisplay,
+ EGLContext eglContext,
+ DebugViewProvider debugViewProvider,
+ ColorInfo inputColorInfo,
+ ColorInfo outputColorInfo,
+ boolean isInputTextureExternal,
+ boolean releaseFramesAutomatically,
+ Executor executor,
+ Listener listener)
+ throws VideoFrameProcessingException {
+ ImmutableList.Builder shaderProgramListBuilder = new ImmutableList.Builder<>();
+ ImmutableList.Builder matrixTransformationListBuilder =
+ new ImmutableList.Builder<>();
+ ImmutableList.Builder 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 matrixTransformations =
+ matrixTransformationListBuilder.build();
+ ImmutableList 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 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.
*
* 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++) {
diff --git a/library/effect/src/main/java/com/google/android/exoplayer2/effect/ExternalTextureManager.java b/library/effect/src/main/java/com/google/android/exoplayer2/effect/ExternalTextureManager.java
index 978aaec52e..6526ad0abe 100644
--- a/library/effect/src/main/java/com/google/android/exoplayer2/effect/ExternalTextureManager.java
+++ b/library/effect/src/main/java/com/google/android/exoplayer2/effect/ExternalTextureManager.java
@@ -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
diff --git a/library/effect/src/main/java/com/google/android/exoplayer2/effect/FinalShaderProgramWrapper.java b/library/effect/src/main/java/com/google/android/exoplayer2/effect/FinalShaderProgramWrapper.java
index f1aa9246a0..5264cd96ce 100644
--- a/library/effect/src/main/java/com/google/android/exoplayer2/effect/FinalShaderProgramWrapper.java
+++ b/library/effect/src/main/java/com/google/android/exoplayer2/effect/FinalShaderProgramWrapper.java
@@ -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}.
+ *
+ *
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 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}.
- *
- * 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.
+ *
+ *
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();
+ }
}
}
diff --git a/library/effect/src/main/java/com/google/android/exoplayer2/effect/InternalTextureManager.java b/library/effect/src/main/java/com/google/android/exoplayer2/effect/InternalTextureManager.java
index 34d2ffec96..d5f5ce8dd2 100644
--- a/library/effect/src/main/java/com/google/android/exoplayer2/effect/InternalTextureManager.java
+++ b/library/effect/src/main/java/com/google/android/exoplayer2/effect/InternalTextureManager.java
@@ -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.
*
- *
Methods in this class can be called from any thread.
+ *
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()