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

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