Android TV 设备可以同时连接多个音频输出: 电视扬声器、通过 HDMI 连接的家庭影院、蓝牙耳机等。 这些音频输出设备可以支持不同的音频功能, 例如编码(Dolby Digital+、DTS 和 PCM)、采样率和声道。 例如,通过 HDMI 连接的电视支持多种编码 而连接的蓝牙耳机通常仅支持 PCM。
可用音频设备列表和路由的音频设备也会因热插拔 HDMI 设备、连接或断开蓝牙耳机或用户更改音频设置而发生变化。由于音频输出功能可能会发生变化,即使应用正在播放媒体也是如此,因此应用需要妥善适应这些 变化,并在新的路由音频设备及其 功能上继续播放。输出错误的音频格式可能会导致错误或 无法播放声音。
应用能够以多种编码输出相同的内容 以便根据音频设备 的功能为用户提供最佳音频体验。例如,如果电视支持 Dolby Digital,则播放 Dolby Digital 编码的音频流 ;如果不支持 Dolby Digital,则选择更广泛支持的 PCM 音频流 。如需查看用于将音频流转换为 PCM 的内置 Android 解码器列表,请参阅 支持的媒体格式。
在播放时,流式传输应用应创建
AudioTrack,并使用输出
音频设备支持的最佳
AudioFormat。
使用正确的格式创建音轨
应用应创建 AudioTrack,开始播放,然后调用
getRoutedDevice()
以确定要从中播放声音的默认音频设备。
例如,这可以是仅用于
确定路由设备及其音频功能的安全短静音 PCM 编码音轨。
获取支持的编码
使用
getAudioProfiles()
(API 级别 31 及更高级别)或
getEncodings()
(API 级别 23 及更高级别)确定
默认音频设备上可用的音频格式。
检查支持的音频配置文件和格式
使用 AudioProfile
(API 级别 31 及更高级别)或
isDirectPlaybackSupported()
(API 级别 29 及更高级别)检查支持的格式、
声道数和采样率组合。
某些 Android 设备能够支持超出输出音频设备支持的编码。应通过 isDirectPlaybackSupported() 检测这些额外的格式。在这些情况下,音频数据
会重新编码为输出音频设备支持的格式。即使所需的格式未出现在 getEncodings() 返回的列表中,也请使用
isDirectPlaybackSupported() 正确检查对该格式的支持
。
预期音频路由
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()
会针对当前路由的音频设备返回支持的
AudioProfile 对象列表,并提供
AudioAttributes。系统会遍历首选 AudioFormat 项的列表,直到找到匹配的支持的
AudioProfile。此 AudioProfile 存储为 bestAudioProfile。
最佳采样率和声道掩码由 bestAudioProfile.
确定。最后,系统会创建一个适当的 AudioFormat
实例。
创建音轨
应用应使用此信息为默认音频设备
(以及所选内容可用)支持的最高质量 AudioFormat 创建 AudioTrack。
拦截音频设备更改
如需拦截音频设备更改并做出响应,应用应执行以下操作:
- 对于 API 级别等于或高于 24,添加
OnRoutingChangedListener以监控音频设备更改(HDMI、蓝牙等)。 - 对于 API 级别 23,注册
AudioDeviceCallback以接收可用音频设备列表中的更改。 - 对于 API 级别 21 和 22,监控 HDMI 插拔事件 并使用广播中的额外数据。
- 此外,还要注册
BroadcastReceiver以监控BluetoothDevice状态更改 对于低于 API 23 的设备,因为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);