Android TV est compatible avec plusieurs sorties audio simultanées : haut-parleurs du téléviseur, home cinéma HDMI, Bluetooth, etc. Ces appareils sont compatibles avec différents encodages (Dolby Digital+, DTS, PCM, etc.), fréquences d'échantillonnage et canaux. Les téléviseurs connectés via HDMI sont compatibles avec de nombreux formats, mais le Bluetooth n'est compatible qu'avec le format PCM.
Les appareils audio et le routage disponibles peuvent changer pendant la lecture : les utilisateurs peuvent brancher un câble HDMI à chaud, connecter un appareil Bluetooth ou modifier les paramètres. Les applications doivent s'adapter correctement pour assurer une lecture continue à l'aide des nouvelles fonctionnalités de l'appareil. Les formats non compatibles peuvent entraîner des erreurs ou des silences.
Proposez plusieurs encodages pour offrir la meilleure expérience possible en fonction des capacités de l'appareil. Par exemple, Dolby Digital est utilisé s'il est pris en charge. Sinon, l'application revient à PCM. Consultez Formats multimédias acceptés pour connaître les décodeurs Android qui transforment les flux en PCM.
Au moment de la lecture, votre application de streaming doit créer un AudioTrack avec le meilleur AudioFormat pris en charge par le périphérique 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'une piste PCM encodée avec un court silence sûr, utilisée uniquement pour déterminer l'appareil routé et ses capacités audio.
Obtenir les encodages acceptés
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 versions ultérieures) ou isDirectPlaybackSupported() (niveau d'API 29 et versions ultérieures) pour vérifier les combinaisons compatibles de format, de nombre de canaux et de fréquence d'échantillonnage.
Certains appareils Android sont capables de prendre en charge des encodages au-delà de ceux pris en charge par le périphérique 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 de sortie audio. Utilisez isDirectPlaybackSupported() pour vérifier correctement la compatibilité du format choisi, même s'il n'est pas présent dans la liste renvoyée par getEncodings().
Acheminement audio anticipé
Android 13 (niveau d'API 33) a introduit les routes audio anticipées. Vous pouvez anticiper la compatibilité 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 directe de contenus multimédias via l'appareil audio actuellement routé. Cela exclut tous les profils non compatibles ou qui, par exemple, seraient 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.
Les langues sont listées par ordre de préférence, de la plus à la moins préférée. getDirectProfilesForAttributes() renvoie une liste d'objets AudioProfile compatibles pour l'appareil audio actuellement routé avec le AudioAttributes fourni. La liste des éléments AudioFormat préférés est parcourue jusqu'à ce qu'un élément AudioProfile compatible correspondant soit trouvé. Ce AudioProfile est stocké sous la forme bestAudioProfile. Les fréquences 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é pris en charge par le périphérique audio par défaut (et disponible pour le contenu sélectionné).
Intercepter les modifications apportées aux périphériques audio
Pour intercepter les changements d'appareil audio et y réagir, les applications doivent :
- Pour les niveaux d'API supérieurs ou égaux à 24, ajoutez un
OnRoutingChangedListenerpour surveiller les modifications apportées aux périphériques 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 branchement HDMI et utilisez les données supplémentaires des diffusions.
- Enregistrez également un
BroadcastReceiverpour surveiller les changements d'étatBluetoothDevicepour les appareils dont le niveau d'API est inférieur à 23, carAudioDeviceCallbackn'est pas encore pris en charge.
Lorsqu'un changement d'appareil audio est détecté pour AudioTrack, l'application doit vérifier les capacités audio mises à jour et, si nécessaire, recréer AudioTrack avec un autre AudioFormat. Faites-le si un encodage de meilleure qualité est désormais pris en charge ou si l'encodage utilisé précédemment ne l'est plus.
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);