AAudio 和 MMAP

AAudio 是在 Android 8.0 版本中引入的一种音频 API。Android 8.1 版本具有增强功能,可在与支持 MMAP 的 HAL 和驱动程序结合使用时缩短延迟时间。本文档说明了需要进行哪些硬件抽象层 (HAL) 及驱动程序方面的更改才能在 Android 中支持 AAudio 的 MMAP 功能。

要支持 AAudio MMAP,须执行以下操作:

  • 报告 HAL 的 MMAP 功能
  • 在 HAL 中实现新功能
  • 为“专有”模式缓冲区实现自定义 ioctl()(可选)
  • 提供一个额外的硬件数据路径
  • 设置用于启用 MMAP 功能的系统属性

AAudio 架构

AAudio 是一种新的本地 C API,可提供 Open SL ES 的替代方案。它使用“生成器”设计模式来创建音频流。

AAudio 提供了一个低延迟数据路径。在“专有”模式下,该功能可让客户端应用代码直接写入到与 ALSA 驱动程序共享的内存映射缓冲区。在“共享”模式下,MMAP 缓冲区由在 AudioServer 中运行的混音器使用。在“专有”模式下,由于数据会绕过混音器,延迟时间会明显缩短。

在“专有”模式下,服务可从 HAL 请求 MMAP 缓冲区并管理资源。MMAP 缓冲区会在 NOIRQ 模式下运行,因此没有共享的读/写计数器可用来管理对缓冲区的访问权限。相反,客户端会维护硬件的计时模型,并预测将在何时读取缓冲区。

在下图中,我们可以看到脉冲编码调制 (PCM) 数据通过 MMAP FIFO 向下流入 ALSA 驱动程序。AAudio 服务会定期请求时间戳,然后通过原子消息队列将其传递给客户端的计时模型。

PCM 数据流图。
图 1. PCM 数据通过 FIFO 流向 ALSA

在“共享”模式下,也会用到计时模型,但它位于 AAudioService 中。

在音频捕获过程中,会用到类似的模型,但 PCM 数据会以相反方向流动。

HAL 变更

对于 tinyALSA,请参阅:

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

对于旧版 HAL,请参阅:

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

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

报告 MMAP 支持

系统属性“aaudio.mmap_policy”应设置为 2 (AAUDIO_POLICY_AUTO),以便音频框架知道音频 HAL 支持 MMAP 模式(请参阅下面的“启用 AAudio MMAP 数据路径”)。

audio_policy_configuration.xml 文件还必须包含特定于 MMAP/NO IRQ 模式的输出和输入配置文件,以便音频政策管理器得知要在创建 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>

打开和关闭 MMAP 流

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

调用 Tinyalsa 函数可以打开和关闭 MMAP 流。

查询 MMAP 位置

传回到计时模型的时间戳包含帧位置和 MONOTONIC 时间(以纳秒为单位):

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

HAL 可以通过调用新 Tinyalsa 函数从 ALSA 驱动程序中获取此信息:

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

内核变更

在驱动程序中启用 MMAP/NOIRQ 模式。

您可以使用由 ALSA 驱动程序生成的文件描述符引用共享内存。如果文件描述符直接与 /dev/snd/ 驱动程序文件关联,则可用于“共享”模式下的 AAudio 服务。但是对于“专有”模式,则无法将此描述符传递给客户端代码。/dev/snd/ 文件描述符会提供过于宽泛的客户端访问权限,因此 SELinux 会将其屏蔽。

要支持“专有”模式,需将 /dev/snd/ 描述符转换为 anon_inode:dmabuffer 文件描述符。SELinux 允许将此文件描述符传递给客户端。该文件描述符也可用于 AAudioService。

可使用 Android Ion 内存库生成 anon_inode:dmabuffer 文件描述符。

有关更多信息,请参阅下列外部资源:

  1. “Android ION 内存分配器”https://lwn.net/Articles/480055/
  2. “Android ION 概览”https://wiki.linaro.org/BenjaminGaignard/ion
  3. “集成 ION 内存分配器”https://lwn.net/Articles/565469/

AAudio 服务需要了解 anon_inode:dmabuffer 是否受支持。目前,验证是否受支持的唯一方法是将 MMAP 缓冲区大小传递为负数(例如 -2048 而非 2048;如果支持的话)。我们已经在规划不必打开流即可报告此信息的更佳方法。

音频子系统变更

AAudio 在音频子系统的音频前端需要一个额外的数据路径,以便它可以与原始 AudioFlinger 路径并行运行。旧路径用于所有其他系统声音和应用声音。该功能可由 DSP 中的软件混音器或由 SOC 中的硬件混音器提供。

启用 AAudio MMAP 数据路径

如果 MMAP 不受支持或无法打开流,AAudio 将使用旧的 AudioFlinger 数据路径。因此,AAudio 将可用于不支持 MMAP/NOIRQ 路径的音频设备。

在测试 AAudio 的 MMAP 支持功能时,了解您是在实际测试 MMAP 数据路径还是仅测试旧的数据路径这一点尤为重要。下面介绍了如何启用或强制执行特定数据路径,以及如何查询流所使用的路径。

系统属性

您可以通过系统属性设置 MMAP 政策:

  • 1 = AAUDIO_POLICY_NEVER - 仅使用旧的路径,甚至不要尝试使用 MMAP。
  • 2 = AAUDIO_POLICY_AUTO - 尝试使用 MMAP。如果此操作失败或 MMAP 不可用,则使用旧路径。
  • 3 = AAUDIO_POLICY_ALWAYS - 仅使用 MMAP 路径。不返回到旧路径。

您可以在设备 Makefile 中设置这些项,如下所示:

# 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

您也可以在设备启动后替换这些值。例如,要为 MMAP 启用“自动”模式,请输入以下内容:

adb root
adb shell setprop aaudio.mmap_policy 2

ndk/sysroot/usr/include/aaudio/AAudioTesting.h 中提供了一些函数,可让您替换使用 MMAP 路径的政策:

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

要确定某个流是否使用 MMAP 路径,请调用:

bool AAudioStream_isMMapUsed(AAudioStream* stream);