Skip to content

Commit

Permalink
Report initial discontinuity for DASH periods that require preroll
Browse files Browse the repository at this point in the history
DASH periods don't have to start at the beginning of a segment. In
these cases, they should report an initial discontinuity to let the
player know it needs to expect preroll data (e.g. to flush renderers)

This information is only available in the ChunkSampleStream after
loading the initialization data, so we need to check the sample
streams and tell them to only report discontinuities at the very
beginning of playback. All other position resets are triggered by
the player itself and don't need this method.

Issue: #1440
PiperOrigin-RevId: 668831563
  • Loading branch information
tonihei authored and copybara-github committed Aug 29, 2024
1 parent 8367e42 commit e8664db
Show file tree
Hide file tree
Showing 9 changed files with 421 additions and 5 deletions.
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
* Cronet Extension:
* RTMP Extension:
* HLS Extension:
* DASH Extension:
* Add support for periods starting in the middle of a segment
([#1440](https://github.com/androidx/media/issues/1440)).
* Smooth Streaming Extension:
* RTSP Extension:
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
Expand Down Expand Up @@ -95,6 +96,8 @@ public interface ReleaseCallback<T extends ChunkSource> {
private long lastSeekPositionUs;
private int nextNotifyPrimaryFormatMediaChunkIndex;
@Nullable private BaseMediaChunk canceledMediaChunk;
private boolean canReportInitialDiscontinuity;
private boolean hasInitialDiscontinuity;

/* package */ boolean loadingFinished;

Expand All @@ -114,6 +117,8 @@ public interface ReleaseCallback<T extends ChunkSource> {
* @param loadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy}.
* @param mediaSourceEventDispatcher A dispatcher to notify of {@link MediaSourceEventListener}
* events.
* @param canReportInitialDiscontinuity Whether the stream can report an initial discontinuity if
* the first chunk can't start at the beginning and needs to preroll data.
*/
public ChunkSampleStream(
@C.TrackType int primaryTrackType,
Expand All @@ -126,14 +131,16 @@ public ChunkSampleStream(
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher) {
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
boolean canReportInitialDiscontinuity) {
this.primaryTrackType = primaryTrackType;
this.embeddedTrackTypes = embeddedTrackTypes == null ? new int[0] : embeddedTrackTypes;
this.embeddedTrackFormats = embeddedTrackFormats == null ? new Format[0] : embeddedTrackFormats;
this.chunkSource = chunkSource;
this.callback = callback;
this.mediaSourceEventDispatcher = mediaSourceEventDispatcher;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.canReportInitialDiscontinuity = canReportInitialDiscontinuity;
loader = new Loader("ChunkSampleStream");
nextChunkHolder = new ChunkHolder();
mediaChunks = new ArrayList<>();
Expand Down Expand Up @@ -258,6 +265,7 @@ public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParame
*/
public void seekToUs(long positionUs) {
lastSeekPositionUs = positionUs;
canReportInitialDiscontinuity = false;
if (isPendingReset()) {
// A reset is already pending. We only need to update its position.
pendingResetPositionUs = positionUs;
Expand Down Expand Up @@ -600,12 +608,22 @@ public boolean continueLoading(LoadingInfo loadingInfo) {
// seeking to a chunk boundary then we want the queue to pass through all of the samples in
// the chunk. Doing this ensures we'll always output the keyframe at the start of the chunk,
// even if its timestamp is slightly earlier than the advertised chunk start time.
if (mediaChunk.startTimeUs != pendingResetPositionUs) {
if (mediaChunk.startTimeUs < pendingResetPositionUs) {
primarySampleQueue.setStartTimeUs(pendingResetPositionUs);
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.setStartTimeUs(pendingResetPositionUs);
}
if (canReportInitialDiscontinuity) {
// Only report it as discontinuity if the SampleQueue can't skip the samples directly.
boolean sampleQueueCanSkipSamples =
MimeTypes.allSamplesAreSyncSamples(
mediaChunk.trackFormat.sampleMimeType, mediaChunk.trackFormat.codecs);
hasInitialDiscontinuity = !sampleQueueCanSkipSamples;
}
}
// Once we started loading the first media chunk, no more initial discontinuities can be
// reported.
canReportInitialDiscontinuity = false;
pendingResetPositionUs = C.TIME_UNSET;
}
mediaChunk.init(chunkOutput);
Expand Down Expand Up @@ -670,6 +688,19 @@ && haveReadFromMediaChunk(/* mediaChunkIndex= */ mediaChunks.size() - 1)) {
}
}

/**
* Consumes a pending initial discontinuity.
*
* @return Whether the stream had an initial discontinuity.
*/
public boolean consumeInitialDiscontinuity() {
try {
return hasInitialDiscontinuity;
} finally {
hasInitialDiscontinuity = false;
}
}

private void discardUpstream(int preferredQueueSize) {
Assertions.checkState(!loader.isLoading());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@
private DashManifest manifest;
private int periodIndex;
private List<EventStream> eventStreams;
private boolean canReportInitialDiscontinuity;
private long initialStartTimeUs;

public DashMediaPeriod(
int id,
Expand Down Expand Up @@ -149,6 +151,7 @@ public DashMediaPeriod(
this.allocator = allocator;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
this.playerId = playerId;
this.canReportInitialDiscontinuity = true;
playerEmsgHandler = new PlayerEmsgHandler(manifest, playerEmsgCallback, allocator);
sampleStreams = newSampleStreamArray(0);
eventSampleStreams = new EventSampleStream[0];
Expand Down Expand Up @@ -305,6 +308,10 @@ public long selectTracks(
compositeSequenceableLoaderFactory.create(
sampleStreamList,
Lists.transform(sampleStreamList, s -> ImmutableList.of(s.primaryTrackType)));
if (canReportInitialDiscontinuity) {
canReportInitialDiscontinuity = false;
initialStartTimeUs = positionUs;
}
return positionUs;
}

Expand Down Expand Up @@ -337,6 +344,11 @@ public long getNextLoadPositionUs() {

@Override
public long readDiscontinuity() {
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
if (sampleStream.consumeInitialDiscontinuity()) {
return initialStartTimeUs;
}
}
return C.TIME_UNSET;
}

Expand Down Expand Up @@ -824,7 +836,8 @@ private ChunkSampleStream<DashChunkSource> buildSampleStream(
drmSessionManager,
drmEventDispatcher,
loadErrorHandlingPolicy,
mediaSourceEventDispatcher);
mediaSourceEventDispatcher,
canReportInitialDiscontinuity);
synchronized (this) {
// The map is also accessed on the loading thread so synchronize access.
trackEmsgHandlerBySampleStream.put(stream, trackPlayerEmsgHandler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,4 +440,27 @@ public void playVideo_usingWithinGopSampleDependencies_withSeekAfterEoS() throws
// TODO: b/352276461 - The last frame might not be rendered. When the bug is fixed,
// assert on the full playback dump.
}

@Test
public void multiPeriod_withOffsetInSegment() throws Exception {
Context applicationContext = ApplicationProvider.getApplicationContext();
CapturingRenderersFactory capturingRenderersFactory =
new CapturingRenderersFactory(applicationContext);
ExoPlayer player =
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
.build();
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);

player.setMediaItem(
MediaItem.fromUri("asset:///media/dash/multi-period-with-offset/sample.mpd"));
player.prepare();
player.play();
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
player.release();

DumpFileAsserts.assertOutput(
applicationContext, playbackOutput, "playbackdumps/dash/multi-period-with-offset.dump");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ private ChunkSampleStream<SsChunkSource> buildSampleStream(
drmSessionManager,
drmEventDispatcher,
loadErrorHandlingPolicy,
mediaSourceEventDispatcher);
mediaSourceEventDispatcher,
/* canReportInitialDiscontinuity= */ false);
}

private static TrackGroupArray buildTrackGroups(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT1.0010000467300415S">
<Period id='0' duration='PT0.5S'>
<AdaptationSet id="1" contentType="video" width="1080" height="720" frameRate="30000/1001" subsegmentAlignment="true" par="3:2">
<Representation id="1" bandwidth="721967" codecs="avc1.64001f" mimeType="video/mp4" sar="1:1">
<BaseURL>sample.video.mp4</BaseURL>
<SegmentBase indexRange="862-905" timescale="30000">
<Initialization range="0-861"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
<Period id='1'>
<AdaptationSet id="1" contentType="video" width="1080" height="720" frameRate="30000/1001" subsegmentAlignment="true" par="3:2">
<Representation id="1" bandwidth="721967" codecs="avc1.64001f" mimeType="video/mp4" sar="1:1">
<BaseURL>sample.video.mp4</BaseURL>
<SegmentBase indexRange="862-905" timescale="30000" presentationTimeOffset="15000">
<Initialization range="0-861"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
</MPD>
Binary file not shown.
Loading

0 comments on commit e8664db

Please sign in to comment.