4. R-Car音频DMA与内存管理

在R-Car音频子系统中,DMA并不是一个“把数据搬来搬去”的简单外设,而是连接DDR、SCU(Sound Control Unit)、SSIU(Serial Sound Interface Unit)以及外部串行音频总线的数据通路核心。理解音频DMA通道分配、SCU与SSIU的FIFO管理,以及零拷贝与低延迟缓冲设计,是让多路音频流稳定运行、避免爆音与丢帧的关键。

一、音频DMA通道分配:从请求源到通道映射

R-Car的音频DMA通常由SCU内部的DMA接口或系统DMAC控制器承担。每个音频方向(播放/录音)在SCU中对应一个或多个FIFO,FIFO通过DMA请求线向DMAC发起搬运请求。通道分配的本质,是把“哪个FIFO的请求”绑定到“哪条DMA通道”,并确定搬运的源地址、目的地址、传输宽度和块长度。

以播放路径为例:DDR中的PCM缓冲区是源,SCU的播放FIFO是目的。DMA通道需要配置为内存到外设方向,传输单元通常为32位(一个立体声采样对),块长度等于FIFO触发阈值。若系统中有多路播放流(如导航提示音、媒体音乐、通话上行),就需要为每路流分配独立通道,避免共享通道导致请求仲裁抖动。实践中,通道优先级应让低延迟流(如提示音)高于媒体流,否则提示音可能因等待媒体流的大块搬运而延迟。

通道分配还要考虑地址对齐。R-Car的SCU FIFO寄存器通常要求32位访问,DDR缓冲区也应至少4字节对齐;若使用缓存,还需处理cache一致性,否则DMA写入DDR后CPU可能读到旧数据。常见做法是把音频缓冲区放在非缓存内存或使用dma_alloc_coherent类接口分配,从源头消除一致性维护开销。

二、SCU与SSIU的FIFO管理:水位、阈值与溢出防护

SCU负责音频数据的混音、路由和格式转换,SSIU负责与外部串行音频接口的时序对接。两者内部都有FIFO,FIFO的“水位”决定了DMA何时被触发、CPU何时被中断。理解FIFO管理,核心是三个参数:FIFO深度、DMA触发阈值、中断阈值。

播放时,DMA把数据写入SCU播放FIFO,FIFO水位上升;当水位低于触发阈值时,DMA请求新数据。若DMA响应不及时,FIFO被读空,SSIU就会输出重复或静音样本,表现为“咔哒”或断音。录音时方向相反:SSIU把接收数据写入FIFO,水位上升到阈值后DMA把数据搬走;若DMA搬走不及时,FIFO溢出,旧数据被覆盖,表现为录音丢帧。

因此,FIFO阈值不能设得过满或过空。播放FIFO的触发阈值通常设为FIFO深度的一半左右,给DMA留出响应窗口;中断阈值则设在更低水位,用于通知CPU补充大块数据。对于低延迟流,FIFO深度应尽量小,但必须大于“DMA响应延迟 + 总线仲裁延迟”对应的样本数,否则必然欠载。

R-Car音频DMA数据通路与FIFO水位关系 DDR PCM环形缓冲区 4字节对齐 / 非缓存 块长 = 阈值样本数 DMAC 通道0:媒体播放 通道1:提示音播放 通道2:录音上行 SCU 混音 / 路由 / 格式转换 播放FIFO 录音FIFO SSIU 串行音频时序 I2S / TDM 外部Codec/DSP 读 写FIFO 串行输出 播放FIFO水位与DMA触发关系(示例:深度64样本,阈值32) 当前水位 32 / 64 DMA触发阈值 32 中断阈值 16 水位 > 阈值:DMA空闲,等待下一次请求 水位 ≤ 阈值:DMA请求搬运,补充数据 水位 = 0:FIFO欠载,输出断音/重复样本 水位 = 64:FIFO溢出,录音场景丢帧 已填充样本 空闲空间

三、零拷贝音频流:让数据只搬一次

传统音频路径中,应用层把PCM写入内核缓冲区,内核再拷贝到DMA缓冲区,DMA再搬到SCU FIFO,一次播放可能发生两到三次内存拷贝。零拷贝的目标是让应用层直接写入DMA可见的缓冲区,DMA从该缓冲区直接搬运到SCU,中间不再经过内核中转。

在Linux ALSA框架下,零拷贝通常通过mmap实现:驱动把DMA缓冲区映射到用户空间,应用直接读写该区域。R-Car平台上,这要求DMA缓冲区物理连续或使用IOMMU映射为连续。若使用IOMMU,可以把分散的物理页映射为设备可见的连续地址,既避免大块连续内存分配失败,又保持零拷贝语义。

零拷贝的另一个关键是“缓冲区所有权”的交替。以播放为例,应用填充一段缓冲区后,通过period通知驱动该段可被DMA读取;DMA完成一段后,通过中断通知应用该段可被重新填充。这个“填充—提交—搬运—回收”的循环,必须与FIFO水位配合:应用提交太慢,FIFO欠载;提交太快,环形缓冲区回绕覆盖未播放数据。因此,环形缓冲区至少应包含两个period,推荐三个以上,给应用留出调度余量。

四、低延迟缓冲设计:从参数到可观察结果

低延迟不是单纯把缓冲区调小,而是在“欠载风险”和“端到端延迟”之间找平衡。端到端延迟可近似为:应用缓冲深度 + DMA块延迟 + SCU FIFO深度 + SSIU串行输出延迟。以48kHz、16位立体声为例,一个样本对占4字节,若应用period为128帧,则单period数据量为512字节,对应时间约2.67ms。若环形缓冲区为4个period,应用侧缓冲延迟约10.7ms;加上FIFO深度64样本(约1.33ms)和SSIU输出延迟,总延迟约12~14ms。

若把period降到64帧、环形缓冲区降到2个period,应用侧延迟约2.67ms,总延迟可压到5ms以内,但DMA必须在约1.33ms内完成一次搬运。此时需要检查总线负载:若DDR带宽被显示或GPU占用,DMA响应可能超过1ms,导致欠载。实践中可先按“FIFO深度 = 2 × DMA最坏响应时间 × 采样率”估算,再通过播放测试观察xrun计数。

判断低延迟设计是否达标,不能只看“有没有声音”。应观察ALSA的xrun计数、SCU FIFO溢出/欠载状态位,以及示波器上I2S的LRCLK与数据对齐情况。若xrun为0且连续播放30分钟无异常,说明当前缓冲参数在目标负载下是安全的;若xrun随负载升高而增加,则应增大period或提高DMA通道优先级,而不是继续压缩缓冲。

在R-Car上调试音频DMA时,优先确认三件事:DMA通道是否与FIFO请求源正确绑定、缓冲区是否满足对齐与一致性要求、FIFO阈值是否大于最坏DMA响应时间对应的样本数。这三项确认后,再调低延迟参数,才能避免“越调越小、越调越爆”的循环。
参数 示例值 作用 调整方向
采样率 48kHz 决定样本时间基准 固定
period帧数 128帧 单次DMA搬运量 降低可减延迟,但增加中断频率
环形缓冲区period数 4 应用调度余量 低于2易回绕覆盖
SCU FIFO深度 64样本 吸收DMA响应抖动 需大于最坏响应样本数
DMA触发阈值 32样本 请求搬运的水位 过低易欠载,过高增延迟

综合来看,R-Car音频DMA与内存管理的核心逻辑是:通道分配决定“谁搬谁”,FIFO管理决定“何时搬”,零拷贝决定“搬几次”,低延迟缓冲决定“搬多快”。四者相互制约,任何一项单独优化都可能被另一项拖累。实际工程中,建议先用保守参数跑通全链路,再逐步压缩period和FIFO深度,同时用xrun计数和FIFO状态位作为回归判据,最终得到既稳定又低延迟的音频通路。