Commit a4fbc2c9 by tonihei Committed by Ian Baker

Clean up parallel adaptation code.

 - The AdaptiveTrackSelection doesn't need to use the experimental
terminolgy because the code is always triggered if there are multiple
adaptive selections.

- It's also confusing to pass the state on the outside after the object
creation, so moving everything into a simple control flow again where
the adaptation checkpoints are passed in via the constructor.

- Instead of triple arrays, we can use more readable named structures.

- The calculation of the checkpoints can be cleaned up to be more
readable by moving things to helper methods.

- The reserved bandwidth from all fixed track selections is really just
a special case of multiple parallel adaptataions. So this logic doesn't
need to be separate.

- The whole logic also didn't have test coverage so far. Added tests
for the actual adaptation using these checkpoints and the builder
calculating the checkpoints.

Overall this should be a no-op change.

PiperOrigin-RevId: 350162834
parent 0901fe6e
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.trackselection; package com.google.android.exoplayer2.trackselection;
import static java.lang.Math.max;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -27,11 +26,14 @@ import com.google.android.exoplayer2.source.TrackGroup; ...@@ -27,11 +26,14 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
...@@ -135,48 +137,23 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -135,48 +137,23 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
BandwidthMeter bandwidthMeter, BandwidthMeter bandwidthMeter,
MediaPeriodId mediaPeriodId, MediaPeriodId mediaPeriodId,
Timeline timeline) { Timeline timeline) {
ImmutableList<ImmutableList<AdaptationCheckpoint>> adaptationCheckpoints =
getAdaptationCheckpoints(definitions);
TrackSelection[] selections = new TrackSelection[definitions.length]; TrackSelection[] selections = new TrackSelection[definitions.length];
int totalFixedBandwidth = 0;
for (int i = 0; i < definitions.length; i++) { for (int i = 0; i < definitions.length; i++) {
Definition definition = definitions[i]; @Nullable Definition definition = definitions[i];
if (definition != null && definition.tracks.length == 1) { if (definition == null || definition.tracks.length == 0) {
// Make fixed selections first to know their total bandwidth. continue;
selections[i] =
new FixedTrackSelection(
definition.group, definition.tracks[0], definition.reason, definition.data);
int trackBitrate = definition.group.getFormat(definition.tracks[0]).bitrate;
if (trackBitrate != Format.NO_VALUE) {
totalFixedBandwidth += trackBitrate;
}
}
}
List<AdaptiveTrackSelection> adaptiveSelections = new ArrayList<>();
for (int i = 0; i < definitions.length; i++) {
Definition definition = definitions[i];
if (definition != null && definition.tracks.length > 1) {
AdaptiveTrackSelection adaptiveSelection =
createAdaptiveTrackSelection(
definition.group, bandwidthMeter, definition.tracks, totalFixedBandwidth);
adaptiveSelections.add(adaptiveSelection);
selections[i] = adaptiveSelection;
}
}
if (adaptiveSelections.size() > 1) {
long[][] adaptiveTrackBitrates = new long[adaptiveSelections.size()][];
for (int i = 0; i < adaptiveSelections.size(); i++) {
AdaptiveTrackSelection adaptiveSelection = adaptiveSelections.get(i);
adaptiveTrackBitrates[i] = new long[adaptiveSelection.length()];
for (int j = 0; j < adaptiveSelection.length(); j++) {
adaptiveTrackBitrates[i][j] =
adaptiveSelection.getFormat(adaptiveSelection.length() - j - 1).bitrate;
}
}
long[][][] bandwidthCheckpoints = getAllocationCheckpoints(adaptiveTrackBitrates);
for (int i = 0; i < adaptiveSelections.size(); i++) {
adaptiveSelections
.get(i)
.experimentalSetBandwidthAllocationCheckpoints(bandwidthCheckpoints[i]);
} }
selections[i] =
definition.tracks.length == 1
? new FixedTrackSelection(
definition.group, definition.tracks[0], definition.reason, definition.data)
: createAdaptiveTrackSelection(
definition.group,
bandwidthMeter,
definition.tracks,
adaptationCheckpoints.get(i));
} }
return selections; return selections;
} }
...@@ -187,23 +164,25 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -187,23 +164,25 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
* @param group The {@link TrackGroup}. * @param group The {@link TrackGroup}.
* @param bandwidthMeter A {@link BandwidthMeter} which can be used to select tracks. * @param bandwidthMeter A {@link BandwidthMeter} which can be used to select tracks.
* @param tracks The indices of the selected tracks in the track group. * @param tracks The indices of the selected tracks in the track group.
* @param totalFixedTrackBandwidth The total bandwidth used by all non-adaptive tracks, in bits * @param adaptationCheckpoints The {@link AdaptationCheckpoint checkpoints} that can be used to
* per second. * calculate available bandwidth for this selection.
* @return An {@link AdaptiveTrackSelection} for the specified tracks. * @return An {@link AdaptiveTrackSelection} for the specified tracks.
*/ */
protected AdaptiveTrackSelection createAdaptiveTrackSelection( protected AdaptiveTrackSelection createAdaptiveTrackSelection(
TrackGroup group, TrackGroup group,
BandwidthMeter bandwidthMeter, BandwidthMeter bandwidthMeter,
int[] tracks, int[] tracks,
int totalFixedTrackBandwidth) { ImmutableList<AdaptationCheckpoint> adaptationCheckpoints) {
return new AdaptiveTrackSelection( return new AdaptiveTrackSelection(
group, group,
tracks, tracks,
new DefaultBandwidthProvider(bandwidthMeter, bandwidthFraction, totalFixedTrackBandwidth), bandwidthMeter,
minDurationForQualityIncreaseMs, minDurationForQualityIncreaseMs,
maxDurationForQualityDecreaseMs, maxDurationForQualityDecreaseMs,
minDurationToRetainAfterDiscardMs, minDurationToRetainAfterDiscardMs,
bandwidthFraction,
bufferedFractionToLiveEdgeForQualityIncrease, bufferedFractionToLiveEdgeForQualityIncrease,
adaptationCheckpoints,
clock); clock);
} }
} }
...@@ -216,11 +195,13 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -216,11 +195,13 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
private static final long MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS = 1000; private static final long MIN_TIME_BETWEEN_BUFFER_REEVALUTATION_MS = 1000;
private final BandwidthProvider bandwidthProvider; private final BandwidthMeter bandwidthMeter;
private final long minDurationForQualityIncreaseUs; private final long minDurationForQualityIncreaseUs;
private final long maxDurationForQualityDecreaseUs; private final long maxDurationForQualityDecreaseUs;
private final long minDurationToRetainAfterDiscardUs; private final long minDurationToRetainAfterDiscardUs;
private final float bandwidthFraction;
private final float bufferedFractionToLiveEdgeForQualityIncrease; private final float bufferedFractionToLiveEdgeForQualityIncrease;
private final ImmutableList<AdaptationCheckpoint> adaptationCheckpoints;
private final Clock clock; private final Clock clock;
private float playbackSpeed; private float playbackSpeed;
...@@ -235,18 +216,17 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -235,18 +216,17 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
* empty. May be in any order. * empty. May be in any order.
* @param bandwidthMeter Provides an estimate of the currently available bandwidth. * @param bandwidthMeter Provides an estimate of the currently available bandwidth.
*/ */
public AdaptiveTrackSelection(TrackGroup group, int[] tracks, public AdaptiveTrackSelection(TrackGroup group, int[] tracks, BandwidthMeter bandwidthMeter) {
BandwidthMeter bandwidthMeter) {
this( this(
group, group,
tracks, tracks,
bandwidthMeter, bandwidthMeter,
/* reservedBandwidth= */ 0,
DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
DEFAULT_BANDWIDTH_FRACTION, DEFAULT_BANDWIDTH_FRACTION,
DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE, DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
/* adaptationCheckpoints= */ ImmutableList.of(),
Clock.DEFAULT); Clock.DEFAULT);
} }
...@@ -255,8 +235,6 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -255,8 +235,6 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
* @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
* empty. May be in any order. * empty. May be in any order.
* @param bandwidthMeter Provides an estimate of the currently available bandwidth. * @param bandwidthMeter Provides an estimate of the currently available bandwidth.
* @param reservedBandwidth The reserved bandwidth, which shouldn't be considered available for
* use, in bits per second.
* @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for the * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for the
* selected track to switch to one of higher quality. * selected track to switch to one of higher quality.
* @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for the * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for the
...@@ -274,62 +252,36 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -274,62 +252,36 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
* when the playback position is closer to the live edge than {@code * when the playback position is closer to the live edge than {@code
* minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a higher * minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a higher
* quality from happening. * quality from happening.
* @param adaptationCheckpoints The {@link AdaptationCheckpoint checkpoints} that can be used to
* calculate available bandwidth for this selection.
* @param clock The {@link Clock}.
*/ */
public AdaptiveTrackSelection( protected AdaptiveTrackSelection(
TrackGroup group, TrackGroup group,
int[] tracks, int[] tracks,
BandwidthMeter bandwidthMeter, BandwidthMeter bandwidthMeter,
long reservedBandwidth,
long minDurationForQualityIncreaseMs, long minDurationForQualityIncreaseMs,
long maxDurationForQualityDecreaseMs, long maxDurationForQualityDecreaseMs,
long minDurationToRetainAfterDiscardMs, long minDurationToRetainAfterDiscardMs,
float bandwidthFraction, float bandwidthFraction,
float bufferedFractionToLiveEdgeForQualityIncrease, float bufferedFractionToLiveEdgeForQualityIncrease,
Clock clock) { List<AdaptationCheckpoint> adaptationCheckpoints,
this(
group,
tracks,
new DefaultBandwidthProvider(bandwidthMeter, bandwidthFraction, reservedBandwidth),
minDurationForQualityIncreaseMs,
maxDurationForQualityDecreaseMs,
minDurationToRetainAfterDiscardMs,
bufferedFractionToLiveEdgeForQualityIncrease,
clock);
}
private AdaptiveTrackSelection(
TrackGroup group,
int[] tracks,
BandwidthProvider bandwidthProvider,
long minDurationForQualityIncreaseMs,
long maxDurationForQualityDecreaseMs,
long minDurationToRetainAfterDiscardMs,
float bufferedFractionToLiveEdgeForQualityIncrease,
Clock clock) { Clock clock) {
super(group, tracks); super(group, tracks);
this.bandwidthProvider = bandwidthProvider; this.bandwidthMeter = bandwidthMeter;
this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L; this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L;
this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L; this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L;
this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L; this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L;
this.bandwidthFraction = bandwidthFraction;
this.bufferedFractionToLiveEdgeForQualityIncrease = this.bufferedFractionToLiveEdgeForQualityIncrease =
bufferedFractionToLiveEdgeForQualityIncrease; bufferedFractionToLiveEdgeForQualityIncrease;
this.adaptationCheckpoints = ImmutableList.copyOf(adaptationCheckpoints);
this.clock = clock; this.clock = clock;
playbackSpeed = 1f; playbackSpeed = 1f;
reason = C.SELECTION_REASON_UNKNOWN; reason = C.SELECTION_REASON_UNKNOWN;
lastBufferEvaluationMs = C.TIME_UNSET; lastBufferEvaluationMs = C.TIME_UNSET;
} }
/**
* Sets checkpoints to determine the allocation bandwidth based on the total bandwidth.
*
* @param allocationCheckpoints List of checkpoints. Each element must be a long[2], with [0]
* being the total bandwidth and [1] being the allocated bandwidth.
*/
public void experimentalSetBandwidthAllocationCheckpoints(long[][] allocationCheckpoints) {
((DefaultBandwidthProvider) bandwidthProvider)
.experimentalSetBandwidthAllocationCheckpoints(allocationCheckpoints);
}
@CallSuper @CallSuper
@Override @Override
public void enable() { public void enable() {
...@@ -502,7 +454,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -502,7 +454,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
* Long#MIN_VALUE} to ignore track exclusion. * Long#MIN_VALUE} to ignore track exclusion.
*/ */
private int determineIdealSelectedIndex(long nowMs) { private int determineIdealSelectedIndex(long nowMs) {
long effectiveBitrate = bandwidthProvider.getAllocatedBandwidth(); long effectiveBitrate = getAllocatedBandwidth();
int lowestBitrateAllowedIndex = 0; int lowestBitrateAllowedIndex = 0;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) { if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) {
...@@ -525,162 +477,181 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { ...@@ -525,162 +477,181 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
: minDurationForQualityIncreaseUs; : minDurationForQualityIncreaseUs;
} }
/** Provides the allocated bandwidth. */ private long getAllocatedBandwidth() {
private interface BandwidthProvider { long totalBandwidth = (long) (bandwidthMeter.getBitrateEstimate() * bandwidthFraction);
if (adaptationCheckpoints.isEmpty()) {
/** Returns the allocated bitrate. */ return totalBandwidth;
long getAllocatedBandwidth(); }
} int nextIndex = 1;
while (nextIndex < adaptationCheckpoints.size() - 1
private static final class DefaultBandwidthProvider implements BandwidthProvider { && adaptationCheckpoints.get(nextIndex).totalBandwidth < totalBandwidth) {
nextIndex++;
private final BandwidthMeter bandwidthMeter;
private final float bandwidthFraction;
private final long reservedBandwidth;
@Nullable private long[][] allocationCheckpoints;
/* package */ DefaultBandwidthProvider(
BandwidthMeter bandwidthMeter, float bandwidthFraction, long reservedBandwidth) {
this.bandwidthMeter = bandwidthMeter;
this.bandwidthFraction = bandwidthFraction;
this.reservedBandwidth = reservedBandwidth;
} }
AdaptationCheckpoint previous = adaptationCheckpoints.get(nextIndex - 1);
AdaptationCheckpoint next = adaptationCheckpoints.get(nextIndex);
float fractionBetweenCheckpoints =
(float) (totalBandwidth - previous.totalBandwidth)
/ (next.totalBandwidth - previous.totalBandwidth);
return previous.allocatedBandwidth
+ (long)
(fractionBetweenCheckpoints * (next.allocatedBandwidth - previous.allocatedBandwidth));
}
@Override /**
public long getAllocatedBandwidth() { * Returns adaptation checkpoints for allocating bandwidth for adaptive track selections.
long totalBandwidth = (long) (bandwidthMeter.getBitrateEstimate() * bandwidthFraction); *
long allocatableBandwidth = max(0L, totalBandwidth - reservedBandwidth); * @param definitions Array of track selection {@link Definition definitions}. Elements may be
if (allocationCheckpoints == null) { * null.
return allocatableBandwidth; * @return List of {@link AdaptationCheckpoint checkpoints} for each adaptive {@link Definition}
* with more than one selected track.
*/
private static ImmutableList<ImmutableList<AdaptationCheckpoint>> getAdaptationCheckpoints(
@NullableType Definition[] definitions) {
List<ImmutableList.@NullableType Builder<AdaptationCheckpoint>> checkPointBuilders =
new ArrayList<>();
for (int i = 0; i < definitions.length; i++) {
if (definitions[i] != null && definitions[i].tracks.length > 1) {
ImmutableList.Builder<AdaptationCheckpoint> builder = ImmutableList.builder();
// Add initial all-zero checkpoint.
builder.add(new AdaptationCheckpoint(/* totalBandwidth= */ 0, /* allocatedBandwidth= */ 0));
checkPointBuilders.add(builder);
} else {
checkPointBuilders.add(null);
} }
int nextIndex = 1; }
while (nextIndex < allocationCheckpoints.length - 1 // Add minimum bitrate selection checkpoint.
&& allocationCheckpoints[nextIndex][0] < allocatableBandwidth) { long[][] trackBitrates = getSortedTrackBitrates(definitions);
nextIndex++; int[] currentTrackIndices = new int[trackBitrates.length];
long[] currentTrackBitrates = new long[trackBitrates.length];
for (int i = 0; i < trackBitrates.length; i++) {
currentTrackBitrates[i] = trackBitrates[i].length == 0 ? 0 : trackBitrates[i][0];
}
addCheckpoint(checkPointBuilders, currentTrackBitrates);
// Iterate through all adaptive checkpoints.
ImmutableList<Integer> switchOrder = getSwitchOrder(trackBitrates);
for (int i = 0; i < switchOrder.size(); i++) {
int switchIndex = switchOrder.get(i);
int newTrackIndex = ++currentTrackIndices[switchIndex];
currentTrackBitrates[switchIndex] = trackBitrates[switchIndex][newTrackIndex];
addCheckpoint(checkPointBuilders, currentTrackBitrates);
}
// Add final checkpoint to extrapolate additional bandwidth for adaptive selections.
for (int i = 0; i < definitions.length; i++) {
if (checkPointBuilders.get(i) != null) {
currentTrackBitrates[i] *= 2;
} }
long[] previous = allocationCheckpoints[nextIndex - 1];
long[] next = allocationCheckpoints[nextIndex];
float fractionBetweenCheckpoints =
(float) (allocatableBandwidth - previous[0]) / (next[0] - previous[0]);
return previous[1] + (long) (fractionBetweenCheckpoints * (next[1] - previous[1]));
} }
addCheckpoint(checkPointBuilders, currentTrackBitrates);
ImmutableList.Builder<ImmutableList<AdaptationCheckpoint>> output = ImmutableList.builder();
for (int i = 0; i < checkPointBuilders.size(); i++) {
@Nullable ImmutableList.Builder<AdaptationCheckpoint> builder = checkPointBuilders.get(i);
output.add(builder == null ? ImmutableList.of() : builder.build());
}
return output.build();
}
/* package */ void experimentalSetBandwidthAllocationCheckpoints( /** Returns sorted track bitrates for all selected tracks. */
long[][] allocationCheckpoints) { private static long[][] getSortedTrackBitrates(@NullableType Definition[] definitions) {
Assertions.checkArgument(allocationCheckpoints.length >= 2); long[][] trackBitrates = new long[definitions.length][];
this.allocationCheckpoints = allocationCheckpoints; for (int i = 0; i < definitions.length; i++) {
@Nullable Definition definition = definitions[i];
if (definition == null) {
trackBitrates[i] = new long[0];
continue;
}
trackBitrates[i] = new long[definition.tracks.length];
for (int j = 0; j < definition.tracks.length; j++) {
trackBitrates[i][j] = definition.group.getFormat(definition.tracks[j]).bitrate;
}
Arrays.sort(trackBitrates[i]);
} }
return trackBitrates;
} }
/** /**
* Returns allocation checkpoints for allocating bandwidth between multiple adaptive track * Returns order of track indices in which the respective track should be switched up.
* selections.
* *
* @param trackBitrates Array of [selectionIndex][trackIndex] -> trackBitrate. * @param trackBitrates Sorted tracks bitrates for each selection.
* @return Array of allocation checkpoints [selectionIndex][checkpointIndex][2] with [0]=total * @return List of track indices indicating in which order tracks should be switched up.
* bandwidth at checkpoint and [1]=allocated bandwidth at checkpoint.
*/ */
private static long[][][] getAllocationCheckpoints(long[][] trackBitrates) { private static ImmutableList<Integer> getSwitchOrder(long[][] trackBitrates) {
// Algorithm: // Algorithm:
// 1. Use log bitrates to treat all resolution update steps equally. // 1. Use log bitrates to treat all bitrate update steps equally.
// 2. Distribute switch points for each selection equally in the same [0.0-1.0] range. // 2. Distribute switch points for each selection equally in the same [0.0-1.0] range.
// 3. Switch up one format at a time in the order of the switch points. // 3. Switch up one format at a time in the order of the switch points.
double[][] logBitrates = getLogArrayValues(trackBitrates); Multimap<Double, Integer> switchPoints = MultimapBuilder.treeKeys().arrayListValues().build();
double[][] switchPoints = getSwitchPoints(logBitrates); for (int i = 0; i < trackBitrates.length; i++) {
if (trackBitrates[i].length <= 1) {
// There will be (count(switch point) + 3) checkpoints: continue;
// [0] = all zero, [1] = minimum bitrates, [2-(end-1)] = up-switch points,
// [end] = extra point to set slope for additional bitrate.
int checkpointCount = countArrayElements(switchPoints) + 3;
long[][][] checkpoints = new long[logBitrates.length][checkpointCount][2];
int[] currentSelection = new int[logBitrates.length];
setCheckpointValues(checkpoints, /* checkpointIndex= */ 1, trackBitrates, currentSelection);
for (int checkpointIndex = 2; checkpointIndex < checkpointCount - 1; checkpointIndex++) {
int nextUpdateIndex = 0;
double nextUpdateSwitchPoint = Double.MAX_VALUE;
for (int i = 0; i < logBitrates.length; i++) {
if (currentSelection[i] + 1 == logBitrates[i].length) {
continue;
}
double switchPoint = switchPoints[i][currentSelection[i]];
if (switchPoint < nextUpdateSwitchPoint) {
nextUpdateSwitchPoint = switchPoint;
nextUpdateIndex = i;
}
} }
currentSelection[nextUpdateIndex]++; double[] logBitrates = new double[trackBitrates[i].length];
setCheckpointValues(checkpoints, checkpointIndex, trackBitrates, currentSelection); for (int j = 0; j < trackBitrates[i].length; j++) {
} logBitrates[j] = trackBitrates[i][j] == Format.NO_VALUE ? 0 : Math.log(trackBitrates[i][j]);
for (long[][] points : checkpoints) { }
points[checkpointCount - 1][0] = 2 * points[checkpointCount - 2][0]; double totalBitrateDiff = logBitrates[logBitrates.length - 1] - logBitrates[0];
points[checkpointCount - 1][1] = 2 * points[checkpointCount - 2][1]; for (int j = 0; j < logBitrates.length - 1; j++) {
} double switchBitrate = 0.5 * (logBitrates[j] + logBitrates[j + 1]);
return checkpoints; double switchPoint =
} totalBitrateDiff == 0.0 ? 1.0 : (switchBitrate - logBitrates[0]) / totalBitrateDiff;
switchPoints.put(switchPoint, i);
/** Converts all input values to Math.log(value). */
private static double[][] getLogArrayValues(long[][] values) {
double[][] logValues = new double[values.length][];
for (int i = 0; i < values.length; i++) {
logValues[i] = new double[values[i].length];
for (int j = 0; j < values[i].length; j++) {
logValues[i][j] = values[i][j] == Format.NO_VALUE ? 0 : Math.log(values[i][j]);
} }
} }
return logValues; return ImmutableList.copyOf(switchPoints.values());
} }
/** /**
* Returns idealized switch points for each switch between consecutive track selection bitrates. * Add a checkpoint to the builders.
* *
* @param logBitrates Log bitrates with [selectionCount][formatCount]. * @param checkPointBuilders Builders for adaptation checkpoints. May have null elements.
* @return Linearly distributed switch points in the range of [0.0-1.0]. * @param checkpointBitrates The bitrates of each track at this checkpoint.
*/ */
private static double[][] getSwitchPoints(double[][] logBitrates) { private static void addCheckpoint(
double[][] switchPoints = new double[logBitrates.length][]; List<ImmutableList.@NullableType Builder<AdaptationCheckpoint>> checkPointBuilders,
for (int i = 0; i < logBitrates.length; i++) { long[] checkpointBitrates) {
switchPoints[i] = new double[logBitrates[i].length - 1]; // Total bitrate includes all fixed tracks.
if (switchPoints[i].length == 0) { long totalBitrate = 0;
for (int i = 0; i < checkpointBitrates.length; i++) {
totalBitrate += checkpointBitrates[i];
}
for (int i = 0; i < checkPointBuilders.size(); i++) {
@Nullable ImmutableList.Builder<AdaptationCheckpoint> builder = checkPointBuilders.get(i);
if (builder == null) {
continue; continue;
} }
double totalBitrateDiff = logBitrates[i][logBitrates[i].length - 1] - logBitrates[i][0]; builder.add(
for (int j = 0; j < logBitrates[i].length - 1; j++) { new AdaptationCheckpoint(
double switchBitrate = 0.5 * (logBitrates[i][j] + logBitrates[i][j + 1]); /* totalBandwidth= */ totalBitrate, /* allocatedBandwidth= */ checkpointBitrates[i]));
switchPoints[i][j] =
totalBitrateDiff == 0.0 ? 1.0 : (switchBitrate - logBitrates[i][0]) / totalBitrateDiff;
}
} }
return switchPoints;
} }
/** Returns total number of elements in a 2D array. */ /** Checkpoint to determine allocated bandwidth. */
private static int countArrayElements(double[][] array) { protected static final class AdaptationCheckpoint {
int count = 0;
for (double[] subArray : array) { /** Total bandwidth in bits per second at which this checkpoint applies. */
count += subArray.length; public final long totalBandwidth;
/** Allocated bandwidth at this checkpoint in bits per second. */
public final long allocatedBandwidth;
public AdaptationCheckpoint(long totalBandwidth, long allocatedBandwidth) {
this.totalBandwidth = totalBandwidth;
this.allocatedBandwidth = allocatedBandwidth;
} }
return count;
}
/** @Override
* Sets checkpoint bitrates. public boolean equals(@Nullable Object o) {
* if (this == o) {
* @param checkpoints Output checkpoints with [selectionIndex][checkpointIndex][2] where [0]=Total return true;
* bitrate and [1]=Allocated bitrate. }
* @param checkpointIndex The checkpoint index. if (!(o instanceof AdaptationCheckpoint)) {
* @param trackBitrates The track bitrates with [selectionIndex][trackIndex]. return false;
* @param selectedTracks The indices of selected tracks for each selection for this checkpoint. }
*/ AdaptationCheckpoint that = (AdaptationCheckpoint) o;
private static void setCheckpointValues( return totalBandwidth == that.totalBandwidth && allocatedBandwidth == that.allocatedBandwidth;
long[][][] checkpoints, int checkpointIndex, long[][] trackBitrates, int[] selectedTracks) {
long totalBitrate = 0;
for (int i = 0; i < checkpoints.length; i++) {
checkpoints[i][checkpointIndex][1] = trackBitrates[i][selectedTracks[i]];
totalBitrate += checkpoints[i][checkpointIndex][1];
} }
for (long[][] points : checkpoints) {
points[checkpointIndex][0] = totalBitrate; @Override
public int hashCode() {
return 31 * (int) totalBandwidth + (int) allocatedBandwidth;
} }
} }
} }
...@@ -22,10 +22,15 @@ import static org.mockito.MockitoAnnotations.initMocks; ...@@ -22,10 +22,15 @@ import static org.mockito.MockitoAnnotations.initMocks;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator; import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.testutil.FakeClock; import com.google.android.exoplayer2.testutil.FakeClock;
import com.google.android.exoplayer2.testutil.FakeMediaChunk; import com.google.android.exoplayer2.testutil.FakeMediaChunk;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection.AdaptationCheckpoint;
import com.google.android.exoplayer2.trackselection.TrackSelection.Definition;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
...@@ -312,7 +317,7 @@ public final class AdaptiveTrackSelectionTest { ...@@ -312,7 +317,7 @@ public final class AdaptiveTrackSelectionTest {
trackGroup, trackGroup,
mockBandwidthMeter, mockBandwidthMeter,
/* tracks= */ new int[] {0, 1}, /* tracks= */ new int[] {0, 1},
/* totalFixedTrackBandwidth= */ 0); /* adaptationCheckpoints= */ ImmutableList.of());
// Make initial selection. // Make initial selection.
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L); when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L);
...@@ -380,6 +385,199 @@ public final class AdaptiveTrackSelectionTest { ...@@ -380,6 +385,199 @@ public final class AdaptiveTrackSelectionTest {
assertThat(adaptiveTrackSelection.getSelectedFormat()).isAnyOf(format1, format2); assertThat(adaptiveTrackSelection.getSelectedFormat()).isAnyOf(format1, format2);
} }
@Test
public void updateSelectedTrack_withAdaptationCheckpoints_usesOnlyAllocatedBandwidth() {
Format format0 = videoFormat(/* bitrate= */ 100, /* width= */ 160, /* height= */ 120);
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
Format format2 = videoFormat(/* bitrate= */ 1000, /* width= */ 640, /* height= */ 480);
Format format3 = videoFormat(/* bitrate= */ 1500, /* width= */ 1024, /* height= */ 768);
TrackGroup trackGroup = new TrackGroup(format0, format1, format2, format3);
// Choose checkpoints relative to formats so that one is in the first range, one somewhere in
// the middle, and one needs to extrapolate beyond the last checkpoint.
List<AdaptationCheckpoint> checkpoints =
ImmutableList.of(
new AdaptationCheckpoint(/* totalBandwidth= */ 0, /* allocatedBandwidth= */ 0),
new AdaptationCheckpoint(/* totalBandwidth= */ 1500, /* allocatedBandwidth= */ 750),
new AdaptationCheckpoint(/* totalBandwidth= */ 3000, /* allocatedBandwidth= */ 750),
new AdaptationCheckpoint(/* totalBandwidth= */ 4000, /* allocatedBandwidth= */ 1250),
new AdaptationCheckpoint(/* totalBandwidth= */ 5000, /* allocatedBandwidth= */ 1300));
AdaptiveTrackSelection adaptiveTrackSelection =
prepareTrackSelection(
adaptiveTrackSelectionWithAdaptationCheckpoints(trackGroup, checkpoints));
// Ensure format0 is selected initially so that we can assert the upswitches.
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1L);
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format0);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(999L);
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format0);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L);
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(2499L);
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format1);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(3500L);
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(8999L);
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(9000L);
adaptiveTrackSelection.updateSelectedTrack(
/* playbackPositionUs= */ 0,
/* bufferedDurationUs= */ 999_999_999_999L,
/* availableDurationUs= */ C.TIME_UNSET,
/* queue= */ ImmutableList.of(),
/* mediaChunkIterators= */ THREE_EMPTY_MEDIA_CHUNK_ITERATORS);
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format3);
}
@Test
public void
builderCreateTrackSelections_withSingleAdaptiveGroup_usesCorrectAdaptationCheckpoints() {
Format formatFixed1 = new Format.Builder().setAverageBitrate(500).build();
Format formatFixed2 = new Format.Builder().setAverageBitrate(1000).build();
Format formatAdaptive1 = new Format.Builder().setAverageBitrate(2000).build();
Format formatAdaptive2 = new Format.Builder().setAverageBitrate(3000).build();
Format formatAdaptive3 = new Format.Builder().setAverageBitrate(4000).build();
Format formatAdaptive4 = new Format.Builder().setAverageBitrate(5000).build();
TrackGroup trackGroupMultipleFixed = new TrackGroup(formatFixed1, formatFixed2);
TrackGroup trackGroupAdaptive =
new TrackGroup(formatAdaptive1, formatAdaptive2, formatAdaptive3, formatAdaptive4);
Definition definitionFixed1 = new Definition(trackGroupMultipleFixed, /* tracks...= */ 0);
Definition definitionFixed2 = new Definition(trackGroupMultipleFixed, /* tracks...= */ 1);
Definition definitionAdaptive = new Definition(trackGroupAdaptive, /* tracks...= */ 1, 2, 3);
List<List<AdaptationCheckpoint>> checkPoints = new ArrayList<>();
AdaptiveTrackSelection.Factory factory =
new AdaptiveTrackSelection.Factory() {
@Override
protected AdaptiveTrackSelection createAdaptiveTrackSelection(
TrackGroup group,
BandwidthMeter bandwidthMeter,
int[] tracks,
ImmutableList<AdaptationCheckpoint> adaptationCheckpoints) {
checkPoints.add(adaptationCheckpoints);
return super.createAdaptiveTrackSelection(
group, bandwidthMeter, tracks, adaptationCheckpoints);
}
};
Timeline timeline = new FakeTimeline();
factory.createTrackSelections(
new Definition[] {null, definitionFixed1, null, definitionFixed2, definitionAdaptive},
mockBandwidthMeter,
new MediaSource.MediaPeriodId(timeline.getUidOfPeriod(/* periodIndex= */ 0)),
timeline);
assertThat(checkPoints).hasSize(1);
assertThat(checkPoints.get(0))
.containsExactly(
new AdaptationCheckpoint(/* totalBandwidth= */ 0, /* allocatedBandwidth= */ 0),
new AdaptationCheckpoint(/* totalBandwidth= */ 4500, /* allocatedBandwidth= */ 3000),
new AdaptationCheckpoint(/* totalBandwidth= */ 5500, /* allocatedBandwidth= */ 4000),
new AdaptationCheckpoint(/* totalBandwidth= */ 6500, /* allocatedBandwidth= */ 5000),
new AdaptationCheckpoint(/* totalBandwidth= */ 11500, /* allocatedBandwidth= */ 10000))
.inOrder();
}
@Test
public void
builderCreateTrackSelections_withMultipleAdaptiveGroups_usesCorrectAdaptationCheckpoints() {
Format group1Format1 = new Format.Builder().setAverageBitrate(500).build();
Format group1Format2 = new Format.Builder().setAverageBitrate(1000).build();
Format group2Format1 = new Format.Builder().setAverageBitrate(250).build();
Format group2Format2 = new Format.Builder().setAverageBitrate(500).build();
Format group2Format3 = new Format.Builder().setAverageBitrate(1250).build();
Format group2UnusedFormat = new Format.Builder().setAverageBitrate(2000).build();
Format fixedFormat = new Format.Builder().setAverageBitrate(5000).build();
TrackGroup trackGroup1 = new TrackGroup(group1Format1, group1Format2);
TrackGroup trackGroup2 =
new TrackGroup(group2Format1, group2Format2, group2Format3, group2UnusedFormat);
TrackGroup fixedGroup = new TrackGroup(fixedFormat);
Definition definition1 = new Definition(trackGroup1, /* tracks...= */ 0, 1);
Definition definition2 = new Definition(trackGroup2, /* tracks...= */ 0, 1, 2);
Definition fixedDefinition = new Definition(fixedGroup, /* tracks...= */ 0);
List<List<AdaptationCheckpoint>> checkPoints = new ArrayList<>();
AdaptiveTrackSelection.Factory factory =
new AdaptiveTrackSelection.Factory() {
@Override
protected AdaptiveTrackSelection createAdaptiveTrackSelection(
TrackGroup group,
BandwidthMeter bandwidthMeter,
int[] tracks,
ImmutableList<AdaptationCheckpoint> adaptationCheckpoints) {
checkPoints.add(adaptationCheckpoints);
return super.createAdaptiveTrackSelection(
group, bandwidthMeter, tracks, adaptationCheckpoints);
}
};
Timeline timeline = new FakeTimeline();
factory.createTrackSelections(
new Definition[] {null, definition1, fixedDefinition, definition2, null},
mockBandwidthMeter,
new MediaSource.MediaPeriodId(timeline.getUidOfPeriod(/* periodIndex= */ 0)),
timeline);
assertThat(checkPoints).hasSize(2);
assertThat(checkPoints.get(0))
.containsExactly(
new AdaptationCheckpoint(/* totalBandwidth= */ 0, /* allocatedBandwidth= */ 0),
new AdaptationCheckpoint(/* totalBandwidth= */ 5750, /* allocatedBandwidth= */ 500),
new AdaptationCheckpoint(/* totalBandwidth= */ 6000, /* allocatedBandwidth= */ 500),
new AdaptationCheckpoint(/* totalBandwidth= */ 6500, /* allocatedBandwidth= */ 1000),
new AdaptationCheckpoint(/* totalBandwidth= */ 7250, /* allocatedBandwidth= */ 1000),
new AdaptationCheckpoint(/* totalBandwidth= */ 9500, /* allocatedBandwidth= */ 2000))
.inOrder();
assertThat(checkPoints.get(1))
.containsExactly(
new AdaptationCheckpoint(/* totalBandwidth= */ 0, /* allocatedBandwidth= */ 0),
new AdaptationCheckpoint(/* totalBandwidth= */ 5750, /* allocatedBandwidth= */ 250),
new AdaptationCheckpoint(/* totalBandwidth= */ 6000, /* allocatedBandwidth= */ 500),
new AdaptationCheckpoint(/* totalBandwidth= */ 6500, /* allocatedBandwidth= */ 500),
new AdaptationCheckpoint(/* totalBandwidth= */ 7250, /* allocatedBandwidth= */ 1250),
new AdaptationCheckpoint(/* totalBandwidth= */ 9500, /* allocatedBandwidth= */ 2500))
.inOrder();
}
private AdaptiveTrackSelection adaptiveTrackSelection(TrackGroup trackGroup) { private AdaptiveTrackSelection adaptiveTrackSelection(TrackGroup trackGroup) {
return adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs( return adaptiveTrackSelectionWithMinDurationForQualityIncreaseMs(
trackGroup, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS); trackGroup, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS);
...@@ -392,12 +590,12 @@ public final class AdaptiveTrackSelectionTest { ...@@ -392,12 +590,12 @@ public final class AdaptiveTrackSelectionTest {
trackGroup, trackGroup,
selectedAllTracksInGroup(trackGroup), selectedAllTracksInGroup(trackGroup),
mockBandwidthMeter, mockBandwidthMeter,
/* reservedBandwidth= */ 0,
minDurationForQualityIncreaseMs, minDurationForQualityIncreaseMs,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
/* bandwidthFraction= */ 1.0f, /* bandwidthFraction= */ 1.0f,
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE, AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
/* adaptationCheckpoints= */ ImmutableList.of(),
fakeClock)); fakeClock));
} }
...@@ -408,12 +606,12 @@ public final class AdaptiveTrackSelectionTest { ...@@ -408,12 +606,12 @@ public final class AdaptiveTrackSelectionTest {
trackGroup, trackGroup,
selectedAllTracksInGroup(trackGroup), selectedAllTracksInGroup(trackGroup),
mockBandwidthMeter, mockBandwidthMeter,
/* reservedBandwidth= */ 0,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
maxDurationForQualityDecreaseMs, maxDurationForQualityDecreaseMs,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
/* bandwidthFraction= */ 1.0f, /* bandwidthFraction= */ 1.0f,
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE, AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
/* adaptationCheckpoints= */ ImmutableList.of(),
fakeClock)); fakeClock));
} }
...@@ -424,12 +622,28 @@ public final class AdaptiveTrackSelectionTest { ...@@ -424,12 +622,28 @@ public final class AdaptiveTrackSelectionTest {
trackGroup, trackGroup,
selectedAllTracksInGroup(trackGroup), selectedAllTracksInGroup(trackGroup),
mockBandwidthMeter, mockBandwidthMeter,
/* reservedBandwidth= */ 0,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
durationToRetainAfterDiscardMs, durationToRetainAfterDiscardMs,
/* bandwidthFraction= */ 1.0f, /* bandwidthFraction= */ 1.0f,
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE, AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
/* adaptationCheckpoints= */ ImmutableList.of(),
fakeClock));
}
private AdaptiveTrackSelection adaptiveTrackSelectionWithAdaptationCheckpoints(
TrackGroup trackGroup, List<AdaptationCheckpoint> adaptationCheckpoints) {
return prepareTrackSelection(
new AdaptiveTrackSelection(
trackGroup,
selectedAllTracksInGroup(trackGroup),
mockBandwidthMeter,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
/* bandwidthFraction= */ 1.0f,
AdaptiveTrackSelection.DEFAULT_BUFFERED_FRACTION_TO_LIVE_EDGE_FOR_QUALITY_INCREASE,
adaptationCheckpoints,
fakeClock)); fakeClock));
} }
......
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