Устройства Android TV могут иметь несколько аудиовыходов одновременно: динамики телевизора, домашний кинотеатр, подключенный по HDMI, Bluetooth-наушники и так далее. Эти устройства могут поддерживать различные аудиовозможности, такие как кодирование (Dolby Digital+, DTS и PCM), частоту дискретизации и количество каналов. Например, телевизоры, подключенные по HDMI, поддерживают множество кодировок, в то время как подключенные Bluetooth-наушники обычно поддерживают только PCM.
Список доступных аудиоустройств и маршрутизируемое аудиоустройство также могут меняться при горячем подключении HDMI-устройств, подключении или отключении Bluetooth-наушников или изменении пользователем настроек звука. Поскольку возможности вывода звука могут меняться даже во время воспроизведения медиафайлов приложениями, приложениям необходимо корректно адаптироваться к этим изменениям и продолжать воспроизведение на новом маршрутизируемом аудиоустройстве с использованием его возможностей. Использование неправильного формата аудио может привести к ошибкам или отсутствию звука.
Приложения могут выводить один и тот же контент в различных кодировках, обеспечивая пользователю наилучшее качество звука в зависимости от возможностей аудиоустройства. Например, аудиопоток в кодировке Dolby Digital воспроизводится, если телевизор его поддерживает, а при отсутствии поддержки Dolby Digital выбирается более распространённый аудиопоток 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()
можно проверить, поддерживается ли прямое воспроизведение на текущем маршрутизируемом аудиоустройстве для заданного формата и атрибутов:
Котлин
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 }
Ява
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:
Котлин
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() }
Ява
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
.
Создать звуковую дорожку
Приложения должны использовать эту информацию для создания AudioTrack
для AudioFormat
наивысшего качества, поддерживаемого аудиоустройством по умолчанию (и доступного для выбранного контента).
Перехват изменений аудиоустройства
Чтобы перехватывать изменения аудиоустройства и реагировать на них, приложения должны:
- Для уровней API, равных или превышающих 24, добавьте
OnRoutingChangedListener
для отслеживания изменений аудиоустройства (HDMI, Bluetooth и т. д.). - Для API уровня 23 зарегистрируйте
AudioDeviceCallback
для получения изменений в списке доступных аудиоустройств. - Для уровней API 21 и 22 отслеживайте события подключения HDMI и используйте дополнительные данные из трансляций.
- Также зарегистрируйте
BroadcastReceiver
для отслеживания изменений состоянияBluetoothDevice
для устройств с API ниже 23, посколькуAudioDeviceCallback
пока не поддерживается.
При обнаружении изменения аудиоустройства для AudioTrack
приложение должно проверить обновлённые аудиовозможности и при необходимости пересоздать AudioTrack
с другим AudioFormat
. Это следует сделать, если теперь поддерживается кодировка более высокого качества или ранее использовавшаяся кодировка больше не поддерживается.
Пример кода
Котлин
// 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)
Ява
// 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);