mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
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:
parent
51f8d103dc
commit
6566237153
5 changed files with 271 additions and 279 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue