mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Remove setPendingVideoEffects from VideoSink.
VideoSink#onInputStreamChanged(int, Format, List<Effect>) should now be used to set video effects on a new input stream. PiperOrigin-RevId: 713627389
This commit is contained in:
parent
e1b57c130d
commit
8a709a7d76
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer.video;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
@ -170,16 +171,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>This method will always throw an {@link UnsupportedOperationException}.
|
||||
*/
|
||||
@Override
|
||||
public void setPendingVideoEffects(List<Effect> videoEffects) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamTimestampInfo(
|
||||
long streamStartPositionUs, long bufferTimestampAdjustmentUs, long lastResetPositionUs) {
|
||||
@ -212,8 +203,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
videoFrameReleaseControl.allowReleaseFirstFrameBeforeStarted();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>{@code videoEffects} is required to be empty
|
||||
*/
|
||||
@Override
|
||||
public void onInputStreamChanged(@InputType int inputType, Format format) {
|
||||
public void onInputStreamChanged(
|
||||
@InputType int inputType, Format format, List<Effect> videoEffects) {
|
||||
checkState(videoEffects.isEmpty());
|
||||
if (format.width != inputFormat.width || format.height != inputFormat.height) {
|
||||
videoFrameRenderControl.onVideoSizeChanged(format.width, format.height);
|
||||
}
|
||||
|
@ -1397,8 +1397,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
decodedVideoSize = new VideoSize(width, height, pixelWidthHeightRatio);
|
||||
|
||||
if (videoSink != null && pendingVideoSinkInputStreamChange) {
|
||||
onReadyToChangeVideoSinkInputStream();
|
||||
videoSink.onInputStreamChanged(
|
||||
changeVideoSinkInputStream(
|
||||
videoSink,
|
||||
/* inputType= */ VideoSink.INPUT_TYPE_SURFACE,
|
||||
format
|
||||
.buildUpon()
|
||||
@ -1413,13 +1413,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when ready to {@linkplain VideoSink#onInputStreamChanged(int, Format) change} the input
|
||||
* stream when {@linkplain #setVideoEffects video effects} are enabled.
|
||||
* Called when ready to {@linkplain VideoSink#onInputStreamChanged(int, Format, List<Effect>)
|
||||
* change} the input stream.
|
||||
*
|
||||
* <p>The default implementation is a no-op.
|
||||
* <p>The default implementation applies this renderer's video effects.
|
||||
*/
|
||||
protected void onReadyToChangeVideoSinkInputStream() {
|
||||
// do nothing.
|
||||
protected void changeVideoSinkInputStream(
|
||||
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
|
||||
List<Effect> videoEffectsToApply = videoEffects != null ? videoEffects : ImmutableList.of();
|
||||
videoSink.onInputStreamChanged(inputType, format, videoEffectsToApply);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -382,13 +382,15 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
// We forward output size changes to the sink even if we are still flushing.
|
||||
videoGraphOutputFormat =
|
||||
videoGraphOutputFormat.buildUpon().setWidth(width).setHeight(height).build();
|
||||
defaultVideoSink.onInputStreamChanged(INPUT_TYPE_SURFACE, videoGraphOutputFormat);
|
||||
defaultVideoSink.onInputStreamChanged(
|
||||
INPUT_TYPE_SURFACE, videoGraphOutputFormat, /* videoEffects= */ ImmutableList.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutputFrameRateChanged(float frameRate) {
|
||||
videoGraphOutputFormat = videoGraphOutputFormat.buildUpon().setFrameRate(frameRate).build();
|
||||
defaultVideoSink.onInputStreamChanged(INPUT_TYPE_SURFACE, videoGraphOutputFormat);
|
||||
defaultVideoSink.onInputStreamChanged(
|
||||
INPUT_TYPE_SURFACE, videoGraphOutputFormat, /* videoEffects= */ ImmutableList.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -685,7 +687,8 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputStreamChanged(@InputType int inputType, Format format) {
|
||||
public void onInputStreamChanged(
|
||||
@InputType int inputType, Format format, List<Effect> videoEffects) {
|
||||
checkState(isInitialized());
|
||||
switch (inputType) {
|
||||
case INPUT_TYPE_SURFACE:
|
||||
@ -694,6 +697,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported input type " + inputType);
|
||||
}
|
||||
setPendingVideoEffects(videoEffects);
|
||||
this.inputType = inputType;
|
||||
this.inputFormat = format;
|
||||
finalBufferPresentationTimeUs = C.TIME_UNSET;
|
||||
@ -729,15 +733,6 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPendingVideoEffects(List<Effect> videoEffects) {
|
||||
this.videoEffects =
|
||||
new ImmutableList.Builder<Effect>()
|
||||
.addAll(videoEffects)
|
||||
.addAll(compositionEffects)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamTimestampInfo(
|
||||
long streamStartPositionUs, long bufferTimestampAdjustmentUs, long lastResetPositionUs) {
|
||||
@ -887,6 +882,19 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* Sets the pending video effects.
|
||||
*
|
||||
* <p>Effects are pending until a new input stream is registered.
|
||||
*/
|
||||
private void setPendingVideoEffects(List<Effect> newVideoEffects) {
|
||||
this.videoEffects =
|
||||
new ImmutableList.Builder<Effect>()
|
||||
.addAll(newVideoEffects)
|
||||
.addAll(compositionEffects)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void registerInputStream(Format inputFormat) {
|
||||
Format adjustedInputFormat =
|
||||
inputFormat
|
||||
|
@ -187,8 +187,9 @@ public interface VideoSink {
|
||||
*
|
||||
* <p>This method returns {@code true} if the end of the last input stream has been {@linkplain
|
||||
* #signalEndOfCurrentInputStream() signaled} and all the input frames have been rendered. Note
|
||||
* that a new input stream can be {@linkplain #onInputStreamChanged(int, Format) signaled} even
|
||||
* when this method returns true (in which case the sink will not be ended anymore).
|
||||
* that a new input stream can be {@linkplain #onInputStreamChanged(int, Format, List<Effect>)
|
||||
* signaled} even when this method returns true (in which case the sink will not be ended
|
||||
* anymore).
|
||||
*/
|
||||
boolean isEnded();
|
||||
|
||||
@ -208,12 +209,6 @@ public interface VideoSink {
|
||||
/** Sets {@linkplain Effect video effects} to apply immediately. */
|
||||
void setVideoEffects(List<Effect> videoEffects);
|
||||
|
||||
/**
|
||||
* Sets {@linkplain Effect video effects} to apply after the next stream {@linkplain
|
||||
* VideoSink#onInputStreamChanged(int, Format) change}.
|
||||
*/
|
||||
void setPendingVideoEffects(List<Effect> videoEffects);
|
||||
|
||||
/**
|
||||
* Sets information about the timestamps of the current input stream.
|
||||
*
|
||||
@ -250,20 +245,21 @@ public interface VideoSink {
|
||||
void enableMayRenderStartOfStream();
|
||||
|
||||
/**
|
||||
* Informs the video sink that a new input stream will be queued.
|
||||
* Informs the video sink that a new input stream will be queued with the given effects.
|
||||
*
|
||||
* <p>Must be called after the sink is {@linkplain #initialize(Format) initialized}.
|
||||
*
|
||||
* @param inputType The {@link InputType} of the stream.
|
||||
* @param format The {@link Format} of the stream.
|
||||
* @param videoEffects The {@link List<Effect>} to apply to the new stream.
|
||||
*/
|
||||
void onInputStreamChanged(@InputType int inputType, Format format);
|
||||
void onInputStreamChanged(@InputType int inputType, Format format, List<Effect> videoEffects);
|
||||
|
||||
/**
|
||||
* Handles a video input frame.
|
||||
*
|
||||
* <p>Must be called after the corresponding stream is {@linkplain #onInputStreamChanged(int,
|
||||
* Format) signaled}.
|
||||
* Format, List<Effect>) signaled}.
|
||||
*
|
||||
* @param framePresentationTimeUs The frame's presentation time, in microseconds.
|
||||
* @param isLastFrame Whether this is the last frame of the video stream. This flag is set on a
|
||||
@ -280,7 +276,7 @@ public interface VideoSink {
|
||||
* Handles an input {@link Bitmap}.
|
||||
*
|
||||
* <p>Must be called after the corresponding stream is {@linkplain #onInputStreamChanged(int,
|
||||
* Format) signaled}.
|
||||
* Format, List<Effect>) signaled}.
|
||||
*
|
||||
* @param inputBitmap The {@link Bitmap} to queue to the video sink.
|
||||
* @param timestampIterator The times within the current stream that the bitmap should be shown
|
||||
|
@ -15,20 +15,28 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer.video;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.ColorInfo;
|
||||
import androidx.media3.common.DebugViewProvider;
|
||||
import androidx.media3.common.Effect;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.OnInputFrameProcessedListener;
|
||||
import androidx.media3.common.PreviewingVideoGraph;
|
||||
import androidx.media3.common.SurfaceInfo;
|
||||
import androidx.media3.common.VideoFrameProcessor;
|
||||
import androidx.media3.common.VideoGraph;
|
||||
import androidx.media3.common.util.TimestampIterator;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import org.junit.Test;
|
||||
@ -51,17 +59,40 @@ public final class PlaybackVideoGraphWrapperTest {
|
||||
|
||||
@Test
|
||||
public void initializeSink_calledTwice_throws() throws VideoSink.VideoSinkException {
|
||||
PlaybackVideoGraphWrapper provider = createPlaybackVideoGraphWrapper();
|
||||
VideoSink sink = provider.getSink();
|
||||
PlaybackVideoGraphWrapper playbackVideoGraphWrapper =
|
||||
createPlaybackVideoGraphWrapper(new FakeVideoFrameProcessor());
|
||||
VideoSink sink = playbackVideoGraphWrapper.getSink();
|
||||
sink.initialize(new Format.Builder().build());
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> sink.initialize(new Format.Builder().build()));
|
||||
}
|
||||
|
||||
private static PlaybackVideoGraphWrapper createPlaybackVideoGraphWrapper() {
|
||||
@Test
|
||||
public void onInputStreamChanged_setsVideoSinkVideoEffects() throws VideoSink.VideoSinkException {
|
||||
ImmutableList<Effect> firstEffects = ImmutableList.of(Mockito.mock(Effect.class));
|
||||
ImmutableList<Effect> secondEffects =
|
||||
ImmutableList.of(Mockito.mock(Effect.class), Mockito.mock(Effect.class));
|
||||
FakeVideoFrameProcessor videoFrameProcessor = new FakeVideoFrameProcessor();
|
||||
PlaybackVideoGraphWrapper playbackVideoGraphWrapper =
|
||||
createPlaybackVideoGraphWrapper(videoFrameProcessor);
|
||||
Format format = new Format.Builder().build();
|
||||
VideoSink sink = playbackVideoGraphWrapper.getSink();
|
||||
|
||||
sink.initialize(format);
|
||||
|
||||
sink.onInputStreamChanged(VideoSink.INPUT_TYPE_SURFACE, format, firstEffects);
|
||||
assertThat(videoFrameProcessor.registeredEffects).isEqualTo(firstEffects);
|
||||
sink.onInputStreamChanged(VideoSink.INPUT_TYPE_SURFACE, format, secondEffects);
|
||||
assertThat(videoFrameProcessor.registeredEffects).isEqualTo(secondEffects);
|
||||
sink.onInputStreamChanged(VideoSink.INPUT_TYPE_SURFACE, format, ImmutableList.of());
|
||||
assertThat(videoFrameProcessor.registeredEffects).isEmpty();
|
||||
}
|
||||
|
||||
private static PlaybackVideoGraphWrapper createPlaybackVideoGraphWrapper(
|
||||
VideoFrameProcessor videoFrameProcessor) {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
return new PlaybackVideoGraphWrapper.Builder(context, createVideoFrameReleaseControl())
|
||||
.setPreviewingVideoGraphFactory(new TestPreviewingVideoGraphFactory())
|
||||
.setPreviewingVideoGraphFactory(new TestPreviewingVideoGraphFactory(videoFrameProcessor))
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -94,12 +125,73 @@ public final class PlaybackVideoGraphWrapperTest {
|
||||
context, frameTimingEvaluator, /* allowedJoiningTimeMs= */ 0);
|
||||
}
|
||||
|
||||
private static class FakeVideoFrameProcessor implements VideoFrameProcessor {
|
||||
|
||||
List<Effect> registeredEffects = ImmutableList.of();
|
||||
|
||||
@Override
|
||||
public boolean queueInputBitmap(Bitmap inputBitmap, TimestampIterator timestampIterator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean queueInputTexture(int textureId, long presentationTimeUs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnInputFrameProcessedListener(OnInputFrameProcessedListener listener) {}
|
||||
|
||||
@Override
|
||||
public void setOnInputSurfaceReadyListener(Runnable listener) {}
|
||||
|
||||
@Override
|
||||
public Surface getInputSurface() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerInputStream(
|
||||
@InputType int inputType, Format format, List<Effect> effects, long offsetToAddUs) {
|
||||
registeredEffects = effects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerInputFrame() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingInputFrameCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutputSurfaceInfo(@Nullable SurfaceInfo outputSurfaceInfo) {}
|
||||
|
||||
@Override
|
||||
public void renderOutputFrame(long renderTimeNs) {}
|
||||
|
||||
@Override
|
||||
public void signalEndOfInput() {}
|
||||
|
||||
@Override
|
||||
public void flush() {}
|
||||
|
||||
@Override
|
||||
public void release() {}
|
||||
}
|
||||
|
||||
private static class TestPreviewingVideoGraphFactory implements PreviewingVideoGraph.Factory {
|
||||
// Using a mock but we don't assert mock interactions. If needed to assert interactions, we
|
||||
// should a fake instead.
|
||||
private final PreviewingVideoGraph previewingVideoGraph =
|
||||
Mockito.mock(PreviewingVideoGraph.class);
|
||||
private final VideoFrameProcessor videoFrameProcessor = Mockito.mock(VideoFrameProcessor.class);
|
||||
private final VideoFrameProcessor videoFrameProcessor;
|
||||
|
||||
public TestPreviewingVideoGraphFactory(VideoFrameProcessor videoFrameProcessor) {
|
||||
this.videoFrameProcessor = videoFrameProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreviewingVideoGraph create(
|
||||
@ -111,7 +203,6 @@ public final class PlaybackVideoGraphWrapperTest {
|
||||
List<Effect> compositionEffects,
|
||||
long initialTimestampOffsetUs) {
|
||||
when(previewingVideoGraph.getProcessor(anyInt())).thenReturn(videoFrameProcessor);
|
||||
when(videoFrameProcessor.registerInputFrame()).thenReturn(true);
|
||||
return previewingVideoGraph;
|
||||
}
|
||||
}
|
||||
|
@ -176,11 +176,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
executeOrDelay(videoSink -> videoSink.setVideoEffects(videoEffects));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPendingVideoEffects(List<Effect> videoEffects) {
|
||||
executeOrDelay(videoSink -> videoSink.setPendingVideoEffects(videoEffects));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStreamTimestampInfo(
|
||||
long streamStartPositionUs, long bufferTimestampAdjustmentUs, long lastResetPositionUs) {
|
||||
@ -211,8 +206,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputStreamChanged(@InputType int inputType, Format format) {
|
||||
executeOrDelay(videoSink -> videoSink.onInputStreamChanged(inputType, format));
|
||||
public void onInputStreamChanged(
|
||||
@InputType int inputType, Format format, List<Effect> videoEffects) {
|
||||
executeOrDelay(videoSink -> videoSink.onInputStreamChanged(inputType, format, videoEffects));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -287,7 +287,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private final VideoSink videoSink;
|
||||
private final boolean requestToneMapping;
|
||||
|
||||
@Nullable private ImmutableList<Effect> pendingEffect;
|
||||
private ImmutableList<Effect> pendingEffects;
|
||||
@Nullable private EditedMediaItem currentEditedMediaItem;
|
||||
private long offsetToCompositionTimeUs;
|
||||
|
||||
@ -312,6 +312,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
this.sequence = sequence;
|
||||
this.videoSink = videoSink;
|
||||
this.requestToneMapping = requestToneMapping;
|
||||
this.pendingEffects = ImmutableList.of();
|
||||
experimentalEnableProcessedStreamChangedAtStart();
|
||||
}
|
||||
|
||||
@ -329,7 +330,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
// previous one.
|
||||
currentEditedMediaItem = getRepeatedEditedMediaItem(sequence, mediaItemIndex);
|
||||
offsetToCompositionTimeUs = getOffsetToCompositionTimeUs(sequence, mediaItemIndex, offsetUs);
|
||||
pendingEffect = sequence.editedMediaItems.get(mediaItemIndex).effects.videoEffects;
|
||||
pendingEffects = sequence.editedMediaItems.get(mediaItemIndex).effects.videoEffects;
|
||||
super.onStreamChanged(formats, startPositionUs, offsetUs, mediaPeriodId);
|
||||
}
|
||||
|
||||
@ -370,12 +371,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReadyToChangeVideoSinkInputStream() {
|
||||
@Nullable ImmutableList<Effect> pendingEffect = this.pendingEffect;
|
||||
if (pendingEffect != null) {
|
||||
videoSink.setPendingVideoEffects(pendingEffect);
|
||||
this.pendingEffect = null;
|
||||
}
|
||||
protected void changeVideoSinkInputStream(
|
||||
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
|
||||
videoSink.onInputStreamChanged(inputType, format, pendingEffects);
|
||||
}
|
||||
}
|
||||
|
||||
@ -519,7 +517,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
long positionUs, long elapsedRealtimeUs, Bitmap outputImage, long timeUs) {
|
||||
if (inputStreamPending) {
|
||||
checkState(streamStartPositionUs != C.TIME_UNSET);
|
||||
videoSink.setPendingVideoEffects(videoEffects);
|
||||
videoSink.setStreamTimestampInfo(
|
||||
streamStartPositionUs,
|
||||
/* bufferTimestampAdjustmentUs= */ offsetToCompositionTimeUs,
|
||||
@ -532,7 +529,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
.setHeight(outputImage.getHeight())
|
||||
.setColorInfo(ColorInfo.SRGB_BT709_FULL)
|
||||
.setFrameRate(/* frameRate= */ DEFAULT_FRAME_RATE)
|
||||
.build());
|
||||
.build(),
|
||||
videoEffects);
|
||||
inputStreamPending = false;
|
||||
}
|
||||
if (!videoSink.handleInputBitmap(outputImage, checkStateNotNull(timestampIterator))) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user