Les appareils Android TV peuvent avoir plusieurs sorties audio connectées en même temps : enceintes du téléviseur, 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), le taux d'échantillonnage et les canaux. Par exemple, les téléviseurs connectés par HDMI sont compatibles avec une multitude d'encodages, tandis que les casques 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 branchant des appareils HDMI en mode "hot-plug", en connectant ou en déconnectant des casques Bluetooth, ou en modifiant les paramètres audio. Étant donné que les fonctionnalités de sortie audio peuvent changer même lorsque les applications diffusent des contenus multimédias, elles doivent s'adapter de manière fluide à ces changements 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 générer le même contenu dans plusieurs encodages afin d'offrir à l'utilisateur la meilleure expérience audio en fonction des capacités de l'appareil audio. Par exemple, un flux audio encodé Dolby Digital est lu si le téléviseur le prend en charge, tandis qu'un flux audio PCM plus largement compatible est choisi lorsque Dolby Digital n'est pas pris en charge. La liste des décodeurs Android intégrés utilisés pour transformer un flux audio en PCM se trouve dans la section Formats multimédias acceptés.
Au moment de la lecture, l'application de streaming doit créer un AudioTrack
avec la meilleure AudioFormat
compatible avec l'appareil audio de sortie.
Créer un titre 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'un court silence encodé PCM sûr utilisé uniquement pour déterminer l'appareil routé et ses fonctionnalité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 version ultérieure) ou isDirectPlaybackSupported()
(niveau d'API 29 ou version ultérieure) pour vérifier les combinaisons de format, de nombre de canaux et de fréquence d'échantillonnage compatibles.
Certains appareils Android sont capables de prendre en charge des encodages en plus 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 le périphérique 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()
.
Itinéraire audio anticipé
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 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 multimédia directe via l'appareil audio actuellement routé. Cela exclut 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'instances AudioFormat
. Il est classé par ordre de préférence, en commençant par le plus préféré en premier et en terminant par le moins préféré.
getDirectProfilesForAttributes()
renvoie une liste des objets AudioProfile
compatibles pour l'appareil audio actuellement routé avec l'objet 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é sous la forme bestAudioProfile
.
Les taux d'échantillonnage et les masques de canaux 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 la plus haute qualité compatible avec l'appareil audio par défaut (et disponible pour le contenu sélectionné).
Interception des modifications apportées aux périphériques 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
afin de surveiller les changements d'appareil audio (HDMI, Bluetooth, etc.). - Pour le niveau d'API 23, enregistrez un
AudioDeviceCallback
afin de 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 antérieurs à l'API 23, carAudioDeviceCallback
n'est pas encore pris en charge.
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 AudioFormat
différent. Effectuez cette opération 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);