Commit 3e613beb by tonihei Committed by Ian Baker

Use time-to-first-byte in AdaptiveTrackSelection.

Knowing the time-to-first-byte allows to update the available
allocatable bandwidth to take this into account.

PiperOrigin-RevId: 363225573
parent 57796914
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.trackselection; package com.google.android.exoplayer2.trackselection;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -318,11 +317,12 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -318,11 +317,12 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
List<? extends MediaChunk> queue, List<? extends MediaChunk> queue,
MediaChunkIterator[] mediaChunkIterators) { MediaChunkIterator[] mediaChunkIterators) {
long nowMs = clock.elapsedRealtime(); long nowMs = clock.elapsedRealtime();
long chunkDurationUs = getChunkDurationUs(mediaChunkIterators, queue);
// Make initial selection // Make initial selection
if (reason == C.SELECTION_REASON_UNKNOWN) { if (reason == C.SELECTION_REASON_UNKNOWN) {
reason = C.SELECTION_REASON_INITIAL; reason = C.SELECTION_REASON_INITIAL;
selectedIndex = determineIdealSelectedIndex(nowMs); selectedIndex = determineIdealSelectedIndex(nowMs, chunkDurationUs);
return; return;
} }
...@@ -334,7 +334,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -334,7 +334,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
previousSelectedIndex = formatIndexOfPreviousChunk; previousSelectedIndex = formatIndexOfPreviousChunk;
previousReason = Iterables.getLast(queue).trackSelectionReason; previousReason = Iterables.getLast(queue).trackSelectionReason;
} }
int newSelectedIndex = determineIdealSelectedIndex(nowMs); int newSelectedIndex = determineIdealSelectedIndex(nowMs, chunkDurationUs);
if (!isBlacklisted(previousSelectedIndex, nowMs)) { if (!isBlacklisted(previousSelectedIndex, nowMs)) {
// Revert back to the previous selection if conditions are not suitable for switching. // Revert back to the previous selection if conditions are not suitable for switching.
Format currentFormat = getFormat(previousSelectedIndex); Format currentFormat = getFormat(previousSelectedIndex);
...@@ -394,7 +394,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -394,7 +394,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
if (playoutBufferedDurationBeforeLastChunkUs < minDurationToRetainAfterDiscardUs) { if (playoutBufferedDurationBeforeLastChunkUs < minDurationToRetainAfterDiscardUs) {
return queueSize; return queueSize;
} }
int idealSelectedIndex = determineIdealSelectedIndex(nowMs); int idealSelectedIndex = determineIdealSelectedIndex(nowMs, getChunkDurationUs(queue));
Format idealFormat = getFormat(idealSelectedIndex); Format idealFormat = getFormat(idealSelectedIndex);
// If the chunks contain video, discard from the first SD chunk beyond // If the chunks contain video, discard from the first SD chunk beyond
// minDurationToRetainAfterDiscardUs whose resolution and bitrate are both lower than the ideal // minDurationToRetainAfterDiscardUs whose resolution and bitrate are both lower than the ideal
...@@ -459,9 +459,11 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -459,9 +459,11 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
* *
* @param nowMs The current time in the timebase of {@link Clock#elapsedRealtime()}, or {@link * @param nowMs The current time in the timebase of {@link Clock#elapsedRealtime()}, or {@link
* Long#MIN_VALUE} to ignore track exclusion. * Long#MIN_VALUE} to ignore track exclusion.
* @param chunkDurationUs The duration of a media chunk in microseconds, or {@link C#TIME_UNSET}
* if unknown.
*/ */
private int determineIdealSelectedIndex(long nowMs) { private int determineIdealSelectedIndex(long nowMs, long chunkDurationUs) {
long effectiveBitrate = getAllocatedBandwidth(); long effectiveBitrate = getAllocatedBandwidth(chunkDurationUs);
int lowestBitrateAllowedIndex = 0; int lowestBitrateAllowedIndex = 0;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) { if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) {
...@@ -484,9 +486,35 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -484,9 +486,35 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
: minDurationForQualityIncreaseUs; : minDurationForQualityIncreaseUs;
} }
private long getAllocatedBandwidth() { private long getChunkDurationUs(
long totalBandwidth = MediaChunkIterator[] mediaChunkIterators, List<? extends MediaChunk> queue) {
(long) (bandwidthMeter.getBitrateEstimate() * bandwidthFraction / playbackSpeed); // First, try to get the chunk duration for currently selected format.
if (selectedIndex < mediaChunkIterators.length && mediaChunkIterators[selectedIndex].next()) {
MediaChunkIterator iterator = mediaChunkIterators[selectedIndex];
return iterator.getChunkEndTimeUs() - iterator.getChunkStartTimeUs();
}
// Second, try to get the chunk duration for another format.
for (MediaChunkIterator iterator : mediaChunkIterators) {
if (iterator.next()) {
return iterator.getChunkEndTimeUs() - iterator.getChunkStartTimeUs();
}
}
// Third, try to get chunk duration for previous chunk in the queue.
return getChunkDurationUs(queue);
}
private long getChunkDurationUs(List<? extends MediaChunk> queue) {
if (queue.isEmpty()) {
return C.TIME_UNSET;
}
MediaChunk lastChunk = Iterables.getLast(queue);
return lastChunk.startTimeUs != C.TIME_UNSET && lastChunk.endTimeUs != C.TIME_UNSET
? lastChunk.endTimeUs - lastChunk.startTimeUs
: C.TIME_UNSET;
}
private long getAllocatedBandwidth(long chunkDurationUs) {
long totalBandwidth = getTotalAllocatableBandwidth(chunkDurationUs);
if (adaptationCheckpoints.isEmpty()) { if (adaptationCheckpoints.isEmpty()) {
return totalBandwidth; return totalBandwidth;
} }
...@@ -505,6 +533,17 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -505,6 +533,17 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
(fractionBetweenCheckpoints * (next.allocatedBandwidth - previous.allocatedBandwidth)); (fractionBetweenCheckpoints * (next.allocatedBandwidth - previous.allocatedBandwidth));
} }
private long getTotalAllocatableBandwidth(long chunkDurationUs) {
long cautiousBandwidthEstimate =
(long) (bandwidthMeter.getBitrateEstimate() * bandwidthFraction);
long timeToFirstByteEstimateUs = bandwidthMeter.getTimeToFirstByteEstimateUs();
if (timeToFirstByteEstimateUs == C.TIME_UNSET || chunkDurationUs == C.TIME_UNSET) {
return (long) (cautiousBandwidthEstimate / playbackSpeed);
}
float availableTimeToLoadUs = chunkDurationUs / playbackSpeed - timeToFirstByteEstimateUs;
return (long) (cautiousBandwidthEstimate * availableTimeToLoadUs / chunkDurationUs);
}
/** /**
* Returns adaptation checkpoints for allocating bandwidth for adaptive track selections. * Returns adaptation checkpoints for allocating bandwidth for adaptive track selections.
* *
......
...@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.Format; ...@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.testutil.FakeClock;
import com.google.android.exoplayer2.testutil.FakeMediaChunk; import com.google.android.exoplayer2.testutil.FakeMediaChunk;
...@@ -32,6 +33,7 @@ import com.google.android.exoplayer2.testutil.FakeTimeline; ...@@ -32,6 +33,7 @@ import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection.AdaptationCheckpoint; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection.AdaptationCheckpoint;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition; import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -46,10 +48,7 @@ import org.mockito.Mock; ...@@ -46,10 +48,7 @@ import org.mockito.Mock;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class AdaptiveTrackSelectionTest { public final class AdaptiveTrackSelectionTest {
private static final MediaChunkIterator[] THREE_EMPTY_MEDIA_CHUNK_ITERATORS = private static final long TEST_CHUNK_DURATION_US = 2_000_000;
new MediaChunkIterator[] {
MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY
};
@Mock private BandwidthMeter mockBandwidthMeter; @Mock private BandwidthMeter mockBandwidthMeter;
private FakeClock fakeClock; private FakeClock fakeClock;
...@@ -58,33 +57,53 @@ public final class AdaptiveTrackSelectionTest { ...@@ -58,33 +57,53 @@ public final class AdaptiveTrackSelectionTest {
public void setUp() { public void setUp() {
initMocks(this); initMocks(this);
fakeClock = new FakeClock(0); fakeClock = new FakeClock(0);
when(mockBandwidthMeter.getTimeToFirstByteEstimateUs()).thenReturn(C.TIME_UNSET);
} }
@Test @Test
public void selectInitialIndexUseMaxInitialBitrateIfNoBandwidthEstimate() { public void initial_updateSelectedTrack_selectsHighestBitrateWithinBandwidth() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240); Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480); Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720); Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
TrackGroup trackGroup = new TrackGroup(format1, format2, format3); TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L);
AdaptiveTrackSelection adaptiveTrackSelection = adaptiveTrackSelection(trackGroup); AdaptiveTrackSelection adaptiveTrackSelection =
prepareAdaptiveTrackSelectionWithBandwidthFraction(trackGroup, /* bandwidthFraction= */ 1f);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL); assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
} }
@Test @Test
public void selectInitialIndexUseBandwidthEstimateIfAvailable() { public void initial_updateSelectedTrack_selectsHighestBitrateWithinBandwidthFraction() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240); Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480); Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720); Format format3 = videoFormat(/* bitrate= */ 2000, /* width= */ 960, /* height= */ 720);
TrackGroup trackGroup = new TrackGroup(format1, format2, format3); TrackGroup trackGroup = new TrackGroup(format1, format2, format3);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(2000L);
AdaptiveTrackSelection adaptiveTrackSelection = adaptiveTrackSelection(trackGroup); AdaptiveTrackSelection adaptiveTrackSelection =
prepareAdaptiveTrackSelectionWithBandwidthFraction(
trackGroup, /* bandwidthFraction= */ 0.5f);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
}
@Test
public void initial_updateSelectedTrack_selectsHighestBitrateWithinBandwidthAndTimeToFirstByte() {
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(2000L);
when(mockBandwidthMeter.getTimeToFirstByteEstimateUs()).thenReturn(1_000_000L);
AdaptiveTrackSelection adaptiveTrackSelection =
prepareAdaptiveTrackSelectionWithBandwidthFraction(trackGroup, /* bandwidthFraction= */ 1f);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL); assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
} }
...@@ -99,7 +118,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -99,7 +118,7 @@ public final class AdaptiveTrackSelectionTest {
// if possible. // if possible.
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 2000L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 2000L);
AdaptiveTrackSelection adaptiveTrackSelection = AdaptiveTrackSelection adaptiveTrackSelection =
adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( prepareAdaptiveTrackSelectionWithMinDurationForQualityIncreaseMs(
trackGroup, /* minDurationForQualityIncreaseMs= */ 10_000); trackGroup, /* minDurationForQualityIncreaseMs= */ 10_000);
adaptiveTrackSelection.updateSelectedTrack( adaptiveTrackSelection.updateSelectedTrack(
...@@ -107,7 +126,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -107,7 +126,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 9_999_000, /* bufferedDurationUs= */ 9_999_000,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(), /* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
// When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate // When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate
// format. However, since we only buffered 9_999_000 us, which is smaller than // format. However, since we only buffered 9_999_000 us, which is smaller than
...@@ -127,7 +146,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -127,7 +146,7 @@ public final class AdaptiveTrackSelectionTest {
// if possible. // if possible.
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 2000L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 2000L);
AdaptiveTrackSelection adaptiveTrackSelection = AdaptiveTrackSelection adaptiveTrackSelection =
adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( prepareAdaptiveTrackSelectionWithMinDurationForQualityIncreaseMs(
trackGroup, /* minDurationForQualityIncreaseMs= */ 10_000); trackGroup, /* minDurationForQualityIncreaseMs= */ 10_000);
adaptiveTrackSelection.updateSelectedTrack( adaptiveTrackSelection.updateSelectedTrack(
...@@ -135,7 +154,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -135,7 +154,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 10_000_000, /* bufferedDurationUs= */ 10_000_000,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(), /* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
// When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate // When bandwidth estimation is updated to 2000L, we can switch up to use a higher bitrate
// format. When we have buffered enough (10_000_000 us, which is equal to // format. When we have buffered enough (10_000_000 us, which is equal to
...@@ -155,7 +174,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -155,7 +174,7 @@ public final class AdaptiveTrackSelectionTest {
// if necessary. // if necessary.
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 500L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 500L);
AdaptiveTrackSelection adaptiveTrackSelection = AdaptiveTrackSelection adaptiveTrackSelection =
adaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs( prepareAdaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs(
trackGroup, /* maxDurationForQualityDecreaseMs= */ 25_000); trackGroup, /* maxDurationForQualityDecreaseMs= */ 25_000);
adaptiveTrackSelection.updateSelectedTrack( adaptiveTrackSelection.updateSelectedTrack(
...@@ -163,7 +182,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -163,7 +182,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 25_000_000, /* bufferedDurationUs= */ 25_000_000,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(), /* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
// When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate // When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate
// format. However, since we have enough buffer at higher quality (25_000_000 us, which is equal // format. However, since we have enough buffer at higher quality (25_000_000 us, which is equal
...@@ -183,7 +202,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -183,7 +202,7 @@ public final class AdaptiveTrackSelectionTest {
// if necessary. // if necessary.
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 500L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 500L);
AdaptiveTrackSelection adaptiveTrackSelection = AdaptiveTrackSelection adaptiveTrackSelection =
adaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs( prepareAdaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs(
trackGroup, /* maxDurationForQualityDecreaseMs= */ 25_000); trackGroup, /* maxDurationForQualityDecreaseMs= */ 25_000);
adaptiveTrackSelection.updateSelectedTrack( adaptiveTrackSelection.updateSelectedTrack(
...@@ -191,7 +210,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -191,7 +210,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 24_999_000, /* bufferedDurationUs= */ 24_999_000,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(), /* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
// When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate // When bandwidth estimation is updated to 500L, we should switch down to use a lower bitrate
// format. When we don't have enough buffer at higher quality (24_999_000 us is smaller than // format. When we don't have enough buffer at higher quality (24_999_000 us is smaller than
...@@ -219,7 +238,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -219,7 +238,7 @@ public final class AdaptiveTrackSelectionTest {
queue.add(chunk3); queue.add(chunk3);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L);
AdaptiveTrackSelection adaptiveTrackSelection = adaptiveTrackSelection(trackGroup); AdaptiveTrackSelection adaptiveTrackSelection = prepareAdaptiveTrackSelection(trackGroup);
int size = adaptiveTrackSelection.evaluateQueueSize(0, queue); int size = adaptiveTrackSelection.evaluateQueueSize(0, queue);
assertThat(size).isEqualTo(3); assertThat(size).isEqualTo(3);
...@@ -245,7 +264,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -245,7 +264,7 @@ public final class AdaptiveTrackSelectionTest {
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L);
AdaptiveTrackSelection adaptiveTrackSelection = AdaptiveTrackSelection adaptiveTrackSelection =
adaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs( prepareAdaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs(
trackGroup, /* durationToRetainAfterDiscardMs= */ 15_000); trackGroup, /* durationToRetainAfterDiscardMs= */ 15_000);
int initialQueueSize = adaptiveTrackSelection.evaluateQueueSize(0, queue); int initialQueueSize = adaptiveTrackSelection.evaluateQueueSize(0, queue);
...@@ -285,7 +304,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -285,7 +304,7 @@ public final class AdaptiveTrackSelectionTest {
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(500L);
AdaptiveTrackSelection adaptiveTrackSelection = AdaptiveTrackSelection adaptiveTrackSelection =
adaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs( prepareAdaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs(
trackGroup, /* durationToRetainAfterDiscardMs= */ 15_000); trackGroup, /* durationToRetainAfterDiscardMs= */ 15_000);
int initialQueueSize = adaptiveTrackSelection.evaluateQueueSize(0, queue); int initialQueueSize = adaptiveTrackSelection.evaluateQueueSize(0, queue);
...@@ -344,7 +363,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -344,7 +363,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 4_000_000, /* bufferedDurationUs= */ 4_000_000,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
queue, queue,
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1);
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_ADAPTIVE); assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_ADAPTIVE);
...@@ -358,7 +377,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -358,7 +377,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 4_000_000, /* bufferedDurationUs= */ 4_000_000,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
queue, queue,
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL); assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_INITIAL);
...@@ -370,7 +389,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -370,7 +389,7 @@ public final class AdaptiveTrackSelectionTest {
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480); Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
TrackGroup trackGroup = new TrackGroup(format1, format2); TrackGroup trackGroup = new TrackGroup(format1, format2);
AdaptiveTrackSelection adaptiveTrackSelection = AdaptiveTrackSelection adaptiveTrackSelection =
prepareTrackSelection(adaptiveTrackSelection(trackGroup)); prepareTrackSelection(prepareAdaptiveTrackSelection(trackGroup));
Format unknownFormat = videoFormat(/* bitrate= */ 42, /* width= */ 300, /* height= */ 123); Format unknownFormat = videoFormat(/* bitrate= */ 42, /* width= */ 300, /* height= */ 123);
FakeMediaChunk chunk = FakeMediaChunk chunk =
new FakeMediaChunk(unknownFormat, /* startTimeUs= */ 0, /* endTimeUs= */ 2_000_000); new FakeMediaChunk(unknownFormat, /* startTimeUs= */ 0, /* endTimeUs= */ 2_000_000);
...@@ -381,7 +400,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -381,7 +400,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 2_000_000, /* bufferedDurationUs= */ 2_000_000,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
queue, queue,
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isAnyOf(format1, format2); assertThat(adaptiveTrackSelection.getSelectedFormat()).isAnyOf(format1, format2);
} }
...@@ -404,7 +423,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -404,7 +423,7 @@ public final class AdaptiveTrackSelectionTest {
new AdaptationCheckpoint(/* totalBandwidth= */ 5000, /* allocatedBandwidth= */ 1300)); new AdaptationCheckpoint(/* totalBandwidth= */ 5000, /* allocatedBandwidth= */ 1300));
AdaptiveTrackSelection adaptiveTrackSelection = AdaptiveTrackSelection adaptiveTrackSelection =
prepareTrackSelection( prepareTrackSelection(
adaptiveTrackSelectionWithAdaptationCheckpoints(trackGroup, checkpoints)); prepareAdaptiveTrackSelectionWithAdaptationCheckpoints(trackGroup, checkpoints));
// Ensure format0 is selected initially so that we can assert the upswitches. // Ensure format0 is selected initially so that we can assert the upswitches.
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1L);
...@@ -413,7 +432,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -413,7 +432,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 999_999_999_999L, /* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(), /* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format0); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format0);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(999L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(999L);
...@@ -422,7 +441,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -422,7 +441,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 999_999_999_999L, /* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(), /* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format0); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format0);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L);
...@@ -431,7 +450,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -431,7 +450,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 999_999_999_999L, /* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(), /* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(2499L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(2499L);
...@@ -440,7 +459,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -440,7 +459,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 999_999_999_999L, /* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(), /* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(3500L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(3500L);
...@@ -449,7 +468,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -449,7 +468,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 999_999_999_999L, /* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(), /* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(8999L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(8999L);
...@@ -458,7 +477,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -458,7 +477,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 999_999_999_999L, /* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(), /* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(9000L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(9000L);
...@@ -467,7 +486,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -467,7 +486,7 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 999_999_999_999L, /* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(), /* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(trackGroup, TEST_CHUNK_DURATION_US));
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format3); assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format3);
} }
...@@ -581,12 +600,29 @@ public final class AdaptiveTrackSelectionTest { ...@@ -581,12 +600,29 @@ public final class AdaptiveTrackSelectionTest {
.inOrder(); .inOrder();
} }
private AdaptiveTrackSelection adaptiveTrackSelection(TrackGroup trackGroup) { private AdaptiveTrackSelection prepareAdaptiveTrackSelection(TrackGroup trackGroup) {
return adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( return prepareAdaptiveTrackSelectionWithMinDurationForQualityIncreaseMs(
trackGroup, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS); trackGroup, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS);
} }
private AdaptiveTrackSelection adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( private AdaptiveTrackSelection prepareAdaptiveTrackSelectionWithBandwidthFraction(
TrackGroup trackGroup, float bandwidthFraction) {
return 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,
bandwidthFraction,
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
/* adaptationCheckpoints= */ ImmutableList.of(),
fakeClock));
}
private AdaptiveTrackSelection prepareAdaptiveTrackSelectionWithMinDurationForQualityIncreaseMs(
TrackGroup trackGroup, long minDurationForQualityIncreaseMs) { TrackGroup trackGroup, long minDurationForQualityIncreaseMs) {
return prepareTrackSelection( return prepareTrackSelection(
new AdaptiveTrackSelection( new AdaptiveTrackSelection(
...@@ -603,7 +639,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -603,7 +639,7 @@ public final class AdaptiveTrackSelectionTest {
fakeClock)); fakeClock));
} }
private AdaptiveTrackSelection adaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs( private AdaptiveTrackSelection prepareAdaptiveTrackSelectionWithMaxDurationForQualityDecreaseMs(
TrackGroup trackGroup, long maxDurationForQualityDecreaseMs) { TrackGroup trackGroup, long maxDurationForQualityDecreaseMs) {
return prepareTrackSelection( return prepareTrackSelection(
new AdaptiveTrackSelection( new AdaptiveTrackSelection(
...@@ -620,8 +656,9 @@ public final class AdaptiveTrackSelectionTest { ...@@ -620,8 +656,9 @@ public final class AdaptiveTrackSelectionTest {
fakeClock)); fakeClock));
} }
private AdaptiveTrackSelection adaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs( private AdaptiveTrackSelection
TrackGroup trackGroup, long durationToRetainAfterDiscardMs) { prepareAdaptiveTrackSelectionWithMinTimeBetweenBufferReevaluationMs(
TrackGroup trackGroup, long durationToRetainAfterDiscardMs) {
return prepareTrackSelection( return prepareTrackSelection(
new AdaptiveTrackSelection( new AdaptiveTrackSelection(
trackGroup, trackGroup,
...@@ -637,7 +674,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -637,7 +674,7 @@ public final class AdaptiveTrackSelectionTest {
fakeClock)); fakeClock));
} }
private AdaptiveTrackSelection adaptiveTrackSelectionWithAdaptationCheckpoints( private AdaptiveTrackSelection prepareAdaptiveTrackSelectionWithAdaptationCheckpoints(
TrackGroup trackGroup, List<AdaptationCheckpoint> adaptationCheckpoints) { TrackGroup trackGroup, List<AdaptationCheckpoint> adaptationCheckpoints) {
return prepareTrackSelection( return prepareTrackSelection(
new AdaptiveTrackSelection( new AdaptiveTrackSelection(
...@@ -662,10 +699,35 @@ public final class AdaptiveTrackSelectionTest { ...@@ -662,10 +699,35 @@ public final class AdaptiveTrackSelectionTest {
/* bufferedDurationUs= */ 0, /* bufferedDurationUs= */ 0,
/* availableDurationUs= */ C.TIME_UNSET, /* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ Collections.emptyList(), /* queue= */ Collections.emptyList(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS); createMediaChunkIterators(adaptiveTrackSelection.getTrackGroup(), TEST_CHUNK_DURATION_US));
return adaptiveTrackSelection; return adaptiveTrackSelection;
} }
private MediaChunkIterator[] createMediaChunkIterators(
TrackGroup trackGroup, long chunkDurationUs) {
MediaChunkIterator[] iterators = new MediaChunkIterator[trackGroup.length];
for (int i = 0; i < trackGroup.length; i++) {
iterators[i] =
new BaseMediaChunkIterator(/* fromIndex= */ 0, /* toIndex= */ 0) {
@Override
public DataSpec getDataSpec() {
return new DataSpec.Builder().setUri("https://test.example").build();
}
@Override
public long getChunkStartTimeUs() {
return 123_456_789;
}
@Override
public long getChunkEndTimeUs() {
return 123_456_789 + chunkDurationUs;
}
};
}
return iterators;
}
private int[] selectedAllTracksInGroup(TrackGroup trackGroup) { private int[] selectedAllTracksInGroup(TrackGroup trackGroup) {
int[] listIndices = new int[trackGroup.length]; int[] listIndices = new int[trackGroup.length];
for (int i = 0; i < trackGroup.length; i++) { for (int i = 0; i < trackGroup.length; i++) {
......
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