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);
}
......@@ -16,18 +16,9 @@
package com.google.android.exoplayer2.trackselection;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.source.chunk.MediaChunkListIterator;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.TrackSelection.Definition;
import com.google.android.exoplayer2.util.Assertions;
import java.util.Arrays;
import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Track selection related utility methods. */
......@@ -106,261 +97,4 @@ public final class TrackSelectionUtil {
}
return builder.build();
}
/**
* Returns average bitrate for chunks in bits per second. Chunks are included in average until
* {@code maxDurationMs} or the first unknown length chunk.
*
* @param iterator Iterator for media chunk sequences.
* @param maxDurationUs Maximum duration of chunks to be included in average bitrate, in
* microseconds.
* @return Average bitrate for chunks in bits per second, or {@link Format#NO_VALUE} if there are
* no chunks or the first chunk length is unknown.
*/
public static int getAverageBitrate(MediaChunkIterator iterator, long maxDurationUs) {
long totalDurationUs = 0;
long totalLength = 0;
while (iterator.next()) {
long chunkLength = iterator.getDataSpec().length;
if (chunkLength == C.LENGTH_UNSET) {
break;
}
long chunkDurationUs = iterator.getChunkEndTimeUs() - iterator.getChunkStartTimeUs();
if (totalDurationUs + chunkDurationUs >= maxDurationUs) {
totalLength += chunkLength * (maxDurationUs - totalDurationUs) / chunkDurationUs;
totalDurationUs = maxDurationUs;
break;
}
totalDurationUs += chunkDurationUs;
totalLength += chunkLength;
}
return totalDurationUs == 0
? Format.NO_VALUE
: (int) (totalLength * C.BITS_PER_BYTE * C.MICROS_PER_SECOND / totalDurationUs);
}
/**
* Returns bitrate values for a set of tracks whose upcoming media chunk iterators and formats are
* given.
*
* <p>If an average bitrate can't be calculated, an estimation is calculated using average bitrate
* of another track and the ratio of the bitrate values defined in the formats of the two tracks.
*
* @param iterators An array of {@link MediaChunkIterator}s providing information about the
* sequence of upcoming media chunks for each track.
* @param formats The track formats.
* @param maxDurationUs Maximum duration of chunks to be included in average bitrate values, in
* microseconds.
* @param bitrates If not null, stores bitrate values in this array.
* @return Average bitrate values for the tracks. If for a track, an average bitrate or an
* estimation can't be calculated, {@link Format#NO_VALUE} is set.
* @see #getAverageBitrate(MediaChunkIterator, long)
*/
@VisibleForTesting
/* package */ static int[] getBitratesUsingFutureInfo(
MediaChunkIterator[] iterators,
Format[] formats,
long maxDurationUs,
@Nullable int[] bitrates) {
int trackCount = iterators.length;
Assertions.checkArgument(trackCount == formats.length);
if (trackCount == 0) {
return new int[0];
}
if (bitrates == null) {
bitrates = new int[trackCount];
}
if (maxDurationUs == 0) {
Arrays.fill(bitrates, Format.NO_VALUE);
return bitrates;
}
int[] formatBitrates = new int[trackCount];
float[] bitrateRatios = new float[trackCount];
boolean needEstimateBitrate = false;
boolean canEstimateBitrate = false;
for (int i = 0; i < trackCount; i++) {
int bitrate = getAverageBitrate(iterators[i], maxDurationUs);
if (bitrate != Format.NO_VALUE) {
int formatBitrate = formats[i].bitrate;
formatBitrates[i] = formatBitrate;
if (formatBitrate != Format.NO_VALUE) {
bitrateRatios[i] = ((float) bitrate) / formatBitrate;
canEstimateBitrate = true;
}
} else {
needEstimateBitrate = true;
formatBitrates[i] = Format.NO_VALUE;
}
bitrates[i] = bitrate;
}
if (needEstimateBitrate && canEstimateBitrate) {
estimateBitrates(bitrates, formats, formatBitrates, bitrateRatios);
}
return bitrates;
}
/**
* Returns bitrate values for a set of tracks whose formats are given, using the given queue of
* already buffered {@link MediaChunk} instances.
*
* @param queue The queue of already buffered {@link MediaChunk} instances. Must not be modified.
* @param formats The track formats.
* @param maxDurationUs Maximum duration of chunks to be included in average bitrate values, in
* microseconds.
* @param bitrates If not null, calculates bitrate values only for indexes set to Format.NO_VALUE
* and stores result in this array.
* @return Bitrate values for the tracks. If for a track, a bitrate value can't be calculated,
* {@link Format#NO_VALUE} is set.
* @see #getBitratesUsingFutureInfo(MediaChunkIterator[], Format[], long, int[])
*/
@VisibleForTesting
/* package */ static int[] getBitratesUsingPastInfo(
List<? extends MediaChunk> queue,
Format[] formats,
long maxDurationUs,
@Nullable int[] bitrates) {
if (bitrates == null) {
bitrates = new int[formats.length];
Arrays.fill(bitrates, Format.NO_VALUE);
}
if (maxDurationUs == 0) {
return bitrates;
}
int queueAverageBitrate = getAverageQueueBitrate(queue, maxDurationUs);
if (queueAverageBitrate == Format.NO_VALUE) {
return bitrates;
}
int queueFormatBitrate = queue.get(queue.size() - 1).trackFormat.bitrate;
if (queueFormatBitrate != Format.NO_VALUE) {
float queueBitrateRatio = ((float) queueAverageBitrate) / queueFormatBitrate;
estimateBitrates(
bitrates, formats, new int[] {queueFormatBitrate}, new float[] {queueBitrateRatio});
}
return bitrates;
}
/**
* Returns bitrate values for a set of tracks whose formats are given, using the given upcoming
* media chunk iterators and the queue of already buffered {@link MediaChunk}s.
*
* @param formats The track formats.
* @param queue The queue of already buffered {@link MediaChunk}s. Must not be modified.
* @param maxPastDurationUs Maximum duration of past chunks to be included in average bitrate
* values, in microseconds.
* @param iterators An array of {@link MediaChunkIterator}s providing information about the
* sequence of upcoming media chunks for each track.
* @param maxFutureDurationUs Maximum duration of future chunks to be included in average bitrate
* values, in microseconds.
* @param useFormatBitrateAsLowerBound Whether to return the estimated bitrate only if it's higher
* than the bitrate of the track's format.
* @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}.
*/
public static int[] getBitratesUsingPastAndFutureInfo(
Format[] formats,
List<? extends MediaChunk> queue,
long maxPastDurationUs,
MediaChunkIterator[] iterators,
long maxFutureDurationUs,
boolean useFormatBitrateAsLowerBound,
@Nullable int[] bitrates) {
bitrates = getBitratesUsingFutureInfo(iterators, formats, maxFutureDurationUs, bitrates);
getBitratesUsingPastInfo(queue, formats, maxPastDurationUs, bitrates);
for (int i = 0; i < bitrates.length; i++) {
int bitrate = bitrates[i];
if (bitrate == Format.NO_VALUE
|| (useFormatBitrateAsLowerBound
&& formats[i].bitrate != Format.NO_VALUE
&& bitrate < formats[i].bitrate)) {
bitrates[i] = formats[i].bitrate;
}
}
return bitrates;
}
/**
* Returns an array containing {@link Format#bitrate} values for given each format in order.
*
* @param formats The format array to copy {@link Format#bitrate} values.
* @param bitrates If not null, stores bitrate values in this array.
* @return An array containing {@link Format#bitrate} values for given each format in order.
*/
public static int[] getFormatBitrates(Format[] formats, @Nullable int[] bitrates) {
int trackCount = formats.length;
if (bitrates == null) {
bitrates = new int[trackCount];
}
for (int i = 0; i < trackCount; i++) {
bitrates[i] = formats[i].bitrate;
}
return bitrates;
}
/**
* Fills missing values in the given {@code bitrates} array by calculates an estimation using the
* closest reference bitrate value.
*
* @param bitrates An array of bitrates to be filled with estimations. Missing values are set to
* {@link Format#NO_VALUE}.
* @param formats An array of formats, one for each bitrate.
* @param referenceBitrates An array of reference bitrates which are used to calculate
* estimations.
* @param referenceBitrateRatios An array containing ratio of reference bitrates to their bitrate
* estimates.
*/
private static void estimateBitrates(
int[] bitrates, Format[] formats, int[] referenceBitrates, float[] referenceBitrateRatios) {
for (int i = 0; i < bitrates.length; i++) {
if (bitrates[i] == Format.NO_VALUE) {
int formatBitrate = formats[i].bitrate;
if (formatBitrate != Format.NO_VALUE) {
int closestReferenceBitrateIndex =
getClosestBitrateIndex(formatBitrate, referenceBitrates);
bitrates[i] =
(int) (referenceBitrateRatios[closestReferenceBitrateIndex] * formatBitrate);
}
}
}
}
private static int getAverageQueueBitrate(List<? extends MediaChunk> queue, long maxDurationUs) {
if (queue.isEmpty()) {
return Format.NO_VALUE;
}
MediaChunkListIterator iterator =
new MediaChunkListIterator(getSingleFormatSubQueue(queue), /* reverseOrder= */ true);
return getAverageBitrate(iterator, maxDurationUs);
}
private static List<? extends MediaChunk> getSingleFormatSubQueue(
List<? extends MediaChunk> queue) {
Format queueFormat = queue.get(queue.size() - 1).trackFormat;
int queueSize = queue.size();
for (int i = queueSize - 2; i >= 0; i--) {
if (!queue.get(i).trackFormat.equals(queueFormat)) {
return queue.subList(i + 1, queueSize);
}
}
return queue;
}
private static int getClosestBitrateIndex(int formatBitrate, int[] formatBitrates) {
int closestDistance = Integer.MAX_VALUE;
int closestFormat = C.INDEX_UNSET;
for (int j = 0; j < formatBitrates.length; j++) {
if (formatBitrates[j] != Format.NO_VALUE) {
int distance = Math.abs(formatBitrates[j] - formatBitrate);
if (distance < closestDistance) {
closestDistance = distance;
closestFormat = j;
}
}
}
return closestFormat;
}
}
/*
* 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.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.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
/** {@link TrackSelectionUtil} tests. */
@RunWith(AndroidJUnit4.class)
public class TrackSelectionUtilTest {
public static final long MAX_DURATION_US = 30 * C.MICROS_PER_SECOND;
@Test
public void getAverageBitrate_emptyIterator_returnsNoValue() {
assertThat(TrackSelectionUtil.getAverageBitrate(MediaChunkIterator.EMPTY, MAX_DURATION_US))
.isEqualTo(Format.NO_VALUE);
}
@Test
public void getAverageBitrate_oneChunk_returnsChunkBitrate() {
long[] chunkTimeBoundariesSec = {12, 17};
long[] chunkLengths = {10};
FakeMediaChunkIterator iterator =
new FakeMediaChunkIterator(chunkTimeBoundariesSec, chunkLengths);
assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)).isEqualTo(16);
}
@Test
public void getAverageBitrate_multipleSameDurationChunks_returnsAverageChunkBitrate() {
long[] chunkTimeBoundariesSec = {0, 5, 10};
long[] chunkLengths = {10, 20};
FakeMediaChunkIterator iterator =
new FakeMediaChunkIterator(chunkTimeBoundariesSec, chunkLengths);
assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)).isEqualTo(24);
}
@Test
public void getAverageBitrate_multipleDifferentDurationChunks_returnsAverageChunkBitrate() {
long[] chunkTimeBoundariesSec = {0, 5, 15, 30};
long[] chunkLengths = {10, 20, 30};
FakeMediaChunkIterator iterator =
new FakeMediaChunkIterator(chunkTimeBoundariesSec, chunkLengths);
assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)).isEqualTo(16);
}
@Test
public void getAverageBitrate_firstChunkLengthUnset_returnsNoValue() {
long[] chunkTimeBoundariesSec = {0, 5, 15, 30};
long[] chunkLengths = {C.LENGTH_UNSET, 20, 30};
FakeMediaChunkIterator iterator =
new FakeMediaChunkIterator(chunkTimeBoundariesSec, chunkLengths);
assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US))
.isEqualTo(Format.NO_VALUE);
}
@Test
public void getAverageBitrate_secondChunkLengthUnset_returnsFirstChunkBitrate() {
long[] chunkTimeBoundariesSec = {0, 5, 15, 30};
long[] chunkLengths = {10, C.LENGTH_UNSET, 30};
FakeMediaChunkIterator iterator =
new FakeMediaChunkIterator(chunkTimeBoundariesSec, chunkLengths);
assertThat(TrackSelectionUtil.getAverageBitrate(iterator, MAX_DURATION_US)).isEqualTo(16);
}
@Test
public void
getAverageBitrate_chunksExceedingMaxDuration_returnsAverageChunkBitrateUpToMaxDuration() {
long[] chunkTimeBoundariesSec = {0, 5, 15, 45, 50};
long[] chunkLengths = {10, 20, 30, 100};
FakeMediaChunkIterator iterator =
new FakeMediaChunkIterator(chunkTimeBoundariesSec, chunkLengths);
long maxDurationUs = 30 * C.MICROS_PER_SECOND;
int averageBitrate = TrackSelectionUtil.getAverageBitrate(iterator, maxDurationUs);
assertThat(averageBitrate).isEqualTo(12);
}
@Test
public void getAverageBitrate_zeroMaxDuration_returnsNoValue() {
long[] chunkTimeBoundariesSec = {0, 5, 10};
long[] chunkLengths = {10, 20};
FakeMediaChunkIterator iterator =
new FakeMediaChunkIterator(chunkTimeBoundariesSec, chunkLengths);
assertThat(TrackSelectionUtil.getAverageBitrate(iterator, /* maxDurationUs= */ 0))
.isEqualTo(Format.NO_VALUE);
}
@Test
public void getBitratesUsingFutureInfo_noIterator_returnsEmptyArray() {
assertThat(
TrackSelectionUtil.getBitratesUsingFutureInfo(
new MediaChunkIterator[0], new Format[0], MAX_DURATION_US, /* bitrates= */ null))
.hasLength(0);
}
@Test
public void getBitratesUsingFutureInfo_emptyIterator_returnsNoValue() {
int[] bitrates =
TrackSelectionUtil.getBitratesUsingFutureInfo(
new MediaChunkIterator[] {MediaChunkIterator.EMPTY},
new Format[] {createFormatWithBitrate(10)},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(Format.NO_VALUE);
}
@Test
public void getBitratesUsingFutureInfo_twoTracksZeroMaxDuration_returnsNoValue() {
FakeMediaChunkIterator iterator1 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10});
FakeMediaChunkIterator iterator2 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30},
/* chunkLengths= */ new long[] {10, 20, 30});
int[] bitrates =
TrackSelectionUtil.getBitratesUsingFutureInfo(
new MediaChunkIterator[] {iterator1, iterator2},
new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)},
/* maxDurationUs= */ 0,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(Format.NO_VALUE, Format.NO_VALUE);
}
@Test
public void getBitratesUsingFutureInfo_twoTracks_returnsBitrates() {
FakeMediaChunkIterator iterator1 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10});
FakeMediaChunkIterator iterator2 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30},
/* chunkLengths= */ new long[] {10, 20, 30});
int[] bitrates =
TrackSelectionUtil.getBitratesUsingFutureInfo(
new MediaChunkIterator[] {iterator1, iterator2},
new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(8, 16).inOrder();
}
@Test
public void getBitratesUsingFutureInfo_bitratesArrayGiven_returnsTheSameArray() {
FakeMediaChunkIterator iterator1 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10});
FakeMediaChunkIterator iterator2 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30},
/* chunkLengths= */ new long[] {10, 20, 30});
int[] bitratesArrayToUse = new int[2];
int[] bitrates =
TrackSelectionUtil.getBitratesUsingFutureInfo(
new MediaChunkIterator[] {iterator1, iterator2},
new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)},
MAX_DURATION_US,
bitratesArrayToUse);
assertThat(bitrates).isSameInstanceAs(bitratesArrayToUse);
}
@Test
public void getBitratesUsingFutureInfo_emptyIterator_returnsEstimationUsingClosest() {
FakeMediaChunkIterator iterator1 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {10});
Format format1 = createFormatWithBitrate(10);
MediaChunkIterator iterator2 = MediaChunkIterator.EMPTY;
Format format2 = createFormatWithBitrate(20);
FakeMediaChunkIterator iterator3 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {50});
Format format3 = createFormatWithBitrate(25);
FakeMediaChunkIterator iterator4 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {20});
Format format4 = createFormatWithBitrate(30);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingFutureInfo(
new MediaChunkIterator[] {iterator1, iterator2, iterator3, iterator4},
new Format[] {format1, format2, format3, format4},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(16, 64, 80, 32).inOrder();
}
@Test
public void getBitratesUsingFutureInfo_formatWithoutBitrate_returnsNoValueForEmpty() {
FakeMediaChunkIterator iterator1 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 5}, /* chunkLengths= */ new long[] {10});
Format format1 = createFormatWithBitrate(10);
MediaChunkIterator iterator2 = MediaChunkIterator.EMPTY;
Format format2 = createFormatWithBitrate(Format.NO_VALUE);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingFutureInfo(
new MediaChunkIterator[] {iterator1, iterator2},
new Format[] {format1, format2},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(16, Format.NO_VALUE).inOrder();
}
@Test
public void getBitratesUsingPastInfo_noFormat_returnsEmptyArray() {
FakeMediaChunk chunk =
createChunk(
createFormatWithBitrate(10),
/* length= */ 10,
/* startTimeSec= */ 0,
/* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.singletonList(chunk), new Format[0], MAX_DURATION_US, /* bitrates= */ null);
assertThat(bitrates).hasLength(0);
}
@Test
public void getBitratesUsingPastInfo_emptyQueue_returnsNoValue() {
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.emptyList(),
new Format[] {createFormatWithBitrate(10)},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(Format.NO_VALUE);
}
@Test
public void getBitratesUsingPastInfo_oneChunkFormatNoBitrate_returnsNoValue() {
Format format = createFormatWithBitrate(Format.NO_VALUE);
FakeMediaChunk chunk =
createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.singletonList(chunk),
new Format[] {format},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(Format.NO_VALUE);
}
@Test
public void getBitratesUsingPastInfo_oneChunkNoLength_returnsNoValue() {
Format format = createFormatWithBitrate(10);
FakeMediaChunk chunk =
createChunk(
format, /* length= */ C.LENGTH_UNSET, /* startTimeSec= */ 0, /* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.singletonList(chunk),
new Format[] {format},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(Format.NO_VALUE);
}
@Test
public void getBitratesUsingPastInfo_oneChunkWithSameFormat_returnsBitrates() {
Format format = createFormatWithBitrate(10);
FakeMediaChunk chunk =
createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.singletonList(chunk),
new Format[] {format},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(8).inOrder();
}
@Test
public void getBitratesUsingPastInfo_zeroMaxDuration_returnsNoValue() {
Format format = createFormatWithBitrate(10);
FakeMediaChunk chunk =
createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.singletonList(chunk),
new Format[] {format},
/* maxDurationUs= */ 0,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(Format.NO_VALUE).inOrder();
}
@Test
public void getBitratesUsingPastInfo_multipleChunkWithSameFormat_returnsAverageBitrate() {
Format format = createFormatWithBitrate(10);
FakeMediaChunk chunk =
createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 10);
FakeMediaChunk chunk2 =
createChunk(format, /* length= */ 20, /* startTimeSec= */ 10, /* endTimeSec= */ 20);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Arrays.asList(chunk, chunk2),
new Format[] {format},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(12).inOrder();
}
@Test
public void getBitratesUsingPastInfo_oneChunkWithDifferentFormat_returnsEstimationBitrate() {
FakeMediaChunk chunk =
createChunk(
createFormatWithBitrate(10),
/* length= */ 10,
/* startTimeSec= */ 0,
/* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.singletonList(chunk),
new Format[] {createFormatWithBitrate(20)},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(16).inOrder();
}
@Test
public void getBitratesUsingPastInfo_trackFormatNoBitrate_returnsNoValue() {
FakeMediaChunk chunk =
createChunk(
createFormatWithBitrate(10),
/* length= */ 10,
/* startTimeSec= */ 0,
/* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.singletonList(chunk),
new Format[] {createFormatWithBitrate(Format.NO_VALUE)},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(Format.NO_VALUE);
}
@Test
public void getBitratesUsingPastInfo_multipleTracks_returnsBitrates() {
FakeMediaChunk chunk =
createChunk(
createFormatWithBitrate(10),
/* length= */ 10,
/* startTimeSec= */ 0,
/* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.singletonList(chunk),
new Format[] {createFormatWithBitrate(20), createFormatWithBitrate(30)},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(16, 24).inOrder();
}
@Test
public void getBitratesUsingPastInfo_bitratesArrayGiven_returnsTheSameArray() {
FakeMediaChunk chunk =
createChunk(
createFormatWithBitrate(10),
/* length= */ 10,
/* startTimeSec= */ 0,
/* endTimeSec= */ 10);
int[] bitratesArrayToUse = new int[2];
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Collections.singletonList(chunk),
new Format[] {createFormatWithBitrate(20), createFormatWithBitrate(30)},
MAX_DURATION_US,
bitratesArrayToUse);
assertThat(bitrates).isSameInstanceAs(bitratesArrayToUse);
}
@Test
public void
getBitratesUsingPastInfo_multipleChunkExceedingMaxDuration_returnsAverageUntilMaxDuration() {
Format format = createFormatWithBitrate(10);
FakeMediaChunk chunk =
createChunk(format, /* length= */ 10, /* startTimeSec= */ 0, /* endTimeSec= */ 20);
FakeMediaChunk chunk2 =
createChunk(format, /* length= */ 40, /* startTimeSec= */ 20, /* endTimeSec= */ 40);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Arrays.asList(chunk, chunk2),
new Format[] {format},
/* maxDurationUs= */ 30 * C.MICROS_PER_SECOND,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(12).inOrder();
}
@Test
public void
getBitratesUsingPastInfo_chunksWithDifferentFormats_returnsChunkAverageBitrateForLastFormat() {
FakeMediaChunk chunk =
createChunk(
createFormatWithBitrate(10),
/* length= */ 10,
/* startTimeSec= */ 0,
/* endTimeSec= */ 10);
FakeMediaChunk chunk2 =
createChunk(
createFormatWithBitrate(20),
/* length= */ 40,
/* startTimeSec= */ 10,
/* endTimeSec= */ 20);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastInfo(
Arrays.asList(chunk, chunk2),
new Format[] {createFormatWithBitrate(10)},
MAX_DURATION_US,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(16).inOrder();
}
@Test
public void getBitratesUsingPastAndFutureInfo_noPastInfo_returnsBitratesUsingOnlyFutureInfo() {
FakeMediaChunkIterator iterator1 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10});
FakeMediaChunkIterator iterator2 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30},
/* chunkLengths= */ new long[] {10, 20, 30});
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastAndFutureInfo(
new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)},
Collections.emptyList(),
MAX_DURATION_US,
new MediaChunkIterator[] {iterator1, iterator2},
MAX_DURATION_US,
/* useFormatBitrateAsLowerBound= */ false,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(8, 16).inOrder();
}
@Test
public void getBitratesUsingPastAndFutureInfo_noFutureInfo_returnsBitratesUsingOnlyPastInfo() {
FakeMediaChunk chunk =
createChunk(
createFormatWithBitrate(10),
/* length= */ 10,
/* startTimeSec= */ 0,
/* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastAndFutureInfo(
new Format[] {createFormatWithBitrate(20), createFormatWithBitrate(30)},
Collections.singletonList(chunk),
MAX_DURATION_US,
new MediaChunkIterator[] {MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY},
MAX_DURATION_US,
/* useFormatBitrateAsLowerBound= */ false,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(16, 24).inOrder();
}
@Test
public void
getBitratesUsingPastAndFutureInfo_pastAndFutureInfo_returnsBitratesUsingOnlyFutureInfo() {
FakeMediaChunk chunk =
createChunk(
createFormatWithBitrate(5),
/* length= */ 10,
/* startTimeSec= */ 0,
/* endTimeSec= */ 10);
FakeMediaChunkIterator iterator1 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10});
FakeMediaChunkIterator iterator2 =
new FakeMediaChunkIterator(
/* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30},
/* chunkLengths= */ new long[] {10, 20, 30});
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastAndFutureInfo(
new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)},
Collections.singletonList(chunk),
MAX_DURATION_US,
new MediaChunkIterator[] {iterator1, iterator2},
MAX_DURATION_US,
/* useFormatBitrateAsLowerBound= */ false,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(8, 16).inOrder();
}
@Test
public void getBitratesUsingPastAndFutureInfo_noPastAndFutureInfo_returnsBitratesOfFormats() {
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastAndFutureInfo(
new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)},
Collections.emptyList(),
MAX_DURATION_US,
new MediaChunkIterator[] {MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY},
MAX_DURATION_US,
/* useFormatBitrateAsLowerBound= */ false,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(10, 20).inOrder();
}
@Test
public void
getBitratesUsingPastAndFutureInfo_estimatesLowerAndUseFormatBitrateAsLowerBoundTrue_returnsBitratesOfFormats() {
FakeMediaChunk chunk =
createChunk(
createFormatWithBitrate(10),
/* length= */ 10,
/* startTimeSec= */ 0,
/* endTimeSec= */ 10);
int[] bitrates =
TrackSelectionUtil.getBitratesUsingPastAndFutureInfo(
new Format[] {createFormatWithBitrate(20), createFormatWithBitrate(30)},
Collections.singletonList(chunk),
MAX_DURATION_US,
new MediaChunkIterator[] {MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY},
MAX_DURATION_US,
/* useFormatBitrateAsLowerBound= */ true,
/* bitrates= */ null);
assertThat(bitrates).asList().containsExactly(20, 30).inOrder();
}
private static FakeMediaChunk createChunk(
Format format, int length, int startTimeSec, int endTimeSec) {
DataSpec dataSpec =
new DataSpec(
Uri.EMPTY, /* absoluteStreamPosition= */ 0, length, /* key= */ null, /* flags= */ 0);
return new FakeMediaChunk(
dataSpec, format, startTimeSec * C.MICROS_PER_SECOND, endTimeSec * C.MICROS_PER_SECOND);
}
private static Format createFormatWithBitrate(int bitrate) {
return Format.createSampleFormat(
/* id= */ null,
/* sampleMimeType= */ null,
/* codecs= */ null,
bitrate,
/* drmInitData= */ null);
}
}
/*
* 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