Аудио возможности

Устройства Android TV могут одновременно подключать несколько аудиовыходов: телевизионные колонки, домашние кинотеатры с HDMI-подключением, Bluetooth-наушники и так далее. Эти устройства аудиовыхода могут поддерживать различные аудиовозможности, такие как кодирование (Dolby Digital+, DTS и PCM), частота дискретизации и количество каналов. Например, телевизоры с HDMI-подключением поддерживают множество кодировок, в то время как подключенные Bluetooth-наушники обычно поддерживают только PCM.

Список доступных аудиоустройств и маршрутизируемое аудиоустройство также могут изменяться при подключении HDMI-устройств в режиме «на горячую», подключении или отключении Bluetooth-наушников, а также при изменении пользователем настроек звука. Поскольку возможности вывода звука могут меняться даже во время воспроизведения мультимедиа, приложениям необходимо корректно адаптироваться к этим изменениям и продолжать воспроизведение на новом маршрутизируемом аудиоустройстве с использованием его возможностей. Вывод неправильного аудиоформата может привести к ошибкам или отсутствию звука.

Приложения способны выводить один и тот же контент в нескольких кодировках, чтобы обеспечить пользователю наилучшее качество звука в зависимости от возможностей аудиоустройства. Например, если телевизор поддерживает аудиопоток Dolby Digital, воспроизводится аудиопоток в формате PCM, а если поддержка 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);