Устройства Android TV могут иметь несколько аудиовыходов, подключенных одновременно: динамики телевизора, домашний кинотеатр, подключенный через HDMI, наушники Bluetooth и т. д. Эти устройства аудиовыходов могут поддерживать различные аудиовозможности, такие как кодировки (Dolby Digital+, DTS и PCM), частоту дискретизации и каналы. Например, телевизоры, подключенные через HDMI, поддерживают множество кодировок, тогда как подключенные наушники Bluetooth обычно поддерживают только PCM.
Список доступных аудиоустройств и маршрутизируемое аудиоустройство также могут меняться при горячем подключении устройств HDMI, подключении или отключении наушников Bluetooth или изменении пользователем аудионастроек. Поскольку возможности вывода звука могут меняться даже при воспроизведении мультимедиа приложениями, приложениям необходимо корректно адаптироваться к этим изменениям и продолжать воспроизведение на новом маршрутизируемом аудиоустройстве и его возможностях. Вывод неправильного аудиоформата может привести к ошибкам или отсутствию звука.
Приложения имеют возможность выводить один и тот же контент в нескольких кодировках, чтобы предложить пользователю наилучшее качество звука в зависимости от возможностей аудиоустройства. Например, аудиопоток в кодировке Dolby Digital воспроизводится, если телевизор его поддерживает, в то время как более широко поддерживаемый аудиопоток PCM выбирается, если поддержка Dolby Digital отсутствует. Список встроенных декодеров 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);