Возможности аудио

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():

Kotlin

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:

Kotlin

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. Это нужно сделать, если теперь поддерживается кодировка более высокого качества или ранее использовавшаяся кодировка больше не поддерживается.

Пример кода

Kotlin

// 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);