Skip to content

Commit

Permalink
Update ABR logic in AdaptiveTrackSelection for live streaming case
Browse files Browse the repository at this point in the history
In live streaming, if the playback position is very close to live edge,
the buffered duration will never reach minDurationForQualityIncreaseMs,
which prevents switching from ever happening. So we will provide the
durationToLiveEdgeUs to AdaptiveTrackSelection in live streaming case,
so it can handle this edge case.

GitHub: #3017

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=168535969
  • Loading branch information
botaydotcom authored and ojw28 committed Sep 15, 2017
1 parent 39dbb9a commit 5019da3
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static final class Factory implements TrackSelection.Factory {
private final int maxDurationForQualityDecreaseMs;
private final int minDurationToRetainAfterDiscardMs;
private final float bandwidthFraction;
private final float bufferedFractionToLiveEdgeForQualityIncrease;

/**
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
Expand All @@ -48,7 +49,9 @@ public Factory(BandwidthMeter bandwidthMeter) {
this (bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE,
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION);
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
DEFAULT_BANDWIDTH_FRACTION,
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE);
}

/**
Expand All @@ -70,19 +73,53 @@ public Factory(BandwidthMeter bandwidthMeter) {
public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate,
int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs,
int minDurationToRetainAfterDiscardMs, float bandwidthFraction) {
this (bandwidthMeter, maxInitialBitrate, minDurationForQualityIncreaseMs,
maxDurationForQualityDecreaseMs, minDurationToRetainAfterDiscardMs,
bandwidthFraction, DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE);
}

/**
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
* @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed
* when a bandwidth estimate is unavailable.
* @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for
* the selected track to switch to one of higher quality.
* @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for
* the selected track to switch to one of lower quality.
* @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher
* quality, the selection may indicate that media already buffered at the lower quality can
* be discarded to speed up the switch. This is the minimum duration of media that must be
* retained at the lower quality.
* @param bandwidthFraction The fraction of the available bandwidth that the selection should
* consider available for use. Setting to a value less than 1 is recommended to account
* for inaccuracies in the bandwidth estimator.
* @param bufferedFractionToLiveEdgeForQualityIncrease For live streaming, the fraction of
* the duration from current playback position to the live edge that has to be buffered
* before the selected track can be switched to one of higher quality. This parameter is
* only applied when the playback position is closer to the live edge than
* {@code minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a
* higher quality from happening.
*/
public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate,
int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs,
int minDurationToRetainAfterDiscardMs, float bandwidthFraction,
float bufferedFractionToLiveEdgeForQualityIncrease) {
this.bandwidthMeter = bandwidthMeter;
this.maxInitialBitrate = maxInitialBitrate;
this.minDurationForQualityIncreaseMs = minDurationForQualityIncreaseMs;
this.maxDurationForQualityDecreaseMs = maxDurationForQualityDecreaseMs;
this.minDurationToRetainAfterDiscardMs = minDurationToRetainAfterDiscardMs;
this.bandwidthFraction = bandwidthFraction;
this.bufferedFractionToLiveEdgeForQualityIncrease =
bufferedFractionToLiveEdgeForQualityIncrease;
}

@Override
public AdaptiveTrackSelection createTrackSelection(TrackGroup group, int... tracks) {
return new AdaptiveTrackSelection(group, tracks, bandwidthMeter, maxInitialBitrate,
minDurationForQualityIncreaseMs, maxDurationForQualityDecreaseMs,
minDurationToRetainAfterDiscardMs, bandwidthFraction);
minDurationToRetainAfterDiscardMs, bandwidthFraction,
bufferedFractionToLiveEdgeForQualityIncrease);
}

}
Expand All @@ -92,13 +129,15 @@ public AdaptiveTrackSelection createTrackSelection(TrackGroup group, int... trac
public static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000;
public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000;
public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f;
public static final float DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE = 0.75f;

private final BandwidthMeter bandwidthMeter;
private final int maxInitialBitrate;
private final long minDurationForQualityIncreaseUs;
private final long maxDurationForQualityDecreaseUs;
private final long minDurationToRetainAfterDiscardUs;
private final float bandwidthFraction;
private final float bufferedFractionToLiveEdgeForQualityIncrease;

private int selectedIndex;
private int reason;
Expand All @@ -114,7 +153,9 @@ public AdaptiveTrackSelection(TrackGroup group, int[] tracks,
this (group, tracks, bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE,
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION);
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
DEFAULT_BANDWIDTH_FRACTION,
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE);
}

/**
Expand All @@ -135,37 +176,46 @@ public AdaptiveTrackSelection(TrackGroup group, int[] tracks,
* @param bandwidthFraction The fraction of the available bandwidth that the selection should
* consider available for use. Setting to a value less than 1 is recommended to account
* for inaccuracies in the bandwidth estimator.
* @param bufferedFractionToLiveEdgeForQualityIncrease For live streaming, the fraction of
* the duration from current playback position to the live edge that has to be buffered
* before the selected track can be switched to one of higher quality. This parameter is
* only applied when the playback position is closer to the live edge than
* {@code minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a
* higher quality from happening.
*/
public AdaptiveTrackSelection(TrackGroup group, int[] tracks, BandwidthMeter bandwidthMeter,
int maxInitialBitrate, long minDurationForQualityIncreaseMs,
long maxDurationForQualityDecreaseMs, long minDurationToRetainAfterDiscardMs,
float bandwidthFraction) {
float bandwidthFraction, float bufferedFractionToLiveEdgeForQualityIncrease) {
super(group, tracks);
this.bandwidthMeter = bandwidthMeter;
this.maxInitialBitrate = maxInitialBitrate;
this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L;
this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L;
this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L;
this.bandwidthFraction = bandwidthFraction;
this.bufferedFractionToLiveEdgeForQualityIncrease =
bufferedFractionToLiveEdgeForQualityIncrease;
selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE);
reason = C.SELECTION_REASON_INITIAL;
}

@Override
public void updateSelectedTrack(long bufferedDurationUs) {
public void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs) {
long nowMs = SystemClock.elapsedRealtime();
// Stash the current selection, then make a new one.
int currentSelectedIndex = selectedIndex;
selectedIndex = determineIdealSelectedIndex(nowMs);
if (selectedIndex == currentSelectedIndex) {
return;
}

if (!isBlacklisted(currentSelectedIndex, nowMs)) {
// Revert back to the current selection if conditions are not suitable for switching.
Format currentFormat = getFormat(currentSelectedIndex);
Format selectedFormat = getFormat(selectedIndex);
if (selectedFormat.bitrate > currentFormat.bitrate
&& bufferedDurationUs < minDurationForQualityIncreaseUs) {
&& bufferedDurationUs < minDurationForQualityIncreaseUs(availableDurationUs)) {
// The selected track is a higher quality, but we have insufficient buffer to safely switch
// up. Defer switching up for now.
selectedIndex = currentSelectedIndex;
Expand Down Expand Up @@ -251,4 +301,12 @@ private int determineIdealSelectedIndex(long nowMs) {
return lowestBitrateNonBlacklistedIndex;
}

private long minDurationForQualityIncreaseUs(long availableDurationUs) {
boolean isAvailableDurationTooShort = availableDurationUs != C.TIME_UNSET
&& availableDurationUs <= minDurationForQualityIncreaseUs;
return isAvailableDurationTooShort
? (long) (availableDurationUs * bufferedFractionToLiveEdgeForQualityIncrease)
: minDurationForQualityIncreaseUs;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public FixedTrackSelection(TrackGroup group, int track, int reason, Object data)
}

@Override
public void updateSelectedTrack(long bufferedDurationUs) {
public void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs) {
// Do nothing.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public RandomTrackSelection(TrackGroup group, int[] tracks, Random random) {
}

@Override
public void updateSelectedTrack(long bufferedDurationUs) {
public void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs) {
// Count the number of non-blacklisted formats.
long nowMs = SystemClock.elapsedRealtime();
int nonBlacklistedFormatCount = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* {@link TrackGroup}, and a possibly varying individual selected track from the subset.
* <p>
* Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual selected
* track may change as a result of calling {@link #updateSelectedTrack(long)}.
* track may change as a result of calling {@link #updateSelectedTrack(long, long)}.
*/
public interface TrackSelection {

Expand Down Expand Up @@ -126,8 +126,11 @@ interface Factory {
* Updates the selected track.
*
* @param bufferedDurationUs The duration of media currently buffered in microseconds.
* @param availableDurationUs The duration of media available for buffering from the current
* playback position, in microseconds, or {@link C#TIME_UNSET} if media can be buffered
* to the end of the current period.
*/
void updateSelectedTrack(long bufferedDurationUs);
void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs);

/**
* May be called periodically by sources that load media in discrete {@link MediaChunk}s and
Expand All @@ -148,10 +151,10 @@ interface Factory {

/**
* Attempts to blacklist the track at the specified index in the selection, making it ineligible
* for selection by calls to {@link #updateSelectedTrack(long)} for the specified period of time.
* Blacklisting will fail if all other tracks are currently blacklisted. If blacklisting the
* for selection by calls to {@link #updateSelectedTrack(long, long)} for the specified period of
* time. Blacklisting will fail if all other tracks are currently blacklisted. If blacklisting the
* currently selected track, note that it will remain selected until the next call to
* {@link #updateSelectedTrack(long)}.
* {@link #updateSelectedTrack(long, long)}.
*
* @param index The index of the track in the selection.
* @param blacklistDurationMs The duration of time for which the track should be blacklisted, in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderEr
private int periodIndex;
private IOException fatalError;
private boolean missingLastSegment;
private long liveEdgeTimeUs;

/**
* @param manifestLoaderErrorThrower Throws errors affecting loading of manifests.
Expand Down Expand Up @@ -130,6 +131,7 @@ public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
this.maxSegmentsPerLoad = maxSegmentsPerLoad;

long periodDurationUs = manifest.getPeriodDurationUs(periodIndex);
liveEdgeTimeUs = C.TIME_UNSET;
List<Representation> representations = getRepresentations();
representationHolders = new RepresentationHolder[trackSelection.length()];
for (int i = 0; i < representationHolders.length; i++) {
Expand Down Expand Up @@ -179,7 +181,8 @@ public final void getNextChunk(MediaChunk previous, long playbackPositionUs, Chu
}

long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
trackSelection.updateSelectedTrack(bufferedDurationUs);
long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs, previous == null);
trackSelection.updateSelectedTrack(bufferedDurationUs, timeToLiveEdgeUs);

RepresentationHolder representationHolder =
representationHolders[trackSelection.getSelectedIndex()];
Expand All @@ -203,7 +206,6 @@ public final void getNextChunk(MediaChunk previous, long playbackPositionUs, Chu
}
}

long nowUs = getNowUnixTimeUs();
int availableSegmentCount = representationHolder.getSegmentCount();
if (availableSegmentCount == 0) {
// The index doesn't define any segments.
Expand All @@ -216,21 +218,23 @@ public final void getNextChunk(MediaChunk previous, long playbackPositionUs, Chu
if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimeUs = nowUs - manifest.availabilityStartTime * 1000;
long periodStartUs = manifest.getPeriod(periodIndex).startMs * 1000;
long liveEdgeTimeUs = getNowUnixTimeUs() - C.msToUs(manifest.availabilityStartTime);
long periodStartUs = C.msToUs(manifest.getPeriod(periodIndex).startMs);
long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
if (manifest.timeShiftBufferDepth != C.TIME_UNSET) {
long bufferDepthUs = manifest.timeShiftBufferDepth * 1000;
long bufferDepthUs = C.msToUs(manifest.timeShiftBufferDepth);
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs));
}
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
// getSegmentNum(liveEdgeTimeInPeriodUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
} else {
lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1;
}

updateLiveEdgeTimeUs(representationHolder, lastAvailableSegmentNum);

int segmentNum;
if (previous == null) {
segmentNum = Util.constrainValue(representationHolder.getSegmentNum(playbackPositionUs),
Expand Down Expand Up @@ -311,6 +315,19 @@ private ArrayList<Representation> getRepresentations() {
return representations;
}

private void updateLiveEdgeTimeUs(RepresentationHolder representationHolder,
int lastAvailableSegmentNum) {
if (manifest.dynamic) {
DashSegmentIndex segmentIndex = representationHolder.representation.getIndex();
long lastSegmentDurationUs = segmentIndex.getDurationUs(lastAvailableSegmentNum,
manifest.getPeriodDurationUs(periodIndex));
liveEdgeTimeUs = segmentIndex.getTimeUs(lastAvailableSegmentNum)
+ lastSegmentDurationUs;
} else {
liveEdgeTimeUs = C.TIME_UNSET;
}
}

private long getNowUnixTimeUs() {
if (elapsedRealtimeOffsetMs != 0) {
return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000;
Expand Down Expand Up @@ -375,6 +392,12 @@ private static Chunk newMediaChunk(RepresentationHolder representationHolder,
}
}

private long resolveTimeToLiveEdgeUs(long playbackPositionUs, boolean isAfterPositionReset) {
boolean resolveTimeToLiveEdgePossible = manifest.dynamic
&& !isAfterPositionReset && liveEdgeTimeUs != C.TIME_UNSET;
return resolveTimeToLiveEdgePossible ? liveEdgeTimeUs - playbackPositionUs : C.TIME_UNSET;
}

// Protected classes.

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public void clear() {
// the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods
// in TrackSelection to avoid unexpected behavior.
private TrackSelection trackSelection;
private long liveEdgeTimeUs;

/**
* @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists.
Expand All @@ -122,6 +123,7 @@ public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants,
this.variants = variants;
this.timestampAdjusterProvider = timestampAdjusterProvider;
this.muxedCaptionFormats = muxedCaptionFormats;
liveEdgeTimeUs = C.TIME_UNSET;
Format[] variantFormats = new Format[variants.length];
int[] initialTrackSelection = new int[variants.length];
for (int i = 0; i < variants.length; i++) {
Expand Down Expand Up @@ -214,7 +216,8 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChu
(independentSegments ? previous.endTimeUs : previous.startTimeUs) - playbackPositionUs);

// Select the variant.
trackSelection.updateSelectedTrack(bufferedDurationUs);
long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs, previous == null);
trackSelection.updateSelectedTrack(bufferedDurationUs, timeToLiveEdgeUs);
int selectedVariantIndex = trackSelection.getSelectedIndexInTrackGroup();

boolean switchingVariant = oldVariantIndex != selectedVariantIndex;
Expand All @@ -228,6 +231,8 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChu
HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
independentSegments = mediaPlaylist.hasIndependentSegmentsTag;

updateLiveEdgeTimeUs(mediaPlaylist);

// Select the chunk.
int chunkMediaSequence;
if (previous == null || switchingVariant) {
Expand Down Expand Up @@ -360,6 +365,16 @@ public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) {

// Private methods.

private long resolveTimeToLiveEdgeUs(long playbackPositionUs, boolean isAfterPositionReset) {
final boolean resolveTimeToLiveEdgePossible = !isAfterPositionReset
&& liveEdgeTimeUs != C.TIME_UNSET;
return resolveTimeToLiveEdgePossible ? liveEdgeTimeUs - playbackPositionUs : C.TIME_UNSET;
}

private void updateLiveEdgeTimeUs(HlsMediaPlaylist mediaPlaylist) {
liveEdgeTimeUs = mediaPlaylist.hasEndTag ? C.TIME_UNSET : mediaPlaylist.getEndTimeUs();
}

private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex,
int trackSelectionReason, Object trackSelectionData) {
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP);
Expand Down Expand Up @@ -409,7 +424,7 @@ public InitializationTrackSelection(TrackGroup group, int[] tracks) {
}

@Override
public void updateSelectedTrack(long bufferedDurationUs) {
public void updateSelectedTrack(long bufferedDurationUs, long availableDurationUs) {
long nowMs = SystemClock.elapsedRealtime();
if (!isBlacklisted(selectedIndex, nowMs)) {
return;
Expand Down
Loading

0 comments on commit 5019da3

Please sign in to comment.