Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,40 @@ protected boolean canSelectFormat(Format format, int trackBitrate, long effectiv
return trackBitrate <= effectiveBitrate;
}

/**
* Returns the preferred format evaluation order.
*
* @return An array with one format index per priority position, or null to use {@link
* #getFormatIndexForPriorityPosition(int, long, long, long)}. If non-null, indices must be
* unique and in range {@code [0, length())}.
*
* @param nowMs The current time in the timebase of {@link Clock#elapsedRealtime()}, or {@link
* Long#MIN_VALUE} to ignore track exclusion.
* @param chunkDurationUs The duration of a media chunk in microseconds, or {@link C#TIME_UNSET}
* if unknown.
* @param effectiveBitrate The bitrate available to this selection.
*/
@Nullable
protected int[] getFormatPriorityOrder(long nowMs, long chunkDurationUs, long effectiveBitrate) {
return null;
}

/**
* Returns the format index to evaluate at a given priority position. The default implementation
* evaluates formats in order of decreasing bitrate.
*
* @param priorityPosition The zero-based position within the format evaluation priority.
* @param nowMs The current time in the timebase of {@link Clock#elapsedRealtime()}, or {@link
* Long#MIN_VALUE} to ignore track exclusion.
* @param chunkDurationUs The duration of a media chunk in microseconds, or {@link C#TIME_UNSET}
* if unknown.
* @param effectiveBitrate The bitrate available to this selection.
*/
protected int getFormatIndexForPriorityPosition(
int priorityPosition, long nowMs, long chunkDurationUs, long effectiveBitrate) {
return priorityPosition;
}

/**
* Called from {@link #evaluateQueueSize(long, List)} to determine whether an evaluation should be
* performed.
Expand Down Expand Up @@ -601,17 +635,61 @@ private int determineIdealSelectedIndex(long nowMs, long chunkDurationUs) {
int lowestBitrateAllowedIndex = 0;
for (int i = 0; i < length; i++) {
if (nowMs == Long.MIN_VALUE || !isTrackExcluded(i, nowMs)) {
Format format = getFormat(i);
lowestBitrateAllowedIndex = i;
}
}
@Nullable
int[] formatPriorityOrder = getFormatPriorityOrder(nowMs, chunkDurationUs, effectiveBitrate);
if (formatPriorityOrder != null && !isValidFormatPriorityOrder(formatPriorityOrder)) {
Log.w(TAG, "Ignoring invalid format priority order");
formatPriorityOrder = null;
}
for (int i = 0; i < length; i++) {
int formatIndex =
formatPriorityOrder == null
? getFormatIndexForPriorityPosition(i, nowMs, chunkDurationUs, effectiveBitrate)
: formatPriorityOrder[i];
if (nowMs == Long.MIN_VALUE || !isTrackExcluded(formatIndex, nowMs)) {
Format format = getFormat(formatIndex);
if (canSelectFormat(format, format.bitrate, effectiveBitrate)) {
return i;
} else {
lowestBitrateAllowedIndex = i;
return formatIndex;
}
}
}
return lowestBitrateAllowedIndex;
}

private boolean isValidFormatPriorityOrder(int[] formatPriorityOrder) {
if (formatPriorityOrder.length != length) {
return false;
}
if (length <= Long.SIZE) {
long seenMask = 0L;
for (int formatIndex : formatPriorityOrder) {
if (formatIndex < 0 || formatIndex >= length) {
return false;
}
long bit = 1L << formatIndex;
if ((seenMask & bit) != 0L) {
return false;
}
seenMask |= bit;
}
return true;
}
boolean[] seen = new boolean[length];
for (int formatIndex : formatPriorityOrder) {
if (formatIndex < 0 || formatIndex >= length) {
return false;
}
if (seen[formatIndex]) {
return false;
}
seen[formatIndex] = true;
}
return true;
}

private long minDurationForQualityIncreaseUs(long availableDurationUs, long chunkDurationUs) {
if (availableDurationUs == C.TIME_UNSET) {
// We are not in a live stream. Use the configured value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,113 @@ public void initial_updateSelectedTrack_returnsCorrectLatestBitrateEstimate() {
assertThat(adaptiveTrackSelection.getLatestBitrateEstimate()).isEqualTo(2000L);
}

@Test
public void initial_updateSelectedTrack_withCustomFormatPriorityOrder_selectsFirstEligible() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);

when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(5000L);
AdaptiveTrackSelection adaptiveTrackSelection =
prepareTrackSelection(
new AdaptiveTrackSelection(
trackGroup,
selectedAllTracksInGroup(trackGroup),
TrackSelection.TYPE_UNSET,
mockBandwidthMeter,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
AdaptiveTrackSelection.DEFAULT_MAX_WIDTH_TO_DISCARD,
AdaptiveTrackSelection.DEFAULT_MAX_HEIGHT_TO_DISCARD,
/* bandwidthFraction= */ 1.0f,
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
/* adaptationCheckpoints= */ ImmutableList.of(),
fakeClock) {
@Override
protected int[] getFormatPriorityOrder(
long nowMs, long chunkDurationUs, long effectiveBitrate) {
// Provide explicit priority order: lowest bitrate first.
return new int[] {2, 1, 0};
}
});

assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1);
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
}

@Test
public void initial_updateSelectedTrack_withNoFormatPriorityOrder_usesDefaultBitrateOrder() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);

when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(5000L);
AdaptiveTrackSelection adaptiveTrackSelection =
prepareTrackSelection(
new AdaptiveTrackSelection(
trackGroup,
selectedAllTracksInGroup(trackGroup),
TrackSelection.TYPE_UNSET,
mockBandwidthMeter,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
AdaptiveTrackSelection.DEFAULT_MAX_WIDTH_TO_DISCARD,
AdaptiveTrackSelection.DEFAULT_MAX_HEIGHT_TO_DISCARD,
/* bandwidthFraction= */ 1.0f,
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
/* adaptationCheckpoints= */ ImmutableList.of(),
fakeClock) {
@Override
protected int[] getFormatPriorityOrder(
long nowMs, long chunkDurationUs, long effectiveBitrate) {
return null;
}
});

assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format3);
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
}

@Test
public void initial_updateSelectedTrack_withInvalidFormatPriorityOrder_usesDefaultBitrateOrder() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
TrackGroup trackGroup = new TrackGroup(format1, format2, format3);

when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(5000L);
AdaptiveTrackSelection adaptiveTrackSelection =
prepareTrackSelection(
new AdaptiveTrackSelection(
trackGroup,
selectedAllTracksInGroup(trackGroup),
TrackSelection.TYPE_UNSET,
mockBandwidthMeter,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
AdaptiveTrackSelection.DEFAULT_MAX_WIDTH_TO_DISCARD,
AdaptiveTrackSelection.DEFAULT_MAX_HEIGHT_TO_DISCARD,
/* bandwidthFraction= */ 1.0f,
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
/* adaptationCheckpoints= */ ImmutableList.of(),
fakeClock) {
@Override
protected int[] getFormatPriorityOrder(
long nowMs, long chunkDurationUs, long effectiveBitrate) {
// Invalid because of duplicate format index.
return new int[] {2, 2, 0};
}
});

assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format3);
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
}

@Test
public void updateSelectedTrackDoNotSwitchUpIfNotBufferedEnough() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Expand Down