Support DASH multi-segment fetches

Note that multi-segment fetching is only possible in the
case that segments in a representation are defined to have
the same Uri and adjacent ranges (this is very rarely true
for live streams, but is quite often true for on-demand).
In the case that merging is requested but not possible,
the implementation will request one at a time.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=140012443
This commit is contained in:
olly 2016-11-23 03:00:16 -08:00 committed by Oliver Woodman
parent b29ff0cf51
commit b3726cf761
4 changed files with 60 additions and 22 deletions

View File

@ -32,8 +32,9 @@ import java.io.IOException;
*/ */
public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMetadataOutput { public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMetadataOutput {
private final ChunkExtractorWrapper extractorWrapper; private final int chunkCount;
private final long sampleOffsetUs; private final long sampleOffsetUs;
private final ChunkExtractorWrapper extractorWrapper;
private final Format sampleFormat; private final Format sampleFormat;
private volatile int bytesLoaded; private volatile int bytesLoaded;
@ -49,6 +50,9 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk. * @param chunkIndex The index of the chunk.
* @param chunkCount The number of chunks in the underlying media that are spanned by this
* instance. Normally equal to one, but may be larger if multiple chunks as defined by the
* underlying media are being merged into a single load.
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor. * @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
* @param extractorWrapper A wrapped extractor to use for parsing the data. * @param extractorWrapper A wrapped extractor to use for parsing the data.
* @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if * @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if
@ -56,15 +60,21 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
*/ */
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat,
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
int chunkIndex, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper, int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
Format sampleFormat) { Format sampleFormat) {
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
endTimeUs, chunkIndex); endTimeUs, chunkIndex);
this.extractorWrapper = extractorWrapper; this.chunkCount = chunkCount;
this.sampleOffsetUs = sampleOffsetUs; this.sampleOffsetUs = sampleOffsetUs;
this.extractorWrapper = extractorWrapper;
this.sampleFormat = sampleFormat; this.sampleFormat = sampleFormat;
} }
@Override
public int getNextChunkIndex() {
return chunkIndex + chunkCount;
}
@Override @Override
public boolean isLoadCompleted() { public boolean isLoadCompleted() {
return loadCompleted; return loadCompleted;

View File

@ -53,7 +53,7 @@ public abstract class MediaChunk extends Chunk {
/** /**
* Returns the next chunk index. * Returns the next chunk index.
*/ */
public final int getNextChunkIndex() { public int getNextChunkIndex() {
return chunkIndex + 1; return chunkIndex + 1;
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source.dash; package com.google.android.exoplayer2.source.dash;
import android.net.Uri;
import android.os.SystemClock; import android.os.SystemClock;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
@ -54,9 +55,15 @@ public class DefaultDashChunkSource implements DashChunkSource {
public static final class Factory implements DashChunkSource.Factory { public static final class Factory implements DashChunkSource.Factory {
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final int maxSegmentsPerLoad;
public Factory(DataSource.Factory dataSourceFactory) { public Factory(DataSource.Factory dataSourceFactory) {
this(dataSourceFactory, 1);
}
public Factory(DataSource.Factory dataSourceFactory, int maxSegmentsPerLoad) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
} }
@Override @Override
@ -65,7 +72,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
TrackSelection trackSelection, long elapsedRealtimeOffsetMs) { TrackSelection trackSelection, long elapsedRealtimeOffsetMs) {
DataSource dataSource = dataSourceFactory.createDataSource(); DataSource dataSource = dataSourceFactory.createDataSource();
return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex, return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex,
adaptationSetIndex, trackSelection, dataSource, elapsedRealtimeOffsetMs); adaptationSetIndex, trackSelection, dataSource, elapsedRealtimeOffsetMs,
maxSegmentsPerLoad);
} }
} }
@ -76,6 +84,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
private final RepresentationHolder[] representationHolders; private final RepresentationHolder[] representationHolders;
private final DataSource dataSource; private final DataSource dataSource;
private final long elapsedRealtimeOffsetMs; private final long elapsedRealtimeOffsetMs;
private final int maxSegmentsPerLoad;
private DashManifest manifest; private DashManifest manifest;
private int periodIndex; private int periodIndex;
@ -93,10 +102,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
* as the server's unix time minus the local elapsed time. If unknown, set to 0. * as the server's unix time minus the local elapsed time. If unknown, set to 0.
* @param maxSegmentsPerLoad The maximum number of segments to combine into a single request.
* Note that segments will only be combined if their {@link Uri}s are the same and if their
* data ranges are adjacent.
*/ */
public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection, DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
DataSource dataSource, long elapsedRealtimeOffsetMs) { DataSource dataSource, long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest; this.manifest = manifest;
this.adaptationSetIndex = adaptationSetIndex; this.adaptationSetIndex = adaptationSetIndex;
@ -104,6 +116,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
this.dataSource = dataSource; this.dataSource = dataSource;
this.periodIndex = periodIndex; this.periodIndex = periodIndex;
this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs; this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
List<Representation> representations = getRepresentations(); List<Representation> representations = getRepresentations();
@ -219,9 +232,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
return; return;
} }
int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource, Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource,
trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
trackSelection.getSelectionData(), sampleFormat, segmentNum); trackSelection.getSelectionData(), sampleFormat, segmentNum, maxSegmentCount);
out.chunk = nextMediaChunk; out.chunk = nextMediaChunk;
} }
@ -260,7 +274,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
RepresentationHolder representationHolder = RepresentationHolder representationHolder =
representationHolders[trackSelection.indexOf(chunk.trackFormat)]; representationHolders[trackSelection.indexOf(chunk.trackFormat)];
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum(); int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
if (((MediaChunk) chunk).chunkIndex >= lastAvailableSegmentNum) { if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) {
missingLastSegment = true; missingLastSegment = true;
return true; return true;
} }
@ -284,7 +298,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
} }
private Chunk newInitializationChunk(RepresentationHolder representationHolder, private static Chunk newInitializationChunk(RepresentationHolder representationHolder,
DataSource dataSource, Format trackFormat, int trackSelectionReason, DataSource dataSource, Format trackFormat, int trackSelectionReason,
Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) { Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) {
RangedUri requestUri; RangedUri requestUri;
@ -305,24 +319,38 @@ public class DefaultDashChunkSource implements DashChunkSource {
trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper); trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper);
} }
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource, private static Chunk newMediaChunk(RepresentationHolder representationHolder,
Format trackFormat, int trackSelectionReason, DataSource dataSource, Format trackFormat, int trackSelectionReason,
Object trackSelectionData, Format sampleFormat, int segmentNum) { Object trackSelectionData, Format sampleFormat, int firstSegmentNum, int maxSegmentCount) {
Representation representation = representationHolder.representation; Representation representation = representationHolder.representation;
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum); long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum); RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum); String baseUrl = representation.baseUrl;
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(representation.baseUrl),
segmentUri.start, segmentUri.length, representation.getCacheKey());
if (representationHolder.extractorWrapper == null) { if (representationHolder.extractorWrapper == null) {
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum);
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
segmentUri.start, segmentUri.length, representation.getCacheKey());
return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason, return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
trackSelectionData, startTimeUs, endTimeUs, segmentNum, trackFormat); trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, trackFormat);
} else { } else {
int segmentCount = 1;
for (int i = 1; i < maxSegmentCount; i++) {
RangedUri nextSegmentUri = representationHolder.getSegmentUrl(firstSegmentNum + i);
RangedUri mergedSegmentUri = segmentUri.attemptMerge(nextSegmentUri, baseUrl);
if (mergedSegmentUri == null) {
// Unable to merge segment fetches because the URIs do not merge.
break;
}
segmentUri = mergedSegmentUri;
segmentCount++;
}
long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1);
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl),
segmentUri.start, segmentUri.length, representation.getCacheKey());
long sampleOffsetUs = -representation.presentationTimeOffsetUs; long sampleOffsetUs = -representation.presentationTimeOffsetUs;
return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason, return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
trackSelectionData, startTimeUs, endTimeUs, segmentNum, sampleOffsetUs, trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, segmentCount,
representationHolder.extractorWrapper, sampleFormat); sampleOffsetUs, representationHolder.extractorWrapper, sampleFormat);
} }
} }

View File

@ -218,7 +218,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
// To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs. // To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs.
long sampleOffsetUs = chunkStartTimeUs; long sampleOffsetUs = chunkStartTimeUs;
return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason, return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason,
trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, sampleOffsetUs, trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, 1, sampleOffsetUs,
extractorWrapper, format); extractorWrapper, format);
} }