From 3cfd24a91271a97184b452d80fec66917a76010b Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 17 Jan 2022 13:38:11 +0000 Subject: [PATCH] Limit adaptive selections to same level of decoder support Adaptive video and audio selections will be limited to formats with the same level of DecoderSupport and HardwareAccelatationSupport, unless specifically allowed by new flags. If different levels of decoder support are available, prefer primary over fallback decoders and hardware-accelerated over software decoders (in this order). For video, also prefer more efficient codecs, if both are supported by hardware-accelerated primary decoders. Issue: google/ExoPlayer#4835 Issue: google/ExoPlayer#9565 PiperOrigin-RevId: 422345048 --- .../trackselection/DefaultTrackSelector.java | 193 ++++++++++++-- .../DefaultTrackSelectorTest.java | 239 ++++++++++++++++++ 2 files changed, 404 insertions(+), 28 deletions(-) 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")