PiperOrigin-RevId: 722585306
This commit is contained in:
Googler 2025-02-03 03:15:21 -08:00 committed by Copybara-Service
parent 82cb1d8ac7
commit 4ed9abd05b
7 changed files with 162 additions and 379 deletions

View File

@ -61,7 +61,6 @@ import androidx.media3.datasource.DataSourceBitmapLoader;
import androidx.media3.effect.BitmapOverlay;
import androidx.media3.effect.Contrast;
import androidx.media3.effect.DebugTraceUtil;
import androidx.media3.effect.DebugViewEffect;
import androidx.media3.effect.DrawableOverlay;
import androidx.media3.effect.GlEffect;
import androidx.media3.effect.GlShaderProgram;
@ -312,6 +311,10 @@ public final class TransformerActivity extends AppCompatActivity {
transformerBuilder.setMuxerFactory(new InAppFragmentedMp4Muxer.Factory());
}
if (bundle.getBoolean(ConfigurationActivity.ENABLE_DEBUG_PREVIEW)) {
transformerBuilder.setDebugViewProvider(new DemoDebugViewProvider());
}
if (bundle.getBoolean(ConfigurationActivity.ENABLE_ANALYZER_MODE)) {
return ExperimentalAnalyzerModeFactory.buildAnalyzer(
this.getApplicationContext(), transformerBuilder.build());
@ -562,10 +565,6 @@ public final class TransformerActivity extends AppCompatActivity {
effects.add(Presentation.createForHeight(resolutionHeight));
}
if (bundle.getBoolean(ConfigurationActivity.ENABLE_DEBUG_PREVIEW)) {
effects.add(new DebugViewEffect(new DemoDebugViewProvider()));
}
return effects.build();
}

View File

@ -1,53 +0,0 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.effect;
import android.content.Context;
import android.view.SurfaceView;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.util.UnstableApi;
/** {@link GlEffect} that renders to a {@link SurfaceView} provided by {@link DebugViewProvider}. */
@UnstableApi
public final class DebugViewEffect implements GlEffect {
private final DebugViewProvider debugViewProvider;
/**
* Creates a new instance.
*
* @param debugViewProvider The class that provides the {@link SurfaceView} that the debug preview
* will be rendered to.
*/
public DebugViewEffect(DebugViewProvider debugViewProvider) {
this.debugViewProvider = debugViewProvider;
}
/**
* Throws {@link UnsupportedOperationException} because {@link #toGlShaderProgram(Context,
* ColorInfo)} should be used instead.
*/
@Override
public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr) {
throw new UnsupportedOperationException();
}
@Override
public GlShaderProgram toGlShaderProgram(Context context, ColorInfo colorInfo) {
return new DebugViewShaderProgram(context, debugViewProvider, colorInfo);
}
}

View File

@ -1,269 +0,0 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.effect;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.GlUtil.getDefaultEglDisplay;
import static androidx.media3.effect.DefaultVideoFrameProcessor.WORKING_COLOR_SPACE_DEFAULT;
import static androidx.media3.effect.DefaultVideoFrameProcessor.WORKING_COLOR_SPACE_LINEAR;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import android.content.Context;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import java.util.Objects;
import java.util.concurrent.Executor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* {@link GlShaderProgram} that renders to a {@link SurfaceView} provided by {@link
* DebugViewProvider}.
*/
@UnstableApi
public final class DebugViewShaderProgram implements GlShaderProgram {
private static final String TAG = "DebugViewShaderProgram";
private final Context context;
private final DebugViewProvider debugViewProvider;
@Nullable private SurfaceView debugSurfaceView;
@Nullable private DefaultShaderProgram defaultShaderProgram;
@Nullable private SurfaceViewWrapper debugSurfaceViewWrapper;
private final ColorInfo colorInfo;
private InputListener inputListener;
private OutputListener outputListener;
private ErrorListener errorListener;
private Executor errorListenerExecutor;
private @MonotonicNonNull EGLDisplay eglDisplay;
public DebugViewShaderProgram(
Context context, DebugViewProvider debugViewProvider, ColorInfo colorInfo) {
this.context = context;
this.debugViewProvider = debugViewProvider;
this.colorInfo = colorInfo;
inputListener = new InputListener() {};
outputListener = new OutputListener() {};
errorListener =
(frameProcessingException) ->
Log.e(TAG, "Exception caught by errorListener.", frameProcessingException);
errorListenerExecutor = directExecutor();
}
@Override
public void setInputListener(InputListener inputListener) {
this.inputListener = inputListener;
inputListener.onReadyToAcceptInputFrame();
}
@Override
public void setOutputListener(OutputListener outputListener) {
this.outputListener = outputListener;
}
@Override
public void setErrorListener(Executor executor, ErrorListener errorListener) {
this.errorListener = errorListener;
this.errorListenerExecutor = executor;
}
@Override
public void queueInputFrame(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
try {
ensureConfigured(inputTexture.width, inputTexture.height);
DefaultShaderProgram defaultShaderProgram = checkNotNull(this.defaultShaderProgram);
checkNotNull(this.debugSurfaceViewWrapper)
.maybeRenderToSurfaceView(
() -> defaultShaderProgram.drawFrame(inputTexture.texId, presentationTimeUs),
glObjectsProvider);
outputListener.onOutputFrameAvailable(inputTexture, presentationTimeUs);
} catch (VideoFrameProcessingException | GlUtil.GlException e) {
errorListenerExecutor.execute(
() -> errorListener.onError(VideoFrameProcessingException.from(e, presentationTimeUs)));
}
}
@Override
public void releaseOutputFrame(GlTextureInfo outputTexture) {
inputListener.onInputFrameProcessed(outputTexture);
inputListener.onReadyToAcceptInputFrame();
}
@Override
public void signalEndOfCurrentInputStream() {
outputListener.onCurrentOutputStreamEnded();
}
@Override
public void flush() {
if (defaultShaderProgram != null) {
defaultShaderProgram.flush();
}
inputListener.onFlush();
inputListener.onReadyToAcceptInputFrame();
}
@Override
public void release() throws VideoFrameProcessingException {
if (defaultShaderProgram != null) {
defaultShaderProgram.release();
}
try {
GlUtil.checkGlError();
} catch (GlUtil.GlException e) {
throw new VideoFrameProcessingException(e);
}
}
private void ensureConfigured(int inputWidth, int inputHeight)
throws VideoFrameProcessingException, GlUtil.GlException {
if (eglDisplay == null) {
eglDisplay = getDefaultEglDisplay();
}
EGLContext eglContext = GlUtil.getCurrentContext();
@Nullable
SurfaceView debugSurfaceView =
debugViewProvider.getDebugPreviewSurfaceView(inputWidth, inputHeight);
if (debugSurfaceView != null && !Objects.equals(this.debugSurfaceView, debugSurfaceView)) {
debugSurfaceViewWrapper =
new SurfaceViewWrapper(eglDisplay, eglContext, debugSurfaceView, colorInfo.colorTransfer);
}
this.debugSurfaceView = debugSurfaceView;
if (defaultShaderProgram == null) {
defaultShaderProgram =
DefaultShaderProgram.createApplyingOetf(
context,
/* matrixTransformations= */ ImmutableList.of(),
/* rgbMatrices= */ ImmutableList.of(),
colorInfo,
colorInfo.colorTransfer == C.COLOR_TRANSFER_LINEAR
? WORKING_COLOR_SPACE_LINEAR
: WORKING_COLOR_SPACE_DEFAULT);
}
}
/**
* Wrapper around a {@link SurfaceView} that keeps track of whether the output surface is valid,
* and makes rendering a no-op if not.
*/
private static final class SurfaceViewWrapper implements SurfaceHolder.Callback {
public final @C.ColorTransfer int outputColorTransfer;
private final EGLDisplay eglDisplay;
private final EGLContext eglContext;
@GuardedBy("this")
@Nullable
private Surface surface;
@GuardedBy("this")
@Nullable
private EGLSurface eglSurface;
private int width;
private int height;
public SurfaceViewWrapper(
EGLDisplay eglDisplay,
EGLContext eglContext,
SurfaceView surfaceView,
@C.ColorTransfer int outputColorTransfer) {
this.eglDisplay = eglDisplay;
this.eglContext = eglContext;
// PQ SurfaceView output is supported from API 33, but HLG output is supported from API 34.
// Therefore, convert HLG to PQ below API 34, so that HLG input can be displayed properly on
// API 33.
this.outputColorTransfer =
outputColorTransfer == C.COLOR_TRANSFER_HLG && Util.SDK_INT < 34
? C.COLOR_TRANSFER_ST2084
: outputColorTransfer;
surfaceView.getHolder().addCallback(this);
surface = surfaceView.getHolder().getSurface();
width = surfaceView.getWidth();
height = surfaceView.getHeight();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {}
@Override
public synchronized void surfaceChanged(
SurfaceHolder holder, int format, int width, int height) {
this.width = width;
this.height = height;
Surface newSurface = holder.getSurface();
if (!newSurface.equals(surface)) {
surface = newSurface;
eglSurface = null;
}
}
@Override
public synchronized void surfaceDestroyed(SurfaceHolder holder) {
surface = null;
eglSurface = null;
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(
VideoFrameProcessingTaskExecutor.Task renderingTask, GlObjectsProvider glObjectsProvider)
throws GlUtil.GlException, VideoFrameProcessingException {
if (surface == null) {
return;
}
if (eglSurface == null) {
eglSurface =
glObjectsProvider.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.
// TODO(b/393316699) - Investigate removing this to speed up transcoding.
GLES20.glFinish();
}
}
}

View File

@ -419,6 +419,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
() ->
createOpenGlObjectsAndFrameProcessor(
context,
debugViewProvider,
outputColorInfo,
sdrWorkingColorSpace,
renderFramesAutomatically,
@ -478,10 +479,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private final List<Effect> activeEffects;
private final Object lock;
/** The {@link ColorInfo} that all {@link Effect effects} work in. */
private final ColorInfo workingColorInfo;
private final ColorInfo outputColorInfo;
private volatile @MonotonicNonNull FrameInfo nextInputFrameInfo;
@ -497,7 +494,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
Executor listenerExecutor,
FinalShaderProgramWrapper finalShaderProgramWrapper,
boolean renderFramesAutomatically,
ColorInfo workingColorInfo,
ColorInfo outputColorInfo) {
this.context = context;
this.glObjectsProvider = glObjectsProvider;
@ -509,7 +505,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
this.renderFramesAutomatically = renderFramesAutomatically;
this.activeEffects = new ArrayList<>();
this.lock = new Object();
this.workingColorInfo = workingColorInfo;
this.outputColorInfo = outputColorInfo;
this.finalShaderProgramWrapper = finalShaderProgramWrapper;
this.intermediateGlShaderPrograms = new ArrayList<>();
@ -822,6 +817,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
*/
private static DefaultVideoFrameProcessor createOpenGlObjectsAndFrameProcessor(
Context context,
DebugViewProvider debugViewProvider,
ColorInfo outputColorInfo,
@WorkingColorSpace int sdrWorkingColorSpace,
boolean renderFramesAutomatically,
@ -849,13 +845,12 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
.setColorTransfer(C.COLOR_TRANSFER_LINEAR)
.setHdrStaticInfo(null)
.build();
ColorInfo intermediateColorInfo;
if (ColorInfo.isTransferHdr(outputColorInfo)
|| sdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR) {
intermediateColorInfo = linearColorInfo;
} else {
intermediateColorInfo = outputColorInfo;
}
ColorInfo intermediateColorInfo =
ColorInfo.isTransferHdr(outputColorInfo)
? linearColorInfo
: sdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR
? linearColorInfo
: outputColorInfo;
InputSwitcher inputSwitcher =
new InputSwitcher(
context,
@ -875,6 +870,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
eglDisplay,
eglContextAndPlaceholderSurface.first,
eglContextAndPlaceholderSurface.second,
debugViewProvider,
outputColorInfo,
videoFrameProcessingTaskExecutor,
videoFrameProcessorListenerExecutor,
@ -894,7 +890,6 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
videoFrameProcessorListenerExecutor,
finalShaderProgramWrapper,
renderFramesAutomatically,
intermediateColorInfo,
outputColorInfo);
}
@ -907,7 +902,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
*
* @param context The {@link Context}.
* @param effects The list of {@link GlEffect effects}.
* @param workingColorInfo The {@link ColorInfo} the {@link List<Effect> effects} work in.
* @param outputColorInfo The {@link ColorInfo} on {@code DefaultVideoFrameProcessor} output.
* @param finalShaderProgramWrapper The {@link FinalShaderProgramWrapper} to apply the {@link
* GlMatrixTransformation GlMatrixTransformations} and {@link RgbMatrix RgbMatrices} after all
* other {@link GlEffect GlEffects}.
@ -916,7 +911,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
private static ImmutableList<GlShaderProgram> createGlShaderPrograms(
Context context,
List<Effect> effects,
ColorInfo workingColorInfo,
ColorInfo outputColorInfo,
FinalShaderProgramWrapper finalShaderProgramWrapper)
throws VideoFrameProcessingException {
ImmutableList.Builder<GlShaderProgram> shaderProgramListBuilder = new ImmutableList.Builder<>();
@ -943,18 +938,16 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
ImmutableList<GlMatrixTransformation> matrixTransformations =
matrixTransformationListBuilder.build();
ImmutableList<RgbMatrix> rgbMatrices = rgbMatrixListBuilder.build();
boolean isOutputTransferHdr = ColorInfo.isTransferHdr(outputColorInfo);
if (!matrixTransformations.isEmpty() || !rgbMatrices.isEmpty()) {
DefaultShaderProgram defaultShaderProgram =
DefaultShaderProgram.create(
context,
matrixTransformations,
rgbMatrices,
ColorInfo.isTransferHdr(workingColorInfo));
context, matrixTransformations, rgbMatrices, isOutputTransferHdr);
shaderProgramListBuilder.add(defaultShaderProgram);
matrixTransformationListBuilder = new ImmutableList.Builder<>();
rgbMatrixListBuilder = new ImmutableList.Builder<>();
}
shaderProgramListBuilder.add(glEffect.toGlShaderProgram(context, workingColorInfo));
shaderProgramListBuilder.add(glEffect.toGlShaderProgram(context, isOutputTransferHdr));
}
finalShaderProgramWrapper.setMatrixTransformations(
@ -1030,7 +1023,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
// FinalShaderProgramWrapper.
intermediateGlShaderPrograms.addAll(
createGlShaderPrograms(
context, inputStreamInfo.effects, workingColorInfo, finalShaderProgramWrapper));
context, inputStreamInfo.effects, outputColorInfo, finalShaderProgramWrapper));
inputSwitcher.setDownstreamShaderProgram(
getFirst(intermediateGlShaderPrograms, /* defaultValue= */ finalShaderProgramWrapper));
chainShaderProgramsWithListeners(

View File

@ -21,6 +21,7 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.effect.DebugTraceUtil.COMPONENT_VFP;
import static androidx.media3.effect.DebugTraceUtil.EVENT_RENDERED_TO_OUTPUT_SURFACE;
import static androidx.media3.effect.DefaultVideoFrameProcessor.WORKING_COLOR_SPACE_LINEAR;
import android.content.Context;
import android.opengl.EGL14;
@ -28,11 +29,16 @@ import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLExt;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.util.Pair;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.SurfaceInfo;
@ -82,6 +88,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final EGLDisplay eglDisplay;
private final EGLContext eglContext;
private final EGLSurface placeholderSurface;
private final DebugViewProvider debugViewProvider;
private final ColorInfo outputColorInfo;
private final VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor;
private final Executor videoFrameProcessorListenerExecutor;
@ -97,6 +104,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private int inputWidth;
private int inputHeight;
@Nullable private DefaultShaderProgram defaultShaderProgram;
@Nullable private SurfaceViewWrapper debugSurfaceViewWrapper;
// Whether the input stream has ended, but not all input has been released. This is relevant only
// when renderFramesAutomatically is false. Ensures all frames are rendered before reporting
// onInputStreamProcessed.
@ -104,6 +112,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private boolean isInputStreamEndedWithPendingAvailableFrames;
private InputListener inputListener;
private @MonotonicNonNull Size outputSizeBeforeSurfaceTransformation;
@Nullable private SurfaceView debugSurfaceView;
@Nullable private OnInputStreamProcessedListener onInputStreamProcessedListener;
private boolean matrixTransformationsChanged;
private boolean outputSurfaceInfoChanged;
@ -117,6 +126,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
EGLDisplay eglDisplay,
EGLContext eglContext,
EGLSurface placeholderSurface,
DebugViewProvider debugViewProvider,
ColorInfo outputColorInfo,
VideoFrameProcessingTaskExecutor videoFrameProcessingTaskExecutor,
Executor videoFrameProcessorListenerExecutor,
@ -131,6 +141,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.eglDisplay = eglDisplay;
this.eglContext = eglContext;
this.placeholderSurface = placeholderSurface;
this.debugViewProvider = debugViewProvider;
this.outputColorInfo = outputColorInfo;
this.videoFrameProcessingTaskExecutor = videoFrameProcessingTaskExecutor;
this.videoFrameProcessorListenerExecutor = videoFrameProcessorListenerExecutor;
@ -411,6 +422,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
videoFrameProcessorListener.onError(
VideoFrameProcessingException.from(e, presentationTimeUs)));
}
if (debugSurfaceViewWrapper != null && defaultShaderProgram != null) {
renderFrameToDebugSurface(glObjectsProvider, inputTexture, presentationTimeUs);
}
inputListener.onInputFrameProcessed(inputTexture);
}
@ -523,6 +537,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputTexturePool.ensureConfigured(glObjectsProvider, outputWidth, outputHeight);
}
@Nullable
SurfaceView debugSurfaceView =
debugViewProvider.getDebugPreviewSurfaceView(outputWidth, outputHeight);
if (debugSurfaceView != null && !Util.areEqual(this.debugSurfaceView, debugSurfaceView)) {
debugSurfaceViewWrapper =
new SurfaceViewWrapper(
eglDisplay, eglContext, debugSurfaceView, outputColorInfo.colorTransfer);
}
this.debugSurfaceView = debugSurfaceView;
if (defaultShaderProgram != null
&& (outputSurfaceInfoChanged || inputSizeChanged || matrixTransformationsChanged)) {
defaultShaderProgram.release();
@ -576,4 +600,123 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
return defaultShaderProgram;
}
private void renderFrameToDebugSurface(
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
DefaultShaderProgram defaultShaderProgram = checkNotNull(this.defaultShaderProgram);
SurfaceViewWrapper debugSurfaceViewWrapper = checkNotNull(this.debugSurfaceViewWrapper);
try {
checkNotNull(debugSurfaceViewWrapper)
.maybeRenderToSurfaceView(
() -> {
GlUtil.clearFocusedBuffers();
if (sdrWorkingColorSpace == WORKING_COLOR_SPACE_LINEAR) {
@C.ColorTransfer
int configuredColorTransfer = defaultShaderProgram.getOutputColorTransfer();
defaultShaderProgram.setOutputColorTransfer(
debugSurfaceViewWrapper.outputColorTransfer);
defaultShaderProgram.drawFrame(inputTexture.texId, presentationTimeUs);
defaultShaderProgram.setOutputColorTransfer(configuredColorTransfer);
} else {
defaultShaderProgram.drawFrame(inputTexture.texId, presentationTimeUs);
}
},
glObjectsProvider);
} catch (VideoFrameProcessingException | GlUtil.GlException e) {
Log.d(TAG, "Error rendering to debug preview", e);
}
}
/**
* Wrapper around a {@link SurfaceView} that keeps track of whether the output surface is valid,
* and makes rendering a no-op if not.
*
* <p>This class should only be used for displaying a debug preview.
*/
private static final class SurfaceViewWrapper implements SurfaceHolder.Callback {
public final @C.ColorTransfer int outputColorTransfer;
private final EGLDisplay eglDisplay;
private final EGLContext eglContext;
@GuardedBy("this")
@Nullable
private Surface surface;
@GuardedBy("this")
@Nullable
private EGLSurface eglSurface;
private int width;
private int height;
public SurfaceViewWrapper(
EGLDisplay eglDisplay,
EGLContext eglContext,
SurfaceView surfaceView,
@C.ColorTransfer int outputColorTransfer) {
this.eglDisplay = eglDisplay;
this.eglContext = eglContext;
// PQ SurfaceView output is supported from API 33, but HLG output is supported from API 34.
// Therefore, convert HLG to PQ below API 34, so that HLG input can be displayed properly on
// API 33.
this.outputColorTransfer =
outputColorTransfer == C.COLOR_TRANSFER_HLG && Util.SDK_INT < 34
? C.COLOR_TRANSFER_ST2084
: outputColorTransfer;
surfaceView.getHolder().addCallback(this);
surface = surfaceView.getHolder().getSurface();
width = surfaceView.getWidth();
height = surfaceView.getHeight();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {}
@Override
public synchronized void surfaceChanged(
SurfaceHolder holder, int format, int width, int height) {
this.width = width;
this.height = height;
Surface newSurface = holder.getSurface();
if (surface == null || !surface.equals(newSurface)) {
surface = newSurface;
eglSurface = null;
}
}
@Override
public synchronized void surfaceDestroyed(SurfaceHolder holder) {
surface = null;
eglSurface = null;
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(
VideoFrameProcessingTaskExecutor.Task renderingTask, GlObjectsProvider glObjectsProvider)
throws GlUtil.GlException, VideoFrameProcessingException {
if (surface == null) {
return;
}
if (eglSurface == null) {
eglSurface =
glObjectsProvider.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

@ -16,7 +16,6 @@
package androidx.media3.effect;
import android.content.Context;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Effect;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.UnstableApi;
@ -43,19 +42,6 @@ public interface GlEffect extends Effect {
GlShaderProgram toGlShaderProgram(Context context, boolean useHdr)
throws VideoFrameProcessingException;
/**
* Returns a {@link GlShaderProgram} that applies the effect.
*
* @param context A {@link Context}.
* @param colorInfo The {@link ColorInfo} of the input.
* @throws VideoFrameProcessingException If an error occurs while creating the {@link
* GlShaderProgram}.
*/
default GlShaderProgram toGlShaderProgram(Context context, ColorInfo colorInfo)
throws VideoFrameProcessingException {
return toGlShaderProgram(context, /* useHdr= */ ColorInfo.isTransferHdr(colorInfo));
}
/**
* Returns whether a {@link GlEffect} applies no change at every timestamp.
*

View File

@ -57,7 +57,6 @@ import androidx.media3.common.util.ListenerSet;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.effect.DebugTraceUtil;
import androidx.media3.effect.DebugViewEffect;
import androidx.media3.effect.DefaultVideoFrameProcessor;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import com.google.common.collect.ImmutableList;
@ -493,10 +492,8 @@ public final class Transformer {
*
* @param debugViewProvider Provider for debug views.
* @return This builder.
* @deprecated Add a {@link DebugViewEffect} to the list of video effects instead.
*/
@CanIgnoreReturnValue
@Deprecated
public Builder setDebugViewProvider(DebugViewProvider debugViewProvider) {
this.debugViewProvider = debugViewProvider;
return this;
@ -1593,19 +1590,6 @@ public final class Transformer {
}
editingMetricsCollector = new EditingMetricsCollector(context, EXPORTER_NAME, muxerName);
}
if (debugViewProvider != DebugViewProvider.NONE) {
ImmutableList<Effect> videoEffectsWithDebugView =
new ImmutableList.Builder<Effect>()
.addAll(composition.effects.videoEffects)
.add(new DebugViewEffect(debugViewProvider))
.build();
composition =
composition
.buildUpon()
.setEffects(
new Effects(composition.effects.audioProcessors, videoEffectsWithDebugView))
.build();
}
transformerInternal =
new TransformerInternal(
context,