Fix offset passed to VideoFrameReleaseControl

In some cases, the streamOffsetUs was passed to
VideoFrameReleaseControl.getFrameReleaseAction() but it should be the
streamStartPositionUs.

PiperOrigin-RevId: 691040172
This commit is contained in:
kimvde 2024-10-29 09:02:00 -07:00 committed by Copybara-Service
parent 7f94aaf49f
commit 14094b5094
8 changed files with 51 additions and 74 deletions

View file

@ -124,10 +124,7 @@ import java.util.concurrent.Executor;
@Override @Override
public void setStreamTimestampInfo( public void setStreamTimestampInfo(
long streamStartPositionUs, long streamStartPositionUs, long bufferTimestampAdjustmentUs, long lastResetPositionUs) {
long streamOffsetUs,
long bufferTimestampAdjustmentUs,
long lastResetPositionUs) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -806,7 +806,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
videoSink.flush(/* resetPosition= */ true); videoSink.flush(/* resetPosition= */ true);
videoSink.setStreamTimestampInfo( videoSink.setStreamTimestampInfo(
getOutputStreamStartPositionUs(), getOutputStreamStartPositionUs(),
getOutputStreamOffsetUs(),
getBufferTimestampAdjustmentUs(), getBufferTimestampAdjustmentUs(),
getLastResetPositionUs()); getLastResetPositionUs());
pendingVideoSinkInputStreamChange = true; pendingVideoSinkInputStreamChange = true;
@ -1608,7 +1607,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
if (videoSink != null) { if (videoSink != null) {
videoSink.setStreamTimestampInfo( videoSink.setStreamTimestampInfo(
getOutputStreamStartPositionUs(), getOutputStreamStartPositionUs(),
getOutputStreamOffsetUs(),
getBufferTimestampAdjustmentUs(), getBufferTimestampAdjustmentUs(),
getLastResetPositionUs()); getLastResetPositionUs());
} else { } else {

View file

@ -468,10 +468,11 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
defaultVideoSink.setPlaybackSpeed(speed); defaultVideoSink.setPlaybackSpeed(speed);
} }
private void onStreamOffsetChange( private void onStreamTimestampInfoChange(
long bufferTimestampAdjustmentUs, long bufferPresentationTimeUs, long streamOffsetUs) { long bufferTimestampAdjustmentUs, long bufferPresentationTimeUs, long streamStartPositionUs) {
this.bufferTimestampAdjustmentUs = bufferTimestampAdjustmentUs; this.bufferTimestampAdjustmentUs = bufferTimestampAdjustmentUs;
videoFrameRenderControl.onStreamOffsetChange(bufferPresentationTimeUs, streamOffsetUs); videoFrameRenderControl.onStreamStartPositionChange(
bufferPresentationTimeUs, streamStartPositionUs);
} }
private static ColorInfo getAdjustedInputColorInfo(@Nullable ColorInfo inputColorInfo) { private static ColorInfo getAdjustedInputColorInfo(@Nullable ColorInfo inputColorInfo) {
@ -493,10 +494,9 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
@Nullable private Format inputFormat; @Nullable private Format inputFormat;
private @InputType int inputType; private @InputType int inputType;
private long inputStreamStartPositionUs; private long inputStreamStartPositionUs;
private long inputStreamOffsetUs;
private long inputBufferTimestampAdjustmentUs; private long inputBufferTimestampAdjustmentUs;
private long lastResetPositionUs; private long lastResetPositionUs;
private boolean pendingInputStreamOffsetChange; private boolean pendingInputStreamTimestampInfoChange;
/** The buffer presentation time, in microseconds, of the final frame in the stream. */ /** The buffer presentation time, in microseconds, of the final frame in the stream. */
private long finalBufferPresentationTimeUs; private long finalBufferPresentationTimeUs;
@ -575,8 +575,8 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
lastBufferPresentationTimeUs = C.TIME_UNSET; lastBufferPresentationTimeUs = C.TIME_UNSET;
PlaybackVideoGraphWrapper.this.flush(resetPosition); PlaybackVideoGraphWrapper.this.flush(resetPosition);
pendingInputStreamBufferPresentationTimeUs = C.TIME_UNSET; pendingInputStreamBufferPresentationTimeUs = C.TIME_UNSET;
// Don't change input stream offset or reset the pending input stream offset change so that // Don't change input stream start position or reset the pending input stream timestamp info
// it's announced with the next input frame. // change so that it's announced with the next input frame.
// Don't reset isInputStreamChangePending because it's not guaranteed to receive a new input // Don't reset isInputStreamChangePending because it's not guaranteed to receive a new input
// stream after seeking. // stream after seeking.
} }
@ -658,16 +658,12 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
@Override @Override
public void setStreamTimestampInfo( public void setStreamTimestampInfo(
long streamStartPositionUs, long streamStartPositionUs, long bufferTimestampAdjustmentUs, long lastResetPositionUs) {
long streamOffsetUs, // Ors because this method could be called multiple times on a timestamp info change.
long bufferTimestampAdjustmentUs, pendingInputStreamTimestampInfoChange |=
long lastResetPositionUs) { inputStreamStartPositionUs != streamStartPositionUs
// Ors because this method could be called multiple times on a stream offset change.
pendingInputStreamOffsetChange |=
inputStreamOffsetUs != streamOffsetUs
|| inputBufferTimestampAdjustmentUs != bufferTimestampAdjustmentUs; || inputBufferTimestampAdjustmentUs != bufferTimestampAdjustmentUs;
inputStreamStartPositionUs = streamStartPositionUs; inputStreamStartPositionUs = streamStartPositionUs;
inputStreamOffsetUs = streamOffsetUs;
inputBufferTimestampAdjustmentUs = bufferTimestampAdjustmentUs; inputBufferTimestampAdjustmentUs = bufferTimestampAdjustmentUs;
this.lastResetPositionUs = lastResetPositionUs; this.lastResetPositionUs = lastResetPositionUs;
} }
@ -765,7 +761,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
return false; return false;
} }
maybeSetStreamOffsetChange(bufferPresentationTimeUs); maybeSetStreamTimestampInfo(bufferPresentationTimeUs);
lastBufferPresentationTimeUs = bufferPresentationTimeUs; lastBufferPresentationTimeUs = bufferPresentationTimeUs;
if (isLastFrame) { if (isLastFrame) {
finalBufferPresentationTimeUs = bufferPresentationTimeUs; finalBufferPresentationTimeUs = bufferPresentationTimeUs;
@ -798,7 +794,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
long lastBufferPresentationTimeUs = long lastBufferPresentationTimeUs =
copyTimestampIterator.getLastTimestampUs() - inputBufferTimestampAdjustmentUs; copyTimestampIterator.getLastTimestampUs() - inputBufferTimestampAdjustmentUs;
checkState(lastBufferPresentationTimeUs != C.TIME_UNSET); checkState(lastBufferPresentationTimeUs != C.TIME_UNSET);
maybeSetStreamOffsetChange(bufferPresentationTimeUs); maybeSetStreamTimestampInfo(bufferPresentationTimeUs);
this.lastBufferPresentationTimeUs = lastBufferPresentationTimeUs; this.lastBufferPresentationTimeUs = lastBufferPresentationTimeUs;
finalBufferPresentationTimeUs = lastBufferPresentationTimeUs; finalBufferPresentationTimeUs = lastBufferPresentationTimeUs;
return true; return true;
@ -821,13 +817,11 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
// Other methods // Other methods
private void maybeSetStreamOffsetChange(long bufferPresentationTimeUs) { private void maybeSetStreamTimestampInfo(long bufferPresentationTimeUs) {
if (pendingInputStreamOffsetChange) { if (pendingInputStreamTimestampInfoChange) {
PlaybackVideoGraphWrapper.this.onStreamOffsetChange( PlaybackVideoGraphWrapper.this.onStreamTimestampInfoChange(
inputBufferTimestampAdjustmentUs, inputBufferTimestampAdjustmentUs, bufferPresentationTimeUs, inputStreamStartPositionUs);
bufferPresentationTimeUs, pendingInputStreamTimestampInfoChange = false;
/* streamOffsetUs= */ inputStreamOffsetUs);
pendingInputStreamOffsetChange = false;
} }
} }

View file

@ -65,7 +65,7 @@ import androidx.media3.exoplayer.ExoPlaybackException;
private final VideoFrameReleaseControl videoFrameReleaseControl; private final VideoFrameReleaseControl videoFrameReleaseControl;
private final VideoFrameReleaseControl.FrameReleaseInfo videoFrameReleaseInfo; private final VideoFrameReleaseControl.FrameReleaseInfo videoFrameReleaseInfo;
private final TimedValueQueue<VideoSize> videoSizeChanges; private final TimedValueQueue<VideoSize> videoSizeChanges;
private final TimedValueQueue<Long> streamOffsets; private final TimedValueQueue<Long> streamStartPositionsUs;
private final LongArrayQueue presentationTimestampsUs; private final LongArrayQueue presentationTimestampsUs;
/** /**
@ -76,7 +76,7 @@ import androidx.media3.exoplayer.ExoPlaybackException;
@Nullable private VideoSize pendingOutputVideoSize; @Nullable private VideoSize pendingOutputVideoSize;
private VideoSize reportedVideoSize; private VideoSize reportedVideoSize;
private long outputStreamOffsetUs; private long outputStreamStartPositionUs;
private long lastPresentationTimeUs; private long lastPresentationTimeUs;
/** Creates an instance. */ /** Creates an instance. */
@ -86,7 +86,7 @@ import androidx.media3.exoplayer.ExoPlaybackException;
this.videoFrameReleaseControl = videoFrameReleaseControl; this.videoFrameReleaseControl = videoFrameReleaseControl;
videoFrameReleaseInfo = new VideoFrameReleaseControl.FrameReleaseInfo(); videoFrameReleaseInfo = new VideoFrameReleaseControl.FrameReleaseInfo();
videoSizeChanges = new TimedValueQueue<>(); videoSizeChanges = new TimedValueQueue<>();
streamOffsets = new TimedValueQueue<>(); streamStartPositionsUs = new TimedValueQueue<>();
presentationTimestampsUs = new LongArrayQueue(); presentationTimestampsUs = new LongArrayQueue();
reportedVideoSize = VideoSize.UNKNOWN; reportedVideoSize = VideoSize.UNKNOWN;
lastPresentationTimeUs = C.TIME_UNSET; lastPresentationTimeUs = C.TIME_UNSET;
@ -96,13 +96,13 @@ import androidx.media3.exoplayer.ExoPlaybackException;
public void flush() { public void flush() {
presentationTimestampsUs.clear(); presentationTimestampsUs.clear();
lastPresentationTimeUs = C.TIME_UNSET; lastPresentationTimeUs = C.TIME_UNSET;
if (streamOffsets.size() > 0) { if (streamStartPositionsUs.size() > 0) {
// There is a pending streaming offset change. If seeking within the same stream, keep the // There is a pending streaming start position change. If seeking within the same stream, keep
// pending offset with timestamp zero ensures the offset is applied on the frames after // the pending start position with timestamp zero ensures the start position is applied on the
// flushing. Otherwise if seeking to another stream, a new offset will be set before a new // frames after flushing. Otherwise if seeking to another stream, a new start position will be
// frame arrives so we'll be able to apply the new offset. // set before a new frame arrives so we'll be able to apply the new start position.
long lastStreamOffset = getLastAndClear(streamOffsets); long lastStartPositionUs = getLastAndClear(streamStartPositionsUs);
streamOffsets.add(/* timestamp= */ 0, lastStreamOffset); streamStartPositionsUs.add(/* timestamp= */ 0, lastStartPositionUs);
} }
if (pendingOutputVideoSize == null) { if (pendingOutputVideoSize == null) {
if (videoSizeChanges.size() > 0) { if (videoSizeChanges.size() > 0) {
@ -139,8 +139,8 @@ import androidx.media3.exoplayer.ExoPlaybackException;
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
while (!presentationTimestampsUs.isEmpty()) { while (!presentationTimestampsUs.isEmpty()) {
long presentationTimeUs = presentationTimestampsUs.element(); long presentationTimeUs = presentationTimestampsUs.element();
// Check whether this buffer comes with a new stream offset. // Check whether this buffer comes with a new stream start position.
if (maybeUpdateOutputStreamOffset(presentationTimeUs)) { if (maybeUpdateOutputStreamStartPosition(presentationTimeUs)) {
videoFrameReleaseControl.onProcessedStreamChange(); videoFrameReleaseControl.onProcessedStreamChange();
} }
@VideoFrameReleaseControl.FrameReleaseAction @VideoFrameReleaseControl.FrameReleaseAction
@ -149,7 +149,7 @@ import androidx.media3.exoplayer.ExoPlaybackException;
presentationTimeUs, presentationTimeUs,
positionUs, positionUs,
elapsedRealtimeUs, elapsedRealtimeUs,
outputStreamOffsetUs, outputStreamStartPositionUs,
/* isLastFrame= */ false, /* isLastFrame= */ false,
videoFrameReleaseInfo); videoFrameReleaseInfo);
switch (frameReleaseAction) { switch (frameReleaseAction) {
@ -195,8 +195,8 @@ import androidx.media3.exoplayer.ExoPlaybackException;
// TODO b/257464707 - Support extensively modified media. // TODO b/257464707 - Support extensively modified media.
} }
public void onStreamOffsetChange(long presentationTimeUs, long streamOffsetUs) { public void onStreamStartPositionChange(long presentationTimeUs, long streamStartPositionUs) {
streamOffsets.add(presentationTimeUs, streamOffsetUs); streamStartPositionsUs.add(presentationTimeUs, streamStartPositionUs);
} }
private void dropFrame() { private void dropFrame() {
@ -219,10 +219,12 @@ import androidx.media3.exoplayer.ExoPlaybackException;
renderTimeNs, presentationTimeUs, videoFrameReleaseControl.onFrameReleasedIsFirstFrame()); renderTimeNs, presentationTimeUs, videoFrameReleaseControl.onFrameReleasedIsFirstFrame());
} }
private boolean maybeUpdateOutputStreamOffset(long presentationTimeUs) { private boolean maybeUpdateOutputStreamStartPosition(long presentationTimeUs) {
@Nullable Long newOutputStreamOffsetUs = streamOffsets.pollFloor(presentationTimeUs); @Nullable
if (newOutputStreamOffsetUs != null && newOutputStreamOffsetUs != outputStreamOffsetUs) { Long newOutputStreamStartPositionUs = streamStartPositionsUs.pollFloor(presentationTimeUs);
outputStreamOffsetUs = newOutputStreamOffsetUs; if (newOutputStreamStartPositionUs != null
&& newOutputStreamStartPositionUs != outputStreamStartPositionUs) {
outputStreamStartPositionUs = newOutputStreamStartPositionUs;
return true; return true;
} }
return false; return false;

View file

@ -207,17 +207,12 @@ public interface VideoSink {
* *
* @param streamStartPositionUs The start position of the buffer presentation timestamps of the * @param streamStartPositionUs The start position of the buffer presentation timestamps of the
* current stream, in microseconds. * current stream, in microseconds.
* @param streamOffsetUs The offset that is added to the buffer presentation timestamps by the
* player, in microseconds.
* @param bufferTimestampAdjustmentUs The timestamp adjustment to add to the buffer presentation * @param bufferTimestampAdjustmentUs The timestamp adjustment to add to the buffer presentation
* timestamps to convert them to frame presentation timestamps, in microseconds. * timestamps to convert them to frame presentation timestamps, in microseconds.
* @param lastResetPositionUs The renderer last reset position, in microseconds. * @param lastResetPositionUs The renderer last reset position, in microseconds.
*/ */
void setStreamTimestampInfo( void setStreamTimestampInfo(
long streamStartPositionUs, long streamStartPositionUs, long bufferTimestampAdjustmentUs, long lastResetPositionUs);
long streamOffsetUs,
long bufferTimestampAdjustmentUs,
long lastResetPositionUs);
/** Sets the output surface info. */ /** Sets the output surface info. */
void setOutputSurfaceInfo(Surface outputSurface, Size outputResolution); void setOutputSurfaceInfo(Surface outputSurface, Size outputResolution);

View file

@ -21,7 +21,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import androidx.media3.common.VideoSize; import androidx.media3.common.VideoSize;
import androidx.media3.exoplayer.ExoPlaybackException;
import androidx.media3.test.utils.FakeClock; import androidx.media3.test.utils.FakeClock;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -109,7 +108,7 @@ public class VideoFrameRenderControlTest {
} }
@Test @Test
public void renderFrames_withStreamOffsetSetChange_firstFrameAgain() throws Exception { public void renderFrames_withStreamStartPositionChange_firstFrameAgain() throws Exception {
VideoFrameRenderControl.FrameRenderer frameRenderer = VideoFrameRenderControl.FrameRenderer frameRenderer =
mock(VideoFrameRenderControl.FrameRenderer.class); mock(VideoFrameRenderControl.FrameRenderer.class);
FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false); FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false);
@ -122,8 +121,8 @@ public class VideoFrameRenderControlTest {
videoFrameReleaseControl.onStarted(); videoFrameReleaseControl.onStarted();
videoFrameRenderControl.onOutputSizeChanged( videoFrameRenderControl.onOutputSizeChanged(
/* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT); /* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT);
videoFrameRenderControl.onStreamOffsetChange( videoFrameRenderControl.onStreamStartPositionChange(
/* presentationTimeUs= */ 0, /* streamOffsetUs= */ 10_000); /* presentationTimeUs= */ 0, /* streamStartPositionUs= */ 10_000);
videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 0); videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 0);
videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0);
@ -131,18 +130,18 @@ public class VideoFrameRenderControlTest {
inOrder inOrder
.verify(frameRenderer) .verify(frameRenderer)
.onVideoSizeChanged(new VideoSize(/* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT)); .onVideoSizeChanged(new VideoSize(/* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT));
// First frame has the first stream offset. // First frame has the first stream start position.
inOrder.verify(frameRenderer).renderFrame(anyLong(), eq(0L), eq(true)); inOrder.verify(frameRenderer).renderFrame(anyLong(), eq(0L), eq(true));
inOrder.verifyNoMoreInteractions(); inOrder.verifyNoMoreInteractions();
// 10 milliseconds pass // 10 milliseconds pass
clock.advanceTime(/* timeDiffMs= */ 10); clock.advanceTime(/* timeDiffMs= */ 10);
videoFrameRenderControl.onStreamOffsetChange( videoFrameRenderControl.onStreamStartPositionChange(
/* presentationTimeUs= */ 10_000, /* streamOffsetUs= */ 20_000); /* presentationTimeUs= */ 10_000, /* streamStartPositionUs= */ 20_000);
videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 10_000); videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 10_000);
videoFrameRenderControl.render(/* positionUs= */ 10_000, /* elapsedRealtimeUs= */ 0); videoFrameRenderControl.render(/* positionUs= */ 10_000, /* elapsedRealtimeUs= */ 0);
// Second frame has the second stream offset and it is also a first frame. // Second frame has the second stream start position and it is also a first frame.
inOrder inOrder
.verify(frameRenderer) .verify(frameRenderer)
.renderFrame( .renderFrame(
@ -344,8 +343,7 @@ public class VideoFrameRenderControlTest {
long positionUs, long positionUs,
long elapsedRealtimeUs, long elapsedRealtimeUs,
boolean isLastFrame, boolean isLastFrame,
boolean treatDroppedBuffersAsSkipped) boolean treatDroppedBuffersAsSkipped) {
throws ExoPlaybackException {
return shouldIgnoreFrames; return shouldIgnoreFrames;
} }
} }

View file

@ -172,17 +172,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void setStreamTimestampInfo( public void setStreamTimestampInfo(
long streamStartPositionUs, long streamStartPositionUs, long bufferTimestampAdjustmentUs, long lastResetPositionUs) {
long streamOffsetUs,
long bufferTimestampAdjustmentUs,
long lastResetPositionUs) {
executeOrDelay( executeOrDelay(
videoSink -> videoSink ->
videoSink.setStreamTimestampInfo( videoSink.setStreamTimestampInfo(
streamStartPositionUs, streamStartPositionUs, bufferTimestampAdjustmentUs, lastResetPositionUs));
streamOffsetUs,
bufferTimestampAdjustmentUs,
lastResetPositionUs));
} }
@Override @Override

View file

@ -460,7 +460,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
videoSink.setPendingVideoEffects(videoEffects); videoSink.setPendingVideoEffects(videoEffects);
videoSink.setStreamTimestampInfo( videoSink.setStreamTimestampInfo(
streamStartPositionUs, streamStartPositionUs,
getStreamOffsetUs(),
/* bufferTimestampAdjustmentUs= */ offsetToCompositionTimeUs, /* bufferTimestampAdjustmentUs= */ offsetToCompositionTimeUs,
getLastResetPositionUs()); getLastResetPositionUs());
videoSink.onInputStreamChanged( videoSink.onInputStreamChanged(