diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java index 4264670486..0dfe0eb8fc 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java @@ -21,7 +21,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.media.AudioAttributes; import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; @@ -33,6 +32,7 @@ import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; +import androidx.media3.common.AudioAttributes; import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; @@ -58,6 +58,7 @@ public final class AudioCapabilities { new AudioCapabilities(new int[] {AudioFormat.ENCODING_PCM_16BIT}, DEFAULT_MAX_CHANNEL_COUNT); /** Encodings supported when the device specifies external surround sound. */ + @SuppressLint("InlinedApi") // Compile-time access to integer constants defined in API 21. private static final ImmutableList EXTERNAL_SURROUND_SOUND_ENCODINGS = ImmutableList.of( AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_AC3, AudioFormat.ENCODING_E_AC3); @@ -82,21 +83,33 @@ public final class AudioCapabilities { private static final String EXTERNAL_SURROUND_SOUND_KEY = "external_surround_sound_enabled"; /** - * Returns the current audio capabilities for the device. + * @deprecated Use {@link #getCapabilities(Context, AudioAttributes)} instead. + */ + @Deprecated + public static AudioCapabilities getCapabilities(Context context) { + return getCapabilities(context, AudioAttributes.DEFAULT); + } + + /** + * Returns the current audio capabilities. * * @param context A context for obtaining the current audio capabilities. + * @param audioAttributes The {@link AudioAttributes} to obtain capabilities for. * @return The current audio capabilities for the device. */ @SuppressWarnings("InlinedApi") - public static AudioCapabilities getCapabilities(Context context) { + @SuppressLint("UnprotectedReceiver") // ACTION_HDMI_AUDIO_PLUG is protected since API 16 + public static AudioCapabilities getCapabilities( + Context context, AudioAttributes audioAttributes) { Intent intent = context.registerReceiver( /* receiver= */ null, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)); - return getCapabilities(context, intent); + return getCapabilities(context, intent, audioAttributes); } @SuppressLint("InlinedApi") - /* package */ static AudioCapabilities getCapabilities(Context context, @Nullable Intent intent) { + /* package */ static AudioCapabilities getCapabilities( + Context context, @Nullable Intent intent, AudioAttributes audioAttributes) { // If a connection to Bluetooth device is detected, we only return the minimum capabilities that // is supported by all the devices. if (Util.SDK_INT >= 23 && Api23.isBluetoothConnected(context)) { @@ -113,7 +126,7 @@ public final class AudioCapabilities { // it on TV and automotive devices, which generally shouldn't support audio offload for surround // encodings. if (Util.SDK_INT >= 29 && (Util.isTv(context) || Util.isAutomotive(context))) { - supportedEncodings.addAll(Api29.getDirectPlaybackSupportedEncodings()); + supportedEncodings.addAll(Api29.getDirectPlaybackSupportedEncodings(audioAttributes)); return new AudioCapabilities( Ints.toArray(supportedEncodings.build()), DEFAULT_MAX_CHANNEL_COUNT); } @@ -155,8 +168,9 @@ public final class AudioCapabilities { * Constructs new audio capabilities based on a set of supported encodings and a maximum channel * count. * - *

Applications should generally call {@link #getCapabilities(Context)} to obtain an instance - * based on the capabilities advertised by the platform, rather than calling this constructor. + *

Applications should generally call {@link #getCapabilities(Context, AudioAttributes)} to + * obtain an instance based on the capabilities advertised by the platform, rather than calling + * this constructor. * * @param supportedEncodings Supported audio encodings from {@link android.media.AudioFormat}'s * {@code ENCODING_*} constants. Passing {@code null} indicates that no encodings are @@ -188,22 +202,42 @@ public final class AudioCapabilities { return maxChannelCount; } - /** Returns whether the device can do passthrough playback for {@code format}. */ + /** + * @deprecated Use {@link #isPassthroughPlaybackSupported(Format, AudioAttributes)} instead. + */ + @Deprecated public boolean isPassthroughPlaybackSupported(Format format) { - return getEncodingAndChannelConfigForPassthrough(format) != null; + return isPassthroughPlaybackSupported(format, AudioAttributes.DEFAULT); + } + + /** Returns whether the device can do passthrough playback for {@code format}. */ + public boolean isPassthroughPlaybackSupported(Format format, AudioAttributes audioAttributes) { + return getEncodingAndChannelConfigForPassthrough(format, audioAttributes) != null; + } + + /** + * @deprecated Use {@link #getEncodingAndChannelConfigForPassthrough(Format, AudioAttributes)} + * instead. + */ + @Deprecated + @Nullable + public Pair getEncodingAndChannelConfigForPassthrough(Format format) { + return getEncodingAndChannelConfigForPassthrough(format, AudioAttributes.DEFAULT); } /** * Returns the encoding and channel config to use when configuring an {@link AudioTrack} in - * passthrough mode for the specified {@link Format}. Returns {@code null} if passthrough of the - * format is unsupported. + * passthrough mode for the specified {@link Format} and {@link AudioAttributes}. Returns {@code + * null} if passthrough of the format is unsupported. * * @param format The {@link Format}. + * @param audioAttributes The {@link AudioAttributes}. * @return The encoding and channel config to use, or {@code null} if passthrough of the format is * unsupported. */ @Nullable - public Pair getEncodingAndChannelConfigForPassthrough(Format format) { + public Pair getEncodingAndChannelConfigForPassthrough( + Format format, AudioAttributes audioAttributes) { @C.Encoding int encoding = MimeTypes.getEncoding(checkNotNull(format.sampleMimeType), format.codecs); // Check that this is an encoding known to work for passthrough. This avoids trying to use @@ -231,7 +265,8 @@ public final class AudioCapabilities { // For E-AC3 JOC, the format is object based so the format channel count is arbitrary. int sampleRate = format.sampleRate != Format.NO_VALUE ? format.sampleRate : DEFAULT_SAMPLE_RATE_HZ; - channelCount = getMaxSupportedChannelCountForPassthrough(encoding, sampleRate); + channelCount = + getMaxSupportedChannelCountForPassthrough(encoding, sampleRate, audioAttributes); } else { channelCount = format.channelCount; // Some DTS:X TVs reports ACTION_HDMI_AUDIO_PLUG.EXTRA_MAX_CHANNEL_COUNT as 8 @@ -288,12 +323,12 @@ public final class AudioCapabilities { * encoding, or {@code 0} if the format is unsupported. */ private static int getMaxSupportedChannelCountForPassthrough( - @C.Encoding int encoding, int sampleRate) { + @C.Encoding int encoding, int sampleRate, AudioAttributes audioAttributes) { // From API 29 we can get the channel count from the platform, but before then there is no way // to query the platform so we assume the channel count matches the maximum channel count per // audio encoding spec. if (Util.SDK_INT >= 29) { - return Api29.getMaxSupportedChannelCountForPassthrough(encoding, sampleRate); + return Api29.getMaxSupportedChannelCountForPassthrough(encoding, sampleRate, audioAttributes); } return checkNotNull(ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS.getOrDefault(encoding, 0)); } @@ -325,13 +360,13 @@ public final class AudioCapabilities { private Api23() {} @DoNotInline - public static final boolean isBluetoothConnected(Context context) { + public static boolean isBluetoothConnected(Context context) { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); AudioDeviceInfo[] audioDeviceInfos = checkNotNull(audioManager).getDevices(AudioManager.GET_DEVICES_OUTPUTS); ImmutableSet allBluetoothDeviceTypesSet = getAllBluetoothDeviceTypes(); - for (int i = 0; i < audioDeviceInfos.length; i++) { - if (allBluetoothDeviceTypesSet.contains(audioDeviceInfos[i].getType())) { + for (AudioDeviceInfo audioDeviceInfo : audioDeviceInfos) { + if (allBluetoothDeviceTypesSet.contains(audioDeviceInfo.getType())) { return true; } } @@ -348,7 +383,7 @@ public final class AudioCapabilities { * API 31. And the type {@link AudioDeviceInfo#TYPE_BLE_BROADCAST} is added from API 33. */ @DoNotInline - private static final ImmutableSet getAllBluetoothDeviceTypes() { + private static ImmutableSet getAllBluetoothDeviceTypes() { ImmutableSet.Builder allBluetoothDeviceTypes = new ImmutableSet.Builder() .add(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, AudioDeviceInfo.TYPE_BLUETOOTH_SCO); @@ -365,17 +400,12 @@ public final class AudioCapabilities { @RequiresApi(29) private static final class Api29 { - private static final AudioAttributes DEFAULT_AUDIO_ATTRIBUTES = - new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) - .setFlags(0) - .build(); private Api29() {} @DoNotInline - public static ImmutableList getDirectPlaybackSupportedEncodings() { + public static ImmutableList getDirectPlaybackSupportedEncodings( + AudioAttributes audioAttributes) { ImmutableList.Builder supportedEncodingsListBuilder = ImmutableList.builder(); for (int encoding : ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS.keySet()) { if (Util.SDK_INT < Util.getApiLevelThatAudioFormatIntroducedAudioEncoding(encoding)) { @@ -388,7 +418,7 @@ public final class AudioCapabilities { .setEncoding(encoding) .setSampleRate(DEFAULT_SAMPLE_RATE_HZ) .build(), - DEFAULT_AUDIO_ATTRIBUTES)) { + audioAttributes.getAudioAttributesV21().audioAttributes)) { supportedEncodingsListBuilder.add(encoding); } } @@ -402,7 +432,7 @@ public final class AudioCapabilities { */ @DoNotInline public static int getMaxSupportedChannelCountForPassthrough( - @C.Encoding int encoding, int sampleRate) { + @C.Encoding int encoding, int sampleRate, AudioAttributes audioAttributes) { // TODO(internal b/234351617): Query supported channel masks directly once it's supported, // see also b/25994457. for (int channelCount = DEFAULT_MAX_CHANNEL_COUNT; channelCount > 0; channelCount--) { @@ -416,7 +446,8 @@ public final class AudioCapabilities { .setSampleRate(sampleRate) .setChannelMask(channelConfig) .build(); - if (AudioTrack.isDirectPlaybackSupported(audioFormat, DEFAULT_AUDIO_ATTRIBUTES)) { + if (AudioTrack.isDirectPlaybackSupported( + audioFormat, audioAttributes.getAudioAttributesV21().audioAttributes)) { return channelCount; } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilitiesReceiver.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilitiesReceiver.java index ad6095fa98..d329a776f2 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilitiesReceiver.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilitiesReceiver.java @@ -31,6 +31,7 @@ import android.os.Handler; import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.media3.common.AudioAttributes; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; @@ -59,17 +60,21 @@ public final class AudioCapabilitiesReceiver { @Nullable private final BroadcastReceiver hdmiAudioPlugBroadcastReceiver; @Nullable private final ExternalSurroundSoundSettingObserver externalSurroundSoundSettingObserver; - @Nullable /* package */ AudioCapabilities audioCapabilities; + @Nullable private AudioCapabilities audioCapabilities; + private AudioAttributes audioAttributes; private boolean registered; /** * @param context A context for registering the receiver. * @param listener The listener to notify when audio capabilities change. + * @param audioAttributes The {@link AudioAttributes}. */ - public AudioCapabilitiesReceiver(Context context, Listener listener) { + public AudioCapabilitiesReceiver( + Context context, Listener listener, AudioAttributes audioAttributes) { context = context.getApplicationContext(); this.context = context; this.listener = checkNotNull(listener); + this.audioAttributes = audioAttributes; handler = Util.createHandlerForCurrentOrMainLooper(); audioDeviceCallback = Util.SDK_INT >= 23 ? new AudioDeviceCallbackV23() : null; hdmiAudioPlugBroadcastReceiver = @@ -82,6 +87,16 @@ public final class AudioCapabilitiesReceiver { : null; } + /** + * Updates the {@link AudioAttributes} used by this instance. + * + * @param audioAttributes The {@link AudioAttributes}. + */ + public void setAudioAttributes(AudioAttributes audioAttributes) { + this.audioAttributes = audioAttributes; + onNewAudioCapabilities(AudioCapabilities.getCapabilities(context, audioAttributes)); + } + /** * Registers the receiver, meaning it will notify the listener when audio capability changes * occur. The current audio capabilities will be returned. It is important to call {@link @@ -111,7 +126,7 @@ public final class AudioCapabilitiesReceiver { /* broadcastPermission= */ null, handler); } - audioCapabilities = AudioCapabilities.getCapabilities(context, stickyIntent); + audioCapabilities = AudioCapabilities.getCapabilities(context, stickyIntent, audioAttributes); return audioCapabilities; } @@ -148,7 +163,7 @@ public final class AudioCapabilitiesReceiver { @Override public void onReceive(Context context, Intent intent) { if (!isInitialStickyBroadcast()) { - onNewAudioCapabilities(AudioCapabilities.getCapabilities(context, intent)); + onNewAudioCapabilities(AudioCapabilities.getCapabilities(context, intent, audioAttributes)); } } } @@ -175,7 +190,7 @@ public final class AudioCapabilitiesReceiver { @Override public void onChange(boolean selfChange) { - onNewAudioCapabilities(AudioCapabilities.getCapabilities(context)); + onNewAudioCapabilities(AudioCapabilities.getCapabilities(context, audioAttributes)); } } @@ -183,12 +198,12 @@ public final class AudioCapabilitiesReceiver { private final class AudioDeviceCallbackV23 extends AudioDeviceCallback { @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { - onNewAudioCapabilities(AudioCapabilities.getCapabilities(context)); + onNewAudioCapabilities(AudioCapabilities.getCapabilities(context, audioAttributes)); } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { - onNewAudioCapabilities(AudioCapabilities.getCapabilities(context)); + onNewAudioCapabilities(AudioCapabilities.getCapabilities(context, audioAttributes)); } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java index a2b6a21b71..8346f4f9db 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java @@ -559,7 +559,9 @@ public final class DefaultAudioSink implements AudioSink { @RequiresNonNull("#1.audioProcessorChain") private DefaultAudioSink(Builder builder) { context = builder.context; - audioCapabilities = context != null ? getCapabilities(context) : builder.audioCapabilities; + audioAttributes = AudioAttributes.DEFAULT; + audioCapabilities = + context != null ? getCapabilities(context, audioAttributes) : builder.audioCapabilities; audioProcessorChain = builder.audioProcessorChain; enableFloatOutput = Util.SDK_INT >= 21 && builder.enableFloatOutput; preferAudioTrackPlaybackParams = Util.SDK_INT >= 23 && builder.enableAudioTrackPlaybackParams; @@ -576,7 +578,6 @@ public final class DefaultAudioSink implements AudioSink { new ToInt16PcmAudioProcessor(), channelMappingAudioProcessor, trimmingAudioProcessor); toFloatPcmAvailableAudioProcessors = ImmutableList.of(new ToFloatPcmAudioProcessor()); volume = 1f; - audioAttributes = AudioAttributes.DEFAULT; audioSessionId = C.AUDIO_SESSION_ID_UNSET; auxEffectInfo = new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, 0f); mediaPositionParameters = @@ -630,7 +631,7 @@ public final class DefaultAudioSink implements AudioSink { // guaranteed to support. return SINK_FORMAT_SUPPORTED_WITH_TRANSCODING; } - if (audioCapabilities.isPassthroughPlaybackSupported(format)) { + if (audioCapabilities.isPassthroughPlaybackSupported(format, audioAttributes)) { return SINK_FORMAT_SUPPORTED_DIRECTLY; } return SINK_FORMAT_UNSUPPORTED; @@ -736,7 +737,8 @@ public final class DefaultAudioSink implements AudioSink { outputMode = OUTPUT_MODE_PASSTHROUGH; @Nullable Pair encodingAndChannelConfig = - audioCapabilities.getEncodingAndChannelConfigForPassthrough(inputFormat); + audioCapabilities.getEncodingAndChannelConfigForPassthrough( + inputFormat, audioAttributes); if (encodingAndChannelConfig == null) { throw new ConfigurationException( "Unable to configure passthrough for: " + inputFormat, inputFormat); @@ -1320,6 +1322,9 @@ public final class DefaultAudioSink implements AudioSink { // The audio attributes are ignored in tunneling mode, so no need to reset. return; } + if (audioCapabilitiesReceiver != null) { + audioCapabilitiesReceiver.setAudioAttributes(audioAttributes); + } flush(); } @@ -1711,7 +1716,7 @@ public final class DefaultAudioSink implements AudioSink { // current (playback) thread as the constructor is not called in the playback thread. playbackLooper = Looper.myLooper(); audioCapabilitiesReceiver = - new AudioCapabilitiesReceiver(context, this::onAudioCapabilitiesChanged); + new AudioCapabilitiesReceiver(context, this::onAudioCapabilitiesChanged, audioAttributes); audioCapabilities = audioCapabilitiesReceiver.register(); } }