Commit bdc87908 by tonihei Committed by Andrew Lewis

Remove experimental track bitrate estimator features.

We are not planning to use them in the near future, so remove the experimental
flags and related features.

PiperOrigin-RevId: 263356590
parent f3a1b099
......@@ -47,8 +47,6 @@ 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(
......@@ -202,19 +200,6 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
bufferedFractionToLiveEdgeForQualityIncrease;
this.minTimeBetweenBufferReevaluationMs = minTimeBetweenBufferReevaluationMs;
this.clock = clock;
trackBitrateEstimator = TrackBitrateEstimator.DEFAULT;
}
/**
* Sets a TrackBitrateEstimator.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*
* @param trackBitrateEstimator A {@link TrackBitrateEstimator}.
*/
public final void experimental_setTrackBitrateEstimator(
TrackBitrateEstimator trackBitrateEstimator) {
this.trackBitrateEstimator = trackBitrateEstimator;
}
@Override
......@@ -245,7 +230,6 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
AdaptiveTrackSelection adaptiveSelection =
createAdaptiveTrackSelection(
definition.group, bandwidthMeter, definition.tracks, totalFixedBandwidth);
adaptiveSelection.experimental_setTrackBitrateEstimator(trackBitrateEstimator);
adaptiveSelections.add(adaptiveSelection);
selections[i] = adaptiveSelection;
}
......@@ -312,11 +296,7 @@ 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[] trackBitrates;
private TrackBitrateEstimator trackBitrateEstimator;
private float playbackSpeed;
private int selectedIndex;
private int reason;
......@@ -419,27 +399,6 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
playbackSpeed = 1f;
reason = C.SELECTION_REASON_UNKNOWN;
lastBufferEvaluationMs = C.TIME_UNSET;
trackBitrateEstimator = TrackBitrateEstimator.DEFAULT;
formats = new Format[length];
formatBitrates = new int[length];
trackBitrates = 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;
}
}
/**
* Sets a TrackBitrateEstimator.
*
* <p>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;
}
/**
......@@ -472,19 +431,16 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
MediaChunkIterator[] mediaChunkIterators) {
long nowMs = clock.elapsedRealtime();
// Update the estimated track bitrates.
trackBitrateEstimator.getBitrates(formats, queue, mediaChunkIterators, trackBitrates);
// Make initial selection
if (reason == C.SELECTION_REASON_UNKNOWN) {
reason = C.SELECTION_REASON_INITIAL;
selectedIndex = determineIdealSelectedIndex(nowMs, trackBitrates);
selectedIndex = determineIdealSelectedIndex(nowMs);
return;
}
// Stash the current selection, then make a new one.
int currentSelectedIndex = selectedIndex;
selectedIndex = determineIdealSelectedIndex(nowMs, trackBitrates);
selectedIndex = determineIdealSelectedIndex(nowMs);
if (selectedIndex == currentSelectedIndex) {
return;
}
......@@ -548,7 +504,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
if (playoutBufferedDurationBeforeLastChunkUs < minDurationToRetainAfterDiscardUs) {
return queueSize;
}
int idealSelectedIndex = determineIdealSelectedIndex(nowMs, formatBitrates);
int idealSelectedIndex = determineIdealSelectedIndex(nowMs);
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
......@@ -613,16 +569,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 trackBitrates The estimated track bitrates. May differ from format bitrates if more
* accurate estimates of the current track bitrates are available.
*/
private int determineIdealSelectedIndex(long nowMs, int[] trackBitrates) {
private int determineIdealSelectedIndex(long nowMs) {
long effectiveBitrate = bandwidthProvider.getAllocatedBandwidth();
int lowestBitrateNonBlacklistedIndex = 0;
for (int i = 0; i < length; i++) {
if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) {
Format format = getFormat(i);
if (canSelectFormat(format, trackBitrates[i], playbackSpeed, effectiveBitrate)) {
if (canSelectFormat(format, format.bitrate, playbackSpeed, effectiveBitrate)) {
return i;
} else {
lowestBitrateNonBlacklistedIndex = i;
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.trackselection;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import java.util.List;
/** Estimates track bitrate values. */
public interface TrackBitrateEstimator {
/**
* A {@link TrackBitrateEstimator} that returns the bitrate values defined in the track formats.
*/
TrackBitrateEstimator DEFAULT =
(formats, queue, iterators, bitrates) ->
TrackSelectionUtil.getFormatBitrates(formats, bitrates);
/**
* Returns bitrate values for a set of tracks whose formats are given.
*
* @param formats The track formats.
* @param queue The queue of already buffered {@link MediaChunk} instances. Must not be modified.
* @param iterators An array of {@link MediaChunkIterator}s providing information about the
* sequence of upcoming media chunks for each track.
* @param bitrates An array into which the bitrate values will be written. If non-null, this array
* is the one that will be returned.
* @return Bitrate values for the tracks. As long as the format of a track has set bitrate, a
* bitrate value is set in the returned array. Otherwise it might be set to {@link
* Format#NO_VALUE}.
*/
int[] getBitrates(
Format[] formats,
List<? extends MediaChunk> queue,
MediaChunkIterator[] iterators,
@Nullable int[] bitrates);
}
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.trackselection;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import java.util.List;
/** A {@link TrackBitrateEstimator} which derives estimates from a window of time. */
public final class WindowedTrackBitrateEstimator implements TrackBitrateEstimator {
private final long maxPastDurationUs;
private final long maxFutureDurationUs;
private final boolean useFormatBitrateAsLowerBound;
/**
* @param maxPastDurationMs Maximum duration of past chunks to be included in average bitrate
* values, in milliseconds.
* @param maxFutureDurationMs Maximum duration of future chunks to be included in average bitrate
* values, in milliseconds.
* @param useFormatBitrateAsLowerBound Whether to use the bitrate of the track's format as a lower
* bound for the estimated bitrate.
*/
public WindowedTrackBitrateEstimator(
long maxPastDurationMs, long maxFutureDurationMs, boolean useFormatBitrateAsLowerBound) {
this.maxPastDurationUs = C.msToUs(maxPastDurationMs);
this.maxFutureDurationUs = C.msToUs(maxFutureDurationMs);
this.useFormatBitrateAsLowerBound = useFormatBitrateAsLowerBound;
}
@Override
public int[] getBitrates(
Format[] formats,
List<? extends MediaChunk> queue,
MediaChunkIterator[] iterators,
@Nullable int[] bitrates) {
if (maxFutureDurationUs > 0 || maxPastDurationUs > 0) {
return TrackSelectionUtil.getBitratesUsingPastAndFutureInfo(
formats,
queue,
maxPastDurationUs,
iterators,
maxFutureDurationUs,
useFormatBitrateAsLowerBound,
bitrates);
}
return TrackSelectionUtil.getFormatBitrates(formats, bitrates);
}
}
......@@ -16,9 +16,6 @@
package com.google.android.exoplayer2.trackselection;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.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;
......@@ -37,13 +34,11 @@ import com.google.android.exoplayer2.trackselection.TrackSelection.Definition;
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;
/** Unit test for {@link AdaptiveTrackSelection}. */
......@@ -232,54 +227,6 @@ public final class AdaptiveTrackSelectionTest {
}
@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()))
.then(
(invocation) -> {
int[] returnValue = new int[] {500, 1000, 1500};
int[] inputArray = (int[]) invocation.getArguments()[3];
System.arraycopy(returnValue, 0, inputArray, 0, returnValue.length);
return returnValue;
});
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<Format[]> matcher =
formats ->
formats.length == 3
&& Arrays.asList(formats).containsAll(Arrays.asList(format1, format2, format3));
verify(estimator)
.getBitrates(
argThat(matcher),
eq(Collections.emptyList()),
eq(THREE_EMPTY_MEDIA_CHUNK_ITERATORS),
any());
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format3);
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_ADAPTIVE);
}
@Test
public void testEvaluateQueueSizeReturnQueueSizeIfBandwidthIsNotImproved() {
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.trackselection;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.testutil.FakeMediaChunk;
import com.google.android.exoplayer2.testutil.FakeMediaChunkIterator;
import com.google.android.exoplayer2.upstream.DataSpec;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
/** {@link WindowedTrackBitrateEstimator} tests. */
@RunWith(AndroidJUnit4.class)
public class WindowedTrackBitrateEstimatorTest {
private static final long MAX_DURATION_MS = 30_000;
@Test
public void getBitrates_zeroMaxDuration_returnsFormatBitrates() {
WindowedTrackBitrateEstimator estimator =
new WindowedTrackBitrateEstimator(
/* maxPastDurationMs= */ 0,
/* maxFutureDurationMs= */ 0,
/* useFormatBitrateAsLowerBound= */ false);
MediaChunk chunk = createMediaChunk(/* formatBitrate= */ 5, /* actualBitrate= */ 10);
MediaChunkIterator iterator1 = createMediaChunkIteratorWithBitrate(8);
MediaChunkIterator iterator2 = createMediaChunkIteratorWithBitrate(16);
Format format1 = createFormatWithBitrate(10);
Format format2 = createFormatWithBitrate(20);
int[] bitrates =
estimator.getBitrates(
new Format[] {format1, format2},
Collections.singletonList(chunk),
new MediaChunkIterator[] {iterator1, iterator2},
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(10, 20).inOrder();
}
@Test
public void getBitrates_futureMaxDurationSet_returnsEstimateUsingFutureChunks() {
WindowedTrackBitrateEstimator estimator =
new WindowedTrackBitrateEstimator(
/* maxPastDurationMs= */ 0, MAX_DURATION_MS, /* useFormatBitrateAsLowerBound= */ false);
MediaChunk chunk = createMediaChunk(/* formatBitrate= */ 5, /* actualBitrate= */ 10);
MediaChunkIterator iterator1 = createMediaChunkIteratorWithBitrate(8);
MediaChunkIterator iterator2 = createMediaChunkIteratorWithBitrate(16);
Format format1 = createFormatWithBitrate(10);
Format format2 = createFormatWithBitrate(20);
int[] bitrates =
estimator.getBitrates(
new Format[] {format1, format2},
Collections.singletonList(chunk),
new MediaChunkIterator[] {iterator1, iterator2},
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(8, 16).inOrder();
}
@Test
public void getBitrates_pastMaxDurationSet_returnsEstimateUsingPastChunks() {
WindowedTrackBitrateEstimator estimator =
new WindowedTrackBitrateEstimator(
MAX_DURATION_MS,
/* maxFutureDurationMs= */ 0,
/* useFormatBitrateAsLowerBound= */ false);
MediaChunk chunk = createMediaChunk(/* formatBitrate= */ 5, /* actualBitrate= */ 10);
MediaChunkIterator iterator1 = createMediaChunkIteratorWithBitrate(8);
MediaChunkIterator iterator2 = createMediaChunkIteratorWithBitrate(16);
Format format1 = createFormatWithBitrate(10);
Format format2 = createFormatWithBitrate(20);
int[] bitrates =
estimator.getBitrates(
new Format[] {format1, format2},
Collections.singletonList(chunk),
new MediaChunkIterator[] {iterator1, iterator2},
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(16, 32).inOrder();
}
@Test
public void
getBitrates_useFormatBitrateAsLowerBoundSetTrue_returnsEstimateIfOnlyHigherThanFormat() {
WindowedTrackBitrateEstimator estimator =
new WindowedTrackBitrateEstimator(
MAX_DURATION_MS, MAX_DURATION_MS, /* useFormatBitrateAsLowerBound= */ true);
MediaChunk chunk = createMediaChunk(/* formatBitrate= */ 5, /* actualBitrate= */ 10);
MediaChunkIterator iterator1 = createMediaChunkIteratorWithBitrate(80);
MediaChunkIterator iterator2 = createMediaChunkIteratorWithBitrate(16);
Format format1 = createFormatWithBitrate(10);
Format format2 = createFormatWithBitrate(20);
int[] bitrates =
estimator.getBitrates(
new Format[] {format1, format2},
Collections.singletonList(chunk),
new MediaChunkIterator[] {iterator1, iterator2},
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(80, 20).inOrder();
}
@Test
public void getBitrates_bitratesArrayGiven_returnsTheSameArray() {
WindowedTrackBitrateEstimator estimator =
new WindowedTrackBitrateEstimator(
MAX_DURATION_MS, MAX_DURATION_MS, /* useFormatBitrateAsLowerBound= */ true);
MediaChunk chunk = createMediaChunk(/* formatBitrate= */ 5, /* actualBitrate= */ 10);
MediaChunkIterator iterator1 = createMediaChunkIteratorWithBitrate(8);
MediaChunkIterator iterator2 = createMediaChunkIteratorWithBitrate(16);
Format format1 = createFormatWithBitrate(10);
Format format2 = createFormatWithBitrate(20);
int[] bitratesArrayToUse = new int[2];
int[] bitrates =
estimator.getBitrates(
new Format[] {format1, format2},
Collections.singletonList(chunk),
new MediaChunkIterator[] {iterator1, iterator2},
bitratesArrayToUse);
assertThat(bitrates).isSameInstanceAs(bitratesArrayToUse);
}
private static MediaChunk createMediaChunk(int formatBitrate, int actualBitrate) {
int length = actualBitrate / C.BITS_PER_BYTE;
DataSpec dataSpec =
new DataSpec(
Uri.EMPTY, /* absoluteStreamPosition= */ 0, length, /* key= */ null, /* flags= */ 0);
Format format = createFormatWithBitrate(formatBitrate);
return new FakeMediaChunk(
dataSpec, format, /* startTimeUs= */ 0L, /* endTimeUs= */ C.MICROS_PER_SECOND);
}
private static Format createFormatWithBitrate(int bitrate) {
return Format.createSampleFormat(
/* id= */ null,
/* sampleMimeType= */ null,
/* codecs= */ null,
bitrate,
/* drmInitData= */ null);
}
private static MediaChunkIterator createMediaChunkIteratorWithBitrate(int bitrate) {
return new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 1},
/* chunkLengths= */ new long[] {bitrate / C.BITS_PER_BYTE});
}
}
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