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 提供了一个低延迟数据路径。在 EXCLUSIVE 模式下,使用该功能可将客户端应用代码直接写入与 ALSA 驱动程序共享的内存映射缓冲区。在 SHARED 模式下,MMAP 缓冲区由 AudioServer 中运行的混音器使用。在 EXCLUSIVE 模式下,由于数据会绕过混音器,延迟时间会明显缩短。
在 EXCLUSIVE 模式下,服务可从 HAL 请求 MMAP 缓冲区并管理资源。MMAP 缓冲区将在 NOIRQ 模式下运行,因此没有共享的读/写计数器来管理缓冲区的访问权限。相反,客户端会维护硬件的计时模型,并预测将在何时读取缓冲区。
在下图中,我们可以看到脉冲编码调制 (PCM) 数据通过 MMAP FIFO 向下流入 ALSA 驱动程序。AAudio 服务会定期请求时间戳,然后通过原子消息队列将其传递给客户端的计时模型。
![PCM 数据流图。](https://source.android.google.cn/static/docs/core/audio/images/pcm_data_flow.png?authuser=6&hl=da)
在 SHARED 模式下,也会用到计时模型,但它位于 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);
共享内存的文件描述符
AAudio MMAP 数据路径使用在硬件和音频服务之间共享的内存区域。您可以使用由 ALSA 驱动程序生成的文件描述符引用共享内存。
内核变更
如果文件描述符直接与 /dev/snd/
驱动程序文件关联,则可供 SHARED 模式下的 AAudio 服务使用。但是对于 EXCLUSIVE 模式,则无法将此描述符传递给客户端代码。/dev/snd/
文件描述符会提供过于宽泛的客户端访问权限,因此 SELinux 会将其屏蔽。
为了支持 EXCLUSIVE 模式,需将 /dev/snd/
描述符转换为 anon_inode:dmabuf
文件描述符。SELinux 允许将此文件描述符传递给客户端。该文件描述符也可供 AAudioService 使用。
您可以使用 Android Ion 内存库生成 anon_inode:dmabuf
文件描述符。
如需了解详情,请参阅下列外部资源:
- “Android ION 内存分配器”https://lwn.net/Articles/480055/
- “Android ION 概览”https://wiki.linaro.org/BenjaminGaignard/ion
- “集成 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。如果此操作失败或 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);