Commit 3d6bed16 by hschlueter Committed by microkatz

FrameProcessor: Replace SurfaceInfo.Provider with setter.

The FinalMatrixTransformationProcessorWrapper ensures that the
surface is only replaced when it is not being rendered to and vice
versa.

PiperOrigin-RevId: 458007639
(cherry picked from commit e5527a8a)
parent 583c12f8
...@@ -357,6 +357,16 @@ public final class GlEffectsFrameProcessorPixelTest { ...@@ -357,6 +357,16 @@ public final class GlEffectsFrameProcessorPixelTest {
context, context,
new FrameProcessor.Listener() { new FrameProcessor.Listener() {
@Override @Override
public void onOutputSizeChanged(int width, int height) {
outputImageReader =
ImageReader.newInstance(
width, height, PixelFormat.RGBA_8888, /* maxImages= */ 1);
checkNotNull(glEffectsFrameProcessor)
.setOutputSurfaceInfo(
new SurfaceInfo(outputImageReader.getSurface(), width, height));
}
@Override
public void onFrameProcessingError(FrameProcessingException exception) { public void onFrameProcessingError(FrameProcessingException exception) {
frameProcessingException.set(exception); frameProcessingException.set(exception);
} }
...@@ -368,16 +378,6 @@ public final class GlEffectsFrameProcessorPixelTest { ...@@ -368,16 +378,6 @@ public final class GlEffectsFrameProcessorPixelTest {
}, },
/* streamOffsetUs= */ 0L, /* streamOffsetUs= */ 0L,
effects, effects,
/* outputSurfaceProvider= */ (requestedWidth, requestedHeight) -> {
outputImageReader =
ImageReader.newInstance(
requestedWidth,
requestedHeight,
PixelFormat.RGBA_8888,
/* maxImages= */ 1);
return new SurfaceInfo(
outputImageReader.getSurface(), requestedWidth, requestedHeight);
},
Transformer.DebugViewProvider.NONE, Transformer.DebugViewProvider.NONE,
/* enableExperimentalHdrEditing= */ false)); /* enableExperimentalHdrEditing= */ false));
glEffectsFrameProcessor.setInputFrameInfo( glEffectsFrameProcessor.setInputFrameInfo(
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.transformer; package com.google.android.exoplayer2.transformer;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable;
/** Interface for a frame processor that applies changes to individual video frames. */ /** Interface for a frame processor that applies changes to individual video frames. */
/* package */ interface FrameProcessor { /* package */ interface FrameProcessor {
...@@ -27,6 +28,14 @@ import android.view.Surface; ...@@ -27,6 +28,14 @@ import android.view.Surface;
interface Listener { interface Listener {
/** /**
* Called when the output size after applying the final effect changes.
*
* <p>The output size after applying the final effect can differ from the size specified using
* {@link #setOutputSurfaceInfo(SurfaceInfo)}.
*/
void onOutputSizeChanged(int width, int height);
/**
* Called when an exception occurs during asynchronous frame processing. * Called when an exception occurs during asynchronous frame processing.
* *
* <p>If an error occurred, consuming and producing further frames will not work as expected and * <p>If an error occurred, consuming and producing further frames will not work as expected and
...@@ -69,6 +78,23 @@ import android.view.Surface; ...@@ -69,6 +78,23 @@ import android.view.Surface;
int getPendingInputFrameCount(); int getPendingInputFrameCount();
/** /**
* Sets the output surface and supporting information.
*
* <p>The new output {@link SurfaceInfo} is applied from the next output frame rendered onwards.
* If the output {@link SurfaceInfo} is {@code null}, the {@code FrameProcessor} will stop
* rendering and resume rendering pending frames once a non-null {@link SurfaceInfo} is set.
*
* <p>If the dimensions given in {@link SurfaceInfo} do not match the {@linkplain
* Listener#onOutputSizeChanged(int,int) output size after applying the final effect} the frames
* are resized before rendering to the surface and letter/pillar-boxing is applied.
*
* <p>The caller is responsible for tracking the lifecycle of the {@link SurfaceInfo#surface}
* including calling this method with a new surface if it is destroyed. When this method returns,
* the previous output surface is no longer being used and can safely be released by the caller.
*/
void setOutputSurfaceInfo(@Nullable SurfaceInfo outputSurfaceInfo);
/**
* Informs the {@code FrameProcessor} that no further input frames should be accepted. * Informs the {@code FrameProcessor} that no further input frames should be accepted.
* *
* @throws IllegalStateException If called more than once. * @throws IllegalStateException If called more than once.
......
...@@ -55,16 +55,27 @@ import androidx.annotation.Nullable; ...@@ -55,16 +55,27 @@ import androidx.annotation.Nullable;
this.orientationDegrees = orientationDegrees; this.orientationDegrees = orientationDegrees;
} }
/** A provider for a {@link SurfaceInfo} instance. */ @Override
public interface Provider { public boolean equals(@Nullable Object o) {
/** if (this == o) {
* Provides a {@linkplain SurfaceInfo surface} for the requested dimensions. return true;
* }
* <p>The dimensions given in the provided {@link SurfaceInfo} may differ from the requested if (!(o instanceof SurfaceInfo)) {
* dimensions. It is up to the caller to transform frames from the requested dimensions to the return false;
* provided dimensions before rendering them to the {@link SurfaceInfo#surface}. }
*/ SurfaceInfo that = (SurfaceInfo) o;
@Nullable return width == that.width
SurfaceInfo getSurfaceInfo(int requestedWidth, int requestedHeight); && height == that.height
&& orientationDegrees == that.orientationDegrees
&& surface.equals(that.surface);
}
@Override
public int hashCode() {
int result = surface.hashCode();
result = 31 * result + width;
result = 31 * result + height;
result = 31 * result + orientationDegrees;
return result;
} }
} }
...@@ -95,8 +95,7 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -95,8 +95,7 @@ import org.checkerframework.dataflow.qual.Pure;
inputFormat, inputFormat,
allowedOutputMimeTypes, allowedOutputMimeTypes,
transformationRequest, transformationRequest,
fallbackListener, fallbackListener);
asyncErrorListener);
try { try {
frameProcessor = frameProcessor =
...@@ -104,6 +103,16 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -104,6 +103,16 @@ import org.checkerframework.dataflow.qual.Pure;
context, context,
new FrameProcessor.Listener() { new FrameProcessor.Listener() {
@Override @Override
public void onOutputSizeChanged(int width, int height) {
try {
checkNotNull(frameProcessor)
.setOutputSurfaceInfo(encoderWrapper.getSurfaceInfo(width, height));
} catch (TransformationException exception) {
asyncErrorListener.onTransformationException(exception);
}
}
@Override
public void onFrameProcessingError(FrameProcessingException exception) { public void onFrameProcessingError(FrameProcessingException exception) {
asyncErrorListener.onTransformationException( asyncErrorListener.onTransformationException(
TransformationException.createForFrameProcessingException( TransformationException.createForFrameProcessingException(
...@@ -121,7 +130,6 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -121,7 +130,6 @@ import org.checkerframework.dataflow.qual.Pure;
}, },
streamOffsetUs, streamOffsetUs,
effectsListBuilder.build(), effectsListBuilder.build(),
/* outputSurfaceProvider= */ encoderWrapper,
debugViewProvider, debugViewProvider,
transformationRequest.enableHdrEditing); transformationRequest.enableHdrEditing);
} catch (FrameProcessingException e) { } catch (FrameProcessingException e) {
...@@ -284,14 +292,13 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -284,14 +292,13 @@ import org.checkerframework.dataflow.qual.Pure;
* dimensions, the same encoder is used and the provided dimensions stay fixed. * dimensions, the same encoder is used and the provided dimensions stay fixed.
*/ */
@VisibleForTesting @VisibleForTesting
/* package */ static final class EncoderWrapper implements SurfaceInfo.Provider { /* package */ static final class EncoderWrapper {
private final Codec.EncoderFactory encoderFactory; private final Codec.EncoderFactory encoderFactory;
private final Format inputFormat; private final Format inputFormat;
private final List<String> allowedOutputMimeTypes; private final List<String> allowedOutputMimeTypes;
private final TransformationRequest transformationRequest; private final TransformationRequest transformationRequest;
private final FallbackListener fallbackListener; private final FallbackListener fallbackListener;
private final Transformer.AsyncErrorListener asyncErrorListener;
private @MonotonicNonNull SurfaceInfo encoderSurfaceInfo; private @MonotonicNonNull SurfaceInfo encoderSurfaceInfo;
...@@ -304,20 +311,18 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -304,20 +311,18 @@ import org.checkerframework.dataflow.qual.Pure;
Format inputFormat, Format inputFormat,
List<String> allowedOutputMimeTypes, List<String> allowedOutputMimeTypes,
TransformationRequest transformationRequest, TransformationRequest transformationRequest,
FallbackListener fallbackListener, FallbackListener fallbackListener) {
Transformer.AsyncErrorListener asyncErrorListener) {
this.encoderFactory = encoderFactory; this.encoderFactory = encoderFactory;
this.inputFormat = inputFormat; this.inputFormat = inputFormat;
this.allowedOutputMimeTypes = allowedOutputMimeTypes; this.allowedOutputMimeTypes = allowedOutputMimeTypes;
this.transformationRequest = transformationRequest; this.transformationRequest = transformationRequest;
this.fallbackListener = fallbackListener; this.fallbackListener = fallbackListener;
this.asyncErrorListener = asyncErrorListener;
} }
@Override
@Nullable @Nullable
public SurfaceInfo getSurfaceInfo(int requestedWidth, int requestedHeight) { public SurfaceInfo getSurfaceInfo(int requestedWidth, int requestedHeight)
throws TransformationException {
if (releaseEncoder) { if (releaseEncoder) {
return null; return null;
} }
...@@ -349,13 +354,8 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -349,13 +354,8 @@ import org.checkerframework.dataflow.qual.Pure;
: inputFormat.sampleMimeType) : inputFormat.sampleMimeType)
.build(); .build();
try { encoder =
encoder = encoderFactory.createForVideoEncoding(requestedEncoderFormat, allowedOutputMimeTypes);
encoderFactory.createForVideoEncoding(requestedEncoderFormat, allowedOutputMimeTypes);
} catch (TransformationException e) {
asyncErrorListener.onTransformationException(e);
return null;
}
Format encoderSupportedFormat = encoder.getConfigurationFormat(); Format encoderSupportedFormat = encoder.getConfigurationFormat();
fallbackListener.onTransformationRequestFinalized( fallbackListener.onTransformationRequestFinalized(
createFallbackTransformationRequest( createFallbackTransformationRequest(
......
...@@ -50,8 +50,7 @@ public final class VideoEncoderWrapperTest { ...@@ -50,8 +50,7 @@ public final class VideoEncoderWrapperTest {
/* inputFormat= */ new Format.Builder().build(), /* inputFormat= */ new Format.Builder().build(),
/* allowedOutputMimeTypes= */ ImmutableList.of(), /* allowedOutputMimeTypes= */ ImmutableList.of(),
emptyTransformationRequest, emptyTransformationRequest,
fallbackListener, fallbackListener);
mock(Transformer.AsyncErrorListener.class));
@Before @Before
public void registerTrack() { public void registerTrack() {
...@@ -59,7 +58,7 @@ public final class VideoEncoderWrapperTest { ...@@ -59,7 +58,7 @@ public final class VideoEncoderWrapperTest {
} }
@Test @Test
public void getSurfaceInfo_landscape_leavesOrientationUnchanged() { public void getSurfaceInfo_landscape_leavesOrientationUnchanged() throws Exception {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
...@@ -71,7 +70,7 @@ public final class VideoEncoderWrapperTest { ...@@ -71,7 +70,7 @@ public final class VideoEncoderWrapperTest {
} }
@Test @Test
public void getSurfaceInfo_square_leavesOrientationUnchanged() { public void getSurfaceInfo_square_leavesOrientationUnchanged() throws Exception {
int inputWidth = 150; int inputWidth = 150;
int inputHeight = 150; int inputHeight = 150;
...@@ -83,7 +82,7 @@ public final class VideoEncoderWrapperTest { ...@@ -83,7 +82,7 @@ public final class VideoEncoderWrapperTest {
} }
@Test @Test
public void getSurfaceInfo_portrait_flipsOrientation() { public void getSurfaceInfo_portrait_flipsOrientation() throws Exception {
int inputWidth = 150; int inputWidth = 150;
int inputHeight = 200; int inputHeight = 200;
...@@ -95,7 +94,8 @@ public final class VideoEncoderWrapperTest { ...@@ -95,7 +94,8 @@ public final class VideoEncoderWrapperTest {
} }
@Test @Test
public void getSurfaceInfo_withEncoderFallback_usesFallbackResolution() { public void getSurfaceInfo_withEncoderFallback_usesFallbackResolution()
throws TransformationException {
int inputWidth = 200; int inputWidth = 200;
int inputHeight = 150; int inputHeight = 150;
int fallbackWidth = 100; int fallbackWidth = 100;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment