Fonctionnalités audio

Les appareils Android TV peuvent avoir plusieurs sorties audio connectées en même temps : haut-parleurs du téléviseur, home cinéma connecté via HDMI, casque Bluetooth, etc. Ces appareils de sortie audio peuvent prendre en charge différentes fonctionnalités audio, comme 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 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 périphériques audio disponibles et le périphérique audio routé peuvent également changer en cas de branchement à chaud de périphériques HDMI, de connexion ou de déconnexion d'écouteurs Bluetooth, ou si l'utilisateur modifie les paramètres audio. Étant donné que les capacités de sortie audio peuvent changer même lorsque les applications lisent du contenu multimédia, les applications doivent s'adapter correctement à ces changements et continuer la lecture sur le nouvel appareil audio routé et ses capacités. Si vous sélectionnez un format audio incorrect, des erreurs peuvent se produire ou aucun son ne sera 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 capacités de son 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 en l'absence 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.

Au moment 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 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 encodée PCM 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 versions ultérieures) ou getEncodings() (niveau d'API 23 et versions ultérieures) pour déterminer les formats audio disponibles sur le périphérique 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é avec le format souhaité, 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 des appareils 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 le périphérique 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'instances AudioFormat. Ils sont classés par ordre de préférence, du plus préféré au moins préféré. getDirectProfilesForAttributes() renvoie une liste d'objets AudioProfile compatibles pour le périphérique audio actuellement routé avec le AudioAttributes fourni. 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 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 modifications apportées aux périphériques audio et y réagir, les applications doivent :

  • Pour les niveaux d'API supérieurs ou égaux à 24, ajoutez un OnRoutingChangedListener pour surveiller les modifications apportées aux appareils 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 branchement HDMI et utilisez les données supplémentaires des diffusions.
  • Enregistrez également un BroadcastReceiver pour surveiller les changements d'état BluetoothDevice pour les appareils dont le niveau d'API est inférieur à 23, car AudioDeviceCallback n'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 AudioFormat différent. 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);