mirror of
https://github.com/androidx/media.git
synced 2025-05-10 00:59:51 +08:00
Exclude last chunk when applying fraction for live quality increase
We check the fraction of the available duration we have already buffered for live streams to see if we can increase the quality. This fraction compares against the overall available media duration at the time of the track selection, which by definition can't include one of the availabe chunks (as this is the one we want to load next). That means, for example, that for a reasonable live offset of 3 segments we can at most reach a fraction of 0.66, which is less than our default threshold of 0.75, meaning we can never switch up. By subtracting one chunk duration from the available duration, we make this comparison fair again and allow all live streams (regardless of live offset) to reach up to 100% buffered data (which is above our default value of 75%), so that they can increase the quality. Issue: google/ExoPlayer#9784 PiperOrigin-RevId: 416791033
This commit is contained in:
parent
8bb53b409a
commit
9d463725fb
@ -21,6 +21,9 @@
|
||||
* Add `MediaCodecAdapter.getMetrics()` to allow users obtain metrics data
|
||||
from `MediaCodec`.
|
||||
([#9766](https://github.com/google/ExoPlayer/issues/9766)).
|
||||
* Amend logic in `AdaptiveTrackSelection` to allow a quality increase
|
||||
under sufficient network bandwidth even if playback is very close to the
|
||||
live edge ((#9784)[https://github.com/google/ExoPlayer/issues/9784]).
|
||||
* Android 12 compatibility:
|
||||
* Upgrade the Cast extension to depend on
|
||||
`com.google.android.gms:play-services-cast-framework:20.1.0`. Earlier
|
||||
|
@ -458,8 +458,10 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
||||
// Revert back to the previous selection if conditions are not suitable for switching.
|
||||
Format currentFormat = getFormat(previousSelectedIndex);
|
||||
Format selectedFormat = getFormat(newSelectedIndex);
|
||||
long minDurationForQualityIncreaseUs =
|
||||
minDurationForQualityIncreaseUs(availableDurationUs, chunkDurationUs);
|
||||
if (selectedFormat.bitrate > currentFormat.bitrate
|
||||
&& bufferedDurationUs < minDurationForQualityIncreaseUs(availableDurationUs)) {
|
||||
&& bufferedDurationUs < minDurationForQualityIncreaseUs) {
|
||||
// The selected track is a higher quality, but we have insufficient buffer to safely switch
|
||||
// up. Defer switching up for now.
|
||||
newSelectedIndex = previousSelectedIndex;
|
||||
@ -599,13 +601,22 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
||||
return lowestBitrateAllowedIndex;
|
||||
}
|
||||
|
||||
private long minDurationForQualityIncreaseUs(long availableDurationUs) {
|
||||
private long minDurationForQualityIncreaseUs(long availableDurationUs, long chunkDurationUs) {
|
||||
boolean isAvailableDurationTooShort =
|
||||
availableDurationUs != C.TIME_UNSET
|
||||
&& availableDurationUs <= minDurationForQualityIncreaseUs;
|
||||
return isAvailableDurationTooShort
|
||||
? (long) (availableDurationUs * bufferedFractionToLiveEdgeForQualityIncrease)
|
||||
: minDurationForQualityIncreaseUs;
|
||||
if (!isAvailableDurationTooShort) {
|
||||
return minDurationForQualityIncreaseUs;
|
||||
}
|
||||
if (chunkDurationUs != C.TIME_UNSET) {
|
||||
// We are currently selecting a new live chunk. Even under perfect conditions, the buffered
|
||||
// duration can't include the last chunk duration yet because we are still selecting a track
|
||||
// for this or a previous chunk. Hence, we subtract one chunk duration from the total
|
||||
// available live duration to ensure we only compare the buffered duration against what is
|
||||
// actually achievable.
|
||||
availableDurationUs -= chunkDurationUs;
|
||||
}
|
||||
return (long) (availableDurationUs * bufferedFractionToLiveEdgeForQualityIncrease);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,6 +163,40 @@ public final class AdaptiveTrackSelectionTest {
|
||||
assertThat(adaptiveTrackSelection.getSelectionReason()).isEqualTo(C.SELECTION_REASON_ADAPTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateSelectedTrack_liveStream_switchesUpWhenBufferedFractionToLiveEdgeReached() {
|
||||
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 2000L, which prompts the track selection to switch up
|
||||
// if possible.
|
||||
when(mockBandwidthMeter.getBitrateEstimate()).thenReturn(1000L, 2000L);
|
||||
AdaptiveTrackSelection adaptiveTrackSelection =
|
||||
prepareAdaptiveTrackSelectionWithBufferedFractionToLiveEdgeForQualiyIncrease(
|
||||
trackGroup, /* bufferedFractionToLiveEdgeForQualityIncrease= */ 0.75f);
|
||||
|
||||
// Not buffered close to live edge yet.
|
||||
adaptiveTrackSelection.updateSelectedTrack(
|
||||
/* playbackPositionUs= */ 0,
|
||||
/* bufferedDurationUs= */ 1_600_000,
|
||||
/* availableDurationUs= */ 5_600_000,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
createMediaChunkIterators(trackGroup, /* chunkDurationUs= */ 2_000_000));
|
||||
|
||||
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format2);
|
||||
|
||||
// Buffered all possible chunks (except for newly added chunk of 2 seconds).
|
||||
adaptiveTrackSelection.updateSelectedTrack(
|
||||
/* playbackPositionUs= */ 0,
|
||||
/* bufferedDurationUs= */ 3_600_000,
|
||||
/* availableDurationUs= */ 5_600_000,
|
||||
/* queue= */ ImmutableList.of(),
|
||||
createMediaChunkIterators(trackGroup, /* chunkDurationUs= */ 2_000_000));
|
||||
|
||||
assertThat(adaptiveTrackSelection.getSelectedFormat()).isEqualTo(format3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateSelectedTrackDoNotSwitchDownIfBufferedEnough() {
|
||||
Format format1 = videoFormat(/* bitrate= */ 500, /* width= */ 320, /* height= */ 240);
|
||||
@ -731,6 +765,26 @@ public final class AdaptiveTrackSelectionTest {
|
||||
fakeClock));
|
||||
}
|
||||
|
||||
private AdaptiveTrackSelection
|
||||
prepareAdaptiveTrackSelectionWithBufferedFractionToLiveEdgeForQualiyIncrease(
|
||||
TrackGroup trackGroup, float bufferedFractionToLiveEdgeForQualityIncrease) {
|
||||
return prepareTrackSelection(
|
||||
new AdaptiveTrackSelection(
|
||||
trackGroup,
|
||||
selectedAllTracksInGroup(trackGroup),
|
||||
TrackSelection.TYPE_UNSET,
|
||||
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,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_WIDTH_TO_DISCARD,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_HEIGHT_TO_DISCARD,
|
||||
/* bandwidthFraction= */ 1.0f,
|
||||
bufferedFractionToLiveEdgeForQualityIncrease,
|
||||
/* adaptationCheckpoints= */ ImmutableList.of(),
|
||||
fakeClock));
|
||||
}
|
||||
|
||||
private AdaptiveTrackSelection prepareAdaptiveTrackSelectionWithAdaptationCheckpoints(
|
||||
TrackGroup trackGroup, List<AdaptationCheckpoint> adaptationCheckpoints) {
|
||||
return prepareTrackSelection(
|
||||
|
Loading…
x
Reference in New Issue
Block a user