AAudio 和 MMAP

AAudio 是 Android 8.0 版本中引入的音频 API。Android 8.1 版本增强了与支持 MMAP 的 HAL 和驱动程序结合使用时的延迟。本文档介绍了 Android 中支持 AAudio MMAP 功能所需的硬件抽象层 (HAL) 和驱动程序更改。

对 AAudio MMAP 的支持需要

  • 报告 HAL 的 MMAP 功能
  • 在 HAL 中实现新函数
  • 选择性地为 EXCLUSIVE 模式缓冲区实现自定义 ioctl()
  • 提供额外的硬件数据路径
  • 设置启用 MMAP 功能的系统属性

AAudio 架构

AAudio 是一种新的原生 C API,它提供了 Open SL ES 的替代方案。它使用构建器设计模式来创建音频流。

AAudio 提供低延迟数据路径。在独占模式下,此功能允许客户端应用程序代码直接写入与 ALSA 驱动程序共享的内存映射缓冲区。在共享模式下,MMAP 缓冲区由在 AudioServer 中运行的混音器使用。在独占模式下,由于数据绕过了混音器,因此延迟显著降低。

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

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

PCM data flow diagram.
图 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 位置

传递回计时模型的时间戳包含帧位置和以纳秒为单位的单调时间

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

共享内存的文件描述符

AAudio MMAP 数据路径使用硬件和音频服务之间共享的内存区域。共享内存使用由 ALSA 驱动程序生成的文件描述符引用。

内核更改

如果文件描述符直接与 /dev/snd/ 驱动程序文件关联,则 AAudio 服务可以在共享模式下使用它。但是,描述符不能传递给独占模式的客户端代码。/dev/snd/ 文件描述符将为客户端提供过多的访问权限,因此 SELinux 会阻止它。

为了支持独占模式,有必要将 /dev/snd/ 描述符转换为 anon_inode:dmabuf 文件描述符。SELinux 允许将该文件描述符传递给客户端。AAudioService 也可以使用它。

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

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

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

HAL 更改

AAudio 服务需要知道是否支持此 anon_inode:dmabuf。在 Android 10.0 之前,唯一的做法是将 MMAP 缓冲区的大小作为负数传递,例如,如果支持,则传递 -2048 而不是 2048。在 Android 10.0 及更高版本中,您可以设置 AUDIO_MMAP_APPLICATION_SHAREABLE 标志。

mmapBufferInfo |= AUDIO_MMAP_APPLICATION_SHAREABLE;

音频子系统更改

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。如果失败或不可用,则使用旧版路径。
  • 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

您还可以在设备启动后覆盖这些值。您需要重启 audioserver 才能使更改生效。例如,要为 MMAP 启用 AUTO 模式

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

ndk/sysroot/usr/include/aaudio/AAudioTesting.h 中提供了函数,允许您覆盖使用 MMAP 路径的策略

aaudio_result_t AAudio_setMMapPolicy(aaudio_policy_t policy);

要了解流是否正在使用 MMAP 路径,请调用

bool AAudioStream_isMMapUsed(AAudioStream* stream);