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);