Android TV 设备可以同时连接多个音频输出设备:电视扬声器、已连接 HDMI 的家庭影院、蓝牙耳机等。这些音频输出设备可以支持不同的音频功能,如编码(杜比数字 +、DTS 和 PCM)、采样率和声道。例如,连接 HDMI 的电视支持多种编码,而连接的蓝牙耳机通常仅支持 PCM。
通过热插拔 HDMI 设备、连接或断开蓝牙耳机,或者用户更改音频设置,可用音频设备和路由音频设备的列表也会发生变化。由于音频输出 capability 即使在应用播放媒体时也可能会发生变化,因此应用需要妥善适应这些变化,并在新的路由音频设备及其 capability 上继续播放。输出错误的音频格式可能会导致错误或无法播放声音。
应用能够以多种编码输出相同的内容,从而根据音频设备的功能为用户提供最佳音频体验。例如,如果电视支持杜比数字编码音频流,则会播放该流;而当不支持杜比数字时,则会选择支持范围更广的 PCM 音频流。如需了解用于将音频流转换为 PCM 的内置 Android 解码器列表,请参阅支持的媒体格式。
播放时,在线影音应用应使用输出音频设备支持的最佳 AudioFormat
创建一个 AudioTrack
。
使用正确的格式创建曲目
应用应创建 AudioTrack
,开始播放,然后调用 getRoutedDevice()
以确定用于播放声音的默认音频设备。例如,这可以是安全的短无声 PCM 编码音轨,仅用于确定路由设备及其音频功能。
获取支持的编码
使用 getAudioProfiles()
(API 级别 31 及更高级别)或 getEncodings()
(API 级别 23 及更高级别)确定默认音频设备上可用的音频格式。
检查受支持的音频配置文件和格式
使用 AudioProfile
(API 级别 31 及更高级别)或 isDirectPlaybackSupported()
(API 级别 29 及更高级别)检查支持的格式、通道数和采样率组合。
某些 Android 设备能够支持输出音频设备不支持的编码。这些附加格式应通过 isDirectPlaybackSupported()
进行检测。在这些情况下,系统会将音频数据重新编码为输出音频设备支持的格式。请使用 isDirectPlaybackSupported()
正确检查对所需格式的支持,即使所需格式未出现在 getEncodings()
返回的列表中也是如此。
预期音频路由
Android 13(API 级别 33)引入了预期音频路由。您可以预测设备音频属性支持,并为活跃音频设备准备音轨。对于给定的格式和属性,您可以使用 getDirectPlaybackSupport()
检查当前路由的音频设备是否支持直接播放:
Kotlin
val format = AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_E_AC3) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) .setSampleRate(48000) .build() val attributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .build() if (AudioManager.getDirectPlaybackSupport(format, attributes) != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED ) { // The format and attributes are supported for direct playback // on the currently active routed audio path } else { // The format and attributes are NOT supported for direct playback // on the currently active routed audio path }
Java
AudioFormat format = new AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_E_AC3) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) .setSampleRate(48000) .build(); AudioAttributes attributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .build(); if (AudioManager.getDirectPlaybackSupport(format, attributes) != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED) { // The format and attributes are supported for direct playback // on the currently active routed audio path } else { // The format and attributes are NOT supported for direct playback // on the currently active routed audio path }
或者,您也可以通过当前路由的音频设备查询哪些配置文件支持直接媒体播放。这不包括不支持或将(例如)会被 Android 框架转码的所有配置文件:
Kotlin
private fun findBestAudioFormat(audioAttributes: AudioAttributes): AudioFormat { val preferredFormats = listOf( AudioFormat.ENCODING_E_AC3, AudioFormat.ENCODING_AC3, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_DEFAULT ) val audioProfiles = audioManager.getDirectProfilesForAttributes(audioAttributes) val bestAudioProfile = preferredFormats.firstNotNullOf { format -> audioProfiles.firstOrNull { it.format == format } } val sampleRate = findBestSampleRate(bestAudioProfile) val channelMask = findBestChannelMask(bestAudioProfile) return AudioFormat.Builder() .setEncoding(bestAudioProfile.format) .setSampleRate(sampleRate) .setChannelMask(channelMask) .build() }
Java
private AudioFormat findBestAudioFormat(AudioAttributes audioAttributes) { Stream<Integer> preferredFormats = Stream.<Integer>builder() .add(AudioFormat.ENCODING_E_AC3) .add(AudioFormat.ENCODING_AC3) .add(AudioFormat.ENCODING_PCM_16BIT) .add(AudioFormat.ENCODING_DEFAULT) .build(); Stream<AudioProfile> audioProfiles = audioManager.getDirectProfilesForAttributes(audioAttributes).stream(); AudioProfile bestAudioProfile = (AudioProfile) preferredFormats.map(format -> audioProfiles.filter(profile -> profile.getFormat() == format) .findFirst() .orElseThrow(NoSuchElementException::new) ); Integer sampleRate = findBestSampleRate(bestAudioProfile); Integer channelMask = findBestChannelMask(bestAudioProfile); return new AudioFormat.Builder() .setEncoding(bestAudioProfile.getFormat()) .setSampleRate(sampleRate) .setChannelMask(channelMask) .build(); }
在此示例中,preferredFormats
是 AudioFormat
实例的列表。优先级排序由高到低依次排列在列表中,优先级最低的排在最后。getDirectProfilesForAttributes()
通过所提供的 AudioAttributes
返回当前路由的音频设备的受支持 AudioProfile
对象的列表。系统会遍历首选 AudioFormat
项的列表,直到找到匹配的受支持 AudioProfile
。此 AudioProfile
存储为 bestAudioProfile
。最佳采样率和通道掩码由 bestAudioProfile
确定。最后,系统会创建适当的 AudioFormat
实例。
创建音轨
应用应使用这些信息为默认音频设备(并且可用于所选内容)支持的最高质量 AudioFormat
创建 AudioTrack
。
拦截音频设备更改
如需拦截音频设备变化并做出响应,应用应满足以下要求:
- 如果 API 级别不低于 24,请添加
OnRoutingChangedListener
以监控音频设备更改(HDMI、蓝牙等)。 - 对于 API 级别 23,请注册
AudioDeviceCallback
以接收可用音频设备列表中的更改。 - 对于 API 级别 21 和 22,请监控 HDMI 插头事件,并使用广播的额外数据。
- 此外,还要注册
BroadcastReceiver
以监控 API 级别低于 23 的设备的BluetoothDevice
状态变化,因为AudioDeviceCallback
尚不受支持。
当检测到 AudioTrack
的音频设备发生变化时,应用应检查更新后的音频功能,并根据需要使用不同的 AudioFormat
重新创建 AudioTrack
。如果现在支持更高质量的编码,或者以前使用的编码不再受支持,请执行此操作。
示例代码
Kotlin
// audioPlayer is a wrapper around an AudioTrack // which calls a callback for an AudioTrack write error audioPlayer.addAudioTrackWriteErrorListener { // error code can be checked here, // in case of write error try to recreate the audio track restartAudioTrack(findDefaultAudioDeviceInfo()) } audioPlayer.audioTrack.addOnRoutingChangedListener({ audioRouting -> audioRouting?.routedDevice?.let { audioDeviceInfo -> // use the updated audio routed device to determine // what audio format should be used if (needsAudioFormatChange(audioDeviceInfo)) { restartAudioTrack(audioDeviceInfo) } } }, handler)
Java
// audioPlayer is a wrapper around an AudioTrack // which calls a callback for an AudioTrack write error audioPlayer.addAudioTrackWriteErrorListener(new AudioTrackPlayer.AudioTrackWriteError() { @Override public void audioTrackWriteError(int errorCode) { // error code can be checked here, // in case of write error try to recreate the audio track restartAudioTrack(findDefaultAudioDeviceInfo()); } }); audioPlayer.getAudioTrack().addOnRoutingChangedListener(new AudioRouting.OnRoutingChangedListener() { @Override public void onRoutingChanged(AudioRouting audioRouting) { if (audioRouting != null && audioRouting.getRoutedDevice() != null) { AudioDeviceInfo audioDeviceInfo = audioRouting.getRoutedDevice(); // use the updated audio routed device to determine // what audio format should be used if (needsAudioFormatChange(audioDeviceInfo)) { restartAudioTrack(audioDeviceInfo); } } } }, handler);