Fonctionnalités audio

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é via HDMI, casque 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 par 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 changer en branchant les appareils HDMI, en connectant ou en déconnectant des écouteurs Bluetooth, ou en modifiant les paramètres audio par l'utilisateur. Étant donné que les fonctionnalités de sortie audio peuvent changer même lorsque les applications lisent des contenus multimédias, celles-ci 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 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é en Dolby Digital est lu si le téléviseur le prend en charge, tandis qu'un flux audio PCM, plus largement accepté, est choisi lorsqu'il n'est pas compatible avec Dolby Digital. La liste des décodeurs Android intégrés utilisés pour transformer un flux audio au format 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 compatible avec l'appareil audio de sortie.

Créez une piste au bon format

Les applications doivent créer un AudioTrack, lancer sa lecture et appeler getRoutedDevice() pour déterminer l'appareil audio par défaut à partir duquel le son doit être diffusé. Il peut s'agir, par exemple, d'une piste encodée PCM avec silence court et sûr qui sert uniquement à 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 les 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, de nombre de canaux et de taux d'échantillonnage compatibles.

Certains appareils Android sont capables de prendre en charge des encodages autres que 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 compatibilité du format souhaité, même s'il ne figure pas dans la liste renvoyée par getEncodings().

Itinéraire audio anticipé

Android 13 (niveau d'API 33) a introduit les routes audio anticipées. 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 avec la lecture directe de contenu multimédia via l'appareil audio actuellement acheminé. 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. Elles sont triées de la plus préférée en premier dans la liste et de la moins préférée en dernier. getDirectProfilesForAttributes() renvoie une liste d'objets AudioProfile compatibles pour l'appareil audio actuellement acheminé avec le 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é. Cet élément AudioProfile est stocké sous le nom bestAudioProfile. Les taux d'échantillonnage et les masques de chaîne optimaux sont déterminés à partir du bestAudioProfile. Enfin, une instance AudioFormat appropriée est créée.

Créer une piste audio

Les applications doivent utiliser ces informations afin de créer un AudioTrack pour l'AudioFormat de haute qualité compatible avec l'appareil audio par défaut (et disponible pour le contenu sélectionné).

Intercepter les changements d'appareil 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 pour 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 de BluetoothDevice pour les appareils avec un niveau d'API inférieur à 23, car AudioDeviceCallback n'est pas encore pris en charge.

Lorsqu'un changement d'appareil audio a été détecté pour AudioTrack, l'application doit vérifier les fonctionnalités audio mises à jour et, si nécessaire, recréer AudioTrack avec un AudioFormat différent. Effectuez cette opération 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);