mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Remove VideoSink.onRendererEnabled
This is part of the effort to make VideoSink independent from renderers. PiperOrigin-RevId: 740344126
This commit is contained in:
parent
a220b0cb5e
commit
2642d895bd
@ -79,16 +79,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
videoFrameMetadataListener = (presentationTimeUs, releaseTimeNs, format, mediaFormat) -> {};
|
videoFrameMetadataListener = (presentationTimeUs, releaseTimeNs, format, mediaFormat) -> {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>This method will always throw an {@link UnsupportedOperationException}.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onRendererEnabled(boolean mayRenderStartOfStream) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRendererStarted() {
|
public void onRendererStarted() {
|
||||||
videoFrameReleaseControl.onStarted();
|
videoFrameReleaseControl.onStarted();
|
||||||
@ -197,16 +187,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
videoFrameReleaseControl.setChangeFrameRateStrategy(changeFrameRateStrategy);
|
videoFrameReleaseControl.setChangeFrameRateStrategy(changeFrameRateStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>This method will always throw an {@link UnsupportedOperationException}.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void enableMayRenderStartOfStream() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
@ -233,6 +213,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void allowReleaseFirstFrameBeforeStarted() {
|
||||||
|
videoFrameReleaseControl.allowReleaseFirstFrameBeforeStarted();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handleInputFrame(
|
public boolean handleInputFrame(
|
||||||
long framePresentationTimeUs, boolean isLastFrame, VideoFrameHandler videoFrameHandler) {
|
long framePresentationTimeUs, boolean isLastFrame, VideoFrameHandler videoFrameHandler) {
|
||||||
|
@ -190,6 +190,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
private boolean codecHandlesHdr10PlusOutOfBandMetadata;
|
private boolean codecHandlesHdr10PlusOutOfBandMetadata;
|
||||||
private @MonotonicNonNull VideoSink videoSink;
|
private @MonotonicNonNull VideoSink videoSink;
|
||||||
private boolean hasSetVideoSink;
|
private boolean hasSetVideoSink;
|
||||||
|
private @VideoSink.FirstFrameReleaseInstruction int nextVideoSinkFirstFrameReleaseInstruction;
|
||||||
private @MonotonicNonNull List<Effect> videoEffects;
|
private @MonotonicNonNull List<Effect> videoEffects;
|
||||||
@Nullable private Surface displaySurface;
|
@Nullable private Surface displaySurface;
|
||||||
@Nullable private PlaceholderSurface placeholderSurface;
|
@Nullable private PlaceholderSurface placeholderSurface;
|
||||||
@ -928,7 +929,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
if (videoEffects != null) {
|
if (videoEffects != null) {
|
||||||
videoSink.setVideoEffects(videoEffects);
|
videoSink.setVideoEffects(videoEffects);
|
||||||
}
|
}
|
||||||
videoSink.onRendererEnabled(mayRenderStartOfStream);
|
nextVideoSinkFirstFrameReleaseInstruction =
|
||||||
|
mayRenderStartOfStream
|
||||||
|
? RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||||
|
: RELEASE_FIRST_FRAME_WHEN_STARTED;
|
||||||
@Nullable WakeupListener wakeupListener = getWakeupListener();
|
@Nullable WakeupListener wakeupListener = getWakeupListener();
|
||||||
if (wakeupListener != null) {
|
if (wakeupListener != null) {
|
||||||
videoSink.setWakeupListener(wakeupListener);
|
videoSink.setWakeupListener(wakeupListener);
|
||||||
@ -956,7 +960,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
@Override
|
@Override
|
||||||
public void enableMayRenderStartOfStream() {
|
public void enableMayRenderStartOfStream() {
|
||||||
if (videoSink != null) {
|
if (videoSink != null) {
|
||||||
videoSink.enableMayRenderStartOfStream();
|
if (nextVideoSinkFirstFrameReleaseInstruction == RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||||
|
|| nextVideoSinkFirstFrameReleaseInstruction == RELEASE_FIRST_FRAME_WHEN_STARTED) {
|
||||||
|
// The first stream change hasn't been queued to the sink.
|
||||||
|
nextVideoSinkFirstFrameReleaseInstruction = RELEASE_FIRST_FRAME_IMMEDIATELY;
|
||||||
|
} else {
|
||||||
|
videoSink.allowReleaseFirstFrameBeforeStarted();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
videoFrameReleaseControl.allowReleaseFirstFrameBeforeStarted();
|
videoFrameReleaseControl.allowReleaseFirstFrameBeforeStarted();
|
||||||
}
|
}
|
||||||
@ -1642,7 +1652,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
.setWidth(width)
|
.setWidth(width)
|
||||||
.setHeight(height)
|
.setHeight(height)
|
||||||
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
|
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
|
||||||
.build());
|
.build(),
|
||||||
|
nextVideoSinkFirstFrameReleaseInstruction);
|
||||||
|
nextVideoSinkFirstFrameReleaseInstruction =
|
||||||
|
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
||||||
} else {
|
} else {
|
||||||
videoFrameReleaseControl.setFrameRate(format.frameRate);
|
videoFrameReleaseControl.setFrameRate(format.frameRate);
|
||||||
}
|
}
|
||||||
@ -1656,13 +1669,16 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
* <p>The default implementation applies this renderer's video effects.
|
* <p>The default implementation applies this renderer's video effects.
|
||||||
*/
|
*/
|
||||||
protected void changeVideoSinkInputStream(
|
protected void changeVideoSinkInputStream(
|
||||||
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
|
VideoSink videoSink,
|
||||||
|
@VideoSink.InputType int inputType,
|
||||||
|
Format format,
|
||||||
|
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction) {
|
||||||
List<Effect> videoEffectsToApply = videoEffects != null ? videoEffects : ImmutableList.of();
|
List<Effect> videoEffectsToApply = videoEffects != null ? videoEffects : ImmutableList.of();
|
||||||
videoSink.onInputStreamChanged(
|
videoSink.onInputStreamChanged(
|
||||||
inputType,
|
inputType,
|
||||||
format,
|
format,
|
||||||
getOutputStreamStartPositionUs(),
|
getOutputStreamStartPositionUs(),
|
||||||
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
|
firstFrameReleaseInstruction,
|
||||||
videoEffectsToApply);
|
videoEffectsToApply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
|||||||
import static androidx.media3.common.util.Util.contains;
|
import static androidx.media3.common.util.Util.contains;
|
||||||
import static androidx.media3.common.util.Util.getMaxPendingFramesCountForMediaCodecDecoders;
|
import static androidx.media3.common.util.Util.getMaxPendingFramesCountForMediaCodecDecoders;
|
||||||
import static androidx.media3.exoplayer.video.VideoSink.INPUT_TYPE_SURFACE;
|
import static androidx.media3.exoplayer.video.VideoSink.INPUT_TYPE_SURFACE;
|
||||||
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -281,12 +280,6 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
/**
|
|
||||||
* A queue of unprocessed input frame start positions. Each position is associated with the
|
|
||||||
* timestamp from which it should be applied.
|
|
||||||
*/
|
|
||||||
private final TimedValueQueue<Long> streamStartPositionsUs;
|
|
||||||
|
|
||||||
private final VideoGraph.Factory videoGraphFactory;
|
private final VideoGraph.Factory videoGraphFactory;
|
||||||
private final SparseArray<InputVideoSink> inputVideoSinks;
|
private final SparseArray<InputVideoSink> inputVideoSinks;
|
||||||
private final List<Effect> compositionEffects;
|
private final List<Effect> compositionEffects;
|
||||||
@ -297,12 +290,18 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
private final CopyOnWriteArraySet<PlaybackVideoGraphWrapper.Listener> listeners;
|
private final CopyOnWriteArraySet<PlaybackVideoGraphWrapper.Listener> listeners;
|
||||||
private final boolean requestOpenGlToneMapping;
|
private final boolean requestOpenGlToneMapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A queue of unprocessed stream changes. Each stream change is associated with the timestamp from
|
||||||
|
* which it should be applied.
|
||||||
|
*/
|
||||||
|
private TimedValueQueue<StreamChangeInfo> pendingStreamChanges;
|
||||||
|
|
||||||
private Format videoGraphOutputFormat;
|
private Format videoGraphOutputFormat;
|
||||||
private @MonotonicNonNull HandlerWrapper handler;
|
private @MonotonicNonNull HandlerWrapper handler;
|
||||||
private @MonotonicNonNull VideoGraph videoGraph;
|
private @MonotonicNonNull VideoGraph videoGraph;
|
||||||
private @MonotonicNonNull VideoFrameMetadataListener videoFrameMetadataListener;
|
private @MonotonicNonNull VideoFrameMetadataListener videoFrameMetadataListener;
|
||||||
private long outputStreamStartPositionUs;
|
private long outputStreamStartPositionUs;
|
||||||
private @VideoSink.FirstFrameReleaseInstruction int nextFirstOutputFrameReleaseInstruction;
|
private @VideoSink.FirstFrameReleaseInstruction int outputStreamFirstFrameReleaseInstruction;
|
||||||
@Nullable private Pair<Surface, Size> currentSurfaceAndSize;
|
@Nullable private Pair<Surface, Size> currentSurfaceAndSize;
|
||||||
private int pendingFlushCount;
|
private int pendingFlushCount;
|
||||||
private @State int state;
|
private @State int state;
|
||||||
@ -331,7 +330,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
|
|
||||||
private PlaybackVideoGraphWrapper(Builder builder) {
|
private PlaybackVideoGraphWrapper(Builder builder) {
|
||||||
context = builder.context;
|
context = builder.context;
|
||||||
streamStartPositionsUs = new TimedValueQueue<>();
|
pendingStreamChanges = new TimedValueQueue<>();
|
||||||
videoGraphFactory = checkStateNotNull(builder.videoGraphFactory);
|
videoGraphFactory = checkStateNotNull(builder.videoGraphFactory);
|
||||||
inputVideoSinks = new SparseArray<>();
|
inputVideoSinks = new SparseArray<>();
|
||||||
compositionEffects = builder.compositionEffects;
|
compositionEffects = builder.compositionEffects;
|
||||||
@ -432,13 +431,13 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
// We forward output size changes to the sink even if we are still flushing.
|
// We forward output size changes to the sink even if we are still flushing.
|
||||||
videoGraphOutputFormat =
|
videoGraphOutputFormat =
|
||||||
videoGraphOutputFormat.buildUpon().setWidth(width).setHeight(height).build();
|
videoGraphOutputFormat.buildUpon().setWidth(width).setHeight(height).build();
|
||||||
onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
|
onOutputStreamChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOutputFrameRateChanged(float frameRate) {
|
public void onOutputFrameRateChanged(float frameRate) {
|
||||||
videoGraphOutputFormat = videoGraphOutputFormat.buildUpon().setFrameRate(frameRate).build();
|
videoGraphOutputFormat = videoGraphOutputFormat.buildUpon().setFrameRate(frameRate).build();
|
||||||
onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
|
onOutputStreamChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -469,13 +468,11 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
// The frame presentation time is relative to the start of the Composition and without the
|
// The frame presentation time is relative to the start of the Composition and without the
|
||||||
// renderer offset
|
// renderer offset
|
||||||
lastOutputBufferPresentationTimeUs = bufferPresentationTimeUs;
|
lastOutputBufferPresentationTimeUs = bufferPresentationTimeUs;
|
||||||
Long newOutputStreamStartPositionUs =
|
StreamChangeInfo streamChangeInfo = pendingStreamChanges.pollFloor(bufferPresentationTimeUs);
|
||||||
streamStartPositionsUs.pollFloor(bufferPresentationTimeUs);
|
if (streamChangeInfo != null) {
|
||||||
if (newOutputStreamStartPositionUs != null
|
outputStreamStartPositionUs = streamChangeInfo.startPositionUs;
|
||||||
&& newOutputStreamStartPositionUs != outputStreamStartPositionUs) {
|
outputStreamFirstFrameReleaseInstruction = streamChangeInfo.firstFrameReleaseInstruction;
|
||||||
outputStreamStartPositionUs = newOutputStreamStartPositionUs;
|
onOutputStreamChanged();
|
||||||
onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
|
|
||||||
nextFirstOutputFrameReleaseInstruction = RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
|
||||||
}
|
}
|
||||||
boolean isLastFrame =
|
boolean isLastFrame =
|
||||||
finalBufferPresentationTimeUs != C.TIME_UNSET
|
finalBufferPresentationTimeUs != C.TIME_UNSET
|
||||||
@ -623,13 +620,15 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
}
|
}
|
||||||
pendingFlushCount++;
|
pendingFlushCount++;
|
||||||
defaultVideoSink.flush(resetPosition);
|
defaultVideoSink.flush(resetPosition);
|
||||||
while (streamStartPositionsUs.size() > 1) {
|
while (pendingStreamChanges.size() > 1) {
|
||||||
streamStartPositionsUs.pollFirst();
|
pendingStreamChanges.pollFirst();
|
||||||
}
|
}
|
||||||
if (streamStartPositionsUs.size() == 1) {
|
if (pendingStreamChanges.size() == 1) {
|
||||||
// Use the latest startPositionUs if none is passed after flushing.
|
// Use the latest stream change info if none is passed after flushing.
|
||||||
outputStreamStartPositionUs = checkNotNull(streamStartPositionsUs.pollFirst());
|
StreamChangeInfo streamChangeInfo = checkNotNull(pendingStreamChanges.pollFirst());
|
||||||
onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
|
outputStreamStartPositionUs = streamChangeInfo.startPositionUs;
|
||||||
|
outputStreamFirstFrameReleaseInstruction = streamChangeInfo.firstFrameReleaseInstruction;
|
||||||
|
onOutputStreamChanged();
|
||||||
}
|
}
|
||||||
lastOutputBufferPresentationTimeUs = C.TIME_UNSET;
|
lastOutputBufferPresentationTimeUs = C.TIME_UNSET;
|
||||||
finalBufferPresentationTimeUs = C.TIME_UNSET;
|
finalBufferPresentationTimeUs = C.TIME_UNSET;
|
||||||
@ -667,13 +666,12 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
return inputColorInfo;
|
return inputColorInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onOutputStreamChanged(
|
private void onOutputStreamChanged() {
|
||||||
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction) {
|
|
||||||
defaultVideoSink.onInputStreamChanged(
|
defaultVideoSink.onInputStreamChanged(
|
||||||
INPUT_TYPE_SURFACE,
|
INPUT_TYPE_SURFACE,
|
||||||
videoGraphOutputFormat,
|
videoGraphOutputFormat,
|
||||||
outputStreamStartPositionUs,
|
outputStreamStartPositionUs,
|
||||||
firstFrameReleaseInstruction,
|
outputStreamFirstFrameReleaseInstruction,
|
||||||
/* videoEffects= */ ImmutableList.of());
|
/* videoEffects= */ ImmutableList.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -712,14 +710,6 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
listenerExecutor = NO_OP_EXECUTOR;
|
listenerExecutor = NO_OP_EXECUTOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRendererEnabled(boolean mayRenderStartOfStream) {
|
|
||||||
nextFirstOutputFrameReleaseInstruction =
|
|
||||||
mayRenderStartOfStream
|
|
||||||
? RELEASE_FIRST_FRAME_IMMEDIATELY
|
|
||||||
: RELEASE_FIRST_FRAME_WHEN_STARTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRendererStarted() {
|
public void onRendererStarted() {
|
||||||
defaultVideoSink.onRendererStarted();
|
defaultVideoSink.onRendererStarted();
|
||||||
@ -808,11 +798,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
|
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
|
||||||
List<Effect> videoEffects) {
|
List<Effect> videoEffects) {
|
||||||
checkState(isInitialized());
|
checkState(isInitialized());
|
||||||
switch (inputType) {
|
if (inputType != INPUT_TYPE_SURFACE && inputType != INPUT_TYPE_BITMAP) {
|
||||||
case INPUT_TYPE_SURFACE:
|
|
||||||
case INPUT_TYPE_BITMAP:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new UnsupportedOperationException("Unsupported input type " + inputType);
|
throw new UnsupportedOperationException("Unsupported input type " + inputType);
|
||||||
}
|
}
|
||||||
setPendingVideoEffects(videoEffects);
|
setPendingVideoEffects(videoEffects);
|
||||||
@ -822,11 +808,56 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
hasSignaledEndOfCurrentInputStream = false;
|
hasSignaledEndOfCurrentInputStream = false;
|
||||||
registerInputStream(format);
|
registerInputStream(format);
|
||||||
// Input timestamps should always be positive because they are offset by ExoPlayer. Adding a
|
// Input timestamps should always be positive because they are offset by ExoPlayer. Adding a
|
||||||
// position to the queue with timestamp 0 should therefore always apply it as long as it is
|
// stream change info to the queue with timestamp 0 should therefore always apply it as long
|
||||||
// the only position in the queue.
|
// as it is the only one in the queue.
|
||||||
streamStartPositionsUs.add(
|
long fromTimestampUs =
|
||||||
lastBufferPresentationTimeUs == C.TIME_UNSET ? 0 : lastBufferPresentationTimeUs + 1,
|
lastBufferPresentationTimeUs == C.TIME_UNSET ? 0 : lastBufferPresentationTimeUs + 1;
|
||||||
startPositionUs);
|
pendingStreamChanges.add(
|
||||||
|
fromTimestampUs,
|
||||||
|
new StreamChangeInfo(startPositionUs, firstFrameReleaseInstruction, fromTimestampUs));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void allowReleaseFirstFrameBeforeStarted() {
|
||||||
|
// We know that this sink is connected to renderers. Each renderer will first queue a stream
|
||||||
|
// change that has firstFrameReleaseInstruction set to either RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||||
|
// or RELEASE_FIRST_FRAME_WHEN_STARTED, and then queue stream changes that have
|
||||||
|
// firstFrameReleaseInstruction set to RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED.
|
||||||
|
// When a renderer queues the first stream change, all previous streams should have been fully
|
||||||
|
// processed.
|
||||||
|
// We want to release the first frame immediately if the firstFrameReleaseInstruction of the
|
||||||
|
// first stream change queued by the current renderer was RELEASE_FIRST_FRAME_WHEN_STARTED and
|
||||||
|
// the first frame hasn't been released yet.
|
||||||
|
if (pendingStreamChanges.size() == 0) {
|
||||||
|
// All the stream changes have already been processed by the VideoGraph. Delegate to the
|
||||||
|
// downstream component.
|
||||||
|
defaultVideoSink.allowReleaseFirstFrameBeforeStarted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TimedValueQueue<StreamChangeInfo> newPendingStreamChanges = new TimedValueQueue<>();
|
||||||
|
boolean isFirstStreamChange = true;
|
||||||
|
while (pendingStreamChanges.size() > 0) {
|
||||||
|
StreamChangeInfo streamChangeInfo = checkNotNull(pendingStreamChanges.pollFirst());
|
||||||
|
if (isFirstStreamChange) {
|
||||||
|
if (streamChangeInfo.firstFrameReleaseInstruction == RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||||
|
|| streamChangeInfo.firstFrameReleaseInstruction
|
||||||
|
== RELEASE_FIRST_FRAME_WHEN_STARTED) {
|
||||||
|
// The first stream change hasn't been processed by the VideoGraph yet.
|
||||||
|
streamChangeInfo =
|
||||||
|
new StreamChangeInfo(
|
||||||
|
streamChangeInfo.startPositionUs,
|
||||||
|
RELEASE_FIRST_FRAME_IMMEDIATELY,
|
||||||
|
streamChangeInfo.fromTimestampUs);
|
||||||
|
} else {
|
||||||
|
// The first stream change has already been processed by the VideoGraph. Delegate to the
|
||||||
|
// downstream component.
|
||||||
|
defaultVideoSink.allowReleaseFirstFrameBeforeStarted();
|
||||||
|
}
|
||||||
|
isFirstStreamChange = false;
|
||||||
|
}
|
||||||
|
newPendingStreamChanges.add(streamChangeInfo.fromTimestampUs, streamChangeInfo);
|
||||||
|
}
|
||||||
|
pendingStreamChanges = newPendingStreamChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -883,13 +914,6 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
defaultVideoSink.setChangeFrameRateStrategy(changeFrameRateStrategy);
|
defaultVideoSink.setChangeFrameRateStrategy(changeFrameRateStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enableMayRenderStartOfStream() {
|
|
||||||
if (nextFirstOutputFrameReleaseInstruction == RELEASE_FIRST_FRAME_WHEN_STARTED) {
|
|
||||||
nextFirstOutputFrameReleaseInstruction = RELEASE_FIRST_FRAME_IMMEDIATELY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handleInputFrame(
|
public boolean handleInputFrame(
|
||||||
long framePresentationTimeUs, boolean isLastFrame, VideoFrameHandler videoFrameHandler) {
|
long framePresentationTimeUs, boolean isLastFrame, VideoFrameHandler videoFrameHandler) {
|
||||||
@ -1060,6 +1084,21 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class StreamChangeInfo {
|
||||||
|
public final long startPositionUs;
|
||||||
|
public final @VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction;
|
||||||
|
public final long fromTimestampUs;
|
||||||
|
|
||||||
|
public StreamChangeInfo(
|
||||||
|
long startPositionUs,
|
||||||
|
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
|
||||||
|
long fromTimestampUs) {
|
||||||
|
this.startPositionUs = startPositionUs;
|
||||||
|
this.firstFrameReleaseInstruction = firstFrameReleaseInstruction;
|
||||||
|
this.fromTimestampUs = fromTimestampUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Delays reflection for loading a {@link VideoGraph.Factory SingleInputVideoGraph} instance. */
|
/** Delays reflection for loading a {@link VideoGraph.Factory SingleInputVideoGraph} instance. */
|
||||||
private static final class ReflectiveSingleInputVideoGraphFactory implements VideoGraph.Factory {
|
private static final class ReflectiveSingleInputVideoGraphFactory implements VideoGraph.Factory {
|
||||||
|
|
||||||
|
@ -154,9 +154,6 @@ public interface VideoSink {
|
|||||||
*/
|
*/
|
||||||
int RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED = 2;
|
int RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED = 2;
|
||||||
|
|
||||||
/** Called when the {@link Renderer} currently feeding this sink is enabled. */
|
|
||||||
void onRendererEnabled(boolean mayRenderStartOfStream);
|
|
||||||
|
|
||||||
/** Called when the {@link Renderer} currently feeding this sink is started. */
|
/** Called when the {@link Renderer} currently feeding this sink is started. */
|
||||||
void onRendererStarted();
|
void onRendererStarted();
|
||||||
|
|
||||||
@ -261,15 +258,6 @@ public interface VideoSink {
|
|||||||
*/
|
*/
|
||||||
void setChangeFrameRateStrategy(@C.VideoChangeFrameRateStrategy int changeFrameRateStrategy);
|
void setChangeFrameRateStrategy(@C.VideoChangeFrameRateStrategy int changeFrameRateStrategy);
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables this video sink to render the start of the stream to its output surface even if the
|
|
||||||
* renderer is not {@linkplain #onRendererStarted() started} yet.
|
|
||||||
*
|
|
||||||
* <p>This is used to update the value of {@code mayRenderStartOfStream} passed to {@link
|
|
||||||
* #onRendererEnabled(boolean)}.
|
|
||||||
*/
|
|
||||||
void enableMayRenderStartOfStream();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs the video sink that a new input stream will be queued with the given effects.
|
* Informs the video sink that a new input stream will be queued with the given effects.
|
||||||
*
|
*
|
||||||
@ -290,6 +278,15 @@ public interface VideoSink {
|
|||||||
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
|
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
|
||||||
List<Effect> videoEffects);
|
List<Effect> videoEffects);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the sink to release the first frame even if rendering is not {@linkplain
|
||||||
|
* #onRendererStarted() started}.
|
||||||
|
*
|
||||||
|
* <p>This is used to update the {@link FirstFrameReleaseInstruction} of the {@linkplain
|
||||||
|
* #onInputStreamChanged(int, Format, long, int, List) stream} that is currently being processed.
|
||||||
|
*/
|
||||||
|
void allowReleaseFirstFrameBeforeStarted();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a video input frame.
|
* Handles a video input frame.
|
||||||
*
|
*
|
||||||
|
@ -81,11 +81,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
pendingOperations.clear();
|
pendingOperations.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRendererEnabled(boolean mayRenderStartOfStream) {
|
|
||||||
executeOrDelay(videoSink -> videoSink.onRendererEnabled(mayRenderStartOfStream));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRendererStarted() {
|
public void onRendererStarted() {
|
||||||
executeOrDelay(VideoSink::onRendererStarted);
|
executeOrDelay(VideoSink::onRendererStarted);
|
||||||
@ -213,11 +208,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
executeOrDelay(videoSink -> videoSink.setChangeFrameRateStrategy(changeFrameRateStrategy));
|
executeOrDelay(videoSink -> videoSink.setChangeFrameRateStrategy(changeFrameRateStrategy));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enableMayRenderStartOfStream() {
|
|
||||||
executeOrDelay(VideoSink::enableMayRenderStartOfStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInputStreamChanged(
|
public void onInputStreamChanged(
|
||||||
@InputType int inputType,
|
@InputType int inputType,
|
||||||
@ -231,6 +221,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
inputType, format, startPositionUs, firstFrameReleaseInstruction, videoEffects));
|
inputType, format, startPositionUs, firstFrameReleaseInstruction, videoEffects));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void allowReleaseFirstFrameBeforeStarted() {
|
||||||
|
executeOrDelay(VideoSink::allowReleaseFirstFrameBeforeStarted);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
|
@ -24,7 +24,9 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
|||||||
import static androidx.media3.common.util.Util.SDK_INT;
|
import static androidx.media3.common.util.Util.SDK_INT;
|
||||||
import static androidx.media3.exoplayer.DefaultRenderersFactory.DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS;
|
import static androidx.media3.exoplayer.DefaultRenderersFactory.DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS;
|
||||||
import static androidx.media3.exoplayer.DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY;
|
import static androidx.media3.exoplayer.DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY;
|
||||||
|
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_IMMEDIATELY;
|
||||||
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
||||||
|
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_STARTED;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@ -435,12 +437,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void changeVideoSinkInputStream(
|
protected void changeVideoSinkInputStream(
|
||||||
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
|
VideoSink videoSink,
|
||||||
|
@VideoSink.InputType int inputType,
|
||||||
|
Format format,
|
||||||
|
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction) {
|
||||||
videoSink.onInputStreamChanged(
|
videoSink.onInputStreamChanged(
|
||||||
inputType,
|
inputType,
|
||||||
format,
|
format,
|
||||||
getOutputStreamStartPositionUs(),
|
getOutputStreamStartPositionUs(),
|
||||||
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
|
firstFrameReleaseInstruction,
|
||||||
pendingEffects);
|
pendingEffects);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,6 +498,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private boolean inputStreamPending;
|
private boolean inputStreamPending;
|
||||||
private long streamStartPositionUs;
|
private long streamStartPositionUs;
|
||||||
private boolean mayRenderStartOfStream;
|
private boolean mayRenderStartOfStream;
|
||||||
|
private @VideoSink.FirstFrameReleaseInstruction int nextFirstFrameReleaseInstruction;
|
||||||
private long offsetToCompositionTimeUs;
|
private long offsetToCompositionTimeUs;
|
||||||
|
|
||||||
public SequenceImageRenderer(
|
public SequenceImageRenderer(
|
||||||
@ -513,7 +519,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
super.onEnabled(joining, mayRenderStartOfStream);
|
super.onEnabled(joining, mayRenderStartOfStream);
|
||||||
this.mayRenderStartOfStream = mayRenderStartOfStream;
|
this.mayRenderStartOfStream = mayRenderStartOfStream;
|
||||||
videoSink.onRendererEnabled(mayRenderStartOfStream);
|
nextFirstFrameReleaseInstruction =
|
||||||
|
mayRenderStartOfStream
|
||||||
|
? RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||||
|
: RELEASE_FIRST_FRAME_WHEN_STARTED;
|
||||||
// TODO: b/328444280 - Do not set a listener on VideoSink, but MediaCodecVideoRenderer must
|
// TODO: b/328444280 - Do not set a listener on VideoSink, but MediaCodecVideoRenderer must
|
||||||
// unregister itself as a listener too.
|
// unregister itself as a listener too.
|
||||||
videoSink.setListener(VideoSink.Listener.NO_OP, /* executor= */ (runnable) -> {});
|
videoSink.setListener(VideoSink.Listener.NO_OP, /* executor= */ (runnable) -> {});
|
||||||
@ -630,8 +639,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
.setFrameRate(/* frameRate= */ DEFAULT_FRAME_RATE)
|
.setFrameRate(/* frameRate= */ DEFAULT_FRAME_RATE)
|
||||||
.build(),
|
.build(),
|
||||||
streamStartPositionUs,
|
streamStartPositionUs,
|
||||||
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
|
nextFirstFrameReleaseInstruction,
|
||||||
videoEffects);
|
videoEffects);
|
||||||
|
nextFirstFrameReleaseInstruction = RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
||||||
inputStreamPending = false;
|
inputStreamPending = false;
|
||||||
}
|
}
|
||||||
if (!videoSink.handleInputBitmap(outputImage, checkStateNotNull(timestampIterator))) {
|
if (!videoSink.handleInputBitmap(outputImage, checkStateNotNull(timestampIterator))) {
|
||||||
|
@ -40,26 +40,25 @@ public class BufferingVideoSinkTest {
|
|||||||
VideoSink videoSinkMock = mock(VideoSink.class);
|
VideoSink videoSinkMock = mock(VideoSink.class);
|
||||||
|
|
||||||
bufferingVideoSink.setVideoSink(videoSinkMock);
|
bufferingVideoSink.setVideoSink(videoSinkMock);
|
||||||
bufferingVideoSink.onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
|
||||||
bufferingVideoSink.onRendererStarted();
|
bufferingVideoSink.onRendererStarted();
|
||||||
|
bufferingVideoSink.flush(/* resetPosition= */ true);
|
||||||
|
|
||||||
InOrder inOrder = Mockito.inOrder(videoSinkMock);
|
InOrder inOrder = Mockito.inOrder(videoSinkMock);
|
||||||
inOrder.verify(videoSinkMock).onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
|
||||||
inOrder.verify(videoSinkMock).onRendererStarted();
|
inOrder.verify(videoSinkMock).onRendererStarted();
|
||||||
|
inOrder.verify(videoSinkMock).flush(/* resetPosition= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setVideoSink_executesPendingOperations() {
|
public void setVideoSink_executesPendingOperations() {
|
||||||
BufferingVideoSink bufferingVideoSink = new BufferingVideoSink(context);
|
BufferingVideoSink bufferingVideoSink = new BufferingVideoSink(context);
|
||||||
VideoSink videoSinkMock = mock(VideoSink.class);
|
VideoSink videoSinkMock = mock(VideoSink.class);
|
||||||
|
|
||||||
bufferingVideoSink.onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
|
||||||
bufferingVideoSink.onRendererStarted();
|
bufferingVideoSink.onRendererStarted();
|
||||||
|
bufferingVideoSink.flush(/* resetPosition= */ true);
|
||||||
bufferingVideoSink.setVideoSink(videoSinkMock);
|
bufferingVideoSink.setVideoSink(videoSinkMock);
|
||||||
|
|
||||||
InOrder inOrder = Mockito.inOrder(videoSinkMock);
|
InOrder inOrder = Mockito.inOrder(videoSinkMock);
|
||||||
inOrder.verify(videoSinkMock).onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
|
||||||
inOrder.verify(videoSinkMock).onRendererStarted();
|
inOrder.verify(videoSinkMock).onRendererStarted();
|
||||||
|
inOrder.verify(videoSinkMock).flush(/* resetPosition= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -69,11 +68,11 @@ public class BufferingVideoSinkTest {
|
|||||||
bufferingVideoSink.setVideoSink(videoSinkMock);
|
bufferingVideoSink.setVideoSink(videoSinkMock);
|
||||||
|
|
||||||
bufferingVideoSink.setVideoSink(null);
|
bufferingVideoSink.setVideoSink(null);
|
||||||
bufferingVideoSink.onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
|
||||||
bufferingVideoSink.onRendererStarted();
|
bufferingVideoSink.onRendererStarted();
|
||||||
|
bufferingVideoSink.flush(/* resetPosition= */ true);
|
||||||
|
|
||||||
verify(videoSinkMock, never()).onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
|
||||||
verify(videoSinkMock, never()).onRendererStarted();
|
verify(videoSinkMock, never()).onRendererStarted();
|
||||||
|
verify(videoSinkMock, never()).flush(/* resetPosition= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -81,12 +80,12 @@ public class BufferingVideoSinkTest {
|
|||||||
BufferingVideoSink bufferingVideoSink = new BufferingVideoSink(context);
|
BufferingVideoSink bufferingVideoSink = new BufferingVideoSink(context);
|
||||||
VideoSink videoSinkMock = mock(VideoSink.class);
|
VideoSink videoSinkMock = mock(VideoSink.class);
|
||||||
|
|
||||||
bufferingVideoSink.onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
|
||||||
bufferingVideoSink.onRendererStarted();
|
bufferingVideoSink.onRendererStarted();
|
||||||
|
bufferingVideoSink.flush(/* resetPosition= */ true);
|
||||||
bufferingVideoSink.clearPendingOperations();
|
bufferingVideoSink.clearPendingOperations();
|
||||||
bufferingVideoSink.setVideoSink(videoSinkMock);
|
bufferingVideoSink.setVideoSink(videoSinkMock);
|
||||||
|
|
||||||
verify(videoSinkMock, never()).onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
|
||||||
verify(videoSinkMock, never()).onRendererStarted();
|
verify(videoSinkMock, never()).onRendererStarted();
|
||||||
|
verify(videoSinkMock, never()).flush(/* resetPosition= */ true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user