Audio e MMAP

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

Il supporto per AAudio MMAP richiede:

  • segnalando le capacità 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 funzione 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à ESCLUSIVA, la funzione 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 nell'AudioServer. In modalità EXCLUSIVE, la latenza è notevolmente 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, quindi non ci sono contatori di lettura/scrittura condivisi per gestire l'accesso al buffer. Al contrario, il client mantiene un modello di temporizzazione 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 di temporizzazione del client tramite una coda di messaggi atomica.

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

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

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

HAL cambia

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 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'audio HIDL HAL:

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

Segnalazione del 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. (consultare "Abilitazione del percorso dati MMAP 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>

Apertura e chiusura di un flusso MMAP

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

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

Interrogazione della posizione MMAP

Il timestamp restituito al Timing Model contiene una posizione del frame e un tempo MONOTONIC 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 AAudio MMAP utilizza un'area di memoria condivisa tra l'hardware e il servizio audio. La memoria condivisa viene referenziata 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à CONDIVISA. 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 permette che quel descrittore di file venga passato 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, vedere 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/

HAL cambia

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 sul front-end audio del sottosistema audio in modo che possa funzionare in parallelo con il percorso AudioFlinger originale. Quel percorso legacy viene utilizzato per tutti gli altri suoni di sistema e di applicazioni. Questa funzionalità potrebbe essere fornita da un mixer software in un DSP o da un mixer hardware nel SOC.

Abilitazione del percorso dati MMAP di AAudio

AAudio utilizzerà il percorso dati AudioFlinger legacy se MMAP non è supportato o non riesce ad aprire uno stream. Quindi 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 di dati specifici e come eseguire query sul percorso utilizzato da un flusso.

Proprietà di sistema

È possibile impostare la politica MMAP tramite le proprietà di sistema:

  • 1 = AAUDIO_POLICY_NEVER - Usa solo il percorso legacy. Non provare nemmeno a usare MMAP.
  • 2 = AAUDIO_POLICY_AUTO - Prova a usare MMAP. Se fallisce o non è disponibile, usa il percorso legacy.
  • 3 = AAUDIO_POLICY_ALWAYS - Usa solo il percorso MMAP. Non ricadere sul 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

Puoi anche sovrascrivere questi valori dopo l'avvio del dispositivo. Sarà necessario riavviare il server audio per rendere effettive le modifiche. 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 politica 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);