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 allowMixedMimeAdaptiveness, int viewportWidth, int viewportHeight,
boolean orientationMayChange, TrackSelection.Factory adaptiveVideoTrackSelectionFactory,
boolean exceedConstraintsIfNecessary) throws ExoPlaybackException {
TrackSelection selection = null;
if (adaptiveVideoTrackSelectionFactory != null) {
selection = selectAdaptiveVideoTrack(rendererCapabilities, groups, formatSupport,
maxVideoWidth, maxVideoHeight, allowNonSeamlessAdaptiveness,
allowMixedMimeAdaptiveness, viewportWidth, viewportHeight,
orientationMayChange, adaptiveVideoTrackSelectionFactory);
}
if (selection == null) {
selection = selectFixedVideoTrack(groups, formatSupport, maxVideoWidth, maxVideoHeight,
viewportWidth, viewportHeight, orientationMayChange, exceedConstraintsIfNecessary);
}
return selection;
}
private static TrackSelection selectAdaptiveVideoTrack(RendererCapabilities rendererCapabilities,
TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight,
boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth, boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth,
int viewportHeight, boolean orientationMayChange, int viewportHeight, boolean orientationMayChange,
TrackSelection.Factory adaptiveVideoTrackSelectionFactory, TrackSelection.Factory adaptiveVideoTrackSelectionFactory) throws ExoPlaybackException {
boolean exceedVideoConstraintsIfNecessary) throws ExoPlaybackException {
if (adaptiveVideoTrackSelectionFactory != null) {
int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness
? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS)
: RendererCapabilities.ADAPTIVE_SEAMLESS; : RendererCapabilities.ADAPTIVE_SEAMLESS;
boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness
&& (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) && (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0;
!= 0;
TrackGroup largestAdaptiveGroup = null; TrackGroup largestAdaptiveGroup = null;
int[] largestAdaptiveGroupTracks = NO_TRACKS; int[] largestAdaptiveGroupTracks = NO_TRACKS;
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < groups.length; i++) {
TrackGroup trackGroup = trackGroups.get(i); TrackGroup group = groups.get(i);
int[] adaptiveTracks = getAdaptiveTracksOfGroup(trackGroup, formatSupport[i], int[] adaptiveTracks = getAdaptiveTracksForGroup(group, formatSupport[i],
allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight,
viewportWidth, viewportHeight, orientationMayChange); viewportWidth, viewportHeight, orientationMayChange);
if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) { if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) {
largestAdaptiveGroup = trackGroup; largestAdaptiveGroup = group;
largestAdaptiveGroupTracks = adaptiveTracks; largestAdaptiveGroupTracks = adaptiveTracks;
} }
} }
if (largestAdaptiveGroup != null) { return largestAdaptiveGroup == null ? null : adaptiveVideoTrackSelectionFactory
return adaptiveVideoTrackSelectionFactory.createTrackSelection(largestAdaptiveGroup, .createTrackSelection(largestAdaptiveGroup, largestAdaptiveGroupTracks);
largestAdaptiveGroupTracks);
}
} }
// TODO: Should select the best supported video track, not the first one. private static int[] getAdaptiveTracksForGroup(TrackGroup group, int[] formatSupport,
// No adaptive tracks selection could be made, so we select the first supported video track.
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);
}
}
}
if (exceedVideoConstraintsIfNecessary) {
return selectSmallestSupportedVideoTrack(trackGroups, formatSupport);
}
return null;
}
private static int[] getAdaptiveTracksOfGroup(TrackGroup trackGroup, 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);
for (int i = 0; i < formatSupport.length; i++) {
adaptiveTracksOfGroup.add(i);
}
if (viewportWidth != Integer.MAX_VALUE && viewportHeight != Integer.MAX_VALUE) {
filterFormatsForViewport(trackGroup, orientationMayChange, viewportWidth, viewportHeight,
adaptiveTracksOfGroup);
}
String mimeType = null;
int adaptiveTracksCount = 0;
if (!allowMixedMimeTypes) {
for (int i = 0; i < trackGroup.length; i++) {
if (!Util.areEqual(mimeType, trackGroup.getFormat(i).sampleMimeType)) {
int countForMimeType = getAdaptiveTrackCountForMimeType(trackGroup, formatSupport,
requiredAdaptiveSupport, trackGroup.getFormat(i).sampleMimeType, maxVideoWidth,
maxVideoHeight);
if (countForMimeType > adaptiveTracksCount) {
adaptiveTracksCount = countForMimeType;
mimeType = trackGroup.getFormat(i).sampleMimeType;
}
}
}
}
for (int i = adaptiveTracksOfGroup.size() - 1; i >= 0; i--) {
if (!isSupportedAdaptiveTrack(trackGroup.getFormat(adaptiveTracksOfGroup.get(i)), mimeType,
formatSupport[i], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight)) {
adaptiveTracksOfGroup.remove(i);
}
}
if (adaptiveTracksOfGroup.isEmpty()) {
// Not enough tracks to allow adaptation.
return NO_TRACKS; return NO_TRACKS;
} }
return Util.toArray(adaptiveTracksOfGroup);
List<Integer> selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth,
viewportHeight, orientationMayChange);
if (selectedTrackIndices.size() < 2) {
return NO_TRACKS;
} }
private static int getAdaptiveTrackCountForMimeType(TrackGroup trackGroup, int[] formatSupport, String selectedMimeType = null;
int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight) { if (!allowMixedMimeTypes) {
int adaptiveTracksCount = 0; // Select the mime type for which we have the most adaptive tracks.
for (int i = 0; i < trackGroup.length; i++) { HashSet<String> seenMimeTypes = new HashSet<>();
if (isSupportedAdaptiveTrack(trackGroup.getFormat(i), mimeType, formatSupport[i], int selectedMimeTypeTrackCount = 0;
requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight)) { for (int i = 0; i < selectedTrackIndices.size(); i++) {
adaptiveTracksCount++; int trackIndex = selectedTrackIndices.get(i);
String sampleMimeType = group.getFormat(trackIndex).sampleMimeType;
if (!seenMimeTypes.contains(sampleMimeType)) {
seenMimeTypes.add(sampleMimeType);
int countForMimeType = getAdaptiveTrackCountForMimeType(group, formatSupport,
requiredAdaptiveSupport, sampleMimeType, maxVideoWidth, maxVideoHeight,
selectedTrackIndices);
if (countForMimeType > selectedMimeTypeTrackCount) {
selectedMimeType = sampleMimeType;
selectedMimeTypeTrackCount = countForMimeType;
}
} }
} }
return adaptiveTracksCount;
} }
private static boolean isSupportedAdaptiveTrack(Format format, String mimeType, int formatSupport, // Filter by the selected mime type.
int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight) { filterAdaptiveTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport,
return isSupportedVideoTrack(formatSupport, format, maxVideoWidth, maxVideoHeight) selectedMimeType, maxVideoWidth, maxVideoHeight, selectedTrackIndices);
&& (formatSupport & requiredAdaptiveSupport) != 0
&& (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType)); return selectedTrackIndices.size() < 2 ? NO_TRACKS : Util.toArray(selectedTrackIndices);
} }
private static TrackSelection selectSmallestSupportedVideoTrack(TrackGroupArray trackGroups, private static int getAdaptiveTrackCountForMimeType(TrackGroup group, int[] formatSupport,
int[][] formatSupport) { int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight,
int smallestPixelCount = Integer.MAX_VALUE; List<Integer> selectedTrackIndices) {
TrackGroup trackGroupSelection = null; int adaptiveTrackCount = 0;
int trackIndexSelection = -1; for (int i = 0; i < selectedTrackIndices.size(); i++) {
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { int trackIndex = selectedTrackIndices.get(i);
TrackGroup trackGroup = trackGroups.get(groupIndex); if (isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType,
formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight)) {
adaptiveTrackCount++;
}
}
return adaptiveTrackCount;
}
private static void filterAdaptiveTrackCountForMimeType(TrackGroup group, int[] formatSupport,
int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight,
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)) {
selectedTrackIndices.remove(i);
}
}
}
private static boolean isSupportedAdaptiveVideoTrack(Format format, String mimeType,
int formatSupport, int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight) {
return isSupported(formatSupport) && ((formatSupport & requiredAdaptiveSupport) != 0)
&& (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 selectFixedVideoTrack(TrackGroupArray groups,
int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, int viewportWidth,
int viewportHeight, boolean orientationMayChange, boolean exceedConstraintsIfNecessary) {
TrackGroup selectedGroup = null;
int selectedTrackIndex = -1;
int selectedPixelCount = Format.NO_VALUE;
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 }
: new FixedTrackSelection(trackGroupSelection, trackIndexSelection); return selectedGroup == null ? null
: 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 {
} }
} }