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