2. R-Car硬件基础

好,咱们进入第二章。这一章我打算把 QNX 音频子系统的骨架给你拆开看看。说白了,就是搞清楚声音在 QNX 里到底是怎么从应用层流到硬件,又怎么从硬件流回来的。

很多刚接触 QNX 音频的朋友,上来就被 io-audio、devb-sound 这些名字搞晕了。我当年第一次看 QNX 的文档,也是一头雾水。后来在 R-Car 上调试音频时踩了不少坑,才慢慢理清了这里面的门道。

2.1 整体架构:三层模型

QNX 的音频子系统,我习惯把它分成三层。你想想看,任何操作系统处理音频,本质上都是这么个套路:

  • 应用层:播放器、录音机、语音助手这些。它们不关心硬件。
  • 服务层:负责策略、路由、混音。这是 QNX 音频的“大脑”。
  • 驱动层:直接跟硬件寄存器打交道。这是“手脚”。

这三层之间通过标准的 QNX 消息传递(IPC)通信。每一层各司其职,互不干扰。这种设计的好处是——你换硬件,只需要改驱动层;你换应用,只需要适配服务层接口。

核心要点:QNX 音频架构的精髓在于“分层解耦”。驱动开发者只需要关注 devb-sound 这一层,上层的事情可以暂时不管。

QNX 音频子系统三层架构 应用层 (Application Layer) 多媒体播放器 · 语音助手 · 录音应用 · 游戏引擎 接口:libasound / QSA (QNX Sound Architecture) API 服务层 (Service Layer) io-audio 音频管理器 功能:设备枚举 · 策略路由 · 混音 · 音量控制 · 格式协商 进程名:io-audio (系统资源管理器) 驱动层 (Driver Layer) devb-sound 音频设备驱动 功能:DMA 管理 · 中断处理 · 硬件寄存器操作 · 编解码器控制 进程名:devb-sound (资源管理器 + 驱动) 用户态 内核态 各层通过 QNX 消息传递 (MsgSend/MsgReceive) 通信,驱动层运行在内核态或进程外资源管理器模式

2.2 io-audio:音频服务的“大管家”

io-audio 是 QNX 音频系统的核心服务进程。它不直接操作硬件,而是负责管理所有的音频设备、流和策略。

我个人习惯把 io-audio 理解成一个“音频交换机”。应用层把音频数据丢给它,它根据当前的路由策略,决定把数据送到哪个硬件输出口——是喇叭、耳机还是蓝牙?

2.2.1 io-audio 的主要职责

  • 设备发现与管理:当 devb-sound 启动并注册到系统后,io-audio 会自动发现它,并为其创建音频设备节点。
  • 策略路由:决定音频数据流向。比如插上耳机后自动切换输出。
  • 混音:多个应用同时播放时,io-audio 负责把多路 PCM 数据混成一路。
  • 格式协商:应用可能想用 48kHz/16bit,但硬件只支持 44.1kHz/24bit。io-audio 负责做格式转换(SRC、位深转换)。

避坑指南:我曾经在 R-Car H3 上遇到一个问题——播放音乐时声音断断续续。查了两天才发现,是 io-audio 的缓冲区配置太小,导致音频流 underrun。后来把 -b 参数调大就解决了。记住,io-audio 的启动参数直接影响音频质量。

2.2.2 io-audio 的启动示例

# 启动 io-audio,指定缓冲区大小为 8192 帧,使用默认策略文件
io-audio -b 8192 -p /etc/sound/policy.cfg &

这里 -b 指定缓冲区大小(以帧为单位),-p 指定策略配置文件。策略文件里定义了各种路由规则,比如“当耳机插入时,关闭喇叭输出”。

2.3 devb-sound:驱动层的“实干家”

devb-sound 才是真正跟硬件打交道的家伙。它是一个 QNX 资源管理器,既负责管理设备节点(比如 /dev/snd/pcmC0D0p),也负责底层的硬件操作。

说白了,devb-sound 是一个“驱动容器”。它内部可以加载不同的硬件驱动模块(SO 文件)。对于瑞萨 R-Car,我们通常使用 devb-sound-rcar.so 这个模块。

2.3.1 devb-sound 的架构

组件 说明
核心框架 提供资源管理器框架,处理 open/close/read/write/devctl 等系统调用
硬件驱动模块 针对具体 SoC 的驱动,如 devb-sound-rcar.so,负责 DMA、中断、SSI/SCU 等外设
编解码器驱动 管理外部 Codec 芯片(如 DA7212、WM8960),通过 I2C 控制音量、采样率等
DMA 引擎 管理音频数据的 DMA 传输,实现零拷贝或低拷贝数据路径

2.3.2 devb-sound 的启动示例

# 启动 devb-sound,加载 R-Car 驱动模块,指定 DMA 通道数
devb-sound -d rcar -D dma_channels=8 -D buffer_size=4096 &

这里的 -d rcar 告诉框架加载 R-Car 驱动模块。-D 参数可以传递驱动特定的选项。嗯,这里要注意——不同的 R-Car 版本(H3、M3、E3)的 DMA 通道数和缓冲区要求可能不一样,一定要查数据手册。

2.4 音频服务层次与驱动模型

QNX 的音频驱动模型,本质上是一个“资源管理器 + 回调函数”的架构。驱动开发者需要实现一组标准的回调函数,然后注册到 devb-sound 框架中。

2.4.1 驱动模型的核心接口

每个音频驱动模块必须实现以下关键回调:

  • snd_open() / snd_close():设备打开和关闭时的初始化/反初始化。
  • snd_start() / snd_stop():启动/停止音频流传输。
  • snd_ioctl():处理各种控制命令,如设置采样率、格式、音量等。
  • snd_interrupt():中断处理函数,处理 DMA 完成中断或错误中断。

关键理解:QNX 的音频驱动模型不要求你从头写一个完整的驱动。你只需要实现这些回调函数,剩下的框架代码(设备节点管理、消息处理、缓冲区管理)都由 devb-sound 提供。这就是“驱动模型”的意义——标准化接口,减少重复劳动。

2.4.2 数据流路径

音频数据从应用层到硬件,大致经过以下路径:

  1. 应用调用 write() 写入 PCM 数据到 /dev/snd/pcmC0D0p。
  2. devb-sound 的资源管理器框架接收数据,放入 DMA 缓冲区。
  3. 驱动模块配置 DMA 控制器,启动传输。
  4. DMA 将数据从内存搬运到 SSI(串行音频接口)的 FIFO。
  5. SSI 按位时钟将数据发送到外部 Codec。
  6. Codec 将数字信号转换为模拟信号,驱动喇叭或耳机。

录音路径则相反。我在 R-Car M3 上调试录音时,发现录音数据全是噪声。后来定位到是 Codec 的时钟配置不对——SSI 的位时钟和 Codec 的主时钟没有同步。这种问题,没有对数据路径的清晰理解,根本无从下手。

2.5 本章小结

这一章我们拆解了 QNX 音频子系统的三层架构:

  • io-audio:音频服务的“大脑”,负责策略、路由、混音。
  • devb-sound:驱动层的“骨架”,提供资源管理器框架和硬件驱动模块。
  • 驱动模型:标准化的回调接口,让开发者专注于硬件操作。

理解了这个架构,你就能明白——在 R-Car 上做音频驱动适配,核心工作就是编写一个 devb-sound 的硬件驱动模块,实现那几个关键回调函数。至于上层策略和路由,那是 io-audio 的事,我们暂时不用操心。

重要提醒:千万不要把 io-audio 和 devb-sound 混为一谈。它们是两个独立的进程,职责完全不同。我见过有人把驱动代码写进 io-audio 的策略文件里,结果系统直接崩溃。记住——io-audio 管策略,devb-sound 管硬件,各司其职。