From 5a18a98fbd8ce75786de804f97b3c80722d19fde Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 7 Nov 2018 05:28:34 -0800 Subject: [PATCH] Add TrackSelectionUtil getBitratesUsingPastAndFutureInfo ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=220447459 --- .../trackselection/TrackSelectionUtil.java | 97 +++++++++++++--- .../TrackSelectionUtilTest.java | 108 ++++++++++++++++++ 2 files changed, 190 insertions(+), 15 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java index fdfd87910e..052cf36309 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtil.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.trackselection; +import android.support.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.chunk.MediaChunk; @@ -77,7 +78,8 @@ public final class TrackSelectionUtil { * estimation can't be calculated, {@link Format#NO_VALUE} is set. * @see #getAverageBitrate(MediaChunkIterator, long) */ - public static int[] getBitratesUsingFutureInfo( + @VisibleForTesting + /* package */ static int[] getBitratesUsingFutureInfo( MediaChunkIterator[] iterators, Format[] formats, long maxDurationUs) { int trackCount = iterators.length; Assertions.checkArgument(trackCount == formats.length); @@ -86,6 +88,11 @@ public final class TrackSelectionUtil { } int[] 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; @@ -124,10 +131,14 @@ public final class TrackSelectionUtil { * {@link Format#NO_VALUE} is set. * @see #getBitratesUsingFutureInfo(MediaChunkIterator[], Format[], long) */ - public static int[] getBitratesUsingPastInfo( + @VisibleForTesting + /* package */ static int[] getBitratesUsingPastInfo( List queue, Format[] formats, long maxDurationUs) { int[] 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; @@ -141,6 +152,75 @@ public final class TrackSelectionUtil { 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 iterators An array of {@link MediaChunkIterator}s providing information about the + * 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 + * 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. + * @return Bitrate values for the tracks. If for a track, a bitrate value can't be calculated, + * {@link Format#NO_VALUE} is set. + */ + public static int[] getBitratesUsingPastAndFutureInfo( + MediaChunkIterator[] iterators, + List queue, + Format[] formats, + long maxFutureDurationUs, + long maxPastDurationUs, + boolean useFormatBitrateAsLowerBound) { + int[] bitrates = getBitratesUsingFutureInfo(iterators, formats, maxFutureDurationUs); + int[] bitratesUsingPastInfo = getBitratesUsingPastInfo(queue, formats, maxPastDurationUs); + for (int i = 0; i < bitrates.length; i++) { + int bitrate = bitrates[i]; + if (bitrate == Format.NO_VALUE) { + bitrate = bitratesUsingPastInfo[i]; + } + if (bitrate == Format.NO_VALUE + || (useFormatBitrateAsLowerBound + && formats[i].bitrate != Format.NO_VALUE + && bitrate < formats[i].bitrate)) { + bitrate = formats[i].bitrate; + } + bitrates[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 queue, long maxDurationUs) { if (queue.isEmpty()) { return Format.NO_VALUE; @@ -162,19 +242,6 @@ public final class TrackSelectionUtil { return queue; } - private static void estimateBitrates( - int[] bitrates, Format[] formats, int[] formatBitrates, float[] bitrateRatios) { - 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 closestFormat = getClosestBitrateIndex(formatBitrate, formatBitrates); - bitrates[i] = (int) (bitrateRatios[closestFormat] * formatBitrate); - } - } - } - } - private static int getClosestBitrateIndex(int formatBitrate, int[] formatBitrates) { int closestDistance = Integer.MAX_VALUE; int closestFormat = C.INDEX_UNSET; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java index 49da9b567a..4596ea5ded 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/TrackSelectionUtilTest.java @@ -408,6 +408,114 @@ public class TrackSelectionUtilTest { assertThat(bitrates).asList().containsExactly(16).inOrder(); } + @Test + public void getBitratesUsingPastAndFutureInfo_noPastInfo_returnsBitratesUsingOnlyFutureInfo() { + FakeIterator iterator1 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10}); + FakeIterator iterator2 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30}, + /* chunkLengths= */ new long[] {10, 20, 30}); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastAndFutureInfo( + new MediaChunkIterator[] {iterator1, iterator2}, + Collections.emptyList(), + new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ false); + + 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 MediaChunkIterator[] {MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY}, + Collections.singletonList(chunk), + new Format[] {createFormatWithBitrate(20), createFormatWithBitrate(30)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ false); + + assertThat(bitrates).asList().containsExactly(16, 24).inOrder(); + } + + @Test + public void + getBitratesUsingPastAndFutureInfo_pastAndFutureInfo_returnsBitratesUsingOnlyFutureInfo() { + FakeMediaChunk chunk = + createChunk( + createFormatWithBitrate(5), + /* length= */ 10, + /* startTimeSec= */ 0, + /* endTimeSec= */ 10); + FakeIterator iterator1 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 10}, /* chunkLengths= */ new long[] {10}); + FakeIterator iterator2 = + new FakeIterator( + /* chunkTimeBoundariesSec= */ new long[] {0, 5, 15, 30}, + /* chunkLengths= */ new long[] {10, 20, 30}); + + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastAndFutureInfo( + new MediaChunkIterator[] {iterator1, iterator2}, + Collections.singletonList(chunk), + new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ false); + + assertThat(bitrates).asList().containsExactly(8, 16).inOrder(); + } + + @Test + public void getBitratesUsingPastAndFutureInfo_noPastAndFutureInfo_returnsBitratesOfFormats() { + int[] bitrates = + TrackSelectionUtil.getBitratesUsingPastAndFutureInfo( + new MediaChunkIterator[] {MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY}, + Collections.emptyList(), + new Format[] {createFormatWithBitrate(10), createFormatWithBitrate(20)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ false); + + 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 MediaChunkIterator[] {MediaChunkIterator.EMPTY, MediaChunkIterator.EMPTY}, + Collections.singletonList(chunk), + new Format[] {createFormatWithBitrate(20), createFormatWithBitrate(30)}, + MAX_DURATION_US, + MAX_DURATION_US, + /* useFormatBitrateAsLowerBound= */ true); + + 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, 0, length, null, 0);