diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
index 0ab4f62866..42eeebde11 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
@@ -762,10 +762,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
protected TrackSelection selectAudioTrack(TrackGroupArray groups, int[][] formatSupport,
Parameters params, TrackSelection.Factory adaptiveTrackSelectionFactory)
throws ExoPlaybackException {
- int selectedGroupIndex = C.INDEX_UNSET;
int selectedTrackIndex = C.INDEX_UNSET;
- int selectedTrackScore = 0;
- int selectedBitrate = Format.NO_VALUE;
+ int selectedGroupIndex = C.INDEX_UNSET;
+ AudioTrackScore selectedTrackScore = null;
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
TrackGroup trackGroup = groups.get(groupIndex);
int[] trackFormatSupport = formatSupport[groupIndex];
@@ -773,15 +772,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
if (isSupported(trackFormatSupport[trackIndex],
params.exceedRendererCapabilitiesIfNecessary)) {
Format format = trackGroup.getFormat(trackIndex);
- int trackScore = getAudioTrackScore(trackFormatSupport[trackIndex],
- params.preferredAudioLanguage, format);
- if (trackScore > selectedTrackScore
- || (trackScore == selectedTrackScore && params.forceLowestBitrate
- && compareFormatValues(format.bitrate, selectedBitrate) < 0)) {
+ AudioTrackScore trackScore =
+ new AudioTrackScore(format, params, trackFormatSupport[trackIndex]);
+ if (selectedTrackScore == null || trackScore.compareTo(selectedTrackScore) > 0) {
selectedGroupIndex = groupIndex;
selectedTrackIndex = trackIndex;
selectedTrackScore = trackScore;
- selectedBitrate = format.bitrate;
}
}
}
@@ -804,27 +800,6 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return new FixedTrackSelection(selectedGroup, selectedTrackIndex);
}
- private static int getAudioTrackScore(int formatSupport, String preferredLanguage,
- Format format) {
- boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
- int trackScore;
- if (formatHasLanguage(format, preferredLanguage)) {
- if (isDefault) {
- trackScore = 4;
- } else {
- trackScore = 3;
- }
- } else if (isDefault) {
- trackScore = 2;
- } else {
- trackScore = 1;
- }
- if (isSupported(formatSupport, false)) {
- trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS;
- }
- return trackScore;
- }
-
private static int[] getAdaptiveAudioTracks(TrackGroup group, int[] formatSupport,
boolean allowMixedMimeTypes) {
int selectedConfigurationTrackCount = 0;
@@ -1090,6 +1065,103 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
}
+ /**
+ * A representation of how well a track fits with our track selection {@link Parameters}.
+ *
+ *
This is used to rank different audio tracks relatively with each other.
+ */
+ private static final class AudioTrackScore implements Comparable {
+ private final Parameters parameters;
+ private final int withinRendererCapabilitiesScore;
+ private final int matchLanguageScore;
+ private final int defaultSelectionFlagScore;
+ private final int channelCount;
+ private final int sampleRate;
+ private final int bitrate;
+
+ public AudioTrackScore(Format format, Parameters parameters, int formatSupport) {
+ this.parameters = parameters;
+ withinRendererCapabilitiesScore = isSupported(formatSupport, false) ? 1 : 0;
+ matchLanguageScore = formatHasLanguage(format, parameters.preferredAudioLanguage) ? 1 : 0;
+ defaultSelectionFlagScore = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0 ? 1 : 0;
+ channelCount = format.channelCount;
+ sampleRate = format.sampleRate;
+ bitrate = format.bitrate;
+ }
+
+ /**
+ * Compares the score of the current track format with another {@link AudioTrackScore}.
+ *
+ * @param other The other score to compare to.
+ * @return A positive integer if this score is better than the other. Zero if they are
+ * equal. A negative integer if this score is worse than the other.
+ */
+ @Override
+ public int compareTo(AudioTrackScore other) {
+ if (this.withinRendererCapabilitiesScore != other.withinRendererCapabilitiesScore) {
+ return compareInts(this.withinRendererCapabilitiesScore,
+ other.withinRendererCapabilitiesScore);
+ } else if (this.matchLanguageScore != other.matchLanguageScore) {
+ return compareInts(this.matchLanguageScore, other.matchLanguageScore);
+ } else if (this.defaultSelectionFlagScore != other.defaultSelectionFlagScore) {
+ return compareInts(this.defaultSelectionFlagScore, other.defaultSelectionFlagScore);
+ } else if (parameters.forceLowestBitrate) {
+ return compareInts(other.bitrate, this.bitrate);
+ } else {
+ // If the format are within renderer capabilities, prefer higher values of channel count,
+ // sample rate and bit rate in that order. Otherwise, prefer lower values.
+ int resultSign = withinRendererCapabilitiesScore == 1 ? 1 : -1;
+ if (this.channelCount != other.channelCount) {
+ return resultSign * compareInts(this.channelCount, other.channelCount);
+ } else if (this.sampleRate != other.sampleRate) {
+ return resultSign * compareInts(this.sampleRate, other.sampleRate);
+ }
+ return resultSign * compareInts(this.bitrate, other.bitrate);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ AudioTrackScore that = (AudioTrackScore) o;
+
+ return withinRendererCapabilitiesScore == that.withinRendererCapabilitiesScore
+ && matchLanguageScore == that.matchLanguageScore
+ && defaultSelectionFlagScore == that.defaultSelectionFlagScore
+ && channelCount == that.channelCount && sampleRate == that.sampleRate
+ && bitrate == that.bitrate;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = withinRendererCapabilitiesScore;
+ result = 31 * result + matchLanguageScore;
+ result = 31 * result + defaultSelectionFlagScore;
+ result = 31 * result + channelCount;
+ result = 31 * result + sampleRate;
+ result = 31 * result + bitrate;
+ return result;
+ }
+ }
+
+ /**
+ * Compares two integers in a safe way and avoiding potential overflow.
+ *
+ * @param first The first value.
+ * @param second The second value.
+ * @return A negative integer if the first value is less than the second. Zero if they are equal.
+ * A positive integer if the first value is greater than the second.
+ */
+ private static int compareInts(int first, int second) {
+ return first > second ? 1 : (second > first ? -1 : 0);
+ }
+
private static final class AudioConfigurationTuple {
public final int channelCount;
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java
new file mode 100644
index 0000000000..a0e499139c
--- /dev/null
+++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java
@@ -0,0 +1,660 @@
+package com.google.android.exoplayer2.trackselection;
+
+import static com.google.android.exoplayer2.RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES;
+import static com.google.android.exoplayer2.RendererCapabilities.FORMAT_HANDLED;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.ExoPlaybackException;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.RendererCapabilities;
+import com.google.android.exoplayer2.source.TrackGroup;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters;
+import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationListener;
+import com.google.android.exoplayer2.util.MimeTypes;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/**
+ * Unit tests for {@link DefaultTrackSelector}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE)
+public final class DefaultTrackSelectorTest {
+
+ private static final Parameters DEFAULT_PARAMETERS = new Parameters();
+ private static final RendererCapabilities ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES =
+ new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO);
+ private static final RendererCapabilities ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES =
+ new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO, FORMAT_EXCEEDS_CAPABILITIES);
+
+ @Mock
+ private InvalidationListener invalidationListener;
+
+ private DefaultTrackSelector trackSelector;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ trackSelector = new DefaultTrackSelector();
+ }
+
+ /**
+ * Tests that track selector will not call
+ * {@link InvalidationListener#onTrackSelectionsInvalidated()} when it's set with default
+ * values of {@link Parameters}.
+ */
+ @Test
+ public void testSetParameterWithDefaultParametersDoesNotNotifyInvalidationListener()
+ throws Exception {
+ trackSelector.init(invalidationListener);
+ trackSelector.setParameters(DEFAULT_PARAMETERS);
+
+ verify(invalidationListener, never()).onTrackSelectionsInvalidated();
+ }
+
+ /**
+ * Tests that track selector will call {@link InvalidationListener#onTrackSelectionsInvalidated()}
+ * when it's set with non-default values of {@link Parameters}.
+ */
+ @Test
+ public void testSetParameterWithNonDefaultParameterNotifyInvalidationListener()
+ throws Exception {
+ Parameters parameters = DEFAULT_PARAMETERS.withPreferredAudioLanguage("en");
+ trackSelector.init(invalidationListener);
+ trackSelector.setParameters(parameters);
+
+ verify(invalidationListener).onTrackSelectionsInvalidated();
+ }
+
+ /**
+ * Tests that track selector will not call
+ * {@link InvalidationListener#onTrackSelectionsInvalidated()} again when it's set with
+ * the same values of {@link Parameters}.
+ */
+ @Test
+ public void testSetParameterWithSameParametersDoesNotNotifyInvalidationListenerAgain()
+ throws Exception {
+ Parameters parameters = DEFAULT_PARAMETERS.withPreferredAudioLanguage("en");
+ trackSelector.init(invalidationListener);
+ trackSelector.setParameters(parameters);
+ trackSelector.setParameters(parameters);
+
+ verify(invalidationListener, times(1)).onTrackSelectionsInvalidated();
+ }
+
+ /**
+ * Tests that track selector will select audio track with {@link C#SELECTION_FLAG_DEFAULT}
+ * given default values of {@link Parameters}.
+ */
+ @Test
+ public void testSelectTracksSelectTrackWithSelectionFlag() throws Exception {
+ Format audioFormat =
+ Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format formatWithSelectionFlag =
+ Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, C.SELECTION_FLAG_DEFAULT, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
+ singleTrackGroup(formatWithSelectionFlag, audioFormat));
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(formatWithSelectionFlag);
+ }
+
+ /**
+ * Tests that track selector will select audio track with language that match preferred language
+ * given by {@link Parameters}.
+ */
+ @Test
+ public void testSelectTracksSelectPreferredAudioLanguage()
+ throws Exception {
+ Parameters parameters = DEFAULT_PARAMETERS.withPreferredAudioLanguage("en");
+ trackSelector.setParameters(parameters);
+
+ Format frAudioFormat =
+ Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, "fr");
+ Format enAudioFormat =
+ Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, "en");
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
+ singleTrackGroup(frAudioFormat, enAudioFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(enAudioFormat);
+ }
+
+ /**
+ * Tests that track selector will prefer selecting audio track with language that match preferred
+ * language given by {@link Parameters} over track with {@link C#SELECTION_FLAG_DEFAULT}.
+ */
+ @Test
+ public void testSelectTracksSelectPreferredAudioLanguageOverSelectionFlag()
+ throws Exception {
+ Parameters parameters = DEFAULT_PARAMETERS.withPreferredAudioLanguage("en");
+ trackSelector.setParameters(parameters);
+
+ Format frAudioFormat =
+ Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, C.SELECTION_FLAG_DEFAULT, "fr");
+ Format enAudioFormat =
+ Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, "en");
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
+ singleTrackGroup(frAudioFormat, enAudioFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(enAudioFormat);
+ }
+
+ /**
+ * Tests that track selector will prefer tracks that are within renderer's capabilities over
+ * track that exceed renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksPreferTrackWithinCapabilities()
+ throws Exception {
+ Format supportedFormat =
+ Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null,
+ Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format exceededFormat =
+ Format.createAudioSampleFormat("exceededFormat", MimeTypes.AUDIO_AAC, null,
+ Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);
+
+ Map mappedCapabilities = new HashMap<>();
+ mappedCapabilities.put(supportedFormat.id, FORMAT_HANDLED);
+ mappedCapabilities.put(exceededFormat.id, FORMAT_EXCEEDS_CAPABILITIES);
+ RendererCapabilities mappedAudioRendererCapabilities =
+ new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {mappedAudioRendererCapabilities},
+ singleTrackGroup(exceededFormat, supportedFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFormat);
+ }
+
+ /**
+ * Tests that track selector will select a track that exceeds the renderer's capabilities when
+ * there are no other choice, given the default {@link Parameters}.
+ */
+ @Test
+ public void testSelectTracksWithNoTrackWithinCapabilitiesSelectExceededCapabilityTrack()
+ throws Exception {
+
+ Format audioFormat =
+ Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES},
+ singleTrackGroup(audioFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(audioFormat);
+ }
+
+ /**
+ * Tests that track selector will return a null track selection for a renderer when
+ * all tracks exceed that renderer's capabilities when {@link Parameters} does not allow
+ * exceeding-capabilities tracks.
+ */
+ @Test
+ public void testSelectTracksWithNoTrackWithinCapabilitiesAndSetByParamsReturnNoSelection()
+ throws Exception {
+ Parameters parameters = DEFAULT_PARAMETERS.withExceedRendererCapabilitiesIfNecessary(false);
+ trackSelector.setParameters(parameters);
+
+ Format audioFormat =
+ Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES},
+ singleTrackGroup(audioFormat));
+
+ assertThat(result.selections.get(0)).isNull();
+ }
+
+ /**
+ * Tests that track selector will prefer tracks that are within renderer's capabilities over
+ * tracks that have {@link C#SELECTION_FLAG_DEFAULT} but exceed renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksPreferTrackWithinCapabilitiesOverSelectionFlag()
+ throws Exception {
+ Format supportedFormat =
+ Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null,
+ Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format exceededWithSelectionFlagFormat =
+ Format.createAudioSampleFormat("exceededFormat", MimeTypes.AUDIO_AAC, null,
+ Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, C.SELECTION_FLAG_DEFAULT, null);
+
+ Map mappedCapabilities = new HashMap<>();
+ mappedCapabilities.put(supportedFormat.id, FORMAT_HANDLED);
+ mappedCapabilities.put(exceededWithSelectionFlagFormat.id, FORMAT_EXCEEDS_CAPABILITIES);
+ RendererCapabilities mappedAudioRendererCapabilities =
+ new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {mappedAudioRendererCapabilities},
+ singleTrackGroup(exceededWithSelectionFlagFormat, supportedFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFormat);
+ }
+
+ /**
+ * Tests that track selector will prefer tracks that are within renderer's capabilities over
+ * track that have language matching preferred audio given by {@link Parameters} but exceed
+ * renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksPreferTrackWithinCapabilitiesOverPreferredLanguage()
+ throws Exception {
+ Parameters parameters = DEFAULT_PARAMETERS.withPreferredAudioLanguage("en");
+ trackSelector.setParameters(parameters);
+
+ Format supportedFrFormat =
+ Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null,
+ Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "fr");
+ Format exceededEnFormat =
+ Format.createAudioSampleFormat("exceededFormat", MimeTypes.AUDIO_AAC, null,
+ Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "en");
+
+ Map mappedCapabilities = new HashMap<>();
+ mappedCapabilities.put(exceededEnFormat.id, FORMAT_EXCEEDS_CAPABILITIES);
+ mappedCapabilities.put(supportedFrFormat.id, FORMAT_HANDLED);
+ RendererCapabilities mappedAudioRendererCapabilities =
+ new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {mappedAudioRendererCapabilities},
+ singleTrackGroup(exceededEnFormat, supportedFrFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFrFormat);
+ }
+
+ /**
+ * Tests that track selector will prefer tracks that are within renderer's capabilities over
+ * track that have both language matching preferred audio given by {@link Parameters} and
+ * {@link C#SELECTION_FLAG_DEFAULT}, but exceed renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksPreferTrackWithinCapabilitiesOverSelectionFlagAndPreferredLanguage()
+ throws Exception {
+ Parameters parameters = DEFAULT_PARAMETERS.withPreferredAudioLanguage("en");
+ trackSelector.setParameters(parameters);
+
+ Format supportedFrFormat =
+ Format.createAudioSampleFormat("supportedFormat", MimeTypes.AUDIO_AAC, null,
+ Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, "fr");
+ Format exceededDefaultSelectionEnFormat =
+ Format.createAudioSampleFormat("exceededFormat", MimeTypes.AUDIO_AAC, null,
+ Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, C.SELECTION_FLAG_DEFAULT, "en");
+
+ Map mappedCapabilities = new HashMap<>();
+ mappedCapabilities.put(exceededDefaultSelectionEnFormat.id, FORMAT_EXCEEDS_CAPABILITIES);
+ mappedCapabilities.put(supportedFrFormat.id, FORMAT_HANDLED);
+ RendererCapabilities mappedAudioRendererCapabilities =
+ new FakeMappedRendererCapabilities(C.TRACK_TYPE_AUDIO, mappedCapabilities);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {mappedAudioRendererCapabilities},
+ singleTrackGroup(exceededDefaultSelectionEnFormat, supportedFrFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(supportedFrFormat);
+ }
+
+ /**
+ * Tests that track selector will select audio tracks with higher num channel when other factors
+ * are the same, and tracks are within renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksWithinCapabilitiesSelectHigherNumChannel()
+ throws Exception {
+ Format lowerChannelFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format higherChannelFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 6, 44100, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
+ singleTrackGroup(higherChannelFormat, lowerChannelFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherChannelFormat);
+ }
+
+ /**
+ * Tests that track selector will select audio tracks with higher sample rate when other factors
+ * are the same, and tracks are within renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksWithinCapabilitiesSelectHigherSampleRate()
+ throws Exception {
+ Format higherSampleRateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format lowerSampleRateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 22050, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
+ singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherSampleRateFormat);
+ }
+
+ /**
+ * Tests that track selector will select audio tracks with higher bit-rate when other factors
+ * are the same, and tracks are within renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksWithinCapabilitiesSelectHigherBitrate()
+ throws Exception {
+ Format lowerBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format higherBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
+ singleTrackGroup(lowerBitrateFormat, higherBitrateFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(higherBitrateFormat);
+ }
+
+ /**
+ * Tests that track selector will prefer audio tracks with higher channel count over tracks with
+ * higher sample rate when other factors are the same, and tracks are within renderer's
+ * capabilities.
+ */
+ @Test
+ public void testSelectTracksPreferHigherNumChannelBeforeSampleRate()
+ throws Exception {
+ Format lowerChannelHigherSampleRateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format higherChannelLowerSampleRateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 6, 22050, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
+ singleTrackGroup(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat())
+ .isEqualTo(higherChannelLowerSampleRateFormat);
+ }
+
+ /**
+ * Tests that track selector will prefer audio tracks with higher sample rate over tracks with
+ * higher bitrate when other factors are the same, and tracks are within renderer's
+ * capabilities.
+ */
+ @Test
+ public void testSelectTracksPreferHigherSampleRateBeforeBitrate()
+ throws Exception {
+ Format higherSampleRateLowerBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format lowerSampleRateHigherBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000,
+ Format.NO_VALUE, 2, 22050, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
+ singleTrackGroup(higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat())
+ .isEqualTo(higherSampleRateLowerBitrateFormat);
+ }
+
+ /**
+ * Tests that track selector will select audio tracks with lower num channel when other factors
+ * are the same, and tracks exceed renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksExceedingCapabilitiesSelectLowerNumChannel()
+ throws Exception {
+ Format lowerChannelFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format higherChannelFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 6, 44100, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES},
+ singleTrackGroup(higherChannelFormat, lowerChannelFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerChannelFormat);
+ }
+
+ /**
+ * Tests that track selector will select audio tracks with lower sample rate when other factors
+ * are the same, and tracks exceed renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksExceedingCapabilitiesSelectLowerSampleRate()
+ throws Exception {
+ Format lowerSampleRateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 22050, null, null, 0, null);
+ Format higherSampleRateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES},
+ singleTrackGroup(higherSampleRateFormat, lowerSampleRateFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerSampleRateFormat);
+ }
+
+ /**
+ * Tests that track selector will select audio tracks with lower bit-rate when other factors
+ * are the same, and tracks exceed renderer's capabilities.
+ */
+ @Test
+ public void testSelectTracksExceedingCapabilitiesSelectLowerBitrate()
+ throws Exception {
+ Format lowerBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format higherBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES},
+ singleTrackGroup(lowerBitrateFormat, higherBitrateFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat);
+ }
+
+ /**
+ * Tests that track selector will prefer audio tracks with lower channel count over tracks with
+ * lower sample rate when other factors are the same, and tracks are within renderer's
+ * capabilities.
+ */
+ @Test
+ public void testSelectTracksExceedingCapabilitiesPreferLowerNumChannelBeforeSampleRate()
+ throws Exception {
+ Format lowerChannelHigherSampleRateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format higherChannelLowerSampleRateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
+ Format.NO_VALUE, 6, 22050, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES},
+ singleTrackGroup(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat())
+ .isEqualTo(lowerChannelHigherSampleRateFormat);
+ }
+
+ /**
+ * Tests that track selector will prefer audio tracks with lower sample rate over tracks with
+ * lower bitrate when other factors are the same, and tracks are within renderer's
+ * capabilities.
+ */
+ @Test
+ public void testSelectTracksExceedingCapabilitiesPreferLowerSampleRateBeforeBitrate()
+ throws Exception {
+ Format higherSampleRateLowerBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format lowerSampleRateHigherBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000,
+ Format.NO_VALUE, 2, 22050, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_EXCEEDED_RENDERER_CAPABILITIES},
+ singleTrackGroup(higherSampleRateLowerBitrateFormat, lowerSampleRateHigherBitrateFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat())
+ .isEqualTo(lowerSampleRateHigherBitrateFormat);
+ }
+
+ /**
+ * Tests that track selector will select audio tracks with lower bitrate when {@link Parameters}
+ * indicate lowest bitrate preference, even when tracks are within capabilities.
+ */
+ @Test
+ public void testSelectTracksWithinCapabilitiesAndForceLowestBitrateSelectLowerBitrate()
+ throws Exception {
+ Parameters parameters = DEFAULT_PARAMETERS.withForceLowestBitrate(true);
+ trackSelector.setParameters(parameters);
+
+ Format lowerBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 15000,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+ Format higherBitrateFormat =
+ Format.createAudioSampleFormat("audioFormat", MimeTypes.AUDIO_AAC, null, 30000,
+ Format.NO_VALUE, 2, 44100, null, null, 0, null);
+
+ TrackSelectorResult result = trackSelector.selectTracks(
+ new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES},
+ singleTrackGroup(lowerBitrateFormat, higherBitrateFormat));
+
+ assertThat(result.selections.get(0).getSelectedFormat()).isEqualTo(lowerBitrateFormat);
+ }
+
+ private static TrackGroupArray singleTrackGroup(Format... formats) {
+ return new TrackGroupArray(new TrackGroup(formats));
+ }
+
+ /**
+ * A {@link RendererCapabilities} that advertises support for all formats of a given type using
+ * a provided support value. For any format that does not have the given track type,
+ * {@link #supportsFormat(Format)} will return {@link #FORMAT_UNSUPPORTED_TYPE}.
+ */
+ private static final class FakeRendererCapabilities implements RendererCapabilities {
+
+ private final int trackType;
+ private final int supportValue;
+
+ /**
+ * Returns {@link FakeRendererCapabilities} that advertises adaptive support for all
+ * tracks of the given type.
+ *
+ * @param trackType the track type of all formats that this renderer capabilities advertises
+ * support for.
+ */
+ FakeRendererCapabilities(int trackType) {
+ this(trackType, FORMAT_HANDLED | ADAPTIVE_SEAMLESS);
+ }
+
+ /**
+ * Returns {@link FakeRendererCapabilities} that advertises support level using given value
+ * for all tracks of the given type.
+ *
+ * @param trackType the track type of all formats that this renderer capabilities advertises
+ * support for.
+ * @param supportValue the support level value that will be returned for formats with
+ * the given type.
+ */
+ FakeRendererCapabilities(int trackType, int supportValue) {
+ this.trackType = trackType;
+ this.supportValue = supportValue;
+ }
+
+ @Override
+ public int getTrackType() {
+ return trackType;
+ }
+
+ @Override
+ public int supportsFormat(Format format) throws ExoPlaybackException {
+ return MimeTypes.getTrackType(format.sampleMimeType) == trackType
+ ? (supportValue) : FORMAT_UNSUPPORTED_TYPE;
+ }
+
+ @Override
+ public int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
+ return ADAPTIVE_SEAMLESS;
+ }
+
+ }
+
+ /**
+ * A {@link RendererCapabilities} that advertises support for different formats using a mapping
+ * between format ID and format-support value.
+ */
+ private static final class FakeMappedRendererCapabilities implements RendererCapabilities {
+
+ private final int trackType;
+ private final Map formatToCapability;
+
+ /**
+ * Returns {@link FakeRendererCapabilities} that advertises support level using the given
+ * mapping between format ID and format-support value.
+ *
+ * @param trackType the track type to be returned for {@link #getTrackType()}
+ * @param formatToCapability a map of (format id, support level) that will be used to return
+ * support level for any given format. For any format that's not in the map,
+ * {@link #supportsFormat(Format)} will return {@link #FORMAT_UNSUPPORTED_TYPE}.
+ */
+ FakeMappedRendererCapabilities(int trackType, Map formatToCapability) {
+ this.trackType = trackType;
+ this.formatToCapability = new HashMap<>(formatToCapability);
+ }
+
+ @Override
+ public int getTrackType() {
+ return trackType;
+ }
+
+ @Override
+ public int supportsFormat(Format format) throws ExoPlaybackException {
+ return format.id != null && formatToCapability.containsKey(format.id)
+ ? formatToCapability.get(format.id)
+ : FORMAT_UNSUPPORTED_TYPE;
+ }
+
+ @Override
+ public int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
+ return ADAPTIVE_SEAMLESS;
+ }
+
+ }
+
+}