Commit 8e2801ce by Oliver Woodman

Improve HLS ABR.

- Add options to switch abruptly at segment boundaries. Third
  parties who guarantee keyframes at the start of segments will
  want this, because it makes switching more efficient and hence
  rebuffering less likely.
- Switch quality faster when performing a splicing switch (when
  we detect that we need to switch variant, we now immediately
  request the same segment as we did last time for the new variant,
  rather than requesting one more segment for the old variant
  before doing this.
parent 410fcdeb
...@@ -80,8 +80,9 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -80,8 +80,9 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
boolean adaptiveDecoder = MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false).adaptive;
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null, HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null,
MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false).adaptive); adaptiveDecoder ? HlsChunkSource.ADAPTIVE_MODE_SPLICE : HlsChunkSource.ADAPTIVE_MODE_NONE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3); HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, player.getMainHandler(), player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, player.getMainHandler(), player, 50);
......
...@@ -77,8 +77,9 @@ import java.io.IOException; ...@@ -77,8 +77,9 @@ import java.io.IOException;
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter); DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
boolean adaptiveDecoder = MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false).adaptive;
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null, HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null,
MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false).adaptive); adaptiveDecoder ? HlsChunkSource.ADAPTIVE_MODE_SPLICE : HlsChunkSource.ADAPTIVE_MODE_NONE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2); HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(), MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
......
...@@ -35,12 +35,18 @@ import java.util.LinkedList; ...@@ -35,12 +35,18 @@ import java.util.LinkedList;
*/ */
public class HlsSampleSource implements SampleSource, Loader.Callback { public class HlsSampleSource implements SampleSource, Loader.Callback {
/**
* The default minimum number of times to retry loading data prior to failing.
*/
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 1;
private static final long BUFFER_DURATION_US = 20000000; private static final long BUFFER_DURATION_US = 20000000;
private static final int NO_RESET_PENDING = -1; private static final int NO_RESET_PENDING = -1;
private final HlsChunkSource chunkSource; private final HlsChunkSource chunkSource;
private final LinkedList<TsExtractor> extractors; private final LinkedList<TsExtractor> extractors;
private final boolean frameAccurateSeeking; private final boolean frameAccurateSeeking;
private final int minLoadableRetryCount;
private int remainingReleaseCount; private int remainingReleaseCount;
private boolean prepared; private boolean prepared;
...@@ -65,11 +71,18 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -65,11 +71,18 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
private int currentLoadableExceptionCount; private int currentLoadableExceptionCount;
private long currentLoadableExceptionTimestamp; private long currentLoadableExceptionTimestamp;
public HlsSampleSource(HlsChunkSource chunkSource, public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking,
boolean frameAccurateSeeking, int downstreamRendererCount) { int downstreamRendererCount) {
this(chunkSource, frameAccurateSeeking, downstreamRendererCount,
DEFAULT_MIN_LOADABLE_RETRY_COUNT);
}
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking,
int downstreamRendererCount, int minLoadableRetryCount) {
this.chunkSource = chunkSource; this.chunkSource = chunkSource;
this.frameAccurateSeeking = frameAccurateSeeking; this.frameAccurateSeeking = frameAccurateSeeking;
this.remainingReleaseCount = downstreamRendererCount; this.remainingReleaseCount = downstreamRendererCount;
this.minLoadableRetryCount = minLoadableRetryCount;
extractors = new LinkedList<TsExtractor>(); extractors = new LinkedList<TsExtractor>();
} }
...@@ -97,8 +110,8 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -97,8 +110,8 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
prepared = true; prepared = true;
} }
} }
if (!prepared && currentLoadableException != null) { if (!prepared) {
throw currentLoadableException; maybeThrowLoadableException();
} }
return prepared; return prepared;
} }
...@@ -157,8 +170,8 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -157,8 +170,8 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return false; return false;
} }
boolean haveSamples = extractors.getFirst().hasSamples(); boolean haveSamples = extractors.getFirst().hasSamples();
if (!haveSamples && currentLoadableException != null) { if (!haveSamples) {
throw currentLoadableException; maybeThrowLoadableException();
} }
return haveSamples; return haveSamples;
} }
...@@ -175,9 +188,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -175,9 +188,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
if (onlyReadDiscontinuity || isPendingReset() || extractors.isEmpty()) { if (onlyReadDiscontinuity || isPendingReset() || extractors.isEmpty()) {
if (currentLoadableException != null) { maybeThrowLoadableException();
throw currentLoadableException;
}
return NOTHING_READ; return NOTHING_READ;
} }
...@@ -202,9 +213,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -202,9 +213,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
if (!extractor.isPrepared()) { if (!extractor.isPrepared()) {
if (currentLoadableException != null) { maybeThrowLoadableException();
throw currentLoadableException;
}
return NOTHING_READ; return NOTHING_READ;
} }
...@@ -225,9 +234,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -225,9 +234,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return END_OF_STREAM; return END_OF_STREAM;
} }
if (currentLoadableException != null) { maybeThrowLoadableException();
throw currentLoadableException;
}
return NOTHING_READ; return NOTHING_READ;
} }
...@@ -283,7 +290,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -283,7 +290,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} finally { } finally {
if (isTsChunk(currentLoadable)) { if (isTsChunk(currentLoadable)) {
TsChunk tsChunk = (TsChunk) loadable; TsChunk tsChunk = (TsChunk) loadable;
loadingFinished = tsChunk.isLastChunk(); loadingFinished = tsChunk.isLastChunk;
} }
if (!currentLoadableExceptionFatal) { if (!currentLoadableExceptionFatal) {
clearCurrentLoadable(); clearCurrentLoadable();
...@@ -309,6 +316,12 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -309,6 +316,12 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
maybeStartLoading(); maybeStartLoading();
} }
private void maybeThrowLoadableException() throws IOException {
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
throw currentLoadableException;
}
}
private void restartFrom(long positionUs) { private void restartFrom(long positionUs) {
pendingResetPositionUs = positionUs; pendingResetPositionUs = positionUs;
loadingFinished = false; loadingFinished = false;
......
...@@ -39,14 +39,13 @@ public final class TsChunk extends HlsChunk { ...@@ -39,14 +39,13 @@ public final class TsChunk extends HlsChunk {
*/ */
public final long endTimeUs; public final long endTimeUs;
/** /**
* The index of the next media chunk, or -1 if this is the last media chunk in the stream. * The chunk index.
*/ */
public final int nextChunkIndex; public final int chunkIndex;
/** /**
* True if this is the final chunk being loaded for the current variant, as we splice to another * True if this is the last chunk in the media. False otherwise.
* one. False otherwise.
*/ */
public final boolean splicingOut; public final boolean isLastChunk;
/** /**
* The extractor into which this chunk is being consumed. * The extractor into which this chunk is being consumed.
*/ */
...@@ -62,19 +61,18 @@ public final class TsChunk extends HlsChunk { ...@@ -62,19 +61,18 @@ public final class TsChunk extends HlsChunk {
* @param variantIndex The index of the variant in the master playlist. * @param variantIndex The index of the variant in the master playlist.
* @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 nextChunkIndex The index of the next chunk, or -1 if this is the last chunk. * @param chunkIndex The index of the chunk.
* @param splicingOut True if this is the final chunk being loaded for the current variant, as we * @param isLastChunk True if this is the last chunk in the media. False otherwise.
* splice to another one. False otherwise.
*/ */
public TsChunk(DataSource dataSource, DataSpec dataSpec, TsExtractor tsExtractor, public TsChunk(DataSource dataSource, DataSpec dataSpec, TsExtractor tsExtractor,
int variantIndex, long startTimeUs, long endTimeUs, int nextChunkIndex, boolean splicingOut) { int variantIndex, long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk) {
super(dataSource, dataSpec); super(dataSource, dataSpec);
this.extractor = tsExtractor; this.extractor = tsExtractor;
this.variantIndex = variantIndex; this.variantIndex = variantIndex;
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs; this.endTimeUs = endTimeUs;
this.nextChunkIndex = nextChunkIndex; this.chunkIndex = chunkIndex;
this.splicingOut = splicingOut; this.isLastChunk = isLastChunk;
} }
@Override @Override
...@@ -82,10 +80,6 @@ public final class TsChunk extends HlsChunk { ...@@ -82,10 +80,6 @@ public final class TsChunk extends HlsChunk {
// Do nothing. // Do nothing.
} }
public boolean isLastChunk() {
return nextChunkIndex == -1;
}
@Override @Override
public boolean isLoadFinished() { public boolean isLoadFinished() {
return loadFinished; return loadFinished;
......
...@@ -56,6 +56,7 @@ public final class TsExtractor { ...@@ -56,6 +56,7 @@ public final class TsExtractor {
private final SparseArray<SampleQueue> sampleQueues; // Indexed by streamType private final SparseArray<SampleQueue> sampleQueues; // Indexed by streamType
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
private final SamplePool samplePool; private final SamplePool samplePool;
private final boolean shouldSpliceIn;
/* package */ final long firstSampleTimestamp; /* package */ final long firstSampleTimestamp;
// Accessed only by the consuming thread. // Accessed only by the consuming thread.
...@@ -69,9 +70,10 @@ public final class TsExtractor { ...@@ -69,9 +70,10 @@ public final class TsExtractor {
private volatile boolean prepared; private volatile boolean prepared;
/* package */ volatile long largestParsedTimestampUs; /* package */ volatile long largestParsedTimestampUs;
public TsExtractor(long firstSampleTimestamp, SamplePool samplePool) { public TsExtractor(long firstSampleTimestamp, SamplePool samplePool, boolean shouldSpliceIn) {
this.firstSampleTimestamp = firstSampleTimestamp; this.firstSampleTimestamp = firstSampleTimestamp;
this.samplePool = samplePool; this.samplePool = samplePool;
this.shouldSpliceIn = shouldSpliceIn;
pendingFirstSampleTimestampAdjustment = true; pendingFirstSampleTimestampAdjustment = true;
tsPacketBuffer = new BitArray(); tsPacketBuffer = new BitArray();
sampleQueues = new SparseArray<SampleQueue>(); sampleQueues = new SparseArray<SampleQueue>();
...@@ -141,9 +143,9 @@ public final class TsExtractor { ...@@ -141,9 +143,9 @@ public final class TsExtractor {
*/ */
public void configureSpliceTo(TsExtractor nextExtractor) { public void configureSpliceTo(TsExtractor nextExtractor) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
if (spliceConfigured || !nextExtractor.isPrepared()) { if (spliceConfigured || !nextExtractor.shouldSpliceIn || !nextExtractor.isPrepared()) {
// The splice is already configured or the next extractor isn't ready to be spliced in. // The splice is already configured, or the next extractor doesn't want to be spliced in, or
// Already configured, or too early to splice. // the next extractor isn't ready to be spliced in.
return; return;
} }
boolean spliceConfigured = true; boolean spliceConfigured = true;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment