AAudio e MMAP

A AAudio é uma API de áudio introduzida na versão 8.0 do Android. A versão do Android 8.1 tem melhorias para reduzir a latência quando usada em conjunto com uma HAL e um driver compatível com MMAP. Este documento descreve a camada de abstração de hardware (HAL, na sigla em inglês) e as mudanças de driver necessárias para oferecer suporte ao recurso MMAP do AAudio no Android.

O suporte ao MMAP do AAudio requer:

  • informando os recursos do MMAP do HAL
  • Implementar novas funções no HAL
  • Implementação opcional de um ioctl() personalizado para o buffer de modo EXCLUSIVO
  • fornecendo um caminho de dados de hardware adicional
  • definir propriedades do sistema que ativam o recurso MMAP

Arquitetura da AAudio

A AAudio é uma nova API C nativa que oferece uma alternativa ao OpenSL ES. Ele usa um padrão de design do Builder para criar streams de áudio.

A AAudio fornece um caminho de dados de baixa latência. No modo EXCLUSIVE, o recurso permite que o código do aplicativo cliente seja gravado diretamente em um buffer mapeado em memória que é compartilhado com o driver ALSA. No modo COMPARTILHADO, o buffer MMAP é usado por um mixer em execução no AudioServer. No modo EXCLUSIVE, a latência é significativamente menor porque os dados ignoram o mixer.

No modo EXCLUSIVO, o serviço solicita o buffer MMAP do HAL e gerencia os recursos. O buffer MMAP está em execução no modo NOIRQ, portanto, não há contadores de leitura/gravação compartilhados para gerenciar o acesso ao buffer. Em vez disso, o cliente mantém um modelo de temporização do hardware e prevê quando o buffer será lido.

No diagrama abaixo, podemos ver os dados de modulação por pulso (PCM) fluindo pelo FIFO do MMAP para o driver ALSA. Os carimbos de data/hora são solicitados periodicamente pelo serviço AAudio e, em seguida, transmitidos para o modelo de temporização do cliente por uma fila de mensagens atômicas.

Diagrama de fluxo de dados do PCM.
Figura 1. Fluxo de dados PCM por FIFO para ALSA

No modo COMPARTILHADO, um modelo de temporização também é usado, mas ele fica no AAudioService.

Para a captura de áudio, um modelo semelhante é usado, mas os dados PCM fluem na direção oposta.

Mudanças na HAL

Para tinyALSA, consulte:

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 o HAL legado, 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 a HAL de áudio 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);

Informar suporte ao MMAP

A propriedade do sistema "aaudio.mmap_policy" precisa ser definida como 2 (AAUDIO_POLICY_AUTO) para que a estrutura de áudio saiba que o modo MMAP é compatível com a HAL de áudio. Consulte "Como ativar o caminho de dados do AAudio MMAP" abaixo.

O arquivo audio_policy_configuration.xml também precisa conter um perfil de saída e entrada específico para o modo MMAP/NO IRQ para que o gerenciador de políticas de áudio saiba qual stream abrir quando os clientes MMAP forem criados:

<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 e fechar um fluxo MMAP

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

O fluxo MMAP pode ser aberto e fechado chamando as funções do Tinyalsa.

Consultar a posição do MMAP

O carimbo de data/hora transmitido de volta para o modelo de temporização contém uma posição de frame e um tempo MONOTONIC em nanossegundos:

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

O HAL pode receber essas informações do driver ALSA chamando uma nova função do Tinyalsa:

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

Descritores de arquivo para memória compartilhada

O caminho de dados MMAP da AAudio usa uma região de memória compartilhada entre o hardware e o serviço de áudio. A memória compartilhada é referenciada usando um descritor de arquivo gerado pelo driver ALSA.

Mudanças no kernel

Se o descritor de arquivo estiver diretamente associado a um arquivo de driver /dev/snd/, ele poderá ser usado pelo serviço AAudio no modo COMPARTILHADO. No entanto, o descritor não pode ser transmitido ao código do cliente para o modo EXCLUSIVO. O descritor de arquivo /dev/snd/ forneceria um acesso muito amplo ao cliente, por isso é bloqueado pelo SELinux.

Para oferecer suporte ao modo EXCLUSIVO, é necessário converter o descritor /dev/snd/ em um descritor de arquivo anon_inode:dmabuf. O SELinux permite que esse descritor de arquivo seja transmitido ao cliente. Ela também pode ser usada pelo AAudioService.

Um descritor de arquivo anon_inode:dmabuf pode ser gerado usando a biblioteca de memória do Android Ion.

Para mais informações, consulte estes recursos externos:

  1. "The Android ION memory allocator" (O alocador de memória do ION do Android) https://lwn.net/Articles/480055/
  2. "Visão geral do ION do Android" https://wiki.linaro.org/BenjaminGaignard/ion
  3. "Integrating the ION memory allocator" https://lwn.net/Articles/565469/

Mudanças na HAL

O serviço AAudio precisa saber se esse anon_inode:dmabuf é compatível. Antes do Android 10.0, a única maneira de fazer isso era transmitir o tamanho do buffer MMAP como um número negativo, por exemplo. -2048 em vez de 2048, se houver suporte. No Android 10.0 e versões mais recentes, é possível definir a flag AUDIO_MMAP_APPLICATION_SHAREABLE.

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

Mudanças no subsistema de áudio

A AAudio exige um caminho de dados adicional no front-end de áudio do subsistema de áudio para operar em paralelo com o caminho original do AudioFlinger. Esse caminho legado é usado para todos os outros sons do sistema e do aplicativo. Essa funcionalidade pode ser fornecida por um mixer de software em um DSP ou um mixer de hardware no SOC.

Ativar o caminho de dados MMAP do AAudio

A AAudio vai usar o caminho de dados legado do AudioFlinger se o MMAP não tiver suporte ou não conseguir abrir um stream. Portanto, o AAudio vai funcionar com um dispositivo de áudio que não oferece suporte ao caminho MMAP/NOIRQ.

Ao testar o suporte ao MMAP para AAudio, é importante saber se você está testando o caminho de dados do MMAP ou apenas o caminho de dados legado. A seguir, descobrimos como ativar ou forçar caminhos de dados específicos e como consultar o caminho usado por um stream.

Propriedades do sistema

É possível definir a política MMAP usando as propriedades do sistema:

  • 1 = AAUDIO_POLICY_NEVER: use apenas o caminho legado. Não tente usar o MMAP.
  • 2 = AAUDIO_POLICY_AUTO: tenta usar o MMAP. Se isso falhar ou não estiver disponível, use o caminho legado.
  • 3 = AAUDIO_POLICY_ALWAYS: use apenas o caminho MMAP. Não usar o caminho legado.

Eles podem ser definidos no Makefile do dispositivo, como este:

# 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

Também é possível substituir esses valores depois que o dispositivo for inicializado. Reinicie o audioserver para que a mudança entre em vigor. Por exemplo, para ativar o modo AUTO para MMAP:

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

Há funções fornecidas em ndk/sysroot/usr/include/aaudio/AAudioTesting.h que permitem substituir a política para usar o caminho MMAP:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

Para descobrir se um stream está usando o caminho MMAP, chame:

bool AAudioStream_isMMapUsed(AAudioStream* stream);