Audio y MMAP

AAudio es una API de audio introducida en la versión Android 8.0. La versión Android 8.1 tiene mejoras para reducir la latencia cuando se usa junto con un HAL y un controlador compatible con MMAP. Este documento describe la capa de abstracción de hardware (HAL) y los cambios de controlador necesarios para admitir la función MMAP de AAudio en Android.

La compatibilidad con AAudio MMAP requiere:

  • informar las capacidades MMAP del HAL
  • Implementando nuevas funciones en el HAL.
  • opcionalmente implementando un ioctl() personalizado para el buffer del modo EXCLUSIVO
  • proporcionando una ruta de datos de hardware adicional
  • configurar las propiedades del sistema que habilitan la función MMAP

Arquitectura de audio

AAudio es una nueva API C nativa que proporciona una alternativa a Open SL ES. Utiliza un patrón de diseño Builder para crear transmisiones de audio.

AAudio proporciona una ruta de datos de baja latencia. En el modo EXCLUSIVO, la función permite que el código de la aplicación cliente escriba directamente en un búfer de memoria asignada que se comparte con el controlador ALSA. En el modo COMPARTIDO, el búfer MMAP lo utiliza un mezclador que se ejecuta en AudioServer. En el modo EXCLUSIVO, la latencia es significativamente menor porque los datos pasan por alto el mezclador.

En modo EXCLUSIVO, el servicio solicita el búfer MMAP del HAL y administra los recursos. El búfer MMAP se ejecuta en modo NOIRQ, por lo que no hay contadores de lectura/escritura compartidos para administrar el acceso al búfer. En cambio, el cliente mantiene un modelo de sincronización del hardware y predice cuándo se leerá el búfer.

En el siguiente diagrama, podemos ver los datos de modulación de código de pulso (PCM) fluyendo a través del MMAP FIFO hacia el controlador ALSA. El servicio AAudio solicita periódicamente marcas de tiempo y luego las pasa al modelo de tiempo del cliente a través de una cola de mensajes atómicos.

Diagrama de flujo de datos PCM.
Figura 1. Flujo de datos PCM a través de FIFO a ALSA

En el modo COMPARTIDO, también se utiliza un modelo de sincronización, pero reside en AAudioService.

Para la captura de audio, se utiliza un modelo similar, pero los datos PCM fluyen en la dirección opuesta.

cambios hal

Para tinyALSA ver:

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

Para el HAL heredado, consulte:

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

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

Informar sobre el soporte de MMAP

La propiedad del sistema "aaudio.mmap_policy" debe establecerse en 2 (AAUDIO_POLICY_AUTO) para que el marco de audio sepa que el modo MMAP es compatible con el HAL de audio. (consulte "Habilitación de la ruta de datos MMAP de AAudio" a continuación).

El archivo audio_policy_configuration.xml también debe contener un perfil de salida y entrada específico para el modo MMAP/NO IRQ para que Audio Policy Manager sepa qué transmisión abrir cuando se crean clientes 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>

Abrir y cerrar una transmisión MMAP

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

La secuencia MMAP se puede abrir y cerrar llamando a las funciones de Tinyalsa.

Consultar posición MMAP

La marca de tiempo devuelta al modelo de tiempo contiene una posición de fotograma y un tiempo MONOTONIC en nanosegundos:

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

HAL puede obtener esta información del controlador ALSA llamando a una nueva función de Tinyalsa:

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

Descriptores de archivos para memoria compartida

La ruta de datos AAudio MMAP utiliza una región de memoria que se comparte entre el hardware y el servicio de audio. Se hace referencia a la memoria compartida mediante un descriptor de archivo generado por el controlador ALSA.

cambios de kernel

Si el descriptor de archivo está asociado directamente con un archivo de controlador /dev/snd/ , el servicio AAudio puede utilizarlo en modo COMPARTIDO. Pero el descriptor no se puede pasar al código del cliente para el modo EXCLUSIVO. El descriptor de archivo /dev/snd/ proporcionaría un acceso demasiado amplio al cliente, por lo que SELinux lo bloquea.

Para admitir el modo EXCLUSIVO, es necesario convertir el descriptor /dev/snd/ en un descriptor de archivo anon_inode:dmabuf . SELinux permite que ese descriptor de archivo se pase al cliente. También puede ser utilizado por AAudioService.

Se puede generar un descriptor de archivo anon_inode:dmabuf utilizando la biblioteca de memoria Ion de Android.

Para obtener información adicional, consulte estos recursos externos:

  1. "El asignador de memoria ION de Android" https://lwn.net/Articles/480055/
  2. "Descripción general de Android ION" https://wiki.linaro.org/BenjaminGaignard/ion
  3. "Integración del asignador de memoria ION" https://lwn.net/Articles/565469/

cambios hal

El servicio AAudio necesita saber si este anon_inode:dmabuf es compatible. Antes de Android 10.0, la única forma de hacerlo era pasar el tamaño del búfer MMAP como un número negativo, por ejemplo. -2048 en lugar de 2048, si es compatible. En Android 10.0 y versiones posteriores, puede configurar el indicador AUDIO_MMAP_APPLICATION_SHAREABLE .

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

Cambios en el subsistema de audio.

AAudio requiere una ruta de datos adicional en la parte frontal de audio del subsistema de audio para que pueda funcionar en paralelo con la ruta AudioFlinger original. Esa ruta heredada se utiliza para todos los demás sonidos del sistema y aplicaciones. Esta funcionalidad podría ser proporcionada por un mezclador de software en un DSP o un mezclador de hardware en el SOC.

Habilitar la ruta de datos AAudio MMAP

AAudio utilizará la ruta de datos heredada de AudioFlinger si MMAP no es compatible o no puede abrir una transmisión. Por lo tanto, AAudio funcionará con un dispositivo de audio que no admita la ruta MMAP/NOIRQ.

Al probar la compatibilidad con MMAP para AAudio, es importante saber si realmente está probando la ruta de datos MMAP o simplemente la ruta de datos heredada. A continuación se describe cómo habilitar o forzar rutas de datos específicas y cómo consultar la ruta utilizada por una secuencia.

Propiedades del sistema

Puede configurar la política MMAP a través de las propiedades del sistema:

  • 1 = AAUDIO_POLICY_NEVER: utilice únicamente la ruta heredada. Ni siquiera intentes utilizar MMAP.
  • 2 = AAUDIO_POLICY_AUTO - Intente utilizar MMAP. Si eso falla o no está disponible, utilice la ruta heredada.
  • 3 = AAUDIO_POLICY_ALWAYS: utilice únicamente la ruta MMAP. No retroceda al camino heredado.

Estos se pueden configurar en el Makefile del dispositivo, así:

# 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

También puede anular estos valores después de que el dispositivo se haya iniciado. Deberá reiniciar el servidor de audio para que el cambio surta efecto. Por ejemplo, para habilitar el modo AUTO para MMAP:

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

Hay funciones proporcionadas en ndk/sysroot/usr/include/aaudio/AAudioTesting.h que le permiten anular la política para usar la ruta MMAP:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

Para saber si una transmisión utiliza la ruta MMAP, llame a:

bool AAudioStream_isMMapUsed(AAudioStream* stream);