From 3596bc332df583dc3422c89ee62c52d782792903 Mon Sep 17 00:00:00 2001 From: tonihei Date: Wed, 10 Jan 2024 03:15:25 -0800 Subject: [PATCH] Use AudioAtributes when determining AudioCapabilities The capabilities change depending on the attributes, so we should pass down the actual attributes used during playback and update the capabilities whenever these change. PiperOrigin-RevId: 597197507 --- .../exoplayer/audio/AudioCapabilities.java | 91 +++++++++++++------ .../audio/AudioCapabilitiesReceiver.java | 29 ++++-- .../exoplayer/audio/DefaultAudioSink.java | 15 ++- 3 files changed, 93 insertions(+), 42 deletions(-) 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(); } }