AAudio et MMAP

AAudio est une API audio introduite dans la version Android 8.0. La version Android 8.1 propose des améliorations visant à réduire la latence lorsqu'elle est utilisée avec un HAL et un pilote compatibles avec 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 de MMAP AAudio nécessite:

  • signaler les fonctionnalités MMAP du HAL ;
  • implémentation de nouvelles fonctions dans le HAL
  • Implémentation facultative d'un ioctl() personnalisé pour le tampon en mode EXCLUSIVE
  • en fournissant un chemin de données matérielles supplémentaire ;
  • définir des propriétés système qui activent la fonctionnalité MMAP ;

Architecture AAudio

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

AAudio fournit un chemin de données à faible latence. En mode EXCLUSIVE, 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 PARTAGÉ, le tampon MMAP est utilisé par un mixeur exécuté dans AudioServer. En mode EXCLUSIVE, la latence est nettement inférieure, car les données contournent le mixeur.

En mode EXCLUSIVE, le service demande le tampon MMAP à l'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 gère un modèle de temporisation du matériel et prédit quand le tampon sera lu.

Dans le schéma ci-dessous, vous pouvez voir les données de modulation de code de pulsation (PCM) qui descendent via le FIFO MMAP dans le pilote ALSA. Les codes temporels sont demandés périodiquement par le service AAudio, puis transmis au modèle de temporisation du client via une file de messages atomiques.

Schéma du 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 se trouve dans AAudioService.

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

Modifications apportées à HAL

Pour tinyALSA, consultez:

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, consultez les pages suivantes:

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 compatibilité avec MMAP

La propriété système "aaudio.mmap_policy" doit être définie sur 2 (AAUDIO_POLICY_AUTO) pour que le framework audio sache que le mode MMAP est compatible avec le HAL audio. (voir la section "Activer le chemin de données MMAP AAudio" 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 stratégie audio sache quel flux ouvrir lorsque des clients MMAP sont créés:

<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 des fonctions Tinyalsa.

Interroger la position MMAP

Le code temporel transmis au modèle de temporisation contient une position de frame et une heure MONOTONIC en nanosecondes:

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

Le HAL peut obtenir ces informations auprès 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);

Déscripteurs de fichiers pour la mémoire partagée

Le chemin de données MMAP d'AAudio 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 apportées au noyau

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

Pour prendre en charge le mode EXCLUSIVE, vous devez convertir le descripteur /dev/snd/ en 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 en savoir plus, consultez les ressources externes suivantes:

  1. "The Android ION memory allocator" (L'outil d'allocation de mémoire ION d'Android) https://lwn.net/Articles/480055/
  2. "Présentation d'ION sur Android" https://wiki.linaro.org/BenjaminGaignard/ion
  3. "Integrating the ION memory allocator" (Intégration de l'outil d'allocation de mémoire ION) https://lwn.net/Articles/565469/

Modifications apportées à HAL

Le service AAudio doit savoir si ce anon_inode:dmabuf est compatible. Avant Android 10.0, le seul moyen de procéder était de transmettre la taille du tampon MMAP sous la forme d'un nombre négatif, par exemple. -2048 au lieu de 2048, si compatible. Sous Android 10.0 et versions ultérieures, vous pouvez définir l'indicateur AUDIO_MMAP_APPLICATION_SHAREABLE.

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

Modifications apportées au sous-système audio

AAudio nécessite un chemin de données supplémentaire au niveau de l'interface audio du sous-système audio afin de pouvoir fonctionner en parallèle avec le chemin AudioFlinger d'origine. Ce chemin d'accès obsolète est utilisé pour tous les autres sons système et sons d'application. Cette fonctionnalité peut être fournie par un mixeur logiciel dans un DSP ou un mixeur matériel dans le SoC.

Activer le chemin de données MMAP AAudio

AAudio utilise l'ancien chemin de données AudioFlinger si MMAP n'est pas compatible ou ne parvient pas à ouvrir un flux. AAudio fonctionne donc avec un appareil audio qui n'est pas compatible avec 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 d'accès aux données MMAP ou si vous testez simplement l'ancien chemin d'accès aux données. La section suivante explique comment activer ou forcer des chemins de données spécifiques, et comment interroger le chemin utilisé par un flux.

Propriétés système

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

  • 1 = AAUDIO_POLICY_NEVER : n'utilisez que l'ancien chemin d'accès. 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 l'ancien chemin d'accès.
  • 3 = AAUDIO_POLICY_ALWAYS : n'utilisez que le chemin d'accès MMAP. Ne pas revenir à l'ancien chemin.

Ils peuvent être définis dans le fichier Makefile des appareils, comme suit:

# 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 ignorer ces valeurs une fois l'appareil démarré. 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

Des fonctions sont fournies dans ndk/sysroot/usr/include/aaudio/AAudioTesting.h pour vous permettre de remplacer la règle d'utilisation du chemin MMAP:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

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

bool AAudioStream_isMMapUsed(AAudioStream* stream);