Audio y MMAP

AAudio es una API de audio introducida en la versión de Android 8.0. La versión de 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 de la HAL
  • implementando nuevas funciones en el HAL
  • implementando opcionalmente un ioctl() personalizado para el búfer de modo EXCLUSIVO
  • proporcionar una ruta de datos de hardware adicional
  • establecer 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 del cliente se escriba directamente en un búfer asignado a la memoria que se comparte con el controlador ALSA. En el modo COMPARTIDO, el búfer MMAP es utilizado por 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 de 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 su lugar, el cliente mantiene un modelo de temporizació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) que fluyen a través del MMAP FIFO hacia el controlador ALSA. Las marcas de tiempo son solicitadas periódicamente por el servicio AAudio y luego pasan al modelo de tiempo del cliente a través de una cola de mensajes atómica.

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 usa un modelo de temporización, pero vive en AAudioService.

Para la captura de audio, se usa 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 la HAL heredada, 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 HAL de 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);

Informes de 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 la 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 entrada y salida específico para el modo MMAP/NO IRQ para que el administrador de políticas de audio sepa qué flujo 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>

Apertura y cierre de una transmisión MMAP

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

El flujo MMAP se puede abrir y cerrar llamando a las funciones de Tinyalsa.

Consultando la posición de MMAP

La marca de tiempo devuelta al modelo de tiempo contiene una posición de cuadro y un tiempo MONOTÓNICO 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 archivo para memoria compartida

La ruta de datos MMAP de AAudio 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 en el kernel

Si el descriptor de archivo está asociado directamente con un archivo de controlador /dev/snd/ , entonces puede ser utilizado por el servicio AAudio en modo COMPARTIDO. Pero el descriptor no se puede pasar al código de 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 mediante la biblioteca de memoria de Android Ion.

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

  1. "El asignador de memoria Android ION" 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, p. -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 el extremo frontal de audio del subsistema de audio para que pueda funcionar en paralelo con la ruta de AudioFlinger original. Esa ruta heredada se usa para todos los demás sonidos del sistema y sonidos de aplicaciones. Esta funcionalidad podría proporcionarla un mezclador de software en un DSP o un mezclador de hardware en el SOC.

Habilitación de la ruta de datos MMAP de AAudio

AAudio utilizará la ruta de datos de AudioFlinger heredada 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 de MMAP o simplemente está probando 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 transmisión.

Propiedades del sistema

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

  • 1 = AAUDIO_POLICY_NEVER: solo use la ruta heredada. Ni siquiera intentes usar MMAP.
  • 2 = AAUDIO_POLICY_AUTO - Intenta usar MMAP. Si eso falla o no está disponible, use la ruta heredada.
  • 3 = AAUDIO_POLICY_ALWAYS: solo use la ruta MMAP. No recurra al camino heredado.

Estos pueden configurarse en el Makefile de los dispositivos, 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 haya arrancado. 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 provistas 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 averiguar si una secuencia está utilizando la ruta MMAP, llame al:

bool AAudioStream_isMMapUsed(AAudioStream* stream);