PAL 总体架构与模块交互指南
1. 宏观架构:PAL 的世界观
欢迎来到 Qualcomm PAL (Platform Audio Layer) 的世界。为了更好地理解,我们继续使用餐厅的比喻:
- Android HAL (Client) : 顾客。负责点菜(提需求)。
- Stream (流) : 订单。记录了顾客要什么(听歌、打电话、录音),并负责整个服务流程的进度。
- Session (会话) : 厨房。负责真正的制作(DSP 音频处理、混音、重采样)。
- Device (设备) : 餐桌/外卖窗口。最终呈现声音的地方(喇叭、耳机)。
- ResourceManager (RM) : 大堂经理。统筹全局,安排哪个订单去哪个厨房,送到哪张桌子,处理突发情况(插队、换桌)。
2. 核心交互流程图
- pal_stream_open 2. 请求资源 3. 分配 Device 4. 创建 Session 5. pal_stream_start 6. start() 7. 冲突检查 & 并发控制 8. start() 9. 打开硬件通路(Mixer) 10. prepare() & start() 11. 配置 DSP Graph 12. pal_stream_write(数据) 13. write() 14. pcm_write Android Audio HAL
Stream(订单)
ResourceManager(经理)
Device(餐桌)
Session(厨房)
Audio Hardware
DSP
Kernel / DSP Driver
3. 深度解析:一次完整的音频播放
让我们跟踪一次"播放音乐"的全过程,看看各模块如何协作。
第一阶段:创建与建立连接 (Open)
- Client : 调用
pal_stream_open(STREAM_LOW_LATENCY, DEVICE_SPEAKER)。 - Stream : 创建
StreamPCM对象。 - Stream -> RM :
- 调用
rm->getDeviceConfig():经理,Speaker 需要什么参数?(RM 查 XML 返回:48kHz, 24bit)。 - 调用
Session::makeSession(rm):创建一个能处理 PCM 的厨房(通常是SessionAlsaPcm)。 - 调用
rm->registerStream():经理,记下来,现在有一单音乐要播放。
- 调用
- 关联 : Stream 内部保存了
Session和Device的指针。连接建立完毕。
第二阶段:启动与资源分配 (Start)
- Client : 调用
pal_stream_start()。 - Stream -> RM: 经理,我要开始播了,可以吗?
- RM :
- 检查并发:现在有电话吗?有语音唤醒吗?(如果有,RM 会通知其他 Stream 暂停或由 DSP 混音)。
- 增加引用计数:Speaker 的使用人数 +1。
- Stream -> Device :
device->start()。- Device : 下发 Mixer Controls (如
"SLIMBUS_0_RX Audio Mixer MultiMedia1"),打通物理通路。
- Device : 下发 Mixer Controls (如
- Stream -> Session :
session->prepare()->session->start()。- Session :
- 从 RM 申请 FrontEnd ID (比如
pcm0p)。 - 从 Device 知道 BackEnd Name (比如
QUAT_TDM_RX_0)。 - 关键动作: 配置 DSP,告诉它"把 PCM0 的数据处理完后,送到 TDM_RX_0 接口"。
- 调用
pcm_start激活内核节点。
- 从 RM 申请 FrontEnd ID (比如
- Session :
第三阶段:数据传输 (Running)
- Client : 循环调用
pal_stream_write(buffer)。 - Stream: 不做处理,直接透传。
- Session : 调用
pcm_write(TinyALSA) 将数据写入共享内存或内核缓冲区。 - DSP/Kernel: 读取数据 -> 音效处理 -> 发送给 Codec -> 喇叭发声。
第四阶段:动态设备切换 (Device Switch)
场景:插耳机
- RM : 收到
ConnectionStateChange事件。 - RM : 扫描所有活跃 Stream,发现
MusicStream在用Speaker。 - RM -> Stream : 强制切换!
- 调用
stream->disconnectStreamDevice(Speaker)。 - 调用
stream->connectStreamDevice(Headphone)。
- 调用
- Stream :
- 暂停 Session 数据写入(防止杂音)。
Device(Speaker)->stop()。Device(Headphone)->start()。Session更新路由配置 (告诉 DSP 换出口)。- 恢复 Session 数据写入。
4. 模块间接口汇总
| 调用方 | 被调用方 | 关键接口 | 目的 |
|---|---|---|---|
| Stream | RM | registerStream |
注册自己,纳入管理 |
| Stream | RM | getDeviceConfig |
获取设备能力 |
| Stream | Session | open, start, write |
控制 DSP 管道 |
| Stream | Device | start, stop |
控制物理硬件开关 |
| RM | Stream | switchDevice |
通知流进行设备切换 |
| Session | RM | allocateFrontEndIds |
申请 PCM 设备号 |
| Session | RM | getVirtualAudioMixer |
获取 Mixer 句柄 |
5. 常见问题 (FAQ)
- Q: 为什么要分 Stream 和 Session?
- A: 解耦。Stream 关注业务(播放/录音/通话),Session 关注底层实现(TinyALSA/GSL/AGM)。如果高通换了底层架构(比如从 ALSA 换到 GSL),只需要重写 Session,Stream 层的业务逻辑不用动。
- Q: ResourceManager 的 XML 到底决定了什么?
- A: 它决定了"静态路由图"。比如:哪个流类型对应哪种 Session 模式?哪个 Device 对应哪个 Backend 接口?所有的硬件拓扑都在 XML 里。
- Q: 为什么会有并发问题?
- A: 硬件资源是有限的。比如 DSP 里的回声消除 (EC) 模块可能只有一个,或者某个 Backend 接口同一时间只能跑一个采样率。RM 的作用就是协调这些冲突。
6. 学习路线建议
- 入门 : 先看
Stream模块介绍,理解业务流程。 - 进阶 : 结合
ResourceManager 模块介绍和 XML 文件,理解系统是如何配置起来的。 - 深入 : 阅读
Session 模块介绍和Device 模块介绍,理解数据到底是怎么送到底层的。 - 实战 : 尝试在
resourcemanager.xml中新增一个 Device,并在代码中打通它。