From aa2beb080c6f621264527a6147a258bdd7f0fa27 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 6 Jan 2021 10:48:58 +0000 Subject: [PATCH] Turn on parallel video and audio adaptation by default. The experimental setting shows positive results and can be turned on by default. To avoid adaptation between HLS audio formats without bitrates, we need to ensure that only formats with bitrates are considered for adaptation. Also added tests for these features. Issue: #5111 PiperOrigin-RevId: 350315296 --- RELEASENOTES.md | 2 + .../trackselection/DefaultTrackSelector.java | 47 +++++++++++---- .../DefaultTrackSelectorTest.java | 60 +++++++++++++++++++ 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bae800caf0..38712ca042 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -43,6 +43,8 @@ allow decoder capability checks based on codec profile/level ([#8393](https://github.com/google/ExoPlayer/issues/8393)). * Track selection: + * Allow parallel adaptation for video and audio + ([#5111](https://github.com/google/ExoPlayer/issues/5111)). * Add option to specify multiple preferred audio or text languages. * Forward `Timeline` and `MediaPeriodId` to `TrackSelection.Factory`. * DASH: diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 08f4c19f3e..3b06239c89 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -195,6 +195,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { private boolean forceHighestSupportedBitrate; private boolean exceedRendererCapabilitiesIfNecessary; private int tunnelingAudioSessionId; + private boolean allowMultipleAdaptiveSelections; private final SparseArray> selectionOverrides; @@ -261,6 +262,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { forceHighestSupportedBitrate = initialValues.forceHighestSupportedBitrate; exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary; tunnelingAudioSessionId = initialValues.tunnelingAudioSessionId; + allowMultipleAdaptiveSelections = initialValues.allowMultipleAdaptiveSelections; // Overrides selectionOverrides = cloneSelectionOverrides(initialValues.selectionOverrides); rendererDisabledFlags = initialValues.rendererDisabledFlags.clone(); @@ -645,6 +647,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } + /** + * Sets whether multiple adaptive selections with more than one track are allowed. + * + * @param allowMultipleAdaptiveSelections Whether multiple adaptive selections are allowed. + * @return This builder. + */ + public ParametersBuilder setAllowMultipleAdaptiveSelections( + boolean allowMultipleAdaptiveSelections) { + this.allowMultipleAdaptiveSelections = allowMultipleAdaptiveSelections; + return this; + } + // Overrides /** @@ -799,6 +813,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { forceHighestSupportedBitrate, exceedRendererCapabilitiesIfNecessary, tunnelingAudioSessionId, + allowMultipleAdaptiveSelections, selectionOverrides, rendererDisabledFlags); } @@ -827,6 +842,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { forceHighestSupportedBitrate = false; exceedRendererCapabilitiesIfNecessary = true; tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; + allowMultipleAdaptiveSelections = true; } private static SparseArray> @@ -1007,6 +1023,15 @@ public class DefaultTrackSelector extends MappingTrackSelector { * disabled). */ public final int tunnelingAudioSessionId; + /** + * Whether multiple adaptive selections with more than one track are allowed. The default value + * is {@code true}. + * + *

Note that tracks are only eligible for adaptation if they define a bitrate, the renderers + * support the tracks and allow adaptation between them, and they are not excluded based on + * other track selection parameters. + */ + public final boolean allowMultipleAdaptiveSelections; // Overrides private final SparseArray> @@ -1047,6 +1072,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { boolean forceHighestSupportedBitrate, boolean exceedRendererCapabilitiesIfNecessary, int tunnelingAudioSessionId, + boolean allowMultipleAdaptiveSelections, // Overrides SparseArray> selectionOverrides, SparseBooleanArray rendererDisabledFlags) { @@ -1083,6 +1109,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; this.tunnelingAudioSessionId = tunnelingAudioSessionId; + this.allowMultipleAdaptiveSelections = allowMultipleAdaptiveSelections; // Overrides this.selectionOverrides = selectionOverrides; this.rendererDisabledFlags = rendererDisabledFlags; @@ -1117,6 +1144,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { this.forceHighestSupportedBitrate = Util.readBoolean(in); this.exceedRendererCapabilitiesIfNecessary = Util.readBoolean(in); this.tunnelingAudioSessionId = in.readInt(); + this.allowMultipleAdaptiveSelections = Util.readBoolean(in); // Overrides this.selectionOverrides = readSelectionOverrides(in); this.rendererDisabledFlags = Util.castNonNull(in.readSparseBooleanArray()); @@ -1203,6 +1231,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { && forceHighestSupportedBitrate == other.forceHighestSupportedBitrate && exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary && tunnelingAudioSessionId == other.tunnelingAudioSessionId + && allowMultipleAdaptiveSelections == other.allowMultipleAdaptiveSelections // Overrides && areRendererDisabledFlagsEqual(rendererDisabledFlags, other.rendererDisabledFlags) && areSelectionOverridesEqual(selectionOverrides, other.selectionOverrides); @@ -1238,6 +1267,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0); result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0); result = 31 * result + tunnelingAudioSessionId; + result = 31 * result + (allowMultipleAdaptiveSelections ? 1 : 0); // Overrides (omitted from hashCode). return result; } @@ -1279,6 +1309,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { Util.writeBoolean(dest, forceHighestSupportedBitrate); Util.writeBoolean(dest, exceedRendererCapabilitiesIfNecessary); dest.writeInt(tunnelingAudioSessionId); + Util.writeBoolean(dest, allowMultipleAdaptiveSelections); // Overrides writeSelectionOverridesToParcel(dest, selectionOverrides); dest.writeSparseBooleanArray(rendererDisabledFlags); @@ -1517,8 +1548,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { private final TrackSelection.Factory trackSelectionFactory; private final AtomicReference parametersReference; - private boolean allowMultipleAdaptiveSelections; - /** @deprecated Use {@link #DefaultTrackSelector(Context)} instead. */ @Deprecated public DefaultTrackSelector() { @@ -1588,15 +1617,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { return getParameters().buildUpon(); } - /** - * Allows the creation of multiple adaptive track selections. - * - *

This method is experimental, and will be renamed or removed in a future release. - */ - public void experimentalAllowMultipleAdaptiveSelections() { - this.allowMultipleAdaptiveSelections = true; - } - // MappingTrackSelector implementation. @Override @@ -1719,7 +1739,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int i = 0; i < rendererCount; i++) { if (C.TRACK_TYPE_AUDIO == mappedTrackInfo.getRendererType(i)) { boolean enableAdaptiveTrackSelection = - allowMultipleAdaptiveSelections || !seenVideoRendererWithMappedTracks; + params.allowMultipleAdaptiveSelections || !seenVideoRendererWithMappedTracks; @Nullable Pair audioSelection = selectAudioTrack( @@ -2207,7 +2227,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { boolean allowMixedSampleRateAdaptiveness, boolean allowAudioMixedChannelCountAdaptiveness) { return isSupported(formatSupport, /* allowExceedsCapabilities= */ false) - && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxAudioBitrate) + && format.bitrate != Format.NO_VALUE + && format.bitrate <= maxAudioBitrate && (allowAudioMixedChannelCountAdaptiveness || (format.channelCount != Format.NO_VALUE && format.channelCount == primaryFormat.channelCount)) diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 80f53addb0..6443dc51a4 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -94,6 +94,7 @@ public final class DefaultTrackSelectorTest { .setSampleMimeType(MimeTypes.AUDIO_AAC) .setChannelCount(2) .setSampleRate(44100) + .setAverageBitrate(128000) .build(); private static final Format TEXT_FORMAT = new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build(); @@ -1107,6 +1108,21 @@ public final class DefaultTrackSelectorTest { assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 0, 6); } + @Test + public void selectTracks_multipleAudioTracksWithoutBitrate_onlySelectsSingleTrack() + throws Exception { + TrackGroupArray trackGroups = + singleTrackGroup( + AUDIO_FORMAT.buildUpon().setId("0").setAverageBitrate(Format.NO_VALUE).build(), + AUDIO_FORMAT.buildUpon().setId("1").setAverageBitrate(Format.NO_VALUE).build()); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + + assertThat(result.length).isEqualTo(1); + assertFixedSelection(result.selections.get(0), trackGroups.get(0), /* expectedTrack= */ 0); + } + @Test public void selectTracksWithMultipleAudioTracksWithMixedSampleRates() throws Exception { Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon(); @@ -1411,6 +1427,48 @@ public final class DefaultTrackSelectorTest { assertAdaptiveSelection(result.selections.get(0), trackGroups.get(0), 1, 2); } + @Test + public void selectTracks_multipleVideoAndAudioTracks() throws Exception { + Format videoFormat1 = VIDEO_FORMAT.buildUpon().setAverageBitrate(1000).build(); + Format videoFormat2 = VIDEO_FORMAT.buildUpon().setAverageBitrate(2000).build(); + Format audioFormat1 = AUDIO_FORMAT.buildUpon().setAverageBitrate(100).build(); + Format audioFormat2 = AUDIO_FORMAT.buildUpon().setAverageBitrate(200).build(); + TrackGroupArray trackGroups = + new TrackGroupArray( + new TrackGroup(videoFormat1, videoFormat2), new TrackGroup(audioFormat1, audioFormat2)); + + // Multiple adaptive selections allowed. + trackSelector.setParameters( + trackSelector.buildUponParameters().setAllowMultipleAdaptiveSelections(true)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {VIDEO_CAPABILITIES, AUDIO_CAPABILITIES}, + trackGroups, + periodId, + TIMELINE); + + assertThat(result.length).isEqualTo(2); + assertAdaptiveSelection( + result.selections.get(0), trackGroups.get(0), /* expectedTracks...= */ 1, 0); + assertAdaptiveSelection( + result.selections.get(1), trackGroups.get(1), /* expectedTracks...= */ 1, 0); + + // Multiple adaptive selection disallowed. + trackSelector.setParameters( + trackSelector.buildUponParameters().setAllowMultipleAdaptiveSelections(false)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {VIDEO_CAPABILITIES, AUDIO_CAPABILITIES}, + trackGroups, + periodId, + TIMELINE); + + assertThat(result.length).isEqualTo(2); + assertAdaptiveSelection( + result.selections.get(0), trackGroups.get(0), /* expectedTracks...= */ 1, 0); + assertFixedSelection(result.selections.get(1), trackGroups.get(1), /* expectedTrack= */ 1); + } + private static void assertSelections(TrackSelectorResult result, TrackSelection[] expected) { assertThat(result.length).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { @@ -1478,6 +1536,7 @@ public final class DefaultTrackSelectorTest { .setSampleMimeType(mimeType) .setChannelCount(channelCount) .setSampleRate(sampleRate) + .setAverageBitrate(128000) .build(); } @@ -1531,6 +1590,7 @@ public final class DefaultTrackSelectorTest { /* forceHighestSupportedBitrate= */ true, /* exceedRendererCapabilitiesIfNecessary= */ false, /* tunnelingAudioSessionId= */ 13, + /* allowMultipleAdaptiveSelections= */ true, // Overrides selectionOverrides, rendererDisabledFlags);