diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java index 696f2bdefe..2c74d2412d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java @@ -47,6 +47,8 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { private final long minTimeBetweenBufferReevaluationMs; private final Clock clock; + private TrackBitrateEstimator trackBitrateEstimator; + /** Creates an adaptive track selection factory with default parameters. */ public Factory() { this( @@ -199,6 +201,18 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { bufferedFractionToLiveEdgeForQualityIncrease; this.minTimeBetweenBufferReevaluationMs = minTimeBetweenBufferReevaluationMs; this.clock = clock; + trackBitrateEstimator = TrackBitrateEstimator.DEFAULT; + } + + /** + * Sets a TrackBitrateEstimator. + * + *
This method is experimental, and will be renamed or removed in a future release. + * + * @param trackBitrateEstimator A {@link TrackBitrateEstimator}. + */ + public void experimental_setTrackBitrateEstimator(TrackBitrateEstimator trackBitrateEstimator) { + this.trackBitrateEstimator = trackBitrateEstimator; } @Override @@ -207,17 +221,20 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { if (this.bandwidthMeter != null) { bandwidthMeter = this.bandwidthMeter; } - return new AdaptiveTrackSelection( - group, - tracks, - bandwidthMeter, - minDurationForQualityIncreaseMs, - maxDurationForQualityDecreaseMs, - minDurationToRetainAfterDiscardMs, - bandwidthFraction, - bufferedFractionToLiveEdgeForQualityIncrease, - minTimeBetweenBufferReevaluationMs, - clock); + AdaptiveTrackSelection adaptiveTrackSelection = + new AdaptiveTrackSelection( + group, + tracks, + bandwidthMeter, + minDurationForQualityIncreaseMs, + maxDurationForQualityDecreaseMs, + minDurationToRetainAfterDiscardMs, + bandwidthFraction, + bufferedFractionToLiveEdgeForQualityIncrease, + minTimeBetweenBufferReevaluationMs, + clock); + adaptiveTrackSelection.experimental_setTrackBitrateEstimator(trackBitrateEstimator); + return adaptiveTrackSelection; } } @@ -236,7 +253,11 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { private final float bufferedFractionToLiveEdgeForQualityIncrease; private final long minTimeBetweenBufferReevaluationMs; private final Clock clock; + private final Format[] formats; + private final int[] formatBitrates; + private final int[] estimatedBitrates; + private TrackBitrateEstimator trackBitrateEstimator; private float playbackSpeed; private int selectedIndex; private int reason; @@ -314,11 +335,32 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { playbackSpeed = 1f; reason = C.SELECTION_REASON_INITIAL; lastBufferEvaluationMs = C.TIME_UNSET; + trackBitrateEstimator = TrackBitrateEstimator.DEFAULT; + formats = new Format[length]; + formatBitrates = new int[length]; + estimatedBitrates = new int[length]; + for (int i = 0; i < length; i++) { + @SuppressWarnings("nullness:method.invocation.invalid") + Format format = getFormat(i); + formats[i] = format; + formatBitrates[i] = formats[i].bitrate; + } @SuppressWarnings("nullness:method.invocation.invalid") - int selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE); + int selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE, formatBitrates); this.selectedIndex = selectedIndex; } + /** + * Sets a TrackBitrateEstimator. + * + *
This method is experimental, and will be renamed or removed in a future release.
+ *
+ * @param trackBitrateEstimator A {@link TrackBitrateEstimator}.
+ */
+ public void experimental_setTrackBitrateEstimator(TrackBitrateEstimator trackBitrateEstimator) {
+ this.trackBitrateEstimator = trackBitrateEstimator;
+ }
+
@Override
public void enable() {
lastBufferEvaluationMs = C.TIME_UNSET;
@@ -338,9 +380,11 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
MediaChunkIterator[] mediaChunkIterators) {
long nowMs = clock.elapsedRealtime();
+ trackBitrateEstimator.getBitrates(formats, queue, mediaChunkIterators, estimatedBitrates);
+
// Stash the current selection, then make a new one.
int currentSelectedIndex = selectedIndex;
- selectedIndex = determineIdealSelectedIndex(nowMs);
+ selectedIndex = determineIdealSelectedIndex(nowMs, estimatedBitrates);
if (selectedIndex == currentSelectedIndex) {
return;
}
@@ -402,7 +446,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
if (playoutBufferedDurationBeforeLastChunkUs < minDurationToRetainAfterDiscardUs) {
return queueSize;
}
- int idealSelectedIndex = determineIdealSelectedIndex(nowMs);
+ int idealSelectedIndex = determineIdealSelectedIndex(nowMs, formatBitrates);
Format idealFormat = getFormat(idealSelectedIndex);
// If the chunks contain video, discard from the first SD chunk beyond
// minDurationToRetainAfterDiscardUs whose resolution and bitrate are both lower than the ideal
@@ -429,14 +473,14 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
*
* @param nowMs The current time in the timebase of {@link Clock#elapsedRealtime()}, or {@link
* Long#MIN_VALUE} to ignore blacklisting.
+ * @param bitrates Track bitrates.
*/
- private int determineIdealSelectedIndex(long nowMs) {
+ private int determineIdealSelectedIndex(long nowMs, int[] bitrates) {
long effectiveBitrate = (long) (bandwidthMeter.getBitrateEstimate() * bandwidthFraction);
int lowestBitrateNonBlacklistedIndex = 0;
for (int i = 0; i < length; i++) {
if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) {
- Format format = getFormat(i);
- if (Math.round(format.bitrate * playbackSpeed) <= effectiveBitrate) {
+ if (Math.round(bitrates[i] * playbackSpeed) <= effectiveBitrate) {
return i;
} else {
lowestBitrateNonBlacklistedIndex = i;
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java
index dcad02fef1..29018a520c 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelectionTest.java
@@ -16,6 +16,9 @@
package com.google.android.exoplayer2.trackselection;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -32,11 +35,13 @@ import com.google.android.exoplayer2.testutil.FakeMediaChunk;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
@@ -220,6 +225,52 @@ public final class AdaptiveTrackSelectionTest {
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_ADAPTIVE);
}
+ @Test
+ public void testUpdateSelectedTrackSwitchUpIfTrackBitrateEstimateIsLow() {
+ 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);
+
+ // The second measurement onward returns 1500L, which isn't enough to switch up to format3 as
+ // the format bitrate is 2000.
+ when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 1500L);
+
+ // But TrackBitrateEstimator returns 1500 for 3rd track so it should switch up.
+ TrackBitrateEstimator estimator = mock(TrackBitrateEstimator.class);
+ when(estimator.getBitrates(any(), any(), any(), any())).thenReturn(new int[] {500, 1000, 1500});
+
+ adaptiveTrackSelection = adaptiveTrackSelection(trackGroup);
+ adaptiveTrackSelection.experimental_setTrackBitrateEstimator(estimator);
+
+ adaptiveTrackSelection.updateSelectedTrack(
+ /* playbackPositionUs= */ 0,
+ /* bufferedDurationUs= */ AdaptiveTrackSelection
+ .DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS
+ * 1000,
+ /* availableDurationUs= */ C.TIME_UNSET,
+ /* queue= */ Collections.emptyList(),
+ /* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
+
+ ArgumentMatcher