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é 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 de BluetoothDevice pour les appareils inférieurs à l'API 23, car AudioDeviceCallback 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);