DefaultTrackSelector: Constrain audio channel count

The track selector will select multi-channel formats when those can be
spatialized, otherwise the selector will prefer stereo/mono audio
tracks. When the device supports audio spatialization (Android 12L+),
the DefaultTrackSelector will monitor for changes in the platform
Spatializer and trigger a new track selection upon a
Spatializer change event.

Devices with a `television` UI mode are excluded from audio channel
count constraints.

#minor-release

PiperOrigin-RevId: 453957269
(cherry picked from commit e2f0fd76730fd4042e8b2226300e5173b0179dc1)
This commit is contained in:
christosts 2022-06-09 17:27:12 +00:00 committed by Marc Baechinger
parent 74fbf0171f
commit 0fd24c2fa3
6 changed files with 514 additions and 49 deletions

View File

@ -39,6 +39,24 @@
`DefaultTrackSelector.Parameters.buildUpon` to return `DefaultTrackSelector.Parameters.buildUpon` to return
`DefaultTrackSelector.Parameters.Builder` instead of the deprecated `DefaultTrackSelector.Parameters.Builder` instead of the deprecated
`DefaultTrackSelector.ParametersBuilder`. `DefaultTrackSelector.ParametersBuilder`.
* Add
`DefaultTrackSelector.Parameters.constrainAudioChannelCountToDeviceCapabilities`.
which is enabled by default. When enabled, the `DefaultTrackSelector`
will prefer audio tracks whose channel count does not exceed the device
output capabilities. On handheld devices, the `DefaultTrackSelector`
will prefer stereo/mono over multichannel audio formats, unless the
multichannel format can be
[Spatialized](https://developer.android.com/reference/android/media/Spatializer)
(Android 12L+) or is a Dolby surround sound format. In addition, on
devices that support audio spatialization, the `DefaultTrackSelector`
will monitor for changes in the
[Spatializer properties](https://developer.android.com/reference/android/media/Spatializer.OnSpatializerStateChangedListener)
and trigger a new track selection upon these. Devices with a
`television`
[UI mode](https://developer.android.com/guide/topics/resources/providing-resources#UiModeQualifier)
are excluded from these constraints and the format with the highest
channel count will be preferred. To enable this feature, the
`DefaultTrackSelector` instance must be constructed with a `Context`.
* Video: * Video:
* Rename `DummySurface` to `PlaceholderSurface`. * Rename `DummySurface` to `PlaceholderSurface`.
* Add AV1 support to the `MediaCodecVideoRenderer.getCodecMaxInputSize`. * Add AV1 support to the `MediaCodecVideoRenderer.getCodecMaxInputSize`.
@ -171,6 +189,8 @@
`DEFAULT_TRACK_SELECTOR_PARAMETERS` constants. Use `DEFAULT_TRACK_SELECTOR_PARAMETERS` constants. Use
`getDefaultTrackSelectorParameters(Context)` instead when possible, and `getDefaultTrackSelectorParameters(Context)` instead when possible, and
`DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT` otherwise. `DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT` otherwise.
* Remove constructor `DefaultTrackSelector(ExoTrackSelection.Factory)`.
Use `DefaultTrackSelector(Context, ExoTrackSelection.Factory)` instead.
### 1.0.0-alpha03 (2022-03-14) ### 1.0.0-alpha03 (2022-03-14)

View File

@ -380,6 +380,7 @@ import java.util.concurrent.TimeoutException;
deviceInfo = createDeviceInfo(streamVolumeManager); deviceInfo = createDeviceInfo(streamVolumeManager);
videoSize = VideoSize.UNKNOWN; videoSize = VideoSize.UNKNOWN;
trackSelector.setAudioAttributes(audioAttributes);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId); sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_AUDIO_SESSION_ID, audioSessionId);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes); sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes);
@ -1375,6 +1376,7 @@ import java.util.concurrent.TimeoutException;
} }
audioFocusManager.setAudioAttributes(handleAudioFocus ? newAudioAttributes : null); audioFocusManager.setAudioAttributes(handleAudioFocus ? newAudioAttributes : null);
trackSelector.setAudioAttributes(newAudioAttributes);
boolean playWhenReady = getPlayWhenReady(); boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand @AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());

View File

@ -110,6 +110,7 @@ public final class DownloadHelper {
DefaultTrackSelector.Parameters.DEFAULT_WITHOUT_CONTEXT DefaultTrackSelector.Parameters.DEFAULT_WITHOUT_CONTEXT
.buildUpon() .buildUpon()
.setForceHighestSupportedBitrate(true) .setForceHighestSupportedBitrate(true)
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.build(); .build();
/** Returns the default parameters used for track selection for downloading. */ /** Returns the default parameters used for track selection for downloading. */
@ -117,6 +118,7 @@ public final class DownloadHelper {
return DefaultTrackSelector.Parameters.getDefaults(context) return DefaultTrackSelector.Parameters.getDefaults(context)
.buildUpon() .buildUpon()
.setForceHighestSupportedBitrate(true) .setForceHighestSupportedBitrate(true)
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.build(); .build();
} }

View File

@ -15,19 +15,29 @@
*/ */
package androidx.media3.exoplayer.trackselection; package androidx.media3.exoplayer.trackselection;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.castNonNull;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import static java.util.Collections.max; import static java.util.Collections.max;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.Point; import android.graphics.Point;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.Spatializer;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair; import android.util.Pair;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.Bundleable; import androidx.media3.common.Bundleable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.C.FormatSupport; import androidx.media3.common.C.FormatSupport;
@ -40,6 +50,7 @@ import androidx.media3.common.TrackSelectionOverride;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.BundleableUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.ExoPlaybackException; import androidx.media3.exoplayer.ExoPlaybackException;
@ -50,6 +61,7 @@ import androidx.media3.exoplayer.RendererCapabilities.Capabilities;
import androidx.media3.exoplayer.RendererConfiguration; import androidx.media3.exoplayer.RendererConfiguration;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.source.TrackGroupArray;
import com.google.common.base.Predicate;
import com.google.common.collect.ComparisonChain; import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
@ -65,7 +77,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
/** /**
@ -101,6 +112,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@UnstableApi @UnstableApi
public class DefaultTrackSelector extends MappingTrackSelector { public class DefaultTrackSelector extends MappingTrackSelector {
private static final String TAG = "DefaultTrackSelector";
private static final String AUDIO_CHANNEL_COUNT_CONSTRAINTS_WARN_MESSAGE =
"Audio channel count constraints cannot be applied without reference to Context. Build the"
+ " track selector instance with one of the non-deprecated constructors that take a"
+ " Context argument.";
/** /**
* @deprecated Use {@link Parameters.Builder} instead. * @deprecated Use {@link Parameters.Builder} instead.
*/ */
@ -680,6 +697,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private boolean allowAudioMixedSampleRateAdaptiveness; private boolean allowAudioMixedSampleRateAdaptiveness;
private boolean allowAudioMixedChannelCountAdaptiveness; private boolean allowAudioMixedChannelCountAdaptiveness;
private boolean allowAudioMixedDecoderSupportAdaptiveness; private boolean allowAudioMixedDecoderSupportAdaptiveness;
private boolean constrainAudioChannelCountToDeviceCapabilities;
// General // General
private boolean exceedRendererCapabilitiesIfNecessary; private boolean exceedRendererCapabilitiesIfNecessary;
private boolean tunnelingEnabled; private boolean tunnelingEnabled;
@ -734,6 +752,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
initialValues.allowAudioMixedChannelCountAdaptiveness; initialValues.allowAudioMixedChannelCountAdaptiveness;
allowAudioMixedDecoderSupportAdaptiveness = allowAudioMixedDecoderSupportAdaptiveness =
initialValues.allowAudioMixedDecoderSupportAdaptiveness; initialValues.allowAudioMixedDecoderSupportAdaptiveness;
constrainAudioChannelCountToDeviceCapabilities =
initialValues.constrainAudioChannelCountToDeviceCapabilities;
// General // General
exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary; exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary;
tunnelingEnabled = initialValues.tunnelingEnabled; tunnelingEnabled = initialValues.tunnelingEnabled;
@ -746,6 +766,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
@SuppressWarnings("method.invocation") // Only setter are invoked. @SuppressWarnings("method.invocation") // Only setter are invoked.
private Builder(Bundle bundle) { private Builder(Bundle bundle) {
super(bundle); super(bundle);
init();
Parameters defaultValue = Parameters.DEFAULT_WITHOUT_CONTEXT; Parameters defaultValue = Parameters.DEFAULT_WITHOUT_CONTEXT;
// Video // Video
setExceedVideoConstraintsIfNecessary( setExceedVideoConstraintsIfNecessary(
@ -788,6 +809,11 @@ public class DefaultTrackSelector extends MappingTrackSelector {
Parameters.keyForField( Parameters.keyForField(
Parameters.FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), Parameters.FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS),
defaultValue.allowAudioMixedDecoderSupportAdaptiveness)); defaultValue.allowAudioMixedDecoderSupportAdaptiveness));
setConstrainAudioChannelCountToDeviceCapabilities(
bundle.getBoolean(
Parameters.keyForField(
Parameters.FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES),
defaultValue.constrainAudioChannelCountToDeviceCapabilities));
// General // General
setExceedRendererCapabilitiesIfNecessary( setExceedRendererCapabilitiesIfNecessary(
bundle.getBoolean( bundle.getBoolean(
@ -1082,6 +1108,36 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return this; return this;
} }
/**
* Whether to only select audio tracks with channel counts that don't exceed the device's
* output capabilities. The default value is {@code true}.
*
* <p>When enabled, the track selector will prefer stereo/mono audio tracks over multichannel
* if the audio cannot be spatialized or the device is outputting stereo audio. For example,
* on a mobile device that outputs non-spatialized audio to its speakers. Dolby surround sound
* formats are excluded from these constraints because some Dolby decoders are known to
* spatialize multichannel audio on Android OS versions that don't support the {@link
* Spatializer} API.
*
* <p>For devices with Android 12L+ that support {@linkplain Spatializer audio
* spatialization}, when this is enabled the track selector will trigger a new track selection
* everytime a change in {@linkplain Spatializer.OnSpatializerStateChangedListener
* spatialization properties} is detected.
*
* <p>The constraints do not apply on devices with <a
* href="https://developer.android.com/guide/topics/resources/providing-resources#UiModeQualifier">{@code
* television} UI mode</a>.
*
* <p>The constraints do not apply when the track selector is created without a reference to a
* {@link Context} via the deprecated {@link
* DefaultTrackSelector#DefaultTrackSelector(TrackSelectionParameters,
* ExoTrackSelection.Factory)} constructor.
*/
public Builder setConstrainAudioChannelCountToDeviceCapabilities(boolean enabled) {
constrainAudioChannelCountToDeviceCapabilities = enabled;
return this;
}
// Text // Text
@Override @Override
@ -1381,6 +1437,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
allowAudioMixedSampleRateAdaptiveness = false; allowAudioMixedSampleRateAdaptiveness = false;
allowAudioMixedChannelCountAdaptiveness = false; allowAudioMixedChannelCountAdaptiveness = false;
allowAudioMixedDecoderSupportAdaptiveness = false; allowAudioMixedDecoderSupportAdaptiveness = false;
constrainAudioChannelCountToDeviceCapabilities = true;
// General // General
exceedRendererCapabilitiesIfNecessary = true; exceedRendererCapabilitiesIfNecessary = true;
tunnelingEnabled = false; tunnelingEnabled = false;
@ -1475,6 +1532,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
// Video // Video
/** /**
* Whether to exceed the {@link #maxVideoWidth}, {@link #maxVideoHeight} and {@link * Whether to exceed the {@link #maxVideoWidth}, {@link #maxVideoHeight} and {@link
* #maxVideoBitrate} constraints when no selection can be made otherwise. The default value is * #maxVideoBitrate} constraints when no selection can be made otherwise. The default value is
@ -1499,6 +1557,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* RendererCapabilities.HardwareAccelerationSupport}. * RendererCapabilities.HardwareAccelerationSupport}.
*/ */
public final boolean allowVideoMixedDecoderSupportAdaptiveness; public final boolean allowVideoMixedDecoderSupportAdaptiveness;
// Audio
/** /**
* Whether to exceed the {@link #maxAudioChannelCount} and {@link #maxAudioBitrate} constraints * Whether to exceed the {@link #maxAudioChannelCount} and {@link #maxAudioBitrate} constraints
* when no selection can be made otherwise. The default value is {@code true}. * when no selection can be made otherwise. The default value is {@code true}.
@ -1526,6 +1587,14 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* RendererCapabilities.HardwareAccelerationSupport}. * RendererCapabilities.HardwareAccelerationSupport}.
*/ */
public final boolean allowAudioMixedDecoderSupportAdaptiveness; public final boolean allowAudioMixedDecoderSupportAdaptiveness;
/**
* Whether to constrain audio track selection so that the selected track's channel count does
* not exceed the device's output capabilities. The default value is {@code true}.
*/
public final boolean constrainAudioChannelCountToDeviceCapabilities;
// General
/** /**
* Whether to exceed renderer capabilities when no selection can be made otherwise. * Whether to exceed renderer capabilities when no selection can be made otherwise.
* *
@ -1566,6 +1635,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
allowAudioMixedSampleRateAdaptiveness = builder.allowAudioMixedSampleRateAdaptiveness; allowAudioMixedSampleRateAdaptiveness = builder.allowAudioMixedSampleRateAdaptiveness;
allowAudioMixedChannelCountAdaptiveness = builder.allowAudioMixedChannelCountAdaptiveness; allowAudioMixedChannelCountAdaptiveness = builder.allowAudioMixedChannelCountAdaptiveness;
allowAudioMixedDecoderSupportAdaptiveness = builder.allowAudioMixedDecoderSupportAdaptiveness; allowAudioMixedDecoderSupportAdaptiveness = builder.allowAudioMixedDecoderSupportAdaptiveness;
constrainAudioChannelCountToDeviceCapabilities =
builder.constrainAudioChannelCountToDeviceCapabilities;
// General // General
exceedRendererCapabilitiesIfNecessary = builder.exceedRendererCapabilitiesIfNecessary; exceedRendererCapabilitiesIfNecessary = builder.exceedRendererCapabilitiesIfNecessary;
tunnelingEnabled = builder.tunnelingEnabled; tunnelingEnabled = builder.tunnelingEnabled;
@ -1654,6 +1725,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
== other.allowAudioMixedChannelCountAdaptiveness == other.allowAudioMixedChannelCountAdaptiveness
&& allowAudioMixedDecoderSupportAdaptiveness && allowAudioMixedDecoderSupportAdaptiveness
== other.allowAudioMixedDecoderSupportAdaptiveness == other.allowAudioMixedDecoderSupportAdaptiveness
&& constrainAudioChannelCountToDeviceCapabilities
== other.constrainAudioChannelCountToDeviceCapabilities
// General // General
&& exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary && exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary
&& tunnelingEnabled == other.tunnelingEnabled && tunnelingEnabled == other.tunnelingEnabled
@ -1678,6 +1751,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
result = 31 * result + (allowAudioMixedSampleRateAdaptiveness ? 1 : 0); result = 31 * result + (allowAudioMixedSampleRateAdaptiveness ? 1 : 0);
result = 31 * result + (allowAudioMixedChannelCountAdaptiveness ? 1 : 0); result = 31 * result + (allowAudioMixedChannelCountAdaptiveness ? 1 : 0);
result = 31 * result + (allowAudioMixedDecoderSupportAdaptiveness ? 1 : 0); result = 31 * result + (allowAudioMixedDecoderSupportAdaptiveness ? 1 : 0);
result = 31 * result + (constrainAudioChannelCountToDeviceCapabilities ? 1 : 0);
// General // General
result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0); result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0);
result = 31 * result + (tunnelingEnabled ? 1 : 0); result = 31 * result + (tunnelingEnabled ? 1 : 0);
@ -1712,6 +1786,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
FIELD_CUSTOM_ID_BASE + 14; FIELD_CUSTOM_ID_BASE + 14;
private static final int FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS = private static final int FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS =
FIELD_CUSTOM_ID_BASE + 15; FIELD_CUSTOM_ID_BASE + 15;
private static final int FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES =
FIELD_CUSTOM_ID_BASE + 16;
@Override @Override
public Bundle toBundle() { public Bundle toBundle() {
@ -1746,6 +1822,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
bundle.putBoolean( bundle.putBoolean(
keyForField(FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS), keyForField(FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS),
allowAudioMixedDecoderSupportAdaptiveness); allowAudioMixedDecoderSupportAdaptiveness);
bundle.putBoolean(
keyForField(FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES),
constrainAudioChannelCountToDeviceCapabilities);
// General // General
bundle.putBoolean( bundle.putBoolean(
keyForField(FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY), keyForField(FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY),
@ -2004,8 +2083,20 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/** Ordering where all elements are equal. */ /** Ordering where all elements are equal. */
private static final Ordering<Integer> NO_ORDER = Ordering.from((first, second) -> 0); private static final Ordering<Integer> NO_ORDER = Ordering.from((first, second) -> 0);
private final Object lock;
@Nullable public final Context context;
private final ExoTrackSelection.Factory trackSelectionFactory; private final ExoTrackSelection.Factory trackSelectionFactory;
private final AtomicReference<Parameters> parametersReference; private final boolean deviceIsTV;
@GuardedBy("lock")
private Parameters parameters;
@GuardedBy("lock")
@Nullable
private SpatializerWrapperV32 spatializer;
@GuardedBy("lock")
private AudioAttributes audioAttributes;
/** /**
* @deprecated Use {@link #DefaultTrackSelector(Context)} instead. * @deprecated Use {@link #DefaultTrackSelector(Context)} instead.
@ -2015,14 +2106,6 @@ public class DefaultTrackSelector extends MappingTrackSelector {
this(Parameters.DEFAULT_WITHOUT_CONTEXT, new AdaptiveTrackSelection.Factory()); this(Parameters.DEFAULT_WITHOUT_CONTEXT, new AdaptiveTrackSelection.Factory());
} }
/**
* @deprecated Use {@link #DefaultTrackSelector(Context, ExoTrackSelection.Factory)}.
*/
@Deprecated
public DefaultTrackSelector(ExoTrackSelection.Factory trackSelectionFactory) {
this(Parameters.DEFAULT_WITHOUT_CONTEXT, trackSelectionFactory);
}
/** /**
* @param context Any {@link Context}. * @param context Any {@link Context}.
*/ */
@ -2035,26 +2118,88 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @param trackSelectionFactory A factory for {@link ExoTrackSelection}s. * @param trackSelectionFactory A factory for {@link ExoTrackSelection}s.
*/ */
public DefaultTrackSelector(Context context, ExoTrackSelection.Factory trackSelectionFactory) { public DefaultTrackSelector(Context context, ExoTrackSelection.Factory trackSelectionFactory) {
this(Parameters.getDefaults(context), trackSelectionFactory); this(context, Parameters.getDefaults(context), trackSelectionFactory);
} }
/** /**
* @param context Any {@link Context}.
* @param parameters Initial {@link TrackSelectionParameters}.
*/
public DefaultTrackSelector(Context context, TrackSelectionParameters parameters) {
this(context, parameters, new AdaptiveTrackSelection.Factory());
}
/**
* @deprecated Use {@link #DefaultTrackSelector(Context, TrackSelectionParameters,
* ExoTrackSelection.Factory)}
*/
@Deprecated
public DefaultTrackSelector(
TrackSelectionParameters parameters, ExoTrackSelection.Factory trackSelectionFactory) {
this(parameters, trackSelectionFactory, /* context= */ null);
}
/**
* @param context Any {@link Context}.
* @param parameters Initial {@link TrackSelectionParameters}. * @param parameters Initial {@link TrackSelectionParameters}.
* @param trackSelectionFactory A factory for {@link ExoTrackSelection}s. * @param trackSelectionFactory A factory for {@link ExoTrackSelection}s.
*/ */
public DefaultTrackSelector( public DefaultTrackSelector(
TrackSelectionParameters parameters, ExoTrackSelection.Factory trackSelectionFactory) { Context context,
TrackSelectionParameters parameters,
ExoTrackSelection.Factory trackSelectionFactory) {
this(parameters, trackSelectionFactory, context);
}
/**
* Exists for backwards compatibility so that the deprecated constructor {@link
* #DefaultTrackSelector(TrackSelectionParameters, ExoTrackSelection.Factory)} can initialize
* {@code context} with {@code null} while we don't have a public constructor with a {@code
* Nullable context}.
*
* @param context Any {@link Context}.
* @param parameters Initial {@link TrackSelectionParameters}.
* @param trackSelectionFactory A factory for {@link ExoTrackSelection}s.
*/
private DefaultTrackSelector(
TrackSelectionParameters parameters,
ExoTrackSelection.Factory trackSelectionFactory,
@Nullable Context context) {
this.lock = new Object();
this.context = context != null ? context.getApplicationContext() : null;
this.trackSelectionFactory = trackSelectionFactory; this.trackSelectionFactory = trackSelectionFactory;
parametersReference = if (parameters instanceof Parameters) {
new AtomicReference<>( this.parameters = (Parameters) parameters;
parameters instanceof Parameters } else {
? (Parameters) parameters Parameters defaultParameters =
: Parameters.DEFAULT_WITHOUT_CONTEXT.buildUpon().set(parameters).build()); context == null ? Parameters.DEFAULT_WITHOUT_CONTEXT : Parameters.getDefaults(context);
this.parameters = defaultParameters.buildUpon().set(parameters).build();
}
this.audioAttributes = AudioAttributes.DEFAULT;
this.deviceIsTV = context != null && Util.isTv(context);
if (!deviceIsTV && context != null && Util.SDK_INT >= 32) {
spatializer = SpatializerWrapperV32.tryCreateInstance(context);
}
if (this.parameters.constrainAudioChannelCountToDeviceCapabilities && context == null) {
Log.w(TAG, AUDIO_CHANNEL_COUNT_CONSTRAINTS_WARN_MESSAGE);
}
}
@Override
public void release() {
synchronized (lock) {
if (Util.SDK_INT >= 32 && spatializer != null) {
spatializer.release();
}
}
super.release();
} }
@Override @Override
public Parameters getParameters() { public Parameters getParameters() {
return parametersReference.get(); synchronized (lock) {
return parameters;
}
} }
@Override @Override
@ -2068,11 +2213,22 @@ public class DefaultTrackSelector extends MappingTrackSelector {
setParametersInternal((Parameters) parameters); setParametersInternal((Parameters) parameters);
} }
// Only add the fields of `TrackSelectionParameters` to `parameters`. // Only add the fields of `TrackSelectionParameters` to `parameters`.
Parameters mergedParameters = Parameters mergedParameters = new Parameters.Builder(getParameters()).set(parameters).build();
new Parameters.Builder(parametersReference.get()).set(parameters).build();
setParametersInternal(mergedParameters); setParametersInternal(mergedParameters);
} }
@Override
public void setAudioAttributes(AudioAttributes audioAttributes) {
boolean audioAttributesChanged;
synchronized (lock) {
audioAttributesChanged = !this.audioAttributes.equals(audioAttributes);
this.audioAttributes = audioAttributes;
}
if (audioAttributesChanged) {
maybeInvalidateForAudioChannelCountConstraints();
}
}
/** /**
* @deprecated Use {@link #setParameters(Parameters.Builder)} instead. * @deprecated Use {@link #setParameters(Parameters.Builder)} instead.
*/ */
@ -2103,7 +2259,16 @@ public class DefaultTrackSelector extends MappingTrackSelector {
*/ */
private void setParametersInternal(Parameters parameters) { private void setParametersInternal(Parameters parameters) {
Assertions.checkNotNull(parameters); Assertions.checkNotNull(parameters);
if (!parametersReference.getAndSet(parameters).equals(parameters)) { boolean parametersChanged;
synchronized (lock) {
parametersChanged = !this.parameters.equals(parameters);
this.parameters = parameters;
}
if (parametersChanged) {
if (parameters.constrainAudioChannelCountToDeviceCapabilities && context == null) {
Log.w(TAG, AUDIO_CHANNEL_COUNT_CONSTRAINTS_WARN_MESSAGE);
}
invalidate(); invalidate();
} }
} }
@ -2119,22 +2284,33 @@ public class DefaultTrackSelector extends MappingTrackSelector {
MediaPeriodId mediaPeriodId, MediaPeriodId mediaPeriodId,
Timeline timeline) Timeline timeline)
throws ExoPlaybackException { throws ExoPlaybackException {
Parameters params = parametersReference.get(); Parameters parameters;
synchronized (lock) {
parameters = this.parameters;
if (parameters.constrainAudioChannelCountToDeviceCapabilities
&& Util.SDK_INT >= 32
&& spatializer != null) {
// Initialize the spatializer now so we can get a reference to the playback looper with
// Looper.myLooper().
spatializer.ensureInitialized(this, checkStateNotNull(Looper.myLooper()));
}
}
int rendererCount = mappedTrackInfo.getRendererCount(); int rendererCount = mappedTrackInfo.getRendererCount();
ExoTrackSelection.@NullableType Definition[] definitions = ExoTrackSelection.@NullableType Definition[] definitions =
selectAllTracks( selectAllTracks(
mappedTrackInfo, mappedTrackInfo,
rendererFormatSupports, rendererFormatSupports,
rendererMixedMimeTypeAdaptationSupports, rendererMixedMimeTypeAdaptationSupports,
params); parameters);
applyTrackSelectionOverrides(mappedTrackInfo, params, definitions); applyTrackSelectionOverrides(mappedTrackInfo, parameters, definitions);
applyLegacyRendererOverrides(mappedTrackInfo, params, definitions); applyLegacyRendererOverrides(mappedTrackInfo, parameters, definitions);
// Disable renderers if needed. // Disable renderers if needed.
for (int i = 0; i < rendererCount; i++) { for (int i = 0; i < rendererCount; i++) {
@C.TrackType int rendererType = mappedTrackInfo.getRendererType(i); @C.TrackType int rendererType = mappedTrackInfo.getRendererType(i);
if (params.getRendererDisabled(i) || params.disabledTrackTypes.contains(rendererType)) { if (parameters.getRendererDisabled(i)
|| parameters.disabledTrackTypes.contains(rendererType)) {
definitions[i] = null; definitions[i] = null;
} }
} }
@ -2151,7 +2327,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int i = 0; i < rendererCount; i++) { for (int i = 0; i < rendererCount; i++) {
@C.TrackType int rendererType = mappedTrackInfo.getRendererType(i); @C.TrackType int rendererType = mappedTrackInfo.getRendererType(i);
boolean forceRendererDisabled = boolean forceRendererDisabled =
params.getRendererDisabled(i) || params.disabledTrackTypes.contains(rendererType); parameters.getRendererDisabled(i) || parameters.disabledTrackTypes.contains(rendererType);
boolean rendererEnabled = boolean rendererEnabled =
!forceRendererDisabled !forceRendererDisabled
&& (mappedTrackInfo.getRendererType(i) == C.TRACK_TYPE_NONE && (mappedTrackInfo.getRendererType(i) == C.TRACK_TYPE_NONE
@ -2160,7 +2336,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
// Configure audio and video renderers to use tunneling if appropriate. // Configure audio and video renderers to use tunneling if appropriate.
if (params.tunnelingEnabled) { if (parameters.tunnelingEnabled) {
maybeConfigureRenderersForTunneling( maybeConfigureRenderersForTunneling(
mappedTrackInfo, rendererFormatSupports, rendererConfigurations, rendererTrackSelections); mappedTrackInfo, rendererFormatSupports, rendererConfigurations, rendererTrackSelections);
} }
@ -2316,10 +2492,50 @@ public class DefaultTrackSelector extends MappingTrackSelector {
rendererFormatSupports, rendererFormatSupports,
(int rendererIndex, TrackGroup group, @Capabilities int[] support) -> (int rendererIndex, TrackGroup group, @Capabilities int[] support) ->
AudioTrackInfo.createForTrackGroup( AudioTrackInfo.createForTrackGroup(
rendererIndex, group, params, support, hasVideoRendererWithMappedTracksFinal), rendererIndex,
group,
params,
support,
hasVideoRendererWithMappedTracksFinal,
this::isAudioFormatWithinAudioChannelCountConstraints),
AudioTrackInfo::compareSelections); AudioTrackInfo::compareSelections);
} }
/**
* Returns whether an audio format is within the audio channel count constraints.
*
* <p>This method returns {@code true} if one of the following holds:
*
* <ul>
* <li>Audio channel count constraints are not applicable (all formats are considered within
* constraints).
* <li>The device has a <a
* href="https://developer.android.com/guide/topics/resources/providing-resources#UiModeQualifier">{@code
* television} UI mode</a>.
* <li>{@code format} has up to 2 channels.
* <li>The device does not support audio spatialization and the format is {@linkplain
* #isDolbyAudio(Format) a Dolby one}.
* <li>Audio spatialization is applicable and {@code format} can be spatialized.
* </ul>
*/
private boolean isAudioFormatWithinAudioChannelCountConstraints(Format format) {
synchronized (lock) {
return !parameters.constrainAudioChannelCountToDeviceCapabilities
|| deviceIsTV
|| format.channelCount <= 2
|| (isDolbyAudio(format)
&& (Util.SDK_INT < 32
|| spatializer == null
|| !spatializer.isSpatializationSupported()))
|| (Util.SDK_INT >= 32
&& spatializer != null
&& spatializer.isSpatializationSupported()
&& spatializer.isAvailable()
&& spatializer.isEnabled()
&& spatializer.canBeSpatialized(audioAttributes, format));
}
}
// Text track selection implementation. // Text track selection implementation.
/** /**
@ -2453,6 +2669,21 @@ public class DefaultTrackSelector extends MappingTrackSelector {
firstTrackInfo.rendererIndex); firstTrackInfo.rendererIndex);
} }
private void maybeInvalidateForAudioChannelCountConstraints() {
boolean shouldInvalidate;
synchronized (lock) {
shouldInvalidate =
parameters.constrainAudioChannelCountToDeviceCapabilities
&& !deviceIsTV
&& Util.SDK_INT >= 32
&& spatializer != null
&& spatializer.isSpatializationSupported();
}
if (shouldInvalidate) {
invalidate();
}
}
// Utility methods. // Utility methods.
private static void applyTrackSelectionOverrides( private static void applyTrackSelectionOverrides(
@ -2777,6 +3008,21 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
} }
private static boolean isDolbyAudio(Format format) {
if (format.sampleMimeType == null) {
return false;
}
switch (format.sampleMimeType) {
case MimeTypes.AUDIO_AC3:
case MimeTypes.AUDIO_E_AC3:
case MimeTypes.AUDIO_E_AC3_JOC:
case MimeTypes.AUDIO_AC4:
return true;
default:
return false;
}
}
/** Base class for track selection information of a {@link Format}. */ /** Base class for track selection information of a {@link Format}. */
private abstract static class TrackInfo<T extends TrackInfo<T>> { private abstract static class TrackInfo<T extends TrackInfo<T>> {
/** Factory for {@link TrackInfo} implementations for a given {@link TrackGroup}. */ /** Factory for {@link TrackInfo} implementations for a given {@link TrackGroup}. */
@ -3026,7 +3272,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
TrackGroup trackGroup, TrackGroup trackGroup,
Parameters params, Parameters params,
@Capabilities int[] formatSupport, @Capabilities int[] formatSupport,
boolean hasMappedVideoTracks) { boolean hasMappedVideoTracks,
Predicate<Format> withinAudioChannelCountConstraints) {
ImmutableList.Builder<AudioTrackInfo> listBuilder = ImmutableList.builder(); ImmutableList.Builder<AudioTrackInfo> listBuilder = ImmutableList.builder();
for (int i = 0; i < trackGroup.length; i++) { for (int i = 0; i < trackGroup.length; i++) {
listBuilder.add( listBuilder.add(
@ -3036,7 +3283,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/* trackIndex= */ i, /* trackIndex= */ i,
params, params,
formatSupport[i], formatSupport[i],
hasMappedVideoTracks)); hasMappedVideoTracks,
withinAudioChannelCountConstraints));
} }
return listBuilder.build(); return listBuilder.build();
} }
@ -3066,7 +3314,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
int trackIndex, int trackIndex,
Parameters parameters, Parameters parameters,
@Capabilities int formatSupport, @Capabilities int formatSupport,
boolean hasMappedVideoTracks) { boolean hasMappedVideoTracks,
Predicate<Format> withinAudioChannelCountConstraints) {
super(rendererIndex, trackGroup, trackIndex); super(rendererIndex, trackGroup, trackIndex);
this.parameters = parameters; this.parameters = parameters;
this.language = normalizeUndeterminedLanguageToNull(format.language); this.language = normalizeUndeterminedLanguageToNull(format.language);
@ -3098,7 +3347,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
isWithinConstraints = isWithinConstraints =
(format.bitrate == Format.NO_VALUE || format.bitrate <= parameters.maxAudioBitrate) (format.bitrate == Format.NO_VALUE || format.bitrate <= parameters.maxAudioBitrate)
&& (format.channelCount == Format.NO_VALUE && (format.channelCount == Format.NO_VALUE
|| format.channelCount <= parameters.maxAudioChannelCount); || format.channelCount <= parameters.maxAudioChannelCount)
&& withinAudioChannelCountConstraints.apply(format);
String[] localeLanguages = Util.getSystemLanguageCodes(); String[] localeLanguages = Util.getSystemLanguageCodes();
int bestLocaleMatchIndex = Integer.MAX_VALUE; int bestLocaleMatchIndex = Integer.MAX_VALUE;
int bestLocaleMatchScore = 0; int bestLocaleMatchScore = 0;
@ -3375,4 +3625,85 @@ public class DefaultTrackSelector extends MappingTrackSelector {
.result(); .result();
} }
} }
/**
* Wraps the {@link Spatializer} in order to encapsulate its APIs within an inner class, to avoid
* runtime linking on devices with {@code API < 32}.
*/
@RequiresApi(32)
private static class SpatializerWrapperV32 {
private final Spatializer spatializer;
private final boolean spatializationSupported;
@Nullable private Handler handler;
@Nullable private Spatializer.OnSpatializerStateChangedListener listener;
@Nullable
public static SpatializerWrapperV32 tryCreateInstance(Context context) {
@Nullable
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
return audioManager == null ? null : new SpatializerWrapperV32(audioManager.getSpatializer());
}
private SpatializerWrapperV32(Spatializer spatializer) {
this.spatializer = spatializer;
this.spatializationSupported =
spatializer.getImmersiveAudioLevel() != Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
}
public void ensureInitialized(DefaultTrackSelector defaultTrackSelector, Looper looper) {
if (listener != null || handler != null) {
return;
}
this.listener =
new Spatializer.OnSpatializerStateChangedListener() {
@Override
public void onSpatializerEnabledChanged(Spatializer spatializer, boolean enabled) {
defaultTrackSelector.maybeInvalidateForAudioChannelCountConstraints();
}
@Override
public void onSpatializerAvailableChanged(Spatializer spatializer, boolean available) {
defaultTrackSelector.maybeInvalidateForAudioChannelCountConstraints();
}
};
this.handler = new Handler(looper);
spatializer.addOnSpatializerStateChangedListener(handler::post, listener);
}
public boolean isSpatializationSupported() {
return spatializationSupported;
}
public boolean isAvailable() {
return spatializer.isAvailable();
}
public boolean isEnabled() {
return spatializer.isEnabled();
}
public boolean canBeSpatialized(AudioAttributes audioAttributes, Format format) {
AudioFormat.Builder builder =
new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(Util.getAudioTrackChannelConfig(format.channelCount));
if (format.sampleRate != Format.NO_VALUE) {
builder.setSampleRate(format.sampleRate);
}
return spatializer.canBeSpatialized(
audioAttributes.getAudioAttributesV21().audioAttributes, builder.build());
}
public void release() {
if (listener == null || handler == null) {
return;
}
spatializer.removeOnSpatializerStateChangedListener(listener);
castNonNull(handler).removeCallbacksAndMessages(/* token= */ null);
handler = null;
listener = null;
}
}
} }

View File

@ -17,7 +17,9 @@ package androidx.media3.exoplayer.trackselection;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.TrackSelectionParameters;
@ -112,7 +114,8 @@ public abstract class TrackSelector {
* it has previously made are no longer valid. * it has previously made are no longer valid.
* @param bandwidthMeter A bandwidth meter which can be used by track selections to select tracks. * @param bandwidthMeter A bandwidth meter which can be used by track selections to select tracks.
*/ */
public final void init(InvalidationListener listener, BandwidthMeter bandwidthMeter) { @CallSuper
public void init(InvalidationListener listener, BandwidthMeter bandwidthMeter) {
this.listener = listener; this.listener = listener;
this.bandwidthMeter = bandwidthMeter; this.bandwidthMeter = bandwidthMeter;
} }
@ -121,9 +124,10 @@ public abstract class TrackSelector {
* Called by the player to release the selector. The selector cannot be used until {@link * Called by the player to release the selector. The selector cannot be used until {@link
* #init(InvalidationListener, BandwidthMeter)} is called again. * #init(InvalidationListener, BandwidthMeter)} is called again.
*/ */
public final void release() { @CallSuper
this.listener = null; public void release() {
this.bandwidthMeter = null; listener = null;
bandwidthMeter = null;
} }
/** /**
@ -178,6 +182,11 @@ public abstract class TrackSelector {
return false; return false;
} }
/** Called by the player to set the {@link AudioAttributes} that will be used for playback. */
public void setAudioAttributes(AudioAttributes audioAttributes) {
// Default implementation is no-op.
}
/** /**
* Calls {@link InvalidationListener#onTrackSelectionsInvalidated()} to invalidate all previously * Calls {@link InvalidationListener#onTrackSelectionsInvalidated()} to invalidate all previously
* generated track selections. * generated track selections.

View File

@ -31,9 +31,9 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import android.content.Context; import android.content.Context;
import android.media.Spatializer;
import androidx.media3.common.Bundleable; import androidx.media3.common.Bundleable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
@ -68,14 +68,19 @@ import java.util.Map;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
/** Unit tests for {@link DefaultTrackSelector}. */ /** Unit tests for {@link DefaultTrackSelector}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class DefaultTrackSelectorTest { public final class DefaultTrackSelectorTest {
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
private static final RendererCapabilities ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES = private static final RendererCapabilities ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES =
new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO); new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO);
private static final RendererCapabilities ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES = private static final RendererCapabilities ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES =
@ -142,7 +147,6 @@ public final class DefaultTrackSelectorTest {
@Before @Before
public void setUp() { public void setUp() {
initMocks(this);
when(bandwidthMeter.getBitrateEstimate()).thenReturn(1000000L); when(bandwidthMeter.getBitrateEstimate()).thenReturn(1000000L);
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
defaultParameters = Parameters.getDefaults(context); defaultParameters = Parameters.getDefaults(context);
@ -877,11 +881,18 @@ public final class DefaultTrackSelectorTest {
* are the same, and tracks are within renderer's capabilities. * are the same, and tracks are within renderer's capabilities.
*/ */
@Test @Test
public void selectTracksWithinCapabilitiesSelectHigherNumChannel() throws Exception { public void
selectTracks_audioChannelCountConstraintsDisabledAndTracksWithinCapabilities_selectHigherNumChannel()
throws Exception {
Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon(); Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
Format higherChannelFormat = formatBuilder.setChannelCount(6).build(); Format higherChannelFormat = formatBuilder.setChannelCount(6).build();
Format lowerChannelFormat = formatBuilder.setChannelCount(2).build(); Format lowerChannelFormat = formatBuilder.setChannelCount(2).build();
TrackGroupArray trackGroups = wrapFormats(higherChannelFormat, lowerChannelFormat); TrackGroupArray trackGroups = wrapFormats(higherChannelFormat, lowerChannelFormat);
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.build());
TrackSelectorResult result = TrackSelectorResult result =
trackSelector.selectTracks( trackSelector.selectTracks(
@ -957,11 +968,13 @@ public final class DefaultTrackSelectorTest {
/** /**
* Tests that track selector will prefer audio tracks with higher channel count over tracks with * 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 * higher sample rate when audio channel count constraints are disabled, other factors are the
* capabilities. * same, and tracks are within renderer's capabilities.
*/ */
@Test @Test
public void selectTracksPreferHigherNumChannelBeforeSampleRate() throws Exception { public void
selectTracks_audioChannelCountConstraintsDisabled_preferHigherNumChannelBeforeSampleRate()
throws Exception {
Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon(); Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
Format higherChannelLowerSampleRateFormat = Format higherChannelLowerSampleRateFormat =
formatBuilder.setChannelCount(6).setSampleRate(22050).build(); formatBuilder.setChannelCount(6).setSampleRate(22050).build();
@ -969,6 +982,11 @@ public final class DefaultTrackSelectorTest {
formatBuilder.setChannelCount(2).setSampleRate(44100).build(); formatBuilder.setChannelCount(2).setSampleRate(44100).build();
TrackGroupArray trackGroups = TrackGroupArray trackGroups =
wrapFormats(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat); wrapFormats(higherChannelLowerSampleRateFormat, lowerChannelHigherSampleRateFormat);
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.build());
TrackSelectorResult result = TrackSelectorResult result =
trackSelector.selectTracks( trackSelector.selectTracks(
@ -1454,9 +1472,67 @@ public final class DefaultTrackSelectorTest {
assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 0, 1); assertAdaptiveSelection(result.selections[0], trackGroups.get(0), 0, 1);
} }
/**
* The following test is subject to the execution context. It currently runs on SDK 30 and the
* environment matches a handheld device ({@link Util#isTv(Context)} returns {@code false}) and
* there is no {@link android.media.Spatializer}. If the execution environment upgrades, the test
* may start failing depending on how the Robolectric Spatializer behaves. If the test starts
* failing, and Robolectric offers a shadow Spatializer whose behavior can be controlled, revise
* this test so that the Spatializer cannot spatialize the multichannel format. Also add tests
* where the Spatializer can spatialize multichannel formats and the track selector picks a
* multichannel format.
*/
@Test @Test
public void selectTracks_multipleAudioTracks_selectsAllTracksInBestConfigurationOnly() public void selectTracks_stereoAndMultichannelAACTracks_selectsStereo()
throws Exception { throws ExoPlaybackException {
Format stereoAudioFormat = AUDIO_FORMAT.buildUpon().setChannelCount(2).setId("0").build();
Format multichannelAudioFormat = AUDIO_FORMAT.buildUpon().setChannelCount(6).setId("1").build();
TrackGroupArray trackGroups = singleTrackGroup(stereoAudioFormat, multichannelAudioFormat);
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertThat(result.selections[0].getSelectedFormat()).isSameInstanceAs(stereoAudioFormat);
}
/**
* The following test is subject to the execution context. It currently runs on SDK 30 and the
* environment matches a handheld device ({@link Util#isTv(Context)} returns {@code false}) and
* there is no {@link android.media.Spatializer}. If the execution environment upgrades, the test
* may start failing depending on how the Robolectric Spatializer behaves. If the test starts
* failing, and Robolectric offers a shadow Spatializer whose behavior can be controlled, revise
* this test so that the Spatializer does not support spatialization ({@link
* Spatializer#getImmersiveAudioLevel()} returns {@link
* Spatializer#SPATIALIZER_IMMERSIVE_LEVEL_NONE}).
*/
@Test
public void
selectTracks_withAACStereoAndDolbyMultichannelTrackWithinCapabilities_selectsDolbyMultichannelTrack()
throws ExoPlaybackException {
Format stereoAudioFormat = AUDIO_FORMAT.buildUpon().setChannelCount(2).setId("0").build();
Format multichannelAudioFormat =
AUDIO_FORMAT
.buildUpon()
.setSampleMimeType(MimeTypes.AUDIO_AC3)
.setChannelCount(6)
.setId("1")
.build();
TrackGroupArray trackGroups = singleTrackGroup(stereoAudioFormat, multichannelAudioFormat);
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertThat(result.selections[0].getSelectedFormat()).isSameInstanceAs(multichannelAudioFormat);
}
@Test
public void
selectTracks_audioChannelCountConstraintsDisabledAndMultipleAudioTracks_selectsAllTracksInBestConfigurationOnly()
throws Exception {
TrackGroupArray trackGroups = TrackGroupArray trackGroups =
singleTrackGroup( singleTrackGroup(
buildAudioFormatWithConfiguration( buildAudioFormatWithConfiguration(
@ -1476,6 +1552,10 @@ public final class DefaultTrackSelectorTest {
/* channelCount= */ 6, /* channelCount= */ 6,
MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AAC,
/* sampleRate= */ 44100)); /* sampleRate= */ 44100));
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setConstrainAudioChannelCountToDeviceCapabilities(false));
TrackSelectorResult result = TrackSelectorResult result =
trackSelector.selectTracks( trackSelector.selectTracks(
@ -1568,10 +1648,17 @@ public final class DefaultTrackSelectorTest {
} }
@Test @Test
public void selectTracksWithMultipleAudioTracksWithMixedChannelCounts() throws Exception { public void
selectTracks_audioChannelCountConstraintsDisabledAndMultipleAudioTracksWithMixedChannelCounts()
throws Exception {
Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon(); Format.Builder formatBuilder = AUDIO_FORMAT.buildUpon();
Format stereoAudioFormat = formatBuilder.setChannelCount(2).build(); Format stereoAudioFormat = formatBuilder.setChannelCount(2).build();
Format surroundAudioFormat = formatBuilder.setChannelCount(5).build(); Format surroundAudioFormat = formatBuilder.setChannelCount(5).build();
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.build());
// Should not adapt between different channel counts, so we expect a fixed selection containing // Should not adapt between different channel counts, so we expect a fixed selection containing
// the track with more channels. // the track with more channels.
@ -1592,7 +1679,11 @@ public final class DefaultTrackSelectorTest {
// If we constrain the channel count to 4 we expect a fixed selection containing the track with // If we constrain the channel count to 4 we expect a fixed selection containing the track with
// fewer channels. // fewer channels.
trackSelector.setParameters(defaultParameters.buildUpon().setMaxAudioChannelCount(4)); trackSelector.setParameters(
defaultParameters
.buildUpon()
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.setMaxAudioChannelCount(4));
result = result =
trackSelector.selectTracks( trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
@ -1601,7 +1692,11 @@ public final class DefaultTrackSelectorTest {
// If we constrain the channel count to 2 we expect a fixed selection containing the track with // If we constrain the channel count to 2 we expect a fixed selection containing the track with
// fewer channels. // fewer channels.
trackSelector.setParameters(defaultParameters.buildUpon().setMaxAudioChannelCount(2)); trackSelector.setParameters(
defaultParameters
.buildUpon()
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.setMaxAudioChannelCount(2));
result = result =
trackSelector.selectTracks( trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
@ -1610,7 +1705,11 @@ public final class DefaultTrackSelectorTest {
// If we constrain the channel count to 1 we expect a fixed selection containing the track with // If we constrain the channel count to 1 we expect a fixed selection containing the track with
// fewer channels. // fewer channels.
trackSelector.setParameters(defaultParameters.buildUpon().setMaxAudioChannelCount(1)); trackSelector.setParameters(
defaultParameters
.buildUpon()
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.setMaxAudioChannelCount(1));
result = result =
trackSelector.selectTracks( trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE); new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
@ -1621,6 +1720,7 @@ public final class DefaultTrackSelectorTest {
trackSelector.setParameters( trackSelector.setParameters(
defaultParameters defaultParameters
.buildUpon() .buildUpon()
.setConstrainAudioChannelCountToDeviceCapabilities(false)
.setMaxAudioChannelCount(1) .setMaxAudioChannelCount(1)
.setExceedAudioConstraintsIfNecessary(false)); .setExceedAudioConstraintsIfNecessary(false));
result = result =
@ -2399,6 +2499,7 @@ public final class DefaultTrackSelectorTest {
.setAllowAudioMixedChannelCountAdaptiveness(true) .setAllowAudioMixedChannelCountAdaptiveness(true)
.setAllowAudioMixedDecoderSupportAdaptiveness(false) .setAllowAudioMixedDecoderSupportAdaptiveness(false)
.setPreferredAudioMimeTypes(MimeTypes.AUDIO_AC3, MimeTypes.AUDIO_E_AC3) .setPreferredAudioMimeTypes(MimeTypes.AUDIO_AC3, MimeTypes.AUDIO_E_AC3)
.setConstrainAudioChannelCountToDeviceCapabilities(false)
// Text // Text
.setPreferredTextLanguages("de", "en") .setPreferredTextLanguages("de", "en")
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)