AAudio e MMAP

AAudio è un'API audio introdotta nella versione Android 8.0. La versione Android 8.1 presenta miglioramenti per ridurre la latenza se utilizzata insieme a un HAL e un driver che supportano MMAP. Questo documento descrive il livello di astrazione hardware (HAL) e le modifiche ai driver necessarie per supportare la funzionalità MMAP di AAudio in Android.

Il supporto per AAudio MMAP richiede:

  • segnalando le funzionalità MMAP dell'HAL
  • implementare nuove funzioni nell'HAL
  • facoltativamente implementando un ioctl() personalizzato per il buffer in modalità EXCLUSIVE
  • fornendo un percorso dati hardware aggiuntivo
  • impostazione delle proprietà di sistema che abilitano la funzionalità MMAP

Architettura audio

AAudio è una nuova API C nativa che fornisce un'alternativa a Open SL ES. Utilizza un modello di progettazione Builder per creare flussi audio.

AAudio fornisce un percorso dati a bassa latenza. In modalità EXCLUSIVE, la funzionalità consente al codice dell'applicazione client di scrivere direttamente in un buffer mappato in memoria condiviso con il driver ALSA. In modalità SHARED, il buffer MMAP viene utilizzato da un mixer in esecuzione nell'AudioServer. Nella modalità EXCLUSIVE, la latenza è significativamente inferiore perché i dati bypassano il mixer.

In modalità ESCLUSIVA, il servizio richiede il buffer MMAP dall'HAL e gestisce le risorse. Il buffer MMAP è in esecuzione in modalità NOIRQ, quindi non sono presenti contatori di lettura/scrittura condivisi per gestire l'accesso al buffer. Invece, il client mantiene un modello temporale dell'hardware e prevede quando verrà letto il buffer.

Nel diagramma seguente, possiamo vedere i dati della modulazione del codice a impulsi (PCM) che fluiscono attraverso il FIFO MMAP nel driver ALSA. I timestamp vengono periodicamente richiesti dal servizio AAudio e quindi passati al modello temporale del client attraverso una coda di messaggi atomica.

Diagramma del flusso dei dati PCM.
Figura 1. Flusso di dati PCM attraverso FIFO verso ALSA

In modalità SHARED viene utilizzato anche un modello di temporizzazione, ma risiede in AAudioService.

Per l'acquisizione audio viene utilizzato un modello simile, ma i dati PCM fluiscono nella direzione opposta.

Modifiche all'HAL

Per tinyALSA vedere:

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

Per l'HAL legacy, vedere:

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

Per l'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);

Segnala il supporto MMAP

La proprietà di sistema "aaudio.mmap_policy" deve essere impostata su 2 (AAUDIO_POLICY_AUTO) in modo che il framework audio sappia che la modalità MMAP è supportata dall'HAL audio. (vedere "Abilitazione del percorso dati MMAP di AAudio" di seguito.)

Il file audio_policy_configuration.xml deve contenere anche un profilo di output e di input specifico per la modalità MMAP/NO IRQ in modo che Audio Policy Manager sappia quale flusso aprire quando vengono creati i client 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>

Apri e chiudi un flusso MMAP

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

Il flusso MMAP può essere aperto e chiuso chiamando le funzioni Tinyalsa.

Interroga la posizione MMAP

Il timestamp restituito al modello temporale contiene una posizione del fotogramma e un tempo MONOTONICO in nanosecondi:

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

L'HAL può ottenere queste informazioni dal driver ALSA chiamando una nuova funzione Tinyalsa:

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

Descrittori di file per la memoria condivisa

Il percorso dati MMAP di AAudio utilizza un'area di memoria condivisa tra l'hardware e il servizio audio. Viene fatto riferimento alla memoria condivisa utilizzando un descrittore di file generato dal driver ALSA.

Modifiche al kernel

Se il descrittore di file è direttamente associato a un file del driver /dev/snd/ , può essere utilizzato dal servizio AAudio in modalità SHARED. Ma il descrittore non può essere passato al codice client per la modalità ESCLUSIVA. Il descrittore di file /dev/snd/ fornirebbe un accesso troppo ampio al client, quindi è bloccato da SELinux.

Per supportare la modalità ESCLUSIVA, è necessario convertire il descrittore /dev/snd/ in un descrittore di file anon_inode:dmabuf . SELinux consente di passare quel descrittore di file al client. Può essere utilizzato anche da AAudioService.

Un descrittore di file anon_inode:dmabuf può essere generato utilizzando la libreria di memoria Android Ion.

Per ulteriori informazioni, consulta queste risorse esterne:

  1. "L'allocatore di memoria Android ION" https://lwn.net/Articles/480055/
  2. "Panoramica di Android ION" https://wiki.linaro.org/BenjaminGaignard/ion
  3. "Integrazione dell'allocatore di memoria ION" https://lwn.net/Articles/565469/

Modifiche all'HAL

Il servizio AAudio deve sapere se questo anon_inode:dmabuf è supportato. Prima di Android 10.0, l'unico modo per farlo era passare la dimensione del buffer MMAP come numero negativo, ad es. -2048 invece di 2048, se supportato. In Android 10.0 e versioni successive puoi impostare il flag AUDIO_MMAP_APPLICATION_SHAREABLE .

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

Modifiche al sottosistema audio

AAudio richiede un percorso dati aggiuntivo nel front-end audio del sottosistema audio in modo che possa funzionare in parallelo con il percorso AudioFlinger originale. Questo percorso legacy viene utilizzato per tutti gli altri suoni di sistema e suoni dell'applicazione. Questa funzionalità potrebbe essere fornita da un mixer software in un DSP o da un mixer hardware nel SOC.

Abilita il percorso dati MMAP di AAudio

AAudio utilizzerà il percorso dati AudioFlinger legacy se MMAP non è supportato o non riesce ad aprire un flusso. Pertanto AAudio funzionerà con un dispositivo audio che non supporta il percorso MMAP/NOIRQ.

Quando si testa il supporto MMAP per AAudio, è importante sapere se si sta effettivamente testando il percorso dati MMAP o semplicemente testando il percorso dati legacy. Di seguito viene descritto come abilitare o forzare percorsi dati specifici e come eseguire query sul percorso utilizzato da un flusso.

Proprietà di sistema

È possibile impostare la policy MMAP tramite le proprietà del sistema:

  • 1 = AAUDIO_POLICY_NEVER: utilizza solo il percorso legacy. Non provare nemmeno a utilizzare MMAP.
  • 2 = AAUDIO_POLICY_AUTO - Prova a utilizzare MMAP. Se fallisce o non è disponibile, utilizza il percorso legacy.
  • 3 = AAUDIO_POLICY_ALWAYS: utilizza solo il percorso MMAP. Non ricorrere al percorso legacy.

Questi possono essere impostati nel Makefile dei dispositivi, in questo modo:

# 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

È inoltre possibile sovrascrivere questi valori dopo l'avvio del dispositivo. Sarà necessario riavviare l'audioserver affinché la modifica abbia effetto. Ad esempio, per abilitare la modalità AUTO per MMAP:

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

Sono disponibili funzioni in ndk/sysroot/usr/include/aaudio/AAudioTesting.h che consentono di ignorare la policy per l'utilizzo del percorso MMAP:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

Per scoprire se uno stream utilizza il percorso MMAP, chiama:

bool AAudioStream_isMMapUsed(AAudioStream* stream);