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

Устройства 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);