音频功能

Android TV 设备可以同时连接多个音频输出设备: 电视音箱、连接 HDMI 的家庭影院、蓝牙耳机等。 这些音频输出设备可以支持不同的音频功能, 例如编码(杜比数字+、DTS 和 PCM)、采样率和声道。 例如,连接 HDMI 的电视支持多种编码 而连接的蓝牙耳机通常仅支持 PCM。

可用音频设备和路由音频设备的列表也可能发生变化 热插拔 HDMI 设备、连接或断开蓝牙耳机、 或用户更改音频设置。由于音频输出功能 应用需要适当地适应这些变化,即使应用正在播放媒体内容, 更改并继续在新的路由音频设备及其 功能。输出错误的音频格式可能会导致错误或 没有声音。

应用能够以多种编码输出相同的内容 根据音频设备为用户提供最佳音频体验 功能。例如,在系统播放杜比数字编码的音频流时, 如果电视支持的话,那么支持更为广泛的 PCM 音频串流 。内置 Android 设备列表 可将音频流转换为 PCM 的解码器可在以下位置找到: 支持的媒体格式

在播放时,影音在线播放应用应创建一个 AudioTrack(含最佳评价) 输出支持 AudioFormat 音频设备。

使用正确的格式创建曲目

应用应创建一个 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();
}

在此示例中,preferredFormatsAudioFormat 实例。有序 列表中优先顺序由高到低排列的,而最不优先考虑的则放在最后 getDirectProfilesForAttributes() 会返回支持的 AudioProfile 当前路由的音频设备, AudioAttributes。 系统会对首选的 AudioFormat 项进行迭代,直到找到支持的匹配项 已找到 AudioProfile。此 AudioProfile 存储为 bestAudioProfile。 最佳采样率和声道掩码由 bestAudioProfile 确定。 最后,一个适当的 AudioFormat 实例。

创建音轨

应用应使用此信息为AudioTrack 默认音频设备支持的最高音质 AudioFormat (适用于所选内容)。

拦截音频设备更改

如需拦截并响应音频设备变化,应用应:

  • 对于等于或大于 24 的 API 级别,请添加 OnRoutingChangedListener 来监控音频设备的变化(HDMI、蓝牙等)。
  • 对于 API 级别 23,请注册 AudioDeviceCallback 以接收可用音频设备列表中的更改。
  • 对于 API 级别 21 和 22,请监控 HDMI 插头事件 并使用广播中的额外数据
  • 同时注册 BroadcastReceiver 以进行监控 BluetoothDevice 状态变化 因为 API 级别低于 23 AudioDeviceCallback不是 尚未获得支持。

当检测到 AudioTrack 的音频设备发生更改时,应用 应检查更新后的音频功能,并根据需要重新创建 具有其他 AudioFormatAudioTrack。如果图片质量较高, 编码受支持,或者之前使用的编码为 不再提供支持

示例代码

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);