Les appareils Android TV peuvent disposer de plusieurs sorties audio connectées en même temps : enceintes de téléviseur, home cinéma connecté en HDMI, casques Bluetooth, etc. Ces périphériques de sortie audio sont compatibles avec différentes fonctionnalités audio, telles que 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 casques Bluetooth connectés ne sont généralement compatibles qu'avec le PCM.
La liste des appareils audio disponibles et de l'appareil audio routé peut également être modifiée par le branchement à chaud des appareils HDMI, la connexion ou la déconnexion des écouteurs Bluetooth, ou la modification des paramètres audio par l'utilisateur. Étant donné que les capacités de sortie audio peuvent changer même lorsque les applications lisent des contenus multimédias, les applications doivent s'adapter de manière fluide à ces modifications et poursuivre la lecture sur le nouvel appareil audio acheminé et ses fonctionnalités. La sortie d'un format audio incorrect peut entraîner des erreurs ou l'absence de son.
Les applications peuvent diffuser le même contenu dans plusieurs encodages pour offrir à l'utilisateur la meilleure expérience audio en fonction des capacités de l'appareil audio. Par exemple, un flux audio encodé en Dolby Digital est lu s'il est compatible avec le téléviseur, tandis qu'un flux audio PCM plus largement pris en charge est choisi lorsque Dolby Digital n'est pas compatible. La liste des décodeurs Android intégrés utilisés pour transformer un flux audio en PCM est disponible dans la section Formats multimédias compatibles.
Au moment de la lecture, l'application de streaming doit créer un AudioTrack
avec la meilleure AudioFormat
prise en charge par l'appareil audio de sortie.
Créer une piste au format approprié
Les applications doivent créer un AudioTrack
, commencer à le lire et appeler getRoutedDevice()
pour déterminer l'appareil audio par défaut à partir duquel diffuser le son.
Il peut s'agir, par exemple, d'une piste sécurisée encodée 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 sur l'appareil audio par défaut.
Vérifier les profils et formats audio compatibles
Utilisez AudioProfile
(niveau d'API 31 ou supérieur) ou isDirectPlaybackSupported()
(niveau d'API 29 ou supérieur) pour vérifier les combinaisons de format, nombre de canaux et taux d'échantillonnage acceptées.
Certains appareils Android sont compatibles avec des encodages autres que ceux compatibles avec 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 compatibilité avec le format souhaité, même s'il ne figure pas dans la liste renvoyée par getEncodings()
.
Piste audio anticipée
Android 13 (niveau d'API 33) a introduit les itinéraires 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 compatible avec l'appareil audio actuellement acheminé 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 pour la lecture directe de contenus multimédias via l'appareil audio actuellement routé. Cela exclut tous les profils qui ne sont pas 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'instances AudioFormat
. Elles sont classées selon les préférences les plus populaires en premier et les moins préférées en dernier.
getDirectProfilesForAttributes()
renvoie une liste d'objets AudioProfile
compatibles pour l'appareil audio actuellement routé avec l'AudioAttributes
fourni. La liste des éléments AudioFormat
préférés est itérée jusqu'à ce qu'un AudioProfile
compatible correspondant soit trouvé. Ce AudioProfile
est stocké en tant que bestAudioProfile
.
Les taux d'échantillonnage et masques de chaîne optimaux sont déterminés à partir de bestAudioProfile
.
Enfin, une instance AudioFormat
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 haute qualité compatible avec l'appareil audio par défaut (et disponible pour le contenu sélectionné).
Intercepter les modifications apportées à l'appareil audio
Pour intercepter les modifications apportées à l'appareil audio et y réagir, les applications doivent:
- Pour les niveaux d'API égaux ou supérieurs à 24, ajoutez un
OnRoutingChangedListener
afin de 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 les événements de prise HDMI et utilisez les données supplémentaires des diffusions.
- Enregistrez également un
BroadcastReceiver
pour surveiller les changements d'état deBluetoothDevice
pour les appareils inférieurs à l'API 23, carAudioDeviceCallback
n'est pas encore compatible.
Lorsqu'un changement d'appareil audio a été détecté pour le AudioTrack
, l'application doit vérifier les fonctionnalités audio mises à jour et, si nécessaire, recréer le AudioTrack
avec un autre AudioFormat
. Faites-le si un encodage de meilleure qualité est désormais accepté ou si l'encodage précédemment utilisé n'est plus accepté.
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);