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()