diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java index b4b159294cc..d4074210786 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java @@ -32,6 +32,7 @@ import androidx.media3.common.C.FormatSupport; import androidx.media3.common.C.RoleFlags; import androidx.media3.common.Format; +import androidx.media3.common.MimeTypes; import androidx.media3.common.Timeline; import androidx.media3.common.TrackGroup; import androidx.media3.common.TrackGroupArray; @@ -112,11 +113,13 @@ public static final class ParametersBuilder extends TrackSelectionParameters.Bui private boolean exceedVideoConstraintsIfNecessary; private boolean allowVideoMixedMimeTypeAdaptiveness; private boolean allowVideoNonSeamlessAdaptiveness; + private boolean allowVideoMixedDecoderSupportAdaptiveness; // Audio private boolean exceedAudioConstraintsIfNecessary; private boolean allowAudioMixedMimeTypeAdaptiveness; private boolean allowAudioMixedSampleRateAdaptiveness; private boolean allowAudioMixedChannelCountAdaptiveness; + private boolean allowAudioMixedDecoderSupportAdaptiveness; // Text @C.SelectionFlags private int disabledTextTrackSelectionFlags; // General @@ -165,12 +168,16 @@ private ParametersBuilder(Parameters initialValues) { exceedVideoConstraintsIfNecessary = initialValues.exceedVideoConstraintsIfNecessary; allowVideoMixedMimeTypeAdaptiveness = initialValues.allowVideoMixedMimeTypeAdaptiveness; allowVideoNonSeamlessAdaptiveness = initialValues.allowVideoNonSeamlessAdaptiveness; + allowVideoMixedDecoderSupportAdaptiveness = + initialValues.allowVideoMixedDecoderSupportAdaptiveness; // Audio exceedAudioConstraintsIfNecessary = initialValues.exceedAudioConstraintsIfNecessary; allowAudioMixedMimeTypeAdaptiveness = initialValues.allowAudioMixedMimeTypeAdaptiveness; allowAudioMixedSampleRateAdaptiveness = initialValues.allowAudioMixedSampleRateAdaptiveness; allowAudioMixedChannelCountAdaptiveness = initialValues.allowAudioMixedChannelCountAdaptiveness; + allowAudioMixedDecoderSupportAdaptiveness = + initialValues.allowAudioMixedDecoderSupportAdaptiveness; // General exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary; tunnelingEnabled = initialValues.tunnelingEnabled; @@ -197,6 +204,11 @@ private ParametersBuilder(Bundle bundle) { bundle.getBoolean( Parameters.keyForField(Parameters.FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS), defaultValue.allowVideoNonSeamlessAdaptiveness)); + setAllowVideoMixedDecoderSupportAdaptiveness( + bundle.getBoolean( + Parameters.keyForField( + Parameters.FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), + defaultValue.allowVideoMixedDecoderSupportAdaptiveness)); // Audio setExceedAudioConstraintsIfNecessary( bundle.getBoolean( @@ -214,6 +226,11 @@ private ParametersBuilder(Bundle bundle) { bundle.getBoolean( Parameters.keyForField(Parameters.FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS), defaultValue.allowAudioMixedChannelCountAdaptiveness)); + setAllowAudioMixedDecoderSupportAdaptiveness( + bundle.getBoolean( + Parameters.keyForField( + Parameters.FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), + defaultValue.allowAudioMixedDecoderSupportAdaptiveness)); // Text setDisabledTextTrackSelectionFlags( bundle.getInt( @@ -345,6 +362,21 @@ public ParametersBuilder setAllowVideoNonSeamlessAdaptiveness( return this; } + /** + * Sets whether to allow adaptive video selections with mixed levels of {@link + * RendererCapabilities.DecoderSupport} and {@link + * RendererCapabilities.HardwareAccelerationSupport}. + * + * @param allowVideoMixedDecoderSupportAdaptiveness Whether to allow adaptive video selections + * with mixed levels of decoder and hardware acceleration support. + * @return This builder. + */ + public ParametersBuilder setAllowVideoMixedDecoderSupportAdaptiveness( + boolean allowVideoMixedDecoderSupportAdaptiveness) { + this.allowVideoMixedDecoderSupportAdaptiveness = allowVideoMixedDecoderSupportAdaptiveness; + return this; + } + @Override public ParametersBuilder setViewportSizeToPhysicalDisplaySize( Context context, boolean viewportOrientationMayChange) { @@ -475,6 +507,21 @@ public ParametersBuilder setAllowAudioMixedChannelCountAdaptiveness( return this; } + /** + * Sets whether to allow adaptive audio selections with mixed levels of {@link + * RendererCapabilities.DecoderSupport} and {@link + * RendererCapabilities.HardwareAccelerationSupport}. + * + * @param allowAudioMixedDecoderSupportAdaptiveness Whether to allow adaptive audio selections + * with mixed levels of decoder and hardware acceleration support. + * @return This builder. + */ + public ParametersBuilder setAllowAudioMixedDecoderSupportAdaptiveness( + boolean allowAudioMixedDecoderSupportAdaptiveness) { + this.allowAudioMixedDecoderSupportAdaptiveness = allowAudioMixedDecoderSupportAdaptiveness; + return this; + } + @Override public ParametersBuilder setPreferredAudioMimeType(@Nullable String mimeType) { super.setPreferredAudioMimeType(mimeType); @@ -745,11 +792,13 @@ private void init(ParametersBuilder this) { exceedVideoConstraintsIfNecessary = true; allowVideoMixedMimeTypeAdaptiveness = false; allowVideoNonSeamlessAdaptiveness = true; + allowVideoMixedDecoderSupportAdaptiveness = false; // Audio exceedAudioConstraintsIfNecessary = true; allowAudioMixedMimeTypeAdaptiveness = false; allowAudioMixedSampleRateAdaptiveness = false; allowAudioMixedChannelCountAdaptiveness = false; + allowAudioMixedDecoderSupportAdaptiveness = false; // Text disabledTextTrackSelectionFlags = 0; // General @@ -869,6 +918,12 @@ public static Parameters getDefaults(Context context) { * The default value is {@code true}. */ public final boolean allowVideoNonSeamlessAdaptiveness; + /** + * Whether to allow adaptive video selections with mixed levels of {@link + * RendererCapabilities.DecoderSupport} and {@link + * RendererCapabilities.HardwareAccelerationSupport}. + */ + public final boolean allowVideoMixedDecoderSupportAdaptiveness; /** * Whether to exceed the {@link #maxAudioChannelCount} and {@link #maxAudioBitrate} constraints * when no selection can be made otherwise. The default value is {@code true}. @@ -890,6 +945,12 @@ public static Parameters getDefaults(Context context) { * false}. */ public final boolean allowAudioMixedChannelCountAdaptiveness; + /** + * Whether to allow adaptive audio selections with mixed levels of {@link + * RendererCapabilities.DecoderSupport} and {@link + * RendererCapabilities.HardwareAccelerationSupport}. + */ + public final boolean allowAudioMixedDecoderSupportAdaptiveness; /** * Whether to exceed renderer capabilities when no selection can be made otherwise. * @@ -923,11 +984,13 @@ private Parameters(ParametersBuilder builder) { exceedVideoConstraintsIfNecessary = builder.exceedVideoConstraintsIfNecessary; allowVideoMixedMimeTypeAdaptiveness = builder.allowVideoMixedMimeTypeAdaptiveness; allowVideoNonSeamlessAdaptiveness = builder.allowVideoNonSeamlessAdaptiveness; + allowVideoMixedDecoderSupportAdaptiveness = builder.allowVideoMixedDecoderSupportAdaptiveness; // Audio exceedAudioConstraintsIfNecessary = builder.exceedAudioConstraintsIfNecessary; allowAudioMixedMimeTypeAdaptiveness = builder.allowAudioMixedMimeTypeAdaptiveness; allowAudioMixedSampleRateAdaptiveness = builder.allowAudioMixedSampleRateAdaptiveness; allowAudioMixedChannelCountAdaptiveness = builder.allowAudioMixedChannelCountAdaptiveness; + allowAudioMixedDecoderSupportAdaptiveness = builder.allowAudioMixedDecoderSupportAdaptiveness; // Text disabledTextTrackSelectionFlags = builder.disabledTextTrackSelectionFlags; // General @@ -1005,12 +1068,16 @@ public boolean equals(@Nullable Object obj) { && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary && allowVideoMixedMimeTypeAdaptiveness == other.allowVideoMixedMimeTypeAdaptiveness && allowVideoNonSeamlessAdaptiveness == other.allowVideoNonSeamlessAdaptiveness + && allowVideoMixedDecoderSupportAdaptiveness + == other.allowVideoMixedDecoderSupportAdaptiveness // Audio && exceedAudioConstraintsIfNecessary == other.exceedAudioConstraintsIfNecessary && allowAudioMixedMimeTypeAdaptiveness == other.allowAudioMixedMimeTypeAdaptiveness && allowAudioMixedSampleRateAdaptiveness == other.allowAudioMixedSampleRateAdaptiveness && allowAudioMixedChannelCountAdaptiveness == other.allowAudioMixedChannelCountAdaptiveness + && allowAudioMixedDecoderSupportAdaptiveness + == other.allowAudioMixedDecoderSupportAdaptiveness // Text && disabledTextTrackSelectionFlags == other.disabledTextTrackSelectionFlags // General @@ -1030,11 +1097,13 @@ public int hashCode() { result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); result = 31 * result + (allowVideoMixedMimeTypeAdaptiveness ? 1 : 0); result = 31 * result + (allowVideoNonSeamlessAdaptiveness ? 1 : 0); + result = 31 * result + (allowVideoMixedDecoderSupportAdaptiveness ? 1 : 0); // Audio result = 31 * result + (exceedAudioConstraintsIfNecessary ? 1 : 0); result = 31 * result + (allowAudioMixedMimeTypeAdaptiveness ? 1 : 0); result = 31 * result + (allowAudioMixedSampleRateAdaptiveness ? 1 : 0); result = 31 * result + (allowAudioMixedChannelCountAdaptiveness ? 1 : 0); + result = 31 * result + (allowAudioMixedDecoderSupportAdaptiveness ? 1 : 0); // Text result = 31 * result + disabledTextTrackSelectionFlags; // General @@ -1065,6 +1134,8 @@ public int hashCode() { FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS, FIELD_SELECTION_OVERRIDES, FIELD_RENDERER_DISABLED_INDICES, + FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS, + FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS }) private @interface FieldNumber {} @@ -1084,6 +1155,8 @@ public int hashCode() { private static final int FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS = 1012; private static final int FIELD_SELECTION_OVERRIDES = 1013; private static final int FIELD_RENDERER_DISABLED_INDICES = 1014; + private static final int FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS = 1015; + private static final int FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS = 1016; @Override public Bundle toBundle() { @@ -1099,6 +1172,9 @@ public Bundle toBundle() { bundle.putBoolean( keyForField(FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS), allowVideoNonSeamlessAdaptiveness); + bundle.putBoolean( + keyForField(FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), + allowVideoMixedDecoderSupportAdaptiveness); // Audio bundle.putBoolean( keyForField(FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY), @@ -1112,6 +1188,9 @@ public Bundle toBundle() { bundle.putBoolean( keyForField(FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS), allowAudioMixedChannelCountAdaptiveness); + bundle.putBoolean( + keyForField(FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), + allowAudioMixedDecoderSupportAdaptiveness); // Text bundle.putInt( keyForField(FIELD_DISABLED_TEXT_TRACK_SELECTION_FLAGS), disabledTextTrackSelectionFlags); @@ -1858,7 +1937,7 @@ protected ExoTrackSelection.Definition selectOtherTrack( } @Nullable - private Pair selectTracksForType( + private > Pair selectTracksForType( @C.TrackType int trackType, MappedTrackInfo mappedTrackInfo, @Capabilities int[][][] formatSupport, @@ -2129,10 +2208,32 @@ private static int getRoleFlagMatchScore(int trackRoleFlags, int preferredRoleFl return Integer.bitCount(trackRoleFlags & preferredRoleFlags); } + /** + * Returns preference score for primary, hardware-accelerated video codecs, with higher score + * being preferred. + */ + private static int getVideoCodecPreferenceScore(@Nullable String mimeType) { + if (mimeType == null) { + return 0; + } + switch (mimeType) { + case MimeTypes.VIDEO_AV1: + return 4; + case MimeTypes.VIDEO_H265: + return 3; + case MimeTypes.VIDEO_VP9: + return 2; + case MimeTypes.VIDEO_H264: + return 1; + default: + return 0; + } + } + /** Base class for track selection information of a {@link Format}. */ - private abstract static class TrackInfo { + private abstract static class TrackInfo> { /** Factory for {@link TrackInfo} implementations for a given {@link TrackGroup}. */ - public interface Factory { + public interface Factory> { List create(int rendererIndex, TrackGroup trackGroup, @Capabilities int[] formatSupports); } @@ -2156,10 +2257,10 @@ public TrackInfo(int rendererIndex, TrackGroup trackGroup, int trackIndex) { * Returns whether this track is compatible for an adaptive selection with the specified other * track. */ - public abstract boolean isCompatibleForAdaptationWith(TrackInfo otherTrack); + public abstract boolean isCompatibleForAdaptationWith(T otherTrack); } - private static final class VideoTrackInfo extends TrackInfo { + private static final class VideoTrackInfo extends TrackInfo { public static ImmutableList createForTrackGroup( int rendererIndex, @@ -2203,6 +2304,9 @@ public static ImmutableList createForTrackGroup( private final boolean hasMainOrNoRoleFlag; private final boolean allowMixedMimeTypes; @SelectionEligibility private final int selectionEligibility; + private final boolean usesPrimaryDecoder; + private final boolean usesHardwareAcceleration; + private final int codecPreferenceScore; public VideoTrackInfo( int rendererIndex, @@ -2255,6 +2359,13 @@ public VideoTrackInfo( } } preferredMimeTypeMatchIndex = bestMimeTypeMatchIndex; + usesPrimaryDecoder = + RendererCapabilities.getDecoderSupport(formatSupport) + == RendererCapabilities.DECODER_SUPPORT_PRIMARY; + usesHardwareAcceleration = + RendererCapabilities.getHardwareAccelerationSupport(formatSupport) + == RendererCapabilities.HARDWARE_ACCELERATION_SUPPORTED; + codecPreferenceScore = getVideoCodecPreferenceScore(format.sampleMimeType); selectionEligibility = evaluateSelectionEligibility(formatSupport, requiredAdaptiveSupport); } @@ -2265,9 +2376,12 @@ public int getSelectionEligibility() { } @Override - public boolean isCompatibleForAdaptationWith(TrackInfo otherTrack) { - return allowMixedMimeTypes - || Util.areEqual(format.sampleMimeType, otherTrack.format.sampleMimeType); + public boolean isCompatibleForAdaptationWith(VideoTrackInfo otherTrack) { + return (allowMixedMimeTypes + || Util.areEqual(format.sampleMimeType, otherTrack.format.sampleMimeType)) + && (parameters.allowVideoMixedDecoderSupportAdaptiveness + || (this.usesPrimaryDecoder == otherTrack.usesPrimaryDecoder + && this.usesHardwareAcceleration == otherTrack.usesHardwareAcceleration)); } @SelectionEligibility @@ -2295,20 +2409,28 @@ private int evaluateSelectionEligibility( } private static int compareNonQualityPreferences(VideoTrackInfo info1, VideoTrackInfo info2) { - return ComparisonChain.start() - .compareFalseFirst(info1.isWithinRendererCapabilities, info2.isWithinRendererCapabilities) - // 1. Compare match with specific content preferences set by the parameters. - .compare(info1.preferredRoleFlagsScore, info2.preferredRoleFlagsScore) - // 2. Compare match with implicit content preferences set by the media. - .compareFalseFirst(info1.hasMainOrNoRoleFlag, info2.hasMainOrNoRoleFlag) - // 3. Compare match with technical preferences set by the parameters. - .compareFalseFirst(info1.isWithinMaxConstraints, info2.isWithinMaxConstraints) - .compareFalseFirst(info1.isWithinMinConstraints, info2.isWithinMinConstraints) - .compare( - info1.preferredMimeTypeMatchIndex, - info2.preferredMimeTypeMatchIndex, - Ordering.natural().reverse()) - .result(); + ComparisonChain chain = + ComparisonChain.start() + .compareFalseFirst( + info1.isWithinRendererCapabilities, info2.isWithinRendererCapabilities) + // 1. Compare match with specific content preferences set by the parameters. + .compare(info1.preferredRoleFlagsScore, info2.preferredRoleFlagsScore) + // 2. Compare match with implicit content preferences set by the media. + .compareFalseFirst(info1.hasMainOrNoRoleFlag, info2.hasMainOrNoRoleFlag) + // 3. Compare match with technical preferences set by the parameters. + .compareFalseFirst(info1.isWithinMaxConstraints, info2.isWithinMaxConstraints) + .compareFalseFirst(info1.isWithinMinConstraints, info2.isWithinMinConstraints) + .compare( + info1.preferredMimeTypeMatchIndex, + info2.preferredMimeTypeMatchIndex, + Ordering.natural().reverse()) + // 4. Compare match with renderer capability preferences. + .compareFalseFirst(info1.usesPrimaryDecoder, info2.usesPrimaryDecoder) + .compareFalseFirst(info1.usesHardwareAcceleration, info2.usesHardwareAcceleration); + if (info1.usesPrimaryDecoder && info1.usesHardwareAcceleration) { + chain = chain.compare(info1.codecPreferenceScore, info2.codecPreferenceScore); + } + return chain.result(); } private static int compareQualityPreferences(VideoTrackInfo info1, VideoTrackInfo info2) { @@ -2352,7 +2474,7 @@ public static int compareSelections(List infos1, List implements Comparable { public static ImmutableList createForTrackGroup( @@ -2391,6 +2513,8 @@ public static ImmutableList createForTrackGroup( private final int sampleRate; private final int bitrate; private final int preferredMimeTypeMatchIndex; + private final boolean usesPrimaryDecoder; + private final boolean usesHardwareAcceleration; public AudioTrackInfo( int rendererIndex, @@ -2455,6 +2579,12 @@ public AudioTrackInfo( } } preferredMimeTypeMatchIndex = bestMimeTypeMatchIndex; + usesPrimaryDecoder = + RendererCapabilities.getDecoderSupport(formatSupport) + == RendererCapabilities.DECODER_SUPPORT_PRIMARY; + usesHardwareAcceleration = + RendererCapabilities.getHardwareAccelerationSupport(formatSupport) + == RendererCapabilities.HARDWARE_ACCELERATION_SUPPORTED; selectionEligibility = evaluateSelectionEligibility(formatSupport, hasMappedVideoTracks); } @@ -2465,7 +2595,7 @@ public int getSelectionEligibility() { } @Override - public boolean isCompatibleForAdaptationWith(TrackInfo otherTrack) { + public boolean isCompatibleForAdaptationWith(AudioTrackInfo otherTrack) { return (parameters.allowAudioMixedChannelCountAdaptiveness || (format.channelCount != Format.NO_VALUE && format.channelCount == otherTrack.format.channelCount)) @@ -2474,7 +2604,10 @@ public boolean isCompatibleForAdaptationWith(TrackInfo otherTrack) { && TextUtils.equals(format.sampleMimeType, otherTrack.format.sampleMimeType))) && (parameters.allowAudioMixedSampleRateAdaptiveness || (format.sampleRate != Format.NO_VALUE - && format.sampleRate == otherTrack.format.sampleRate)); + && format.sampleRate == otherTrack.format.sampleRate)) + && (parameters.allowAudioMixedDecoderSupportAdaptiveness + || (this.usesPrimaryDecoder == otherTrack.usesPrimaryDecoder + && this.usesHardwareAcceleration == otherTrack.usesHardwareAcceleration)); } @Override @@ -2512,7 +2645,10 @@ public int compareTo(AudioTrackInfo other) { this.bitrate, other.bitrate, parameters.forceLowestBitrate ? FORMAT_VALUE_ORDERING.reverse() : NO_ORDER) - // 4. Compare technical quality. + // 4. Compare match with renderer capability preferences. + .compareFalseFirst(this.usesPrimaryDecoder, other.usesPrimaryDecoder) + .compareFalseFirst(this.usesHardwareAcceleration, other.usesHardwareAcceleration) + // 5. Compare technical quality. .compare(this.channelCount, other.channelCount, qualityOrdering) .compare(this.sampleRate, other.sampleRate, qualityOrdering) .compare( @@ -2548,7 +2684,8 @@ public static int compareSelections(List infos1, List { + private static final class TextTrackInfo extends TrackInfo + implements Comparable { public static ImmutableList createForTrackGroup( int rendererIndex, @@ -2641,7 +2778,7 @@ public int getSelectionEligibility() { } @Override - public boolean isCompatibleForAdaptationWith(TrackInfo otherTrack) { + public boolean isCompatibleForAdaptationWith(TextTrackInfo otherTrack) { return false; } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java index f5f0191bc8d..5c85327510c 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java @@ -20,6 +20,10 @@ import static androidx.media3.common.C.FORMAT_UNSUPPORTED_SUBTYPE; import static androidx.media3.common.C.FORMAT_UNSUPPORTED_TYPE; import static androidx.media3.exoplayer.RendererCapabilities.ADAPTIVE_NOT_SEAMLESS; +import static androidx.media3.exoplayer.RendererCapabilities.DECODER_SUPPORT_FALLBACK; +import static androidx.media3.exoplayer.RendererCapabilities.DECODER_SUPPORT_PRIMARY; +import static androidx.media3.exoplayer.RendererCapabilities.HARDWARE_ACCELERATION_NOT_SUPPORTED; +import static androidx.media3.exoplayer.RendererCapabilities.HARDWARE_ACCELERATION_SUPPORTED; import static androidx.media3.exoplayer.RendererCapabilities.TUNNELING_NOT_SUPPORTED; import static androidx.media3.exoplayer.RendererConfiguration.DEFAULT; import static com.google.common.truth.Truth.assertThat; @@ -44,6 +48,7 @@ import androidx.media3.common.util.Util; import androidx.media3.exoplayer.ExoPlaybackException; import androidx.media3.exoplayer.RendererCapabilities; +import androidx.media3.exoplayer.RendererCapabilities.Capabilities; import androidx.media3.exoplayer.RendererConfiguration; import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.Parameters; @@ -1625,6 +1630,122 @@ public void selectTracksWithMultipleAudioTracksWithMixedChannelCounts() throws E assertNoSelection(result.selections[0]); } + @Test + public void selectTracksWithMultipleAudioTracksWithMixedDecoderSupportLevels() throws Exception { + Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon(); + Format format0 = formatBuilder.setId("0").setAverageBitrate(200).build(); + Format format1 = formatBuilder.setId("1").setAverageBitrate(400).build(); + Format format2 = formatBuilder.setId("2").setAverageBitrate(600).build(); + Format format3 = formatBuilder.setId("3").setAverageBitrate(800).build(); + TrackGroupArray trackGroups = singleTrackGroup(format0, format1, format2, format3); + @Capabilities int unsupported = RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE); + @Capabilities + int primaryHardware = + RendererCapabilities.create( + FORMAT_HANDLED, + ADAPTIVE_NOT_SEAMLESS, + TUNNELING_NOT_SUPPORTED, + HARDWARE_ACCELERATION_SUPPORTED, + DECODER_SUPPORT_PRIMARY); + @Capabilities + int primarySoftware = + RendererCapabilities.create( + FORMAT_HANDLED, + ADAPTIVE_NOT_SEAMLESS, + TUNNELING_NOT_SUPPORTED, + HARDWARE_ACCELERATION_NOT_SUPPORTED, + DECODER_SUPPORT_PRIMARY); + @Capabilities + int fallbackHardware = + RendererCapabilities.create( + FORMAT_HANDLED, + ADAPTIVE_NOT_SEAMLESS, + TUNNELING_NOT_SUPPORTED, + HARDWARE_ACCELERATION_SUPPORTED, + DECODER_SUPPORT_FALLBACK); + @Capabilities + int fallbackSoftware = + RendererCapabilities.create( + FORMAT_HANDLED, + ADAPTIVE_NOT_SEAMLESS, + TUNNELING_NOT_SUPPORTED, + HARDWARE_ACCELERATION_NOT_SUPPORTED, + DECODER_SUPPORT_FALLBACK); + + // Select all tracks supported by primary, hardware decoder by default. + ImmutableMap rendererCapabilitiesMap = + ImmutableMap.of( + "0", + primaryHardware, + "1", + primaryHardware, + "2", + primarySoftware, + "3", + fallbackHardware); + RendererCapabilities rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, rendererCapabilitiesMap); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 1, 0); + + // Select all tracks supported by primary, software decoder by default if no primary, hardware + // decoder is available. + rendererCapabilitiesMap = + ImmutableMap.of( + "0", + fallbackHardware, + "1", + fallbackHardware, + "2", + primarySoftware, + "3", + fallbackSoftware); + rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, rendererCapabilitiesMap); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections[0], trackGroups.get(0), 2); + + // Select all tracks supported by fallback, hardware decoder if no primary decoder is + // available. + rendererCapabilitiesMap = + ImmutableMap.of( + "0", fallbackHardware, "1", unsupported, "2", fallbackSoftware, "3", fallbackHardware); + rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, rendererCapabilitiesMap); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 3, 0); + + // Select all tracks supported by fallback, software decoder if no other decoder is available. + rendererCapabilitiesMap = + ImmutableMap.of( + "0", fallbackSoftware, "1", fallbackSoftware, "2", unsupported, "3", fallbackSoftware); + rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, rendererCapabilitiesMap); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 3, 1, 0); + + // Select all tracks if mixed decoder support is allowed. + rendererCapabilitiesMap = + ImmutableMap.of( + "0", primaryHardware, "1", unsupported, "2", primarySoftware, "3", fallbackHardware); + rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, rendererCapabilitiesMap); + trackSelector.setParameters( + defaultParameters.buildUpon().setAllowAudioMixedDecoderSupportAdaptiveness(true)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 3, 2, 0); + } + @Test public void selectTracksWithMultipleAudioTracksOverrideReturnsAdaptiveTrackSelection() throws Exception { @@ -1775,6 +1896,122 @@ public void selectTracksWithMultipleVideoTracksWithMixedMimeTypes() throws Excep assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 0, 1); } + @Test + public void selectTracksWithMultipleVideoTracksWithMixedDecoderSupportLevels() throws Exception { + Format.Builder formatBuilder = VIDEO_FORMAT.buildUpon(); + Format format0 = formatBuilder.setId("0").setAverageBitrate(200).build(); + Format format1 = formatBuilder.setId("1").setAverageBitrate(400).build(); + Format format2 = formatBuilder.setId("2").setAverageBitrate(600).build(); + Format format3 = formatBuilder.setId("3").setAverageBitrate(800).build(); + TrackGroupArray trackGroups = singleTrackGroup(format0, format1, format2, format3); + @Capabilities int unsupported = RendererCapabilities.create(FORMAT_UNSUPPORTED_TYPE); + @Capabilities + int primaryHardware = + RendererCapabilities.create( + FORMAT_HANDLED, + ADAPTIVE_NOT_SEAMLESS, + TUNNELING_NOT_SUPPORTED, + HARDWARE_ACCELERATION_SUPPORTED, + DECODER_SUPPORT_PRIMARY); + @Capabilities + int primarySoftware = + RendererCapabilities.create( + FORMAT_HANDLED, + ADAPTIVE_NOT_SEAMLESS, + TUNNELING_NOT_SUPPORTED, + HARDWARE_ACCELERATION_NOT_SUPPORTED, + DECODER_SUPPORT_PRIMARY); + @Capabilities + int fallbackHardware = + RendererCapabilities.create( + FORMAT_HANDLED, + ADAPTIVE_NOT_SEAMLESS, + TUNNELING_NOT_SUPPORTED, + HARDWARE_ACCELERATION_SUPPORTED, + DECODER_SUPPORT_FALLBACK); + @Capabilities + int fallbackSoftware = + RendererCapabilities.create( + FORMAT_HANDLED, + ADAPTIVE_NOT_SEAMLESS, + TUNNELING_NOT_SUPPORTED, + HARDWARE_ACCELERATION_NOT_SUPPORTED, + DECODER_SUPPORT_FALLBACK); + + // Select all tracks supported by primary, hardware decoder by default. + ImmutableMap rendererCapabilitiesMap = + ImmutableMap.of( + "0", + primaryHardware, + "1", + primaryHardware, + "2", + primarySoftware, + "3", + fallbackHardware); + RendererCapabilities rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_VIDEO, rendererCapabilitiesMap); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 1, 0); + + // Select all tracks supported by primary, software decoder by default if no primary, hardware + // decoder is available. + rendererCapabilitiesMap = + ImmutableMap.of( + "0", + fallbackHardware, + "1", + fallbackHardware, + "2", + primarySoftware, + "3", + fallbackSoftware); + rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_VIDEO, rendererCapabilitiesMap); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections[0], trackGroups.get(0), 2); + + // Select all tracks supported by fallback, hardware decoder if no primary decoder is + // available. + rendererCapabilitiesMap = + ImmutableMap.of( + "0", fallbackHardware, "1", unsupported, "2", fallbackSoftware, "3", fallbackHardware); + rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_VIDEO, rendererCapabilitiesMap); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 3, 0); + + // Select all tracks supported by fallback, software decoder if no other decoder is available. + rendererCapabilitiesMap = + ImmutableMap.of( + "0", fallbackSoftware, "1", fallbackSoftware, "2", unsupported, "3", fallbackSoftware); + rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_VIDEO, rendererCapabilitiesMap); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 3, 1, 0); + + // Select all tracks if mixed decoder support is allowed. + rendererCapabilitiesMap = + ImmutableMap.of( + "0", primaryHardware, "1", unsupported, "2", primarySoftware, "3", fallbackHardware); + rendererCapabilities = + new FakeMappedRendererCapabilities(C.TRACK_TYPE_VIDEO, rendererCapabilitiesMap); + trackSelector.setParameters( + defaultParameters.buildUpon().setAllowVideoMixedDecoderSupportAdaptiveness(true)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {rendererCapabilities}, trackGroups, periodId, TIMELINE); + assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 3, 2, 0); + } + @Test public void selectTracksWithMultipleVideoTracksOverrideReturnsAdaptiveTrackSelection() throws Exception { @@ -2121,6 +2358,7 @@ private static Parameters buildParametersForEqualsTest() { .setExceedVideoConstraintsIfNecessary(false) .setAllowVideoMixedMimeTypeAdaptiveness(true) .setAllowVideoNonSeamlessAdaptiveness(false) + .setAllowVideoMixedDecoderSupportAdaptiveness(true) .setViewportSize( /* viewportWidth= */ 8, /* viewportHeight= */ 9, @@ -2135,6 +2373,7 @@ private static Parameters buildParametersForEqualsTest() { .setAllowAudioMixedMimeTypeAdaptiveness(true) .setAllowAudioMixedSampleRateAdaptiveness(false) .setAllowAudioMixedChannelCountAdaptiveness(true) + .setAllowAudioMixedDecoderSupportAdaptiveness(false) .setPreferredAudioMimeTypes(MimeTypes.AUDIO_AC3, MimeTypes.AUDIO_E_AC3) // Text .setPreferredTextLanguages("de", "en")