Commit 327ec97e by tonihei

Reorder adaptive video track preferences.

This change moves the video track selection to the generic
selection method introcuced for audio and text. This ensures
we can apply the same criteria for fixed and adaptive video
track selections. Implicitly, this reorders the preferences
for adaptive tracks to give non-quality preferences (like
preferred MIME type or preferred role flags) a higher priority
than number of tracks in the selection.

Issue: google/ExoPlayer#9519
PiperOrigin-RevId: 422310902
parent 3f47da1f
...@@ -31,6 +31,9 @@ ...@@ -31,6 +31,9 @@
* Disable automatic speed adjustment for live streams that neither have * Disable automatic speed adjustment for live streams that neither have
low-latency features nor a user request setting the speed low-latency features nor a user request setting the speed
((#9329)[https://github.com/google/ExoPlayer/issues/9329]). ((#9329)[https://github.com/google/ExoPlayer/issues/9329]).
* Update video track selection logic to take preferred MIME types and role
flags into account when selecting multiple video tracks for adaptation
((#9519)[https://github.com/google/ExoPlayer/issues/9519]).
* Android 12 compatibility: * Android 12 compatibility:
* Upgrade the Cast extension to depend on * Upgrade the Cast extension to depend on
`com.google.android.gms:play-services-cast-framework:20.1.0`. Earlier `com.google.android.gms:play-services-cast-framework:20.1.0`. Earlier
......
...@@ -57,7 +57,6 @@ import java.util.ArrayList; ...@@ -57,7 +57,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -1358,7 +1357,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -1358,7 +1357,6 @@ public class DefaultTrackSelector extends MappingTrackSelector {
*/ */
private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f; private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f;
private static final int[] NO_TRACKS = new int[0];
/** Ordering of two format values. A known value is considered greater than Format#NO_VALUE. */ /** Ordering of two format values. A known value is considered greater than Format#NO_VALUE. */
private static final Ordering<Integer> FORMAT_VALUE_ORDERING = private static final Ordering<Integer> FORMAT_VALUE_ORDERING =
Ordering.from( Ordering.from(
...@@ -1650,20 +1648,15 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -1650,20 +1648,15 @@ public class DefaultTrackSelector extends MappingTrackSelector {
ExoTrackSelection.@NullableType Definition[] definitions = ExoTrackSelection.@NullableType Definition[] definitions =
new ExoTrackSelection.Definition[rendererCount]; new ExoTrackSelection.Definition[rendererCount];
boolean selectedVideoTracks = false; @Nullable
for (int i = 0; i < rendererCount; i++) { Pair<ExoTrackSelection.Definition, Integer> selectedVideo =
if (C.TRACK_TYPE_VIDEO == mappedTrackInfo.getRendererType(i)) { selectVideoTrack(
if (!selectedVideoTracks) { mappedTrackInfo,
definitions[i] = rendererFormatSupports,
selectVideoTrack( rendererMixedMimeTypeAdaptationSupports,
mappedTrackInfo.getTrackGroups(i), params);
rendererFormatSupports[i], if (selectedVideo != null) {
rendererMixedMimeTypeAdaptationSupports[i], definitions[selectedVideo.second] = selectedVideo.first;
params,
/* enableAdaptiveTrackSelection= */ true);
selectedVideoTracks = definitions[i] != null;
}
}
} }
@Nullable @Nullable
...@@ -1707,303 +1700,34 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -1707,303 +1700,34 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/** /**
* Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a * Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a
* {@link ExoTrackSelection} for a video renderer. * {@link ExoTrackSelection.Definition} for a video track selection.
* *
* @param groups The {@link TrackGroupArray} mapped to the renderer. * @param mappedTrackInfo Mapped track information.
* @param formatSupport The {@link Capabilities} for each mapped track, indexed by track group and * @param rendererFormatSupports The {@link Capabilities} for each mapped track, indexed by
* track (in that order). * renderer, track group and track (in that order).
* @param mixedMimeTypeAdaptationSupports The {@link AdaptiveSupport} for mixed MIME type * @param mixedMimeTypeSupports The {@link AdaptiveSupport} for mixed MIME type adaptation for the
* adaptation for the renderer. * renderer.
* @param params The selector's current constraint parameters. * @param params The selector's current constraint parameters.
* @param enableAdaptiveTrackSelection Whether adaptive track selection is allowed. * @return A pair of the selected {@link ExoTrackSelection.Definition} and the corresponding
* @return The {@link ExoTrackSelection.Definition} for the renderer, or null if no selection was * renderer index, or null if no selection was made.
* made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks. * @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/ */
@SuppressLint("WrongConstant") // Lint doesn't understand arrays of IntDefs.
@Nullable @Nullable
protected ExoTrackSelection.Definition selectVideoTrack( protected Pair<ExoTrackSelection.Definition, Integer> selectVideoTrack(
TrackGroupArray groups, MappedTrackInfo mappedTrackInfo,
@Capabilities int[][] formatSupport, @Capabilities int[][][] rendererFormatSupports,
@AdaptiveSupport int mixedMimeTypeAdaptationSupports, @AdaptiveSupport int[] mixedMimeTypeSupports,
Parameters params, Parameters params)
boolean enableAdaptiveTrackSelection)
throws ExoPlaybackException { throws ExoPlaybackException {
ExoTrackSelection.Definition definition = null; return selectTracksForType(
if (!params.forceHighestSupportedBitrate C.TRACK_TYPE_VIDEO,
&& !params.forceLowestBitrate mappedTrackInfo,
&& enableAdaptiveTrackSelection) { rendererFormatSupports,
definition = (rendererIndex, group, support) ->
selectAdaptiveVideoTrack(groups, formatSupport, mixedMimeTypeAdaptationSupports, params); VideoTrackInfo.createForTrackGroup(
} rendererIndex, group, params, support, mixedMimeTypeSupports[rendererIndex]),
if (definition == null) { VideoTrackInfo::compareSelections);
definition = selectFixedVideoTrack(groups, formatSupport, params);
}
return definition;
}
@Nullable
private static ExoTrackSelection.Definition selectAdaptiveVideoTrack(
TrackGroupArray groups,
@Capabilities int[][] formatSupport,
@AdaptiveSupport int mixedMimeTypeAdaptationSupports,
Parameters params) {
int requiredAdaptiveSupport =
params.allowVideoNonSeamlessAdaptiveness
? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS)
: RendererCapabilities.ADAPTIVE_SEAMLESS;
boolean allowMixedMimeTypes =
params.allowVideoMixedMimeTypeAdaptiveness
&& (mixedMimeTypeAdaptationSupports & requiredAdaptiveSupport) != 0;
for (int i = 0; i < groups.length; i++) {
TrackGroup group = groups.get(i);
int[] adaptiveTracks =
getAdaptiveVideoTracksForGroup(
group,
formatSupport[i],
allowMixedMimeTypes,
requiredAdaptiveSupport,
params.maxVideoWidth,
params.maxVideoHeight,
params.maxVideoFrameRate,
params.maxVideoBitrate,
params.minVideoWidth,
params.minVideoHeight,
params.minVideoFrameRate,
params.minVideoBitrate,
params.viewportWidth,
params.viewportHeight,
params.viewportOrientationMayChange);
if (adaptiveTracks.length > 0) {
return new ExoTrackSelection.Definition(group, adaptiveTracks);
}
}
return null;
}
private static int[] getAdaptiveVideoTracksForGroup(
TrackGroup group,
@Capabilities int[] formatSupport,
boolean allowMixedMimeTypes,
int requiredAdaptiveSupport,
int maxVideoWidth,
int maxVideoHeight,
int maxVideoFrameRate,
int maxVideoBitrate,
int minVideoWidth,
int minVideoHeight,
int minVideoFrameRate,
int minVideoBitrate,
int viewportWidth,
int viewportHeight,
boolean viewportOrientationMayChange) {
if (group.length < 2) {
return NO_TRACKS;
}
List<Integer> selectedTrackIndices =
getViewportFilteredTrackIndices(
group, viewportWidth, viewportHeight, viewportOrientationMayChange);
if (selectedTrackIndices.size() < 2) {
return NO_TRACKS;
}
String selectedMimeType = null;
if (!allowMixedMimeTypes) {
// Select the mime type for which we have the most adaptive tracks.
HashSet<@NullableType String> seenMimeTypes = new HashSet<>();
int selectedMimeTypeTrackCount = 0;
for (int i = 0; i < selectedTrackIndices.size(); i++) {
int trackIndex = selectedTrackIndices.get(i);
String sampleMimeType = group.getFormat(trackIndex).sampleMimeType;
if (seenMimeTypes.add(sampleMimeType)) {
int countForMimeType =
getAdaptiveVideoTrackCountForMimeType(
group,
formatSupport,
requiredAdaptiveSupport,
sampleMimeType,
maxVideoWidth,
maxVideoHeight,
maxVideoFrameRate,
maxVideoBitrate,
minVideoWidth,
minVideoHeight,
minVideoFrameRate,
minVideoBitrate,
selectedTrackIndices);
if (countForMimeType > selectedMimeTypeTrackCount) {
selectedMimeType = sampleMimeType;
selectedMimeTypeTrackCount = countForMimeType;
}
}
}
}
// Filter by the selected mime type.
filterAdaptiveVideoTrackCountForMimeType(
group,
formatSupport,
requiredAdaptiveSupport,
selectedMimeType,
maxVideoWidth,
maxVideoHeight,
maxVideoFrameRate,
maxVideoBitrate,
minVideoWidth,
minVideoHeight,
minVideoFrameRate,
minVideoBitrate,
selectedTrackIndices);
return selectedTrackIndices.size() < 2 ? NO_TRACKS : Ints.toArray(selectedTrackIndices);
}
private static int getAdaptiveVideoTrackCountForMimeType(
TrackGroup group,
@Capabilities int[] formatSupport,
int requiredAdaptiveSupport,
@Nullable String mimeType,
int maxVideoWidth,
int maxVideoHeight,
int maxVideoFrameRate,
int maxVideoBitrate,
int minVideoWidth,
int minVideoHeight,
int minVideoFrameRate,
int minVideoBitrate,
List<Integer> selectedTrackIndices) {
int adaptiveTrackCount = 0;
for (int i = 0; i < selectedTrackIndices.size(); i++) {
int trackIndex = selectedTrackIndices.get(i);
if (isSupportedAdaptiveVideoTrack(
group.getFormat(trackIndex),
mimeType,
formatSupport[trackIndex],
requiredAdaptiveSupport,
maxVideoWidth,
maxVideoHeight,
maxVideoFrameRate,
maxVideoBitrate,
minVideoWidth,
minVideoHeight,
minVideoFrameRate,
minVideoBitrate)) {
adaptiveTrackCount++;
}
}
return adaptiveTrackCount;
}
private static void filterAdaptiveVideoTrackCountForMimeType(
TrackGroup group,
@Capabilities int[] formatSupport,
int requiredAdaptiveSupport,
@Nullable String mimeType,
int maxVideoWidth,
int maxVideoHeight,
int maxVideoFrameRate,
int maxVideoBitrate,
int minVideoWidth,
int minVideoHeight,
int minVideoFrameRate,
int minVideoBitrate,
List<Integer> selectedTrackIndices) {
for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) {
int trackIndex = selectedTrackIndices.get(i);
if (!isSupportedAdaptiveVideoTrack(
group.getFormat(trackIndex),
mimeType,
formatSupport[trackIndex],
requiredAdaptiveSupport,
maxVideoWidth,
maxVideoHeight,
maxVideoFrameRate,
maxVideoBitrate,
minVideoWidth,
minVideoHeight,
minVideoFrameRate,
minVideoBitrate)) {
selectedTrackIndices.remove(i);
}
}
}
private static boolean isSupportedAdaptiveVideoTrack(
Format format,
@Nullable String mimeType,
@Capabilities int formatSupport,
int requiredAdaptiveSupport,
int maxVideoWidth,
int maxVideoHeight,
int maxVideoFrameRate,
int maxVideoBitrate,
int minVideoWidth,
int minVideoHeight,
int minVideoFrameRate,
int minVideoBitrate) {
if ((format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) {
// Ignore trick-play tracks for now.
return false;
}
return isSupported(formatSupport, /* allowExceedsCapabilities= */ false)
&& ((formatSupport & requiredAdaptiveSupport) != 0)
&& (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType))
&& (format.width == Format.NO_VALUE
|| (minVideoWidth <= format.width && format.width <= maxVideoWidth))
&& (format.height == Format.NO_VALUE
|| (minVideoHeight <= format.height && format.height <= maxVideoHeight))
&& (format.frameRate == Format.NO_VALUE
|| (minVideoFrameRate <= format.frameRate && format.frameRate <= maxVideoFrameRate))
&& format.bitrate != Format.NO_VALUE
&& minVideoBitrate <= format.bitrate
&& format.bitrate <= maxVideoBitrate;
}
@Nullable
private static ExoTrackSelection.Definition selectFixedVideoTrack(
TrackGroupArray groups, @Capabilities int[][] formatSupport, Parameters params) {
int selectedTrackIndex = C.INDEX_UNSET;
@Nullable TrackGroup selectedGroup = null;
@Nullable VideoTrackScore selectedTrackScore = null;
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
TrackGroup trackGroup = groups.get(groupIndex);
List<Integer> viewportFilteredTrackIndices =
getViewportFilteredTrackIndices(
trackGroup,
params.viewportWidth,
params.viewportHeight,
params.viewportOrientationMayChange);
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
Format format = trackGroup.getFormat(trackIndex);
if ((format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) {
// Ignore trick-play tracks for now.
continue;
}
if (isSupported(
trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) {
VideoTrackScore trackScore =
new VideoTrackScore(
format,
params,
trackFormatSupport[trackIndex],
viewportFilteredTrackIndices.contains(trackIndex));
if (!trackScore.isWithinMaxConstraints && !params.exceedVideoConstraintsIfNecessary) {
// Track should not be selected.
continue;
}
if (selectedTrackScore == null || trackScore.compareTo(selectedTrackScore) > 0) {
selectedGroup = trackGroup;
selectedTrackIndex = trackIndex;
selectedTrackScore = trackScore;
}
}
}
}
return selectedGroup == null
? null
: new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex);
} }
// Audio track selection implementation. // Audio track selection implementation.
...@@ -2338,25 +2062,16 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2338,25 +2062,16 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return 0; return 0;
} }
private static List<Integer> getViewportFilteredTrackIndices( private static int getMaxVideoPixelsToRetainForViewport(
TrackGroup group, int viewportWidth, int viewportHeight, boolean orientationMayChange) { TrackGroup group, int viewportWidth, int viewportHeight, boolean orientationMayChange) {
// Initially include all indices.
ArrayList<Integer> selectedTrackIndices = new ArrayList<>(group.length);
for (int i = 0; i < group.length; i++) {
selectedTrackIndices.add(i);
}
if (viewportWidth == Integer.MAX_VALUE || viewportHeight == Integer.MAX_VALUE) { if (viewportWidth == Integer.MAX_VALUE || viewportHeight == Integer.MAX_VALUE) {
// Viewport dimensions not set. Return the full set of indices. return Integer.MAX_VALUE;
return selectedTrackIndices;
} }
int maxVideoPixelsToRetain = Integer.MAX_VALUE; int maxVideoPixelsToRetain = Integer.MAX_VALUE;
for (int i = 0; i < group.length; i++) { for (int i = 0; i < group.length; i++) {
Format format = group.getFormat(i); Format format = group.getFormat(i);
// Keep track of the number of pixels of the selected format whose resolution is the // Keep track of the number of pixels of the selected format whose resolution is the
// smallest to exceed the maximum size at which it can be displayed within the viewport. // smallest to exceed the maximum size at which it can be displayed within the viewport.
// We'll discard formats of higher resolution.
if (format.width > 0 && format.height > 0) { if (format.width > 0 && format.height > 0) {
Point maxVideoSizeInViewport = Point maxVideoSizeInViewport =
getMaxVideoSizeInViewport( getMaxVideoSizeInViewport(
...@@ -2369,21 +2084,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2369,21 +2084,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
} }
} }
return maxVideoPixelsToRetain;
// Filter out formats that exceed maxVideoPixelsToRetain. These formats have an unnecessarily
// high resolution given the size at which the video will be displayed within the viewport. Also
// filter out formats with unknown dimensions, since we have some whose dimensions are known.
if (maxVideoPixelsToRetain != Integer.MAX_VALUE) {
for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) {
Format format = group.getFormat(selectedTrackIndices.get(i));
int pixelCount = format.getPixelCount();
if (pixelCount == Format.NO_VALUE || pixelCount > maxVideoPixelsToRetain) {
selectedTrackIndices.remove(i);
}
}
}
return selectedTrackIndices;
} }
/** /**
...@@ -2450,15 +2151,40 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2450,15 +2151,40 @@ public class DefaultTrackSelector extends MappingTrackSelector {
public abstract boolean isCompatibleForAdaptationWith(TrackInfo otherTrack); public abstract boolean isCompatibleForAdaptationWith(TrackInfo otherTrack);
} }
/** Represents how well a video track matches the selection {@link Parameters}. */ private static final class VideoTrackInfo extends TrackInfo {
protected static final class VideoTrackScore implements Comparable<VideoTrackScore> {
/** public static ImmutableList<VideoTrackInfo> createForTrackGroup(
* Whether the provided format is within the parameter maximum constraints. If {@code false}, int rendererIndex,
* the format should not be selected. TrackGroup trackGroup,
*/ Parameters params,
public final boolean isWithinMaxConstraints; @Capabilities int[] formatSupport,
@AdaptiveSupport int mixedMimeTypeAdaptionSupport) {
int maxPixelsToRetainForViewport =
getMaxVideoPixelsToRetainForViewport(
trackGroup,
params.viewportWidth,
params.viewportHeight,
params.viewportOrientationMayChange);
ImmutableList.Builder<VideoTrackInfo> listBuilder = ImmutableList.builder();
for (int i = 0; i < trackGroup.length; i++) {
int pixelCount = trackGroup.getFormat(i).getPixelCount();
boolean isSuitableForViewport =
maxPixelsToRetainForViewport == Integer.MAX_VALUE
|| (pixelCount != Format.NO_VALUE && pixelCount <= maxPixelsToRetainForViewport);
listBuilder.add(
new VideoTrackInfo(
rendererIndex,
trackGroup,
/* trackIndex= */ i,
params,
formatSupport[i],
mixedMimeTypeAdaptionSupport,
isSuitableForViewport));
}
return listBuilder.build();
}
private final boolean isWithinMaxConstraints;
private final Parameters parameters; private final Parameters parameters;
private final boolean isWithinMinConstraints; private final boolean isWithinMinConstraints;
private final boolean isWithinRendererCapabilities; private final boolean isWithinRendererCapabilities;
...@@ -2467,13 +2193,28 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2467,13 +2193,28 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private final int preferredMimeTypeMatchIndex; private final int preferredMimeTypeMatchIndex;
private final int preferredRoleFlagsScore; private final int preferredRoleFlagsScore;
private final boolean hasMainOrNoRoleFlag; private final boolean hasMainOrNoRoleFlag;
private final boolean allowMixedMimeTypes;
@SelectionEligibility private final int selectionEligibility;
public VideoTrackScore( public VideoTrackInfo(
Format format, int rendererIndex,
TrackGroup trackGroup,
int trackIndex,
Parameters parameters, Parameters parameters,
@Capabilities int formatSupport, @Capabilities int formatSupport,
@AdaptiveSupport int mixedMimeTypeAdaptationSupport,
boolean isSuitableForViewport) { boolean isSuitableForViewport) {
super(rendererIndex, trackGroup, trackIndex);
this.parameters = parameters; this.parameters = parameters;
@SuppressLint("WrongConstant")
int requiredAdaptiveSupport =
parameters.allowVideoNonSeamlessAdaptiveness
? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS
| RendererCapabilities.ADAPTIVE_SEAMLESS)
: RendererCapabilities.ADAPTIVE_SEAMLESS;
allowMixedMimeTypes =
parameters.allowVideoMixedMimeTypeAdaptiveness
&& (mixedMimeTypeAdaptationSupport & requiredAdaptiveSupport) != 0;
isWithinMaxConstraints = isWithinMaxConstraints =
isSuitableForViewport isSuitableForViewport
&& (format.width == Format.NO_VALUE || format.width <= parameters.maxVideoWidth) && (format.width == Format.NO_VALUE || format.width <= parameters.maxVideoWidth)
...@@ -2506,10 +2247,63 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2506,10 +2247,63 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
} }
preferredMimeTypeMatchIndex = bestMimeTypeMatchIndex; preferredMimeTypeMatchIndex = bestMimeTypeMatchIndex;
selectionEligibility = evaluateSelectionEligibility(formatSupport, requiredAdaptiveSupport);
} }
@Override @Override
public int compareTo(VideoTrackScore other) { @SelectionEligibility
public int getSelectionEligibility() {
return selectionEligibility;
}
@Override
public boolean isCompatibleForAdaptationWith(TrackInfo otherTrack) {
return allowMixedMimeTypes
|| Util.areEqual(format.sampleMimeType, otherTrack.format.sampleMimeType);
}
@SelectionEligibility
private int evaluateSelectionEligibility(
@Capabilities int rendererSupport, @AdaptiveSupport int requiredAdaptiveSupport) {
if ((format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) {
// Ignore trick-play tracks for now.
return SELECTION_ELIGIBILITY_NO;
}
if (!isSupported(rendererSupport, parameters.exceedRendererCapabilitiesIfNecessary)) {
return SELECTION_ELIGIBILITY_NO;
}
if (!isWithinMaxConstraints && !parameters.exceedVideoConstraintsIfNecessary) {
return SELECTION_ELIGIBILITY_NO;
}
return isSupported(rendererSupport, /* allowExceedsCapabilities= */ false)
&& isWithinMinConstraints
&& isWithinMaxConstraints
&& format.bitrate != Format.NO_VALUE
&& !parameters.forceHighestSupportedBitrate
&& !parameters.forceLowestBitrate
&& ((rendererSupport & requiredAdaptiveSupport) != 0)
? SELECTION_ELIGIBILITY_ADAPTIVE
: SELECTION_ELIGIBILITY_FIXED;
}
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();
}
private static int compareQualityPreferences(VideoTrackInfo info1, VideoTrackInfo info2) {
// The preferred ordering by video quality depends on the constraints: // The preferred ordering by video quality depends on the constraints:
// - Not within renderer capabilities: Prefer lower quality because it's more likely to play. // - Not within renderer capabilities: Prefer lower quality because it's more likely to play.
// - Within min and max constraints: Prefer higher quality. // - Within min and max constraints: Prefer higher quality.
...@@ -2519,29 +2313,33 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -2519,29 +2313,33 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// satisfying the violated max constraints. // satisfying the violated max constraints.
// - Outside min and max constraints: Arbitrarily prefer lower quality. // - Outside min and max constraints: Arbitrarily prefer lower quality.
Ordering<Integer> qualityOrdering = Ordering<Integer> qualityOrdering =
isWithinMaxConstraints && isWithinRendererCapabilities info1.isWithinMaxConstraints && info1.isWithinRendererCapabilities
? FORMAT_VALUE_ORDERING ? FORMAT_VALUE_ORDERING
: FORMAT_VALUE_ORDERING.reverse(); : FORMAT_VALUE_ORDERING.reverse();
return ComparisonChain.start() return ComparisonChain.start()
.compareFalseFirst(this.isWithinRendererCapabilities, other.isWithinRendererCapabilities)
// 1. Compare match with specific content preferences set by the parameters.
.compare(this.preferredRoleFlagsScore, other.preferredRoleFlagsScore)
// 2. Compare match with implicit content preferences set by the media.
.compareFalseFirst(this.hasMainOrNoRoleFlag, other.hasMainOrNoRoleFlag)
// 3. Compare match with technical preferences set by the parameters.
.compareFalseFirst(this.isWithinMaxConstraints, other.isWithinMaxConstraints)
.compareFalseFirst(this.isWithinMinConstraints, other.isWithinMinConstraints)
.compare( .compare(
this.preferredMimeTypeMatchIndex, info1.bitrate,
other.preferredMimeTypeMatchIndex, info2.bitrate,
Ordering.natural().reverse()) info1.parameters.forceLowestBitrate ? FORMAT_VALUE_ORDERING.reverse() : NO_ORDER)
// 4. Compare technical quality. .compare(info1.pixelCount, info2.pixelCount, qualityOrdering)
.compare(info1.bitrate, info2.bitrate, qualityOrdering)
.result();
}
public static int compareSelections(List<VideoTrackInfo> infos1, List<VideoTrackInfo> infos2) {
return ComparisonChain.start()
// Compare non-quality preferences of the best individual track with each other.
.compare( .compare(
this.bitrate, max(infos1, VideoTrackInfo::compareNonQualityPreferences),
other.bitrate, max(infos2, VideoTrackInfo::compareNonQualityPreferences),
parameters.forceLowestBitrate ? FORMAT_VALUE_ORDERING.reverse() : NO_ORDER) VideoTrackInfo::compareNonQualityPreferences)
.compare(this.pixelCount, other.pixelCount, qualityOrdering) // Prefer selections with more formats (all non-quality preferences being equal).
.compare(this.bitrate, other.bitrate, qualityOrdering) .compare(infos1.size(), infos2.size())
// Prefer selections with the best individual track quality.
.compare(
max(infos1, VideoTrackInfo::compareQualityPreferences),
max(infos2, VideoTrackInfo::compareQualityPreferences),
VideoTrackInfo::compareQualityPreferences)
.result(); .result();
} }
} }
......
...@@ -1859,11 +1859,17 @@ public final class DefaultTrackSelectorTest { ...@@ -1859,11 +1859,17 @@ public final class DefaultTrackSelectorTest {
throws Exception { throws Exception {
Format formatAv1 = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_AV1).build(); Format formatAv1 = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_AV1).build();
Format formatVp9 = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_VP9).build(); Format formatVp9 = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_VP9).build();
Format formatH264 = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build(); Format formatH264Low =
TrackGroupArray trackGroups = wrapFormats(formatAv1, formatVp9, formatH264); new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).setAverageBitrate(400).build();
Format formatH264High =
new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).setAverageBitrate(800).build();
// Use an adaptive group to check that MIME type has a higher priority than number of tracks.
TrackGroup adaptiveGroup = new TrackGroup(formatH264Low, formatH264High);
TrackGroupArray trackGroups =
new TrackGroupArray(new TrackGroup(formatAv1), new TrackGroup(formatVp9), adaptiveGroup);
trackSelector.setParameters( trackSelector.setParameters(
trackSelector.buildUponParameters().setPreferredVideoMimeType(MimeTypes.VIDEO_VP9)); defaultParameters.buildUpon().setPreferredVideoMimeType(MimeTypes.VIDEO_VP9));
TrackSelectorResult result = TrackSelectorResult result =
trackSelector.selectTracks( trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
...@@ -1871,8 +1877,8 @@ public final class DefaultTrackSelectorTest { ...@@ -1871,8 +1877,8 @@ public final class DefaultTrackSelectorTest {
assertFixedSelection(result.selections[0], trackGroups, formatVp9); assertFixedSelection(result.selections[0], trackGroups, formatVp9);
trackSelector.setParameters( trackSelector.setParameters(
trackSelector defaultParameters
.buildUponParameters() .buildUpon()
.setPreferredVideoMimeTypes(MimeTypes.VIDEO_VP9, MimeTypes.VIDEO_AV1)); .setPreferredVideoMimeTypes(MimeTypes.VIDEO_VP9, MimeTypes.VIDEO_AV1));
result = result =
trackSelector.selectTracks( trackSelector.selectTracks(
...@@ -1881,23 +1887,22 @@ public final class DefaultTrackSelectorTest { ...@@ -1881,23 +1887,22 @@ public final class DefaultTrackSelectorTest {
assertFixedSelection(result.selections[0], trackGroups, formatVp9); assertFixedSelection(result.selections[0], trackGroups, formatVp9);
trackSelector.setParameters( trackSelector.setParameters(
trackSelector defaultParameters
.buildUponParameters() .buildUpon()
.setPreferredVideoMimeTypes(MimeTypes.VIDEO_DIVX, MimeTypes.VIDEO_H264)); .setPreferredVideoMimeTypes(MimeTypes.VIDEO_DIVX, MimeTypes.VIDEO_H264));
result = result =
trackSelector.selectTracks( trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1); assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections[0], trackGroups, formatH264); assertAdaptiveSelection(result.selections[0], adaptiveGroup, /* expectedTracks...= */ 1, 0);
// Select first in the list if no preference is specified. // Select default (=most tracks) if no preference is specified.
trackSelector.setParameters( trackSelector.setParameters(defaultParameters.buildUpon().setPreferredVideoMimeType(null));
trackSelector.buildUponParameters().setPreferredVideoMimeType(null));
result = result =
trackSelector.selectTracks( trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1); assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections[0], trackGroups, formatAv1); assertAdaptiveSelection(result.selections[0], adaptiveGroup, /* expectedTracks...= */ 1, 0);
} }
/** /**
...@@ -1907,13 +1912,18 @@ public final class DefaultTrackSelectorTest { ...@@ -1907,13 +1912,18 @@ public final class DefaultTrackSelectorTest {
@Test @Test
public void selectTracks_withPreferredVideoRoleFlags_selectPreferredTrack() throws Exception { public void selectTracks_withPreferredVideoRoleFlags_selectPreferredTrack() throws Exception {
Format.Builder formatBuilder = VIDEO_FORMAT.buildUpon(); Format.Builder formatBuilder = VIDEO_FORMAT.buildUpon();
Format noRoleFlags = formatBuilder.build(); Format noRoleFlagsLow = formatBuilder.setAverageBitrate(4000).build();
Format noRoleFlagsHigh = formatBuilder.setAverageBitrate(8000).build();
Format lessRoleFlags = formatBuilder.setRoleFlags(C.ROLE_FLAG_CAPTION).build(); Format lessRoleFlags = formatBuilder.setRoleFlags(C.ROLE_FLAG_CAPTION).build();
Format moreRoleFlags = Format moreRoleFlags =
formatBuilder formatBuilder
.setRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY | C.ROLE_FLAG_DUB) .setRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY | C.ROLE_FLAG_DUB)
.build(); .build();
TrackGroupArray trackGroups = wrapFormats(noRoleFlags, moreRoleFlags, lessRoleFlags); // Use an adaptive group to check that role flags have higher priority than number of tracks.
TrackGroup adaptiveNoRoleFlagsGroup = new TrackGroup(noRoleFlagsLow, noRoleFlagsHigh);
TrackGroupArray trackGroups =
new TrackGroupArray(
adaptiveNoRoleFlagsGroup, new TrackGroup(moreRoleFlags), new TrackGroup(lessRoleFlags));
trackSelector.setParameters( trackSelector.setParameters(
defaultParameters defaultParameters
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment