Fonctionnalités audio

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érentes encodages (Dolby Digital+, DTS, PCM, etc.), fréquences d'échantillonnage et canaux. Les téléviseurs connectés en HDMI sont compatibles avec de nombreux formats, mais le Bluetooth n'est compatible qu'avec le PCM.

Les appareils audio et le routage disponibles peuvent changer pendant la lecture : les utilisateurs peuvent brancher un appareil 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 fonctionnalités du nouvel appareil. Les formats non compatibles peuvent entraîner des erreurs ou un silence.

Proposez plusieurs encodages pour une expérience optimale en fonction des fonctionnalités de l'appareil. Par exemple, Dolby Digital est utilisé s'il est compatible. Sinon, l'application revient au PCM. Consultez Formats multimédias compatibles pour les décodeurs Android qui transforment les flux en PCM.

Lors de la lecture, votre application de streaming doit créer un AudioTrack avec le meilleur AudioFormat compatible avec l'appareil audio de sortie.

Créer une piste au bon format

Les applications doivent créer un AudioTrack, lancer la lecture 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 en PCM courte et silencieuse utilisée 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 supérieur) ou isDirectPlaybackSupported() (niveau d'API 29 ou supérieur) pour vérifier les combinaisons compatibles de format, de nombre de canaux et de fréquence d'échantillonnage.

Certains appareils Android sont compatibles avec 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é avec le format choisi, même s'il n'est pas présent dans la liste renvoyée par getEncodings().

Route audio anticipée

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 contenu multimédia 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. Elle est classée par ordre de préférence, la plus préférée en premier et la moins préférée en dernier. getDirectProfilesForAttributes() renvoie une liste d'objets AudioProfile compatibles pour l'appareil audio actuellement routé avec les AudioAttributes fournis. La liste des éléments AudioFormat préférés est parcourue jusqu'à ce qu'un AudioProfile compatible correspondant soit trouvé. Ceci 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 appropriée AudioFormat 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é).

Intercepter les modifications de l'appareil audio

Pour intercepter les modifications de l'appareil audio et y réagir, les applications doivent procéder comme suit :

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

Lorsqu'une modification de l'appareil audio a été détectée pour l'AudioTrack, l'application doit vérifier les fonctionnalités audio mises à jour et, si nécessaire, recréer l'AudioTrack avec un AudioFormat différent. Procédez ainsi si un encodage de meilleure qualité est désormais compatible ou si l'encodage précédemment utilisé n'est plus compatible.

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);