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

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