Audio et MMAP

AAudio est une API audio introduite dans la version Android 8.0. La version Android 8.1 a des améliorations pour 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'Audio dans Android.

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

  • signaler les capacités MMAP de la HAL
  • mise en place de nouvelles fonctionnalités dans HAL
  • éventuellement implémenter un ioctl() personnalisé pour le tampon en mode EXCLUSIF
  • fournir un chemin de données matériel supplémentaire
  • définition des propriétés 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 cliente 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 tournant dans l'AudioServer. En mode EXCLUSIF, la latence est nettement inférieure car les données contournent le mélangeur.

En mode EXCLUSIF, le service demande le tampon MMAP à HAL et gère les ressources. Le tampon MMAP fonctionne 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 quand le tampon sera lu.

Dans le diagramme ci-dessous, nous pouvons voir les données de modulation par impulsions et codage (PCM) circulant à travers le FIFO MMAP dans 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 PARTAGÉ, 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.

HAL change

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

Prise en charge des rapports 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 la 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 le gestionnaire de politique audio sache quel flux ouvrir lors de la création des 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.

Interrogation de la position MMAP

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

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

La 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 passé 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. "Aperçu d'Android ION" https://wiki.linaro.org/BenjaminGaignard/ion
  3. "Intégration de l'allocateur de mémoire ION" https://lwn.net/Articles/565469/

HAL change

Le service AAudio doit savoir si cet anon_inode:dmabuf est pris en charge. Avant Android 10.0, la seule façon de le faire était de transmettre la taille du tampon MMAP sous la forme d'un nombre négatif, par exemple. -2048 au lieu de 2048, si pris en charge. Dans 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 à l'extrémité avant audio du sous-système audio afin qu'il puisse fonctionner en parallèle avec le chemin AudioFlinger d'origine. Ce chemin hérité est utilisé pour tous les autres sons système et sons d'application. Cette fonctionnalité peut être fournie par un mélangeur logiciel dans un DSP ou un mélangeur matériel dans le SOC.

Activation du 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. Ainsi, AAudio fonctionnera 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 hérité. La section suivante 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 - Utiliser uniquement le chemin hérité. 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 - Utiliser uniquement le chemin MMAP. Ne revenez pas au chemin hérité.

Ceux-ci peuvent être définis dans le Makefile des appareils, 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 de l'appareil. Vous devrez redémarrer le serveur audio pour que le changement prenne effet. Par exemple, pour activer le mode AUTO pour MMAP :

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

Il existe des fonctions fournies dans ndk/sysroot/usr/include/aaudio/AAudioTesting.h qui vous permettent de remplacer la politique 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);