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 {
private final ChunkExtractorWrapper extractorWrapper;
private final int chunkCount;
private final long sampleOffsetUs;
private final ChunkExtractorWrapper extractorWrapper;
private final Format sampleFormat;
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 endTimeUs The end time of the media contained by the chunk, in microseconds.
* @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 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
@ -56,15 +60,21 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
*/
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat,
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
int chunkIndex, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
Format sampleFormat) {
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
endTimeUs, chunkIndex);
this.extractorWrapper = extractorWrapper;
this.chunkCount = chunkCount;
this.sampleOffsetUs = sampleOffsetUs;
this.extractorWrapper = extractorWrapper;
this.sampleFormat = sampleFormat;
}
@Override
public int getNextChunkIndex() {
return chunkIndex + chunkCount;
}
@Override
public boolean isLoadCompleted() {
return loadCompleted;

View File

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

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.dash;
import android.net.Uri;
import android.os.SystemClock;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
@ -54,9 +55,15 @@ public class DefaultDashChunkSource implements DashChunkSource {
public static final class Factory implements DashChunkSource.Factory {
private final DataSource.Factory dataSourceFactory;
private final int maxSegmentsPerLoad;
public Factory(DataSource.Factory dataSourceFactory) {
this(dataSourceFactory, 1);
}
public Factory(DataSource.Factory dataSourceFactory, int maxSegmentsPerLoad) {
this.dataSourceFactory = dataSourceFactory;
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
}
@Override
@ -65,7 +72,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
TrackSelection trackSelection, long elapsedRealtimeOffsetMs) {
DataSource dataSource = dataSourceFactory.createDataSource();
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 DataSource dataSource;
private final long elapsedRealtimeOffsetMs;
private final int maxSegmentsPerLoad;
private DashManifest manifest;
private int periodIndex;
@ -93,10 +102,13 @@ public class DefaultDashChunkSource implements DashChunkSource {
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* 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.
* @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,
DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
DataSource dataSource, long elapsedRealtimeOffsetMs) {
DataSource dataSource, long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest;
this.adaptationSetIndex = adaptationSetIndex;
@ -104,6 +116,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
this.dataSource = dataSource;
this.periodIndex = periodIndex;
this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs;
this.maxSegmentsPerLoad = maxSegmentsPerLoad;
long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
List<Representation> representations = getRepresentations();
@ -219,9 +232,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
return;
}
int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource,
trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
trackSelection.getSelectionData(), sampleFormat, segmentNum);
trackSelection.getSelectionData(), sampleFormat, segmentNum, maxSegmentCount);
out.chunk = nextMediaChunk;
}
@ -260,7 +274,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
RepresentationHolder representationHolder =
representationHolders[trackSelection.indexOf(chunk.trackFormat)];
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
if (((MediaChunk) chunk).chunkIndex >= lastAvailableSegmentNum) {
if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) {
missingLastSegment = 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,
Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) {
RangedUri requestUri;
@ -305,24 +319,38 @@ public class DefaultDashChunkSource implements DashChunkSource {
trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper);
}
private Chunk newMediaChunk(RepresentationHolder representationHolder, DataSource dataSource,
Format trackFormat, int trackSelectionReason,
Object trackSelectionData, Format sampleFormat, int segmentNum) {
private static Chunk newMediaChunk(RepresentationHolder representationHolder,
DataSource dataSource, Format trackFormat, int trackSelectionReason,
Object trackSelectionData, Format sampleFormat, int firstSegmentNum, int maxSegmentCount) {
Representation representation = representationHolder.representation;
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum);
DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(representation.baseUrl),
segmentUri.start, segmentUri.length, representation.getCacheKey());
long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum);
String baseUrl = representation.baseUrl;
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,
trackSelectionData, startTimeUs, endTimeUs, segmentNum, trackFormat);
trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, trackFormat);
} 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;
return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason,
trackSelectionData, startTimeUs, endTimeUs, segmentNum, sampleOffsetUs,
representationHolder.extractorWrapper, sampleFormat);
trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, segmentCount,
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.
long sampleOffsetUs = chunkStartTimeUs;
return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason,
trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, sampleOffsetUs,
trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, 1, sampleOffsetUs,
extractorWrapper, format);
}