Audio et MMAP

AAudio est une API audio introduite dans la version Android 8.0. La version Android 8.1 présente des améliorations permettant de réduire la latence lorsqu'elle est utilisée conjointement avec un HAL et un pilote prenant en charge MMAP. Ce document décrit la couche d'abstraction matérielle (HAL) et les modifications de pilote nécessaires pour prendre en charge la fonctionnalité MMAP d'AAudio dans Android.

La prise en charge d’AAudio MMAP nécessite :

  • rapporter les capacités MMAP du HAL
  • mise en œuvre de nouvelles fonctions dans le HAL
  • implémentant éventuellement un ioctl() personnalisé pour le tampon du mode EXCLUSIF
  • fournir un chemin de données matériel supplémentaire
  • définition des propriétés du système qui activent la fonctionnalité MMAP

Architecture audio

AAudio est une nouvelle API C native qui offre une alternative à Open SL ES. Il utilise un modèle de conception Builder pour créer des flux audio.

AAudio fournit un chemin de données à faible latence. En mode EXCLUSIF, la fonctionnalité permet au code de l'application client d'écrire directement dans un tampon mappé en mémoire partagé avec le pilote ALSA. En mode SHARED, le tampon MMAP est utilisé par un mixeur exécuté dans l'AudioServer. En mode EXCLUSIF, la latence est nettement inférieure car les données contournent le mixeur.

En mode EXCLUSIF, le service demande le tampon MMAP au HAL et gère les ressources. Le tampon MMAP s'exécute en mode NOIRQ, il n'y a donc pas de compteurs de lecture/écriture partagés pour gérer l'accès au tampon. Au lieu de cela, le client maintient un modèle de synchronisation du matériel et prédit le moment où le tampon sera lu.

Dans le diagramme ci-dessous, nous pouvons voir les données de modulation par impulsion et code (PCM) circulant via le MMAP FIFO vers le pilote ALSA. Les horodatages sont périodiquement demandés par le service AAudio, puis transmis au modèle de synchronisation du client via une file d'attente de messages atomiques.

Diagramme de flux de données PCM.
Figure 1. Flux de données PCM via FIFO vers ALSA

En mode SHARED, un modèle de synchronisation est également utilisé, mais il réside dans AAudioService.

Pour la capture audio, un modèle similaire est utilisé, mais les données PCM circulent dans la direction opposée.

Changements dans HAL

Pour tinyALSA, voir :

external/tinyalsa/include/tinyalsa/asoundlib.h
external/tinyalsa/include/tinyalsa/pcm.c
int pcm_start(struct pcm *pcm);
int pcm_stop(struct pcm *pcm);
int pcm_mmap_begin(struct pcm *pcm, void **areas,
           unsigned int *offset,
           unsigned int *frames);
int pcm_get_poll_fd(struct pcm *pcm);
int pcm_mmap_commit(struct pcm *pcm, unsigned int offset,
           unsigned int frames);
int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr,
           struct timespec *tstamp);

Pour l'ancien HAL, voir :

hardware/libhardware/include/hardware/audio.h
hardware/qcom/audio/hal/audio_hw.c
int start(const struct audio_stream_out* stream);
int stop(const struct audio_stream_out* stream);
int create_mmap_buffer(const struct audio_stream_out *stream,
                        int32_t min_size_frames,
                        struct audio_mmap_buffer_info *info);
int get_mmap_position(const struct audio_stream_out *stream,
                        struct audio_mmap_position *position);

Pour le HAL audio HIDL :

hardware/interfaces/audio/2.0/IStream.hal
hardware/interfaces/audio/2.0/types.hal
hardware/interfaces/audio/2.0/default/Stream.h
start() generates (Result retval);
stop() generates (Result retval) ;
createMmapBuffer(int32_t minSizeFrames)
       generates (Result retval, MmapBufferInfo info);
getMmapPosition()
       generates (Result retval, MmapPosition position);

Signaler la prise en charge de MMAP

La propriété système "aaudio.mmap_policy" doit être définie sur 2 (AAUDIO_POLICY_AUTO) afin que le framework audio sache que le mode MMAP est pris en charge par le HAL audio. (voir « Activation du chemin de données AAudio MMAP » ci-dessous.)

Le fichier audio_policy_configuration.xml doit également contenir un profil de sortie et d'entrée spécifique au mode MMAP/NO IRQ afin que Audio Policy Manager sache quel flux ouvrir lors de la création de clients MMAP :

<mixPort name="mmap_no_irq_out" role="source"
            flags="AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_MMAP_NOIRQ">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                                samplingRates="48000"
                                channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>

<mixPort name="mmap_no_irq_in" role="sink" flags="AUDIO_INPUT_FLAG_MMAP_NOIRQ">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                                samplingRates="48000"
                                channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
</mixPort>

Ouvrir et fermer un flux MMAP

createMmapBuffer(int32_t minSizeFrames)
            generates (Result retval, MmapBufferInfo info);

Le flux MMAP peut être ouvert et fermé en appelant les fonctions Tinyalsa.

Interroger la position MMAP

L'horodatage renvoyé au modèle de synchronisation contient une position de trame et une heure MONOTONIQUE en nanosecondes :

getMmapPosition()
        generates (Result retval, MmapPosition position);

Le HAL peut obtenir ces informations du pilote ALSA en appelant une nouvelle fonction Tinyalsa :

int pcm_mmap_get_hw_ptr(struct pcm* pcm,
                        unsigned int *hw_ptr,
                        struct timespec *tstamp);

Descripteurs de fichiers pour la mémoire partagée

Le chemin de données AAudio MMAP utilise une région de mémoire partagée entre le matériel et le service audio. La mémoire partagée est référencée à l'aide d'un descripteur de fichier généré par le pilote ALSA.

Modifications du noyau

Si le descripteur de fichier est directement associé à un fichier de pilote /dev/snd/ , alors il peut être utilisé par le service AAudio en mode SHARED. Mais le descripteur ne peut pas être transmis au code client pour le mode EXCLUSIF. Le descripteur de fichier /dev/snd/ fournirait un accès trop large au client, il est donc bloqué par SELinux.

Afin de prendre en charge le mode EXCLUSIF, il est nécessaire de convertir le descripteur /dev/snd/ en un descripteur de fichier anon_inode:dmabuf . SELinux permet de transmettre ce descripteur de fichier au client. Il peut également être utilisé par AAudioService.

Un descripteur de fichier anon_inode:dmabuf peut être généré à l'aide de la bibliothèque de mémoire Android Ion.

Pour plus d’informations, consultez ces ressources externes :

  1. "L'allocateur de mémoire Android ION" https://lwn.net/Articles/480055/
  2. "Présentation d'Android ION" https://wiki.linaro.org/BenjaminGaignard/ion
  3. "Intégration de l'allocateur de mémoire ION" https://lwn.net/Articles/565469/

Changements dans HAL

Le service AAudio doit savoir si cet anon_inode:dmabuf est pris en charge. Avant Android 10.0, la seule façon de procéder était de transmettre la taille du tampon MMAP sous forme de nombre négatif, par exemple. -2048 au lieu de 2048, si pris en charge. Sous Android 10.0 et versions ultérieures, vous pouvez définir l'indicateur AUDIO_MMAP_APPLICATION_SHAREABLE .

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

Modifications du sous-système audio

AAudio nécessite un chemin de données supplémentaire au niveau de l'avant audio du sous-système audio afin de pouvoir fonctionner en parallèle avec le chemin AudioFlinger d'origine. Ce chemin hérité est utilisé pour tous les autres sons du système et sons d’application. Cette fonctionnalité pourrait être fournie par un mélangeur logiciel dans un DSP ou un mélangeur matériel dans le SOC.

Activer le chemin de données AAudio MMAP

AAudio utilisera l'ancien chemin de données AudioFlinger si MMAP n'est pas pris en charge ou ne parvient pas à ouvrir un flux. AAudio fonctionnera donc avec un périphérique audio qui ne prend pas en charge le chemin MMAP/NOIRQ.

Lorsque vous testez la prise en charge de MMAP pour AAudio, il est important de savoir si vous testez réellement le chemin de données MMAP ou si vous testez simplement le chemin de données existant. Ce qui suit décrit comment activer ou forcer des chemins de données spécifiques et comment interroger le chemin utilisé par un flux.

Propriétés du système

Vous pouvez définir la stratégie MMAP via les propriétés système :

  • 1 = AAUDIO_POLICY_NEVER - Utilisez uniquement le chemin existant. N'essayez même pas d'utiliser MMAP.
  • 2 = AAUDIO_POLICY_AUTO - Essayez d'utiliser MMAP. Si cela échoue ou n’est pas disponible, utilisez le chemin hérité.
  • 3 = AAUDIO_POLICY_ALWAYS - Utilisez uniquement le chemin MMAP. Ne revenez pas au chemin hérité.

Ceux-ci peuvent être définis dans le Makefile de l'appareil, comme ceci :

# Enable AAudio MMAP/NOIRQ data path.
# 2 is AAUDIO_POLICY_AUTO so it will try MMAP then fallback to Legacy path.
PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_policy=2
# Allow EXCLUSIVE then fall back to SHARED.
PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_exclusive_policy=2

Vous pouvez également remplacer ces valeurs après le démarrage du périphérique. Vous devrez redémarrer le serveur audio pour que la modification prenne effet. Par exemple, pour activer le mode AUTO pour MMAP :

adb root
adb shell setprop aaudio.mmap_policy 2
adb shell killall audioserver

Certaines fonctions fournies dans ndk/sysroot/usr/include/aaudio/AAudioTesting.h vous permettent de remplacer la stratégie d'utilisation du chemin MMAP :

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

Pour savoir si un flux utilise le chemin MMAP, appelez :

bool AAudioStream_isMMapUsed(AAudioStream* stream);