mirror of
https://github.com/samsonjs/media.git
synced 2026-04-01 10:35:48 +00:00
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
This commit is contained in:
parent
f3a1b099e6
commit
bdc8790896
7 changed files with 5 additions and 1281 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* 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}. */
|
||||
|
|
@ -231,54 +226,6 @@ 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()))
|
||||
.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);
|
||||
|
|
|
|||
|
|
@ -1,617 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
/*
|
||||
* 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});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue