Discard already written sample data for clipped DASH periods

DASH periods can have a duration that is less than the end of the
last chunk in the period. In these cases, the sample data needs to
be clipped to the declared period duration. This already happens
IF the period duration is known at the point where we start loading
the media chunk. However, if the duration becomes known later or is
reduced (e.g. in a live stream), the existing media chunks are not
clipped. This causes unclean transitions across periods where the
player tries to transition to the next period, but renderers struggle
to output all the remaining surplus samples that should have been
clipped.

This can be fixed by asking ChunkSampleStream to discard surplus
samples that were loaded beyond a clipped duration when evaluating
the sample queue between chunk loads.

Issue: androidx/media#1698
PiperOrigin-RevId: 713288221
This commit is contained in:
tonihei 2025-01-08 07:44:36 -08:00 committed by Copybara-Service
parent bb3b85a359
commit b321c8d3bd
3 changed files with 42 additions and 0 deletions

View File

@ -62,6 +62,9 @@
* Parse `scte214:supplementalCodecs` attribute from DASH manifest to
detect Dolby Vision formats
([#1785](https://github.com/androidx/media/pull/1785)).
* Improve handling of period transitions in live streams where the period
contains media samples beyond the declared period duration
([#1698](https://github.com/androidx/media/issues/1698)).
* Smooth Streaming Extension:
* RTSP Extension:
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):

View File

@ -722,6 +722,39 @@ public class ChunkSampleStream<T extends ChunkSource>
}
}
/**
* Discards upstream samples that exceed the given clipped duration of the stream.
*
* @param clippedDurationUs The clipped duration of the stream in microseconds, or {@link
* C#TIME_UNSET} if not known.
*/
public void discardUpstreamSamplesForClippedDuration(long clippedDurationUs) {
Assertions.checkState(!loader.isLoading());
if (isPendingReset() || clippedDurationUs == C.TIME_UNSET || mediaChunks.isEmpty()) {
return;
}
BaseMediaChunk lastMediaChunk = getLastMediaChunk();
long lastMediaChunkEndTimeUs =
lastMediaChunk.clippedEndTimeUs != C.TIME_UNSET
? lastMediaChunk.clippedEndTimeUs
: lastMediaChunk.endTimeUs;
if (lastMediaChunkEndTimeUs <= clippedDurationUs) {
// Last chunk doesn't need to be clipped further.
return;
}
long largestQueuedTimestampUs = primarySampleQueue.getLargestQueuedTimestampUs();
if (largestQueuedTimestampUs <= clippedDurationUs) {
// No data beyond new duration that needs to be clipped.
return;
}
primarySampleQueue.discardUpstreamFrom(clippedDurationUs);
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.discardUpstreamFrom(clippedDurationUs);
}
mediaSourceEventDispatcher.upstreamDiscarded(
primaryTrackType, clippedDurationUs, largestQueuedTimestampUs);
}
private void discardUpstream(int preferredQueueSize) {
Assertions.checkState(!loader.isLoading());

View File

@ -324,6 +324,12 @@ import java.util.regex.Pattern;
@Override
public void reevaluateBuffer(long positionUs) {
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
if (!sampleStream.isLoading()) {
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
sampleStream.discardUpstreamSamplesForClippedDuration(periodDurationUs);
}
}
compositeSequenceableLoader.reevaluateBuffer(positionUs);
}