mirror of
https://github.com/androidx/media.git
synced 2025-04-29 22:36:54 +08:00
Do not drop decoder input buffers close to a reset position
This is a workaround for a bug where the positionUs seen by MCVR jumps when audio pre-roll samples are discarded. PiperOrigin-RevId: 743538208 (cherry picked from commit 036bed36326130294f50264659913bdcecb4c9bc)
This commit is contained in:
parent
9cfaf78994
commit
82d7c628da
@ -149,6 +149,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
*/
|
*/
|
||||||
private static final long OFFSET_FROM_PERIOD_END_TO_TREAT_AS_LAST_US = 100_000L;
|
private static final long OFFSET_FROM_PERIOD_END_TO_TREAT_AS_LAST_US = 100_000L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The offset from {@link #getLastResetPositionUs()} in microseconds, before which input buffers
|
||||||
|
* are not allowed to be dropped.
|
||||||
|
*
|
||||||
|
* <p>This value must be greater than the pre-roll distance used by common audio codecs, such as
|
||||||
|
* 80ms used by Opus <a
|
||||||
|
* href="https://opus-codec.org/docs/opus_in_isobmff.html#4.3.6.2">Encapsulation of Opus in ISO
|
||||||
|
* Base Media File Format</a>
|
||||||
|
*/
|
||||||
|
private static final long OFFSET_FROM_RESET_POSITION_TO_ALLOW_INPUT_BUFFER_DROPPING_US = 200_000L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum number of consecutive dropped input buffers that allow discarding frame headers.
|
* The maximum number of consecutive dropped input buffers that allow discarding frame headers.
|
||||||
*
|
*
|
||||||
@ -616,7 +627,16 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
boolean treatDroppedBuffersAsSkipped)
|
boolean treatDroppedBuffersAsSkipped)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
if (minEarlyUsToDropDecoderInput != C.TIME_UNSET) {
|
if (minEarlyUsToDropDecoderInput != C.TIME_UNSET) {
|
||||||
shouldDropDecoderInputBuffers = earlyUs < minEarlyUsToDropDecoderInput;
|
// TODO: b/161996553 - Remove the isAwayFromLastResetPosition check when audio pre-rolling
|
||||||
|
// is implemented correctly. Audio codecs such as Opus require pre-roll samples to be decoded
|
||||||
|
// and discarded on a seek. Depending on the audio decoder, the positionUs may jump forward
|
||||||
|
// by the pre-roll duration. Do not drop more frames than necessary when this happens.
|
||||||
|
boolean isAwayFromLastResetPosition =
|
||||||
|
positionUs
|
||||||
|
> getLastResetPositionUs()
|
||||||
|
+ OFFSET_FROM_RESET_POSITION_TO_ALLOW_INPUT_BUFFER_DROPPING_US;
|
||||||
|
shouldDropDecoderInputBuffers =
|
||||||
|
isAwayFromLastResetPosition && earlyUs < minEarlyUsToDropDecoderInput;
|
||||||
}
|
}
|
||||||
return shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs, isLastFrame)
|
return shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs, isLastFrame)
|
||||||
&& maybeDropBuffersToKeyframe(positionUs, treatDroppedBuffersAsSkipped);
|
&& maybeDropBuffersToKeyframe(positionUs, treatDroppedBuffersAsSkipped);
|
||||||
|
@ -29,6 +29,7 @@ import androidx.media3.exoplayer.DecoderCounters;
|
|||||||
import androidx.media3.exoplayer.DefaultRenderersFactory;
|
import androidx.media3.exoplayer.DefaultRenderersFactory;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.exoplayer.Renderer;
|
import androidx.media3.exoplayer.Renderer;
|
||||||
|
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
||||||
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
|
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecAdapter;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector;
|
||||||
@ -103,6 +104,14 @@ public class ParseAv1SampleDependenciesPlaybackTest {
|
|||||||
new ExoPlayer.Builder(applicationContext, renderersFactory)
|
new ExoPlayer.Builder(applicationContext, renderersFactory)
|
||||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||||
.build();
|
.build();
|
||||||
|
player.addAnalyticsListener(
|
||||||
|
new AnalyticsListener() {
|
||||||
|
@Override
|
||||||
|
public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {
|
||||||
|
// Input buffers near the reset position should not be dropped.
|
||||||
|
assertThat(eventTime.currentPlaybackPositionMs).isAtLeast(200);
|
||||||
|
}
|
||||||
|
});
|
||||||
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
|
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
|
||||||
player.setVideoSurface(surface);
|
player.setVideoSurface(surface);
|
||||||
player.setMediaItem(MediaItem.fromUri(TEST_MP4_URI));
|
player.setMediaItem(MediaItem.fromUri(TEST_MP4_URI));
|
||||||
@ -121,7 +130,7 @@ public class ParseAv1SampleDependenciesPlaybackTest {
|
|||||||
// Which input buffer is dropped first depends on the number of MediaCodec buffer slots.
|
// Which input buffer is dropped first depends on the number of MediaCodec buffer slots.
|
||||||
// This means the asserts cannot be isEqualTo.
|
// This means the asserts cannot be isEqualTo.
|
||||||
assertThat(decoderCounters.maxConsecutiveDroppedBufferCount).isAtMost(2);
|
assertThat(decoderCounters.maxConsecutiveDroppedBufferCount).isAtMost(2);
|
||||||
assertThat(decoderCounters.droppedInputBufferCount).isAtLeast(8);
|
assertThat(decoderCounters.droppedInputBufferCount).isAtLeast(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class CapturingRenderersFactoryWithLateThresholdToDropDecoderInputUs
|
private static final class CapturingRenderersFactoryWithLateThresholdToDropDecoderInputUs
|
||||||
@ -155,7 +164,6 @@ public class ParseAv1SampleDependenciesPlaybackTest {
|
|||||||
/* enableDecoderFallback= */ false,
|
/* enableDecoderFallback= */ false,
|
||||||
eventHandler,
|
eventHandler,
|
||||||
videoRendererEventListener,
|
videoRendererEventListener,
|
||||||
DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY,
|
|
||||||
/* parseAv1SampleDependencies= */ true,
|
/* parseAv1SampleDependencies= */ true,
|
||||||
/* lateThresholdToDropDecoderInputUs= */ -100_000_000L)
|
/* lateThresholdToDropDecoderInputUs= */ -100_000_000L)
|
||||||
};
|
};
|
||||||
@ -173,7 +181,6 @@ public class ParseAv1SampleDependenciesPlaybackTest {
|
|||||||
boolean enableDecoderFallback,
|
boolean enableDecoderFallback,
|
||||||
@Nullable Handler eventHandler,
|
@Nullable Handler eventHandler,
|
||||||
@Nullable VideoRendererEventListener eventListener,
|
@Nullable VideoRendererEventListener eventListener,
|
||||||
int maxDroppedFramesToNotify,
|
|
||||||
boolean parseAv1SampleDependencies,
|
boolean parseAv1SampleDependencies,
|
||||||
long lateThresholdToDropDecoderInputUs) {
|
long lateThresholdToDropDecoderInputUs) {
|
||||||
super(
|
super(
|
||||||
@ -184,7 +191,7 @@ public class ParseAv1SampleDependenciesPlaybackTest {
|
|||||||
.setEnableDecoderFallback(enableDecoderFallback)
|
.setEnableDecoderFallback(enableDecoderFallback)
|
||||||
.setEventHandler(eventHandler)
|
.setEventHandler(eventHandler)
|
||||||
.setEventListener(eventListener)
|
.setEventListener(eventListener)
|
||||||
.setMaxDroppedFramesToNotify(maxDroppedFramesToNotify)
|
.setMaxDroppedFramesToNotify(1)
|
||||||
.experimentalSetParseAv1SampleDependencies(parseAv1SampleDependencies)
|
.experimentalSetParseAv1SampleDependencies(parseAv1SampleDependencies)
|
||||||
.experimentalSetLateThresholdToDropDecoderInputUs(
|
.experimentalSetLateThresholdToDropDecoderInputUs(
|
||||||
lateThresholdToDropDecoderInputUs));
|
lateThresholdToDropDecoderInputUs));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user