Les appareils Android TV peuvent avoir plusieurs sorties audio connectées en même temps : haut-parleurs TV, home cinéma connecté en HDMI, casque Bluetooth, etc. Ces appareils de sortie audio peuvent prendre en charge différentes fonctionnalités audio, telles que les encodages (Dolby Digital+, DTS et PCM), la fréquence d'échantillonnage et les canaux. Par exemple, les téléviseurs connectés en HDMI prennent en charge une multitude d'encodages, tandis que les écouteurs Bluetooth connectés ne prennent généralement en charge que le PCM.
La liste des appareils audio disponibles et l'appareil audio routé peuvent également changer en cas de connexion à chaud d'appareils HDMI, de connexion ou de déconnexion d'écouteurs Bluetooth, ou si l'utilisateur modifie les paramètres audio. Étant donné que les fonctionnalités de sortie audio peuvent changer même lorsque les applications lisent des contenus multimédias, elles doivent s'adapter correctement à ces modifications et continuer la lecture sur le nouvel appareil audio routé et ses fonctionnalités. Si le format audio de sortie est incorrect, des erreurs peuvent se produire ou aucun son ne peut être lu.
Les applications peuvent générer le même contenu dans plusieurs encodages pour offrir à l'utilisateur la meilleure expérience audio possible en fonction des fonctionnalités de l'appareil audio. 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 lorsqu'il n'y a pas de prise en charge de Dolby Digital. La liste des décodeurs Android intégrés utilisés pour transformer un flux audio en PCM est disponible dans Formats multimédias acceptés.
Lors de la lecture, l'application de streaming doit créer un
AudioTrack avec le meilleur
AudioFormat pris en charge par l'appareil audio de sortie.
Créer une piste au bon format
Les applications doivent créer un AudioTrack, commencer à le lire et appeler
getRoutedDevice()
pour déterminer l'appareil audio par défaut à partir duquel lire le son.
Il peut s'agir, par exemple, d'une piste encodée en PCM silencieuse et courte, utilisée uniquement pour déterminer l'appareil routé et ses fonctionnalités audio.
Obtenir les encodages compatibles
Utilisez
getAudioProfiles()
(niveau d'API 31 et supérieur) ou
getEncodings()
(niveau d'API 23 et supérieur) pour déterminer les formats audio disponibles sur l'
appareil audio par défaut.
Vérifier les profils et formats audio compatibles
Utilisez AudioProfile
(niveau d'API 31 et supérieur) ou
isDirectPlaybackSupported()
(niveau d'API 29 et supérieur) pour vérifier les combinaisons compatibles de format,
nombre de canaux et fréquence d'échantillonnage.
Certains appareils Android sont capables de prendre en charge des encodages au-delà de ceux pris en charge par l'appareil audio de sortie. Ces formats supplémentaires doivent être détectés via isDirectPlaybackSupported(). Dans ce cas, les données audio sont réencodées dans un format compatible avec l'appareil audio de sortie. Utilisez isDirectPlaybackSupported() pour vérifier correctement la prise en charge du format souhaité, même s'il n'est pas présent dans la liste renvoyée par getEncodings().
Routage audio anticipé
Android 13 (niveau d'API 33) a introduit les routages audio anticipés. Vous pouvez anticiper la prise en charge des attributs audio de l'appareil et préparer des pistes pour l'appareil audio actif. Vous pouvez utiliser
getDirectPlaybackSupport()
pour vérifier si la lecture directe est prise en charge sur l'appareil audio actuellement routé
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 interroger les profils compatibles avec la lecture directe de contenus multimédias via l'appareil audio actuellement routé. Cela exclut tous les profils non compatibles ou qui seraient, par exemple, transcodés par le framework 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(); }
Dans cet exemple, preferredFormats est une liste d'
AudioFormat instances. Elle est classée par ordre de préférence, la plus préférée en premier et la moins préférée en dernier.
getDirectProfilesForAttributes()
renvoie une liste d'objets compatibles pour l'appareil audio actuellement routé avec les
AudioProfile fournis.
AudioAttributes. La liste des éléments AudioFormat préférés est parcourue jusqu'à ce qu'un AudioProfile compatible correspondant soit trouvé. Ce AudioProfile est stocké sous le nom bestAudioProfile.
Les fréquences d'échantillonnage et les masques de canaux optimaux sont déterminés à partir de bestAudioProfile.
Enfin, une AudioFormat
instance appropriée est créée.
Créer une piste audio
Les applications doivent utiliser ces informations pour créer un AudioTrack pour le AudioFormat de la plus haute qualité pris en charge par l'appareil audio par défaut (et disponible pour le contenu sélectionné).
Intercepter les modifications de l'appareil audio
Pour intercepter les modifications de l'appareil audio et y réagir, les applications doivent procéder comme suit :
- Pour les niveaux d'API supérieurs ou égaux à 24, ajoutez un
OnRoutingChangedListenerpour surveiller les modifications de l'appareil audio (HDMI, Bluetooth, etc.). - Pour le niveau d'API 23, enregistrez un
AudioDeviceCallbackpour recevoir les modifications apportées à la liste des appareils audio disponibles. - Pour les niveaux d'API 21 et 22, surveillez les événements de connexion HDMI et utilisez les données supplémentaires des diffusions.
- Enregistrez également un
BroadcastReceiverpour surveillerBluetoothDeviceles changements d'état pour les appareils dont le niveau d'API est inférieur à 23, carAudioDeviceCallbackn'est pas encore pris en charge.
Lorsqu'une modification de l'appareil audio a été détectée pour l'AudioTrack, l'application doit vérifier les fonctionnalités audio mises à jour et, si nécessaire, recréer l'AudioTrack avec un AudioFormat différent. Procédez ainsi si un encodage de meilleure qualité est désormais pris en charge ou si l'encodage précédemment utilisé 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);