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
This commit is contained in:
tonihei 2022-01-17 13:38:11 +00:00 committed by Ian Baker
parent a62a189b41
commit 3cfd24a912
2 changed files with 404 additions and 28 deletions

View File

@ -32,6 +32,7 @@ import androidx.media3.common.C;
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 class DefaultTrackSelector extends MappingTrackSelector {
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 @@ public class DefaultTrackSelector extends MappingTrackSelector {
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 @@ public class DefaultTrackSelector extends MappingTrackSelector {
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 @@ public class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
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 @@ public class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
* 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 class DefaultTrackSelector extends MappingTrackSelector {
* 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 @@ public class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
&& 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 class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
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 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
@Nullable
private <T extends TrackInfo> Pair<ExoTrackSelection.Definition, Integer> selectTracksForType(
private <T extends TrackInfo<T>> Pair<ExoTrackSelection.Definition, Integer> selectTracksForType(
@C.TrackType int trackType,
MappedTrackInfo mappedTrackInfo,
@Capabilities int[][][] formatSupport,
@ -2129,10 +2208,32 @@ public class DefaultTrackSelector extends MappingTrackSelector {
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<T extends TrackInfo<T>> {
/** Factory for {@link TrackInfo} implementations for a given {@link TrackGroup}. */
public interface Factory<T extends TrackInfo> {
public interface Factory<T extends TrackInfo<T>> {
List<T> create(int rendererIndex, TrackGroup trackGroup, @Capabilities int[] formatSupports);
}
@ -2156,10 +2257,10 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* 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<VideoTrackInfo> {
public static ImmutableList<VideoTrackInfo> createForTrackGroup(
int rendererIndex,
@ -2203,6 +2304,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
}
}
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 class DefaultTrackSelector extends MappingTrackSelector {
}
@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 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
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 class DefaultTrackSelector extends MappingTrackSelector {
}
}
private static final class AudioTrackInfo extends TrackInfo
private static final class AudioTrackInfo extends TrackInfo<AudioTrackInfo>
implements Comparable<AudioTrackInfo> {
public static ImmutableList<AudioTrackInfo> createForTrackGroup(
@ -2391,6 +2513,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
}
}
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 class DefaultTrackSelector extends MappingTrackSelector {
}
@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 class DefaultTrackSelector extends MappingTrackSelector {
&& 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 class DefaultTrackSelector extends MappingTrackSelector {
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 class DefaultTrackSelector extends MappingTrackSelector {
}
}
private static final class TextTrackInfo extends TrackInfo implements Comparable<TextTrackInfo> {
private static final class TextTrackInfo extends TrackInfo<TextTrackInfo>
implements Comparable<TextTrackInfo> {
public static ImmutableList<TextTrackInfo> createForTrackGroup(
int rendererIndex,
@ -2641,7 +2778,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
@Override
public boolean isCompatibleForAdaptationWith(TrackInfo otherTrack) {
public boolean isCompatibleForAdaptationWith(TextTrackInfo otherTrack) {
return false;
}

View File

@ -20,6 +20,10 @@ import static androidx.media3.common.C.FORMAT_HANDLED;
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.TracksInfo;
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 final class DefaultTrackSelectorTest {
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<String, Integer> 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 final class DefaultTrackSelectorTest {
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<String, Integer> 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 @@ public final class DefaultTrackSelectorTest {
.setExceedVideoConstraintsIfNecessary(false)
.setAllowVideoMixedMimeTypeAdaptiveness(true)
.setAllowVideoNonSeamlessAdaptiveness(false)
.setAllowVideoMixedDecoderSupportAdaptiveness(true)
.setViewportSize(
/* viewportWidth= */ 8,
/* viewportHeight= */ 9,
@ -2135,6 +2373,7 @@ public final class DefaultTrackSelectorTest {
.setAllowAudioMixedMimeTypeAdaptiveness(true)
.setAllowAudioMixedSampleRateAdaptiveness(false)
.setAllowAudioMixedChannelCountAdaptiveness(true)
.setAllowAudioMixedDecoderSupportAdaptiveness(false)
.setPreferredAudioMimeTypes(MimeTypes.AUDIO_AC3, MimeTypes.AUDIO_E_AC3)
// Text
.setPreferredTextLanguages("de", "en")