Commit 158bc1ed by eguven Committed by Oliver Woodman

Add TrackBitrateEstimator and WindowedTrackBitrateEstimator

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=220529628
parent f895be6b
/*
* 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 android.support.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);
}
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.trackselection; package com.google.android.exoplayer2.trackselection;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -74,20 +75,25 @@ public final class TrackSelectionUtil { ...@@ -74,20 +75,25 @@ public final class TrackSelectionUtil {
* @param formats The track formats. * @param formats The track formats.
* @param maxDurationUs Maximum duration of chunks to be included in average bitrate values, in * @param maxDurationUs Maximum duration of chunks to be included in average bitrate values, in
* microseconds. * 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 * @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. * estimation can't be calculated, {@link Format#NO_VALUE} is set.
* @see #getAverageBitrate(MediaChunkIterator, long) * @see #getAverageBitrate(MediaChunkIterator, long)
*/ */
@VisibleForTesting @VisibleForTesting
/* package */ static int[] getBitratesUsingFutureInfo( /* package */ static int[] getBitratesUsingFutureInfo(
MediaChunkIterator[] iterators, Format[] formats, long maxDurationUs) { MediaChunkIterator[] iterators,
Format[] formats,
long maxDurationUs,
@Nullable int[] bitrates) {
int trackCount = iterators.length; int trackCount = iterators.length;
Assertions.checkArgument(trackCount == formats.length); Assertions.checkArgument(trackCount == formats.length);
if (trackCount == 0) { if (trackCount == 0) {
return new int[0]; return new int[0];
} }
if (bitrates == null) {
int[] bitrates = new int[trackCount]; bitrates = new int[trackCount];
}
if (maxDurationUs == 0) { if (maxDurationUs == 0) {
Arrays.fill(bitrates, Format.NO_VALUE); Arrays.fill(bitrates, Format.NO_VALUE);
return bitrates; return bitrates;
...@@ -127,15 +133,22 @@ public final class TrackSelectionUtil { ...@@ -127,15 +133,22 @@ public final class TrackSelectionUtil {
* @param formats The track formats. * @param formats The track formats.
* @param maxDurationUs Maximum duration of chunks to be included in average bitrate values, in * @param maxDurationUs Maximum duration of chunks to be included in average bitrate values, in
* microseconds. * 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, * @return Bitrate values for the tracks. If for a track, a bitrate value can't be calculated,
* {@link Format#NO_VALUE} is set. * {@link Format#NO_VALUE} is set.
* @see #getBitratesUsingFutureInfo(MediaChunkIterator[], Format[], long) * @see #getBitratesUsingFutureInfo(MediaChunkIterator[], Format[], long, int[])
*/ */
@VisibleForTesting @VisibleForTesting
/* package */ static int[] getBitratesUsingPastInfo( /* package */ static int[] getBitratesUsingPastInfo(
List<? extends MediaChunk> queue, Format[] formats, long maxDurationUs) { List<? extends MediaChunk> queue,
int[] bitrates = new int[formats.length]; Format[] formats,
long maxDurationUs,
@Nullable int[] bitrates) {
if (bitrates == null) {
bitrates = new int[formats.length];
Arrays.fill(bitrates, Format.NO_VALUE); Arrays.fill(bitrates, Format.NO_VALUE);
}
if (maxDurationUs == 0) { if (maxDurationUs == 0) {
return bitrates; return bitrates;
} }
...@@ -156,40 +169,58 @@ public final class TrackSelectionUtil { ...@@ -156,40 +169,58 @@ public final class TrackSelectionUtil {
* Returns bitrate values for a set of tracks whose formats are given, using the given upcoming * 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. * 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 * @param iterators An array of {@link MediaChunkIterator}s providing information about the
* sequence of upcoming media chunks for each track. * sequence of upcoming media chunks for each track.
* @param queue The queue of already buffered {@link MediaChunk}s. Must not be modified.
* @param formats The track formats.
* @param maxFutureDurationUs Maximum duration of future chunks to be included in average bitrate * @param maxFutureDurationUs Maximum duration of future chunks to be included in average bitrate
* values, in microseconds. * values, in microseconds.
* @param maxPastDurationUs Maximum duration of past chunks to be included in average bitrate
* values, in microseconds.
* @param useFormatBitrateAsLowerBound Whether to return the estimated bitrate only if it's higher * @param useFormatBitrateAsLowerBound Whether to return the estimated bitrate only if it's higher
* than the bitrate of the track's format. * than the bitrate of the track's format.
* @return Bitrate values for the tracks. If for a track, a bitrate value can't be calculated, * @param bitrates An array into which the bitrate values will be written. If non-null, this array
* {@link Format#NO_VALUE} is set. * 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( public static int[] getBitratesUsingPastAndFutureInfo(
MediaChunkIterator[] iterators,
List<? extends MediaChunk> queue,
Format[] formats, Format[] formats,
long maxFutureDurationUs, List<? extends MediaChunk> queue,
long maxPastDurationUs, long maxPastDurationUs,
boolean useFormatBitrateAsLowerBound) { MediaChunkIterator[] iterators,
int[] bitrates = getBitratesUsingFutureInfo(iterators, formats, maxFutureDurationUs); long maxFutureDurationUs,
int[] bitratesUsingPastInfo = getBitratesUsingPastInfo(queue, formats, maxPastDurationUs); boolean useFormatBitrateAsLowerBound,
@Nullable int[] bitrates) {
bitrates = getBitratesUsingFutureInfo(iterators, formats, maxFutureDurationUs, bitrates);
getBitratesUsingPastInfo(queue, formats, maxPastDurationUs, bitrates);
for (int i = 0; i < bitrates.length; i++) { for (int i = 0; i < bitrates.length; i++) {
int bitrate = bitrates[i]; int bitrate = bitrates[i];
if (bitrate == Format.NO_VALUE) {
bitrate = bitratesUsingPastInfo[i];
}
if (bitrate == Format.NO_VALUE if (bitrate == Format.NO_VALUE
|| (useFormatBitrateAsLowerBound || (useFormatBitrateAsLowerBound
&& formats[i].bitrate != Format.NO_VALUE && formats[i].bitrate != Format.NO_VALUE
&& bitrate < formats[i].bitrate)) { && bitrate < formats[i].bitrate)) {
bitrate = formats[i].bitrate; bitrates[i] = formats[i].bitrate;
} }
bitrates[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; return bitrates;
} }
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.trackselection;
import android.support.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;
/** A {@link TrackBitrateEstimator} which derives estimates from a window of time. */
public final class WindowedTrackBitrateEstimator implements TrackBitrateEstimator {
private final long maxFutureDurationUs;
private final long maxPastDurationUs;
private final boolean useFormatBitrateAsLowerBound;
/**
* @param maxFutureDurationUs Maximum duration of future chunks to be included in average bitrate
* values, in microseconds.
* @param maxPastDurationUs Maximum duration of past 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.
*/
public WindowedTrackBitrateEstimator(
long maxFutureDurationUs, long maxPastDurationUs, boolean useFormatBitrateAsLowerBound) {
this.maxFutureDurationUs = maxFutureDurationUs;
this.maxPastDurationUs = maxPastDurationUs;
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);
}
}
/*
* 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 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;
import org.robolectric.RobolectricTestRunner;
/** {@link WindowedTrackBitrateEstimator} tests. */
@RunWith(RobolectricTestRunner.class)
public class WindowedTrackBitrateEstimatorTest {
private static final long MAX_DURATION_US = 30 * C.MICROS_PER_SECOND;
@Test
public void getBitrates_zeroMaxDuration_returnsFormatBitrates() {
WindowedTrackBitrateEstimator estimator =
new WindowedTrackBitrateEstimator(
/* maxFutureDurationUs= */ 0,
/* maxPastDurationUs= */ 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(
MAX_DURATION_US, /* maxPastDurationUs= */ 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(8, 16).inOrder();
}
@Test
public void getBitrates_pastMaxDurationSet_returnsEstimateUsingPastChunks() {
WindowedTrackBitrateEstimator estimator =
new WindowedTrackBitrateEstimator(
/* maxFutureDurationUs= */ 0,
MAX_DURATION_US,
/* 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_US, MAX_DURATION_US, /* 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_US, MAX_DURATION_US, /* 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).isSameAs(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});
}
}
/*
* 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.testutil;
import android.net.Uri;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator;
import com.google.android.exoplayer2.upstream.DataSpec;
/** Fake {@link com.google.android.exoplayer2.source.chunk.MediaChunkIterator}. */
public final class FakeMediaChunkIterator extends BaseMediaChunkIterator {
private final long[] chunkTimeBoundariesSec;
private final long[] chunkLengths;
/**
* Creates a fake {@link com.google.android.exoplayer2.source.chunk.MediaChunkIterator}.
*
* @param chunkTimeBoundariesSec An array containing the time boundaries where one chunk ends and
* the next one starts. The first value is the start time of the first chunk and the last
* value is the end time of the last chunk. The array should be of length (chunk-count + 1).
* @param chunkLengths An array which contains the length of each chunk, should be of length
* (chunk-count).
*/
public FakeMediaChunkIterator(long[] chunkTimeBoundariesSec, long[] chunkLengths) {
super(/* fromIndex= */ 0, /* toIndex= */ chunkTimeBoundariesSec.length - 2);
this.chunkTimeBoundariesSec = chunkTimeBoundariesSec;
this.chunkLengths = chunkLengths;
}
@Override
public DataSpec getDataSpec() {
checkInBounds();
return new DataSpec(
Uri.EMPTY,
/* absoluteStreamPosition= */ 0,
chunkLengths[(int) getCurrentIndex()],
/* key= */ null);
}
@Override
public long getChunkStartTimeUs() {
checkInBounds();
return chunkTimeBoundariesSec[(int) getCurrentIndex()] * C.MICROS_PER_SECOND;
}
@Override
public long getChunkEndTimeUs() {
checkInBounds();
return chunkTimeBoundariesSec[(int) getCurrentIndex() + 1] * C.MICROS_PER_SECOND;
}
}
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