Enhance DefaultTrackSelector part 1

- Enforce viewport constraints for fixed video track selection.
- Select best fixed video track, not the first one.
- Better handling of video tracks with unknown dimensions.
- Mini bug fix.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=129226593
This commit is contained in:
olly 2016-08-03 10:05:19 -07:00 committed by Oliver Woodman
parent 2395f13682
commit ee565300cb
2 changed files with 185 additions and 130 deletions

View File

@ -410,6 +410,14 @@ public final class Format implements Parcelable {
initializationData, drmInitData); initializationData, drmInitData);
} }
/**
* Returns the number of pixels if this is a video format whose {@link #width} and {@link #height}
* are known, or {@link #NO_VALUE} otherwise
*/
public int getPixelCount() {
return width == NO_VALUE || height == NO_VALUE ? NO_VALUE : (width * height);
}
/** /**
* Returns a {@link MediaFormat} representation of this format. * Returns a {@link MediaFormat} representation of this format.
*/ */

View File

@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -205,7 +206,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/** /**
* Equivalent to {@code setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}. * Equivalent to {@code setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}.
*/ */
public void clearViewportConstrains() { public void clearViewportConstraints() {
setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true); setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true);
} }
@ -246,156 +247,189 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// Video track selection implementation. // Video track selection implementation.
private static TrackSelection selectTrackForVideoRenderer( private static TrackSelection selectTrackForVideoRenderer(
RendererCapabilities rendererCapabilities, TrackGroupArray trackGroups, RendererCapabilities rendererCapabilities, TrackGroupArray groups, int[][] formatSupport,
int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, int maxVideoWidth, int maxVideoHeight, boolean allowNonSeamlessAdaptiveness,
boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth, boolean allowMixedMimeAdaptiveness, int viewportWidth, int viewportHeight,
int viewportHeight, boolean orientationMayChange, boolean orientationMayChange, TrackSelection.Factory adaptiveVideoTrackSelectionFactory,
TrackSelection.Factory adaptiveVideoTrackSelectionFactory, boolean exceedConstraintsIfNecessary) throws ExoPlaybackException {
boolean exceedVideoConstraintsIfNecessary) throws ExoPlaybackException { TrackSelection selection = null;
if (adaptiveVideoTrackSelectionFactory != null) { if (adaptiveVideoTrackSelectionFactory != null) {
int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness selection = selectAdaptiveVideoTrack(rendererCapabilities, groups, formatSupport,
? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) maxVideoWidth, maxVideoHeight, allowNonSeamlessAdaptiveness,
: RendererCapabilities.ADAPTIVE_SEAMLESS; allowMixedMimeAdaptiveness, viewportWidth, viewportHeight,
boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness orientationMayChange, adaptiveVideoTrackSelectionFactory);
&& (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport)
!= 0;
TrackGroup largestAdaptiveGroup = null;
int[] largestAdaptiveGroupTracks = NO_TRACKS;
for (int i = 0; i < trackGroups.length; i++) {
TrackGroup trackGroup = trackGroups.get(i);
int[] adaptiveTracks = getAdaptiveTracksOfGroup(trackGroup, formatSupport[i],
allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight,
viewportWidth, viewportHeight, orientationMayChange);
if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) {
largestAdaptiveGroup = trackGroup;
largestAdaptiveGroupTracks = adaptiveTracks;
}
}
if (largestAdaptiveGroup != null) {
return adaptiveVideoTrackSelectionFactory.createTrackSelection(largestAdaptiveGroup,
largestAdaptiveGroupTracks);
}
} }
if (selection == null) {
// TODO: Should select the best supported video track, not the first one. selection = selectFixedVideoTrack(groups, formatSupport, maxVideoWidth, maxVideoHeight,
// No adaptive tracks selection could be made, so we select the first supported video track. viewportWidth, viewportHeight, orientationMayChange, exceedConstraintsIfNecessary);
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex);
int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupportedVideoTrack(trackFormatSupport[trackIndex], trackGroup.getFormat(trackIndex),
maxVideoWidth, maxVideoHeight)) {
return new FixedTrackSelection(trackGroup, trackIndex);
}
}
} }
return selection;
if (exceedVideoConstraintsIfNecessary) {
return selectSmallestSupportedVideoTrack(trackGroups, formatSupport);
}
return null;
} }
private static int[] getAdaptiveTracksOfGroup(TrackGroup trackGroup, int[] formatSupport, private static TrackSelection selectAdaptiveVideoTrack(RendererCapabilities rendererCapabilities,
TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight,
boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth,
int viewportHeight, boolean orientationMayChange,
TrackSelection.Factory adaptiveVideoTrackSelectionFactory) throws ExoPlaybackException {
int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness
? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS)
: RendererCapabilities.ADAPTIVE_SEAMLESS;
boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness
&& (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0;
TrackGroup largestAdaptiveGroup = null;
int[] largestAdaptiveGroupTracks = NO_TRACKS;
for (int i = 0; i < groups.length; i++) {
TrackGroup group = groups.get(i);
int[] adaptiveTracks = getAdaptiveTracksForGroup(group, formatSupport[i],
allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight,
viewportWidth, viewportHeight, orientationMayChange);
if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) {
largestAdaptiveGroup = group;
largestAdaptiveGroupTracks = adaptiveTracks;
}
}
return largestAdaptiveGroup == null ? null : adaptiveVideoTrackSelectionFactory
.createTrackSelection(largestAdaptiveGroup, largestAdaptiveGroupTracks);
}
private static int[] getAdaptiveTracksForGroup(TrackGroup group, int[] formatSupport,
boolean allowMixedMimeTypes, int requiredAdaptiveSupport, int maxVideoWidth, boolean allowMixedMimeTypes, int requiredAdaptiveSupport, int maxVideoWidth,
int maxVideoHeight, int viewportWidth, int viewportHeight, boolean orientationMayChange) { int maxVideoHeight, int viewportWidth, int viewportHeight, boolean orientationMayChange) {
if (group.length < 2) {
ArrayList<Integer> adaptiveTracksOfGroup = new ArrayList<>(formatSupport.length); return NO_TRACKS;
for (int i = 0; i < formatSupport.length; i++) {
adaptiveTracksOfGroup.add(i);
} }
if (viewportWidth != Integer.MAX_VALUE && viewportHeight != Integer.MAX_VALUE) { List<Integer> selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth,
filterFormatsForViewport(trackGroup, orientationMayChange, viewportWidth, viewportHeight, viewportHeight, orientationMayChange);
adaptiveTracksOfGroup); if (selectedTrackIndices.size() < 2) {
return NO_TRACKS;
} }
String mimeType = null; String selectedMimeType = null;
int adaptiveTracksCount = 0;
if (!allowMixedMimeTypes) { if (!allowMixedMimeTypes) {
for (int i = 0; i < trackGroup.length; i++) { // Select the mime type for which we have the most adaptive tracks.
if (!Util.areEqual(mimeType, trackGroup.getFormat(i).sampleMimeType)) { HashSet<String> seenMimeTypes = new HashSet<>();
int countForMimeType = getAdaptiveTrackCountForMimeType(trackGroup, formatSupport, int selectedMimeTypeTrackCount = 0;
requiredAdaptiveSupport, trackGroup.getFormat(i).sampleMimeType, maxVideoWidth, for (int i = 0; i < selectedTrackIndices.size(); i++) {
maxVideoHeight); int trackIndex = selectedTrackIndices.get(i);
if (countForMimeType > adaptiveTracksCount) { String sampleMimeType = group.getFormat(trackIndex).sampleMimeType;
adaptiveTracksCount = countForMimeType; if (!seenMimeTypes.contains(sampleMimeType)) {
mimeType = trackGroup.getFormat(i).sampleMimeType; seenMimeTypes.add(sampleMimeType);
int countForMimeType = getAdaptiveTrackCountForMimeType(group, formatSupport,
requiredAdaptiveSupport, sampleMimeType, maxVideoWidth, maxVideoHeight,
selectedTrackIndices);
if (countForMimeType > selectedMimeTypeTrackCount) {
selectedMimeType = sampleMimeType;
selectedMimeTypeTrackCount = countForMimeType;
} }
} }
} }
} }
for (int i = adaptiveTracksOfGroup.size() - 1; i >= 0; i--) { // Filter by the selected mime type.
if (!isSupportedAdaptiveTrack(trackGroup.getFormat(adaptiveTracksOfGroup.get(i)), mimeType, filterAdaptiveTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport,
formatSupport[i], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight)) { selectedMimeType, maxVideoWidth, maxVideoHeight, selectedTrackIndices);
adaptiveTracksOfGroup.remove(i);
return selectedTrackIndices.size() < 2 ? NO_TRACKS : Util.toArray(selectedTrackIndices);
}
private static int getAdaptiveTrackCountForMimeType(TrackGroup group, int[] formatSupport,
int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight,
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)) {
adaptiveTrackCount++;
} }
} }
if (adaptiveTracksOfGroup.isEmpty()) { return adaptiveTrackCount;
// Not enough tracks to allow adaptation.
return NO_TRACKS;
}
return Util.toArray(adaptiveTracksOfGroup);
} }
private static int getAdaptiveTrackCountForMimeType(TrackGroup trackGroup, int[] formatSupport, private static void filterAdaptiveTrackCountForMimeType(TrackGroup group, int[] formatSupport,
int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight) { int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight,
int adaptiveTracksCount = 0; List<Integer> selectedTrackIndices) {
for (int i = 0; i < trackGroup.length; i++) { for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) {
if (isSupportedAdaptiveTrack(trackGroup.getFormat(i), mimeType, formatSupport[i], int trackIndex = selectedTrackIndices.get(i);
requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight)) { if (!isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType,
adaptiveTracksCount++; formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight)) {
selectedTrackIndices.remove(i);
} }
} }
return adaptiveTracksCount;
} }
private static boolean isSupportedAdaptiveTrack(Format format, String mimeType, int formatSupport, private static boolean isSupportedAdaptiveVideoTrack(Format format, String mimeType,
int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight) { int formatSupport, int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight) {
return isSupportedVideoTrack(formatSupport, format, maxVideoWidth, maxVideoHeight) return isSupported(formatSupport) && ((formatSupport & requiredAdaptiveSupport) != 0)
&& (formatSupport & requiredAdaptiveSupport) != 0 && (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType))
&& (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType)); && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth)
&& (format.height == Format.NO_VALUE || format.height <= maxVideoHeight);
} }
private static TrackSelection selectSmallestSupportedVideoTrack(TrackGroupArray trackGroups, private static TrackSelection selectFixedVideoTrack(TrackGroupArray groups,
int[][] formatSupport) { int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, int viewportWidth,
int smallestPixelCount = Integer.MAX_VALUE; int viewportHeight, boolean orientationMayChange, boolean exceedConstraintsIfNecessary) {
TrackGroup trackGroupSelection = null; TrackGroup selectedGroup = null;
int trackIndexSelection = -1; int selectedTrackIndex = -1;
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { int selectedPixelCount = Format.NO_VALUE;
TrackGroup trackGroup = trackGroups.get(groupIndex); boolean selectedIsWithinConstraints = false;
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
TrackGroup group = groups.get(groupIndex);
List<Integer> selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth,
viewportHeight, orientationMayChange);
int[] trackFormatSupport = formatSupport[groupIndex]; int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
Format format = trackGroup.getFormat(trackIndex); if (isSupported(trackFormatSupport[trackIndex])) {
int pixelCount = format.width * format.height; Format format = group.getFormat(trackIndex);
if (pixelCount < smallestPixelCount boolean isWithinConstraints = selectedTrackIndices.contains(trackIndex)
&& isSupportedVideoTrack(trackFormatSupport[trackIndex], format, Integer.MAX_VALUE, && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth)
Integer.MAX_VALUE)) { && (format.height == Format.NO_VALUE || format.height <= maxVideoHeight);
smallestPixelCount = pixelCount; int pixelCount = format.getPixelCount();
trackGroupSelection = trackGroup; boolean selectTrack;
trackIndexSelection = trackIndex; if (selectedIsWithinConstraints) {
selectTrack = isWithinConstraints
&& comparePixelCounts(pixelCount, selectedPixelCount) > 0;
} else {
selectTrack = isWithinConstraints || (exceedConstraintsIfNecessary
&& (selectedGroup == null
|| comparePixelCounts(pixelCount, selectedPixelCount) < 0));
}
if (selectTrack) {
selectedGroup = group;
selectedTrackIndex = trackIndex;
selectedPixelCount = pixelCount;
selectedIsWithinConstraints = isWithinConstraints;
}
} }
} }
} }
return trackGroupSelection == null ? null return selectedGroup == null ? null
: new FixedTrackSelection(trackGroupSelection, trackIndexSelection); : new FixedTrackSelection(selectedGroup, selectedTrackIndex);
} }
private static boolean isSupportedVideoTrack(int formatSupport, Format format, int maxVideoWidth, /**
int maxVideoHeight) { * Compares two pixel counts for order. A known pixel count is considered greater than
return isSupported(formatSupport) && format.width <= maxVideoWidth * {@link Format#NO_VALUE}.
&& format.height <= maxVideoHeight; *
* @param first The first pixel count.
* @param second The second pixel count.
* @return A negative integer if the first pixel count is less than the second. Zero if they are
* equal. A positive integer if the first pixel count is greater than the second.
*/
private static int comparePixelCounts(int first, int second) {
return first == Format.NO_VALUE ? (second == Format.NO_VALUE ? 0 : -1)
: (second == Format.NO_VALUE ? 1 : (first - second));
} }
// Audio track selection implementation. // Audio track selection implementation.
private static TrackSelection selectTrackForAudioRenderer(TrackGroupArray trackGroups, private static TrackSelection selectTrackForAudioRenderer(TrackGroupArray groups,
int[][] formatSupport, String preferredLanguage) { int[][] formatSupport, String preferredLanguage) {
if (preferredLanguage != null) { if (preferredLanguage != null) {
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex); TrackGroup trackGroup = groups.get(groupIndex);
int[] trackFormatSupport = formatSupport[groupIndex]; int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex]) if (isSupported(trackFormatSupport[trackIndex])
@ -406,17 +440,17 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
} }
// No preferred language was selected or no audio track presented the preferred language. // No preferred language was selected or no audio track presented the preferred language.
return selectFirstSupportedTrack(trackGroups, formatSupport); return selectFirstSupportedTrack(groups, formatSupport);
} }
// Text track selection implementation. // Text track selection implementation.
private static TrackSelection selectTrackForTextRenderer(TrackGroupArray trackGroups, private static TrackSelection selectTrackForTextRenderer(TrackGroupArray groups,
int[][] formatSupport, String preferredLanguage) { int[][] formatSupport, String preferredLanguage) {
TrackGroup firstForcedGroup = null; TrackGroup firstForcedGroup = null;
int firstForcedTrack = -1; int firstForcedTrack = -1;
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex); TrackGroup trackGroup = groups.get(groupIndex);
int[] trackFormatSupport = formatSupport[groupIndex]; int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex]) if (isSupported(trackFormatSupport[trackIndex])
@ -438,10 +472,10 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// General track selection methods. // General track selection methods.
private static TrackSelection selectFirstSupportedTrack(TrackGroupArray trackGroups, private static TrackSelection selectFirstSupportedTrack(TrackGroupArray groups,
int[][] formatSupport) { int[][] formatSupport) {
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex); TrackGroup trackGroup = groups.get(groupIndex);
int[] trackFormatSupport = formatSupport[groupIndex]; int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) { if (isSupported(trackFormatSupport[trackIndex])) {
@ -463,12 +497,22 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// Viewport size util methods. // Viewport size util methods.
private static void filterFormatsForViewport(TrackGroup trackGroup, boolean orientationMayChange, private static List<Integer> getViewportFilteredTrackIndices(TrackGroup group, int viewportWidth,
int viewportWidth, int viewportHeight, List<Integer> allowedSizeTrackIndices) { int viewportHeight, boolean orientationMayChange) {
int maxVideoPixelsToRetain = Integer.MAX_VALUE; // Initially include all indices.
ArrayList<Integer> selectedTrackIndices = new ArrayList<>(group.length);
for (int i = 0; i < group.length; i++) {
selectedTrackIndices.add(i);
}
for (int i = 0; i < trackGroup.length; i++) { if (viewportWidth == Integer.MAX_VALUE || viewportHeight == Integer.MAX_VALUE) {
Format format = trackGroup.getFormat(i); // Viewport dimensions not set. Return the full set of indices.
return selectedTrackIndices;
}
int maxVideoPixelsToRetain = Integer.MAX_VALUE;
for (int i = 0; i < group.length; 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. // We'll discard formats of higher resolution.
@ -485,15 +529,19 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
// Filter out formats that exceed maxVideoPixelsToRetain. These formats have an unnecessarily // 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. // 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) { if (maxVideoPixelsToRetain != Integer.MAX_VALUE) {
for (int i = allowedSizeTrackIndices.size() - 1; i >= 0; i--) { for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) {
Format format = trackGroup.getFormat(allowedSizeTrackIndices.get(i)); Format format = group.getFormat(selectedTrackIndices.get(i));
if (format.width * format.height > maxVideoPixelsToRetain) { int pixelCount = format.getPixelCount();
allowedSizeTrackIndices.remove(i); if (pixelCount == Format.NO_VALUE || pixelCount > maxVideoPixelsToRetain) {
selectedTrackIndices.remove(i);
} }
} }
} }
return selectedTrackIndices;
} }
/** /**
@ -594,4 +642,3 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
} }