AAudio e MMAP

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

Il supporto di AAudio MMAP richiede:

  • segnalare le funzionalità MMAP dell'HAL
  • implementazione di nuove funzioni nell'HAL
  • Se vuoi, puoi implementare un ioctl() personalizzato per il buffer in modalità EXCLUSIVE
  • Fornendo un percorso dati hardware aggiuntivo
  • Impostazione delle proprietà di sistema che attivano la funzionalità MMAP

Architettura AAudio

AAudio è una nuova API C nativa che fornisce un'alternativa a Open SL ES. Utilizza un pattern di progettazione del generatore per creare stream 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à CONDIVISA, il buffer MMAP viene utilizzato da un mixer in esecuzione in AudioServer. In modalità EXCLUSIVE, la latenza è molto inferiore perché i dati bypassano il mixer.

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

Nel diagramma seguente, possiamo vedere i dati PCM (Pulse-code modulation) che passano attraverso la coda FIFO MMAP nel driver ALSA. I timestamp vengono periodicamente richiesti dal servizio AAudio e poi trasmessi al modello di temporizzazione del client tramite una coda di messaggi atomica.

Diagramma del flusso di dati PCM.
Figura 1. Flusso di dati PCM tramite FIFO ad ALSA

In modalità CONDIVISA viene utilizzato anche un modello di temporizzazione, ma si trova in AAudioService.

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

Modifiche all'HAL

Per tinyALSA, vedi:

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 precedente, vedi:

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

Supporto del report 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. (vedi "Attivazione del percorso dati MMAP AAudio" di seguito).

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

Aprire e chiudere uno stream MMAP

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

Lo stream MMAP può essere aperto e chiuso chiamando le funzioni Tinyalsa.

Query sulla posizione MMAP

Il timestamp passato al modello di temporizzazione contiene una posizione del frame 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 file per la memoria condivisa

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

Modifiche al kernel

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

Per supportare la modalità EXCLUSIVE, è necessario convertire il descrittore /dev/snd/ in un descrittore file anon_inode:dmabuf. SELinux consente di passare il descrittore di file al client. Puoi anche essere utilizzato 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. "The Android ION memory allocator" https://lwn.net/Articles/480055/
  2. "Panoramica di ION per Android" 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 esempio -2048 anziché 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 per poter funzionare in parallelo con il percorso AudioFlinger originale. Questo percorso precedente viene utilizzato per tutti gli altri suoni di sistema e di applicazione. Questa funzionalità potrebbe essere fornita da un mixer software in un DSP o da un mixer hardware nel SoC.

Attiva il percorso dati MMAP di AAudio

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

Quando testi il supporto MMAP per AAudio, è importante sapere se stai effettivamente testando il percorso dei dati MMAP o semplicemente il percorso dei dati precedente. Di seguito viene descritto come attivare o forzare percorsi di dati specifici e come eseguire query sul percorso utilizzato da uno stream.

Proprietà di sistema

Puoi impostare il criterio MMAP tramite le proprietà di 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 non riesce o non è disponibile, utilizza il percorso precedente.
  • 3 = AAUDIO_POLICY_ALWAYS: utilizza solo il percorso MMAP. Non tornare al percorso legacy.

Questi valori possono essere impostati nel file Makefile dei dispositivi, come segue:

# 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

Puoi anche sostituire questi valori dopo l'avvio del dispositivo. Per applicare la modifica, devi riavviare l'audioserver. Ad esempio, per attivare la modalità AUTO per MMAP:

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

In ndk/sysroot/usr/include/aaudio/AAudioTesting.h sono disponibili funzioni che ti consentono di eseguire l'override del criterio 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);