Fonctionnalités audio

Les appareils Android TV peuvent disposer de plusieurs sorties audio connectées en même temps: Les enceintes TV, le home cinéma connecté en HDMI, les écouteurs Bluetooth, etc. Ces périphériques de sortie audio peuvent prendre en charge différentes capacités audio, comme les encodages (Dolby Digital+, DTS et PCM), le taux d'échantillonnage et les canaux. Par exemple, les téléviseurs connectés en HDMI sont compatibles avec une multitude d'encodages. tandis que les écouteurs Bluetooth connectés ne sont généralement compatibles qu'avec le PCM.

La liste des appareils audio disponibles et l'appareil audio acheminé peut également changer en branchant à chaud des appareils HDMI, en connectant ou en débranchant des écouteurs Bluetooth, ou l'utilisateur modifie les paramètres audio. Étant donné que les capacités de sortie audio peuvent même lorsque les applications lisent des contenus multimédias, celles-ci doivent s'adapter change et continue la lecture sur le nouvel appareil audio des fonctionnalités. L'émission d'un format audio incorrect peut entraîner des erreurs ou pas de son.

Les applications peuvent générer le même contenu dans plusieurs encodages pour offrir à l'utilisateur la meilleure expérience audio en fonction de l'appareil audio des fonctionnalités. Par exemple, un flux audio encodé en Dolby Digital est lu si le téléviseur le prend en charge, tandis qu'un flux audio PCM plus largement pris en charge est choisi en l'absence de compatibilité avec Dolby Digital. La liste des applications Android intégrées les décodeurs utilisés pour transformer un flux audio en PCM sont disponibles dans Formats multimédias acceptés.

Au moment de la lecture, l'application de streaming doit créer un AudioTrack avec les meilleurs AudioFormat pris en charge par la sortie appareil audio.

Créez une piste au bon format

Les applications doivent créer un AudioTrack, lancer la lecture et appeler getRoutedDevice() pour déterminer l'appareil audio par défaut à partir duquel le son doit être diffusé. Il peut s'agir, par exemple, d'une piste avec silence court et sûr, encodée par PCM et utilisée uniquement pour déterminer l'appareil routé et ses capacités audio.

Obtenir les encodages compatibles

Utilisez getAudioProfiles() (niveau d'API 31 ou supérieur) ou getEncodings() (niveau d'API 23 ou supérieur) pour déterminer les formats audio disponibles appareil audio par défaut.

Vérifier les profils et les formats audio compatibles

Utiliser AudioProfile (niveau d'API 31 ou supérieur) ou isDirectPlaybackSupported() (niveau d'API 29 ou supérieur) pour vérifier les combinaisons de formats acceptées. le nombre de canaux et le taux d'échantillonnage.

Certains appareils Android sont capables de prendre en charge d'autres encodages pris en charge par l'appareil audio de sortie. Ces formats supplémentaires doivent être détecté via isDirectPlaybackSupported(). Dans ce cas, les données audio est réencodé dans un format compatible avec le périphérique audio de sortie. Utilisez isDirectPlaybackSupported() pour vérifier que le format est compatible avec le format même s'il ne figure pas dans la liste renvoyée par getEncodings().

Itinéraire audio anticipé

Android 13 (niveau d'API 33) a introduit les routes audio anticipées. Vous pouvez anticipent la prise en charge des attributs audio de l'appareil et préparez les pistes appareil audio. Vous pouvez utiliser getDirectPlaybackSupport() pour vérifier si la lecture directe est compatible avec l'audio actuellement acheminé. appareil pour un format et des attributs donnés:

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
}

Vous pouvez également demander les profils compatibles avec les médias directs via l'appareil audio actuellement acheminé. Cela exclut les profils qui ne sont pas compatibles ou qui seraient, par exemple, transcodés par le framework:

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();
}

Dans cet exemple, preferredFormats est une liste de Instances AudioFormat. Elle est ordonnée de la plus appréciée en premier et de la moins préférée en dernier. getDirectProfilesForAttributes() renvoie une liste Objets AudioProfile pour de l'appareil audio actuellement acheminé, AudioAttributes La liste des les éléments AudioFormat préférés sont itérés jusqu'à ce qu'une correspondance AudioProfile a été trouvé. Cet élément AudioProfile est stocké sous le nom bestAudioProfile. Les taux d'échantillonnage et les masques de chaîne optimaux sont déterminés à partir du bestAudioProfile. Enfin, un AudioFormat approprié est créée.

Créer une piste audio

Les applications doivent utiliser ces informations afin de créer un AudioTrack pour le AudioFormat de qualité supérieure compatible avec l'appareil audio par défaut (et disponibles pour le contenu sélectionné).

Intercepter les changements d'appareil audio

Pour intercepter les modifications apportées aux appareils audio et y réagir, les applications doivent:

  • Pour les niveaux d'API égaux ou supérieurs à 24, ajoutez un OnRoutingChangedListener pour surveiller les modifications apportées à l'appareil audio (HDMI, Bluetooth, etc.).
  • Pour le niveau d'API 23, enregistrez un AudioDeviceCallback pour recevoir les modifications apportées à la liste des appareils audio disponibles.
  • Pour les niveaux d'API 21 et 22, surveillez Événements liés aux prises HDMI et utiliser les données supplémentaires provenant des diffusions.
  • Enregistrez également un BroadcastReceiver pour surveiller Changements d'état de BluetoothDevice pour les appareils avec un niveau d'API inférieur 23, AudioDeviceCallback n'est pas déjà pris en charge.

Lorsqu'un changement d'appareil audio a été détecté pour AudioTrack, l'appli vérifiez les nouvelles fonctionnalités audio et, si nécessaire, recréez AudioTrack par un AudioFormat différent. Faites-le si un test de qualité est désormais compatible ou que l'encodage précédemment utilisé est n'est plus pris en charge.

Exemple de code

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