Android 通过 HAL 管理音频通路,Linux 则通过 ALSA Config(Rockchip 的 alsa-config 仓库)实现,其核心是 UCM2(ALSA Use Case Manager 2)。插拔耳机自动切换输出设备、不同场景应用不同的 mixer 设置——这些逻辑都在 HiFi.conf 中配置。本文以 RK730+ES7210 为例,介绍 ALSA 配置的写法和调试方法。
项目的完整结构如下:
alsa-config/ ← Rockchip ALSA 配置仓库└── alsa/ ├── cards/ ← 声卡级别配置 ├── init/ ← 初始化配置 ├── ucm/ ← 旧版 UCM └── ucm2/ ← UCM2(本文重点) └── conf.d/rk730-es7210/ ├── rk730-es7210.conf └── HiFi.conf
安装后会被复制到 /usr/share/alsa/,因此设备上看到的路径为 /usr/share/alsa/ucm2/conf.d/rk730-es7210/。
本文要点:
- UCM2 是 Linux 用户空间的音频场景管理器,通过
cset 命令操作内核 mixer 控件 HiFi.conf 定义了全局初始化(SectionVerb)+ 设备切换逻辑(SectionDevice)- RK730+ES7210 的特殊之处:切换设备时需要同时切换 SAI 的 SDI 数据源映射(ES7210 是多通道 ADC)
一、UCM2 在 Linux 音频栈中的位置
应用层: PulseAudio / PipeWire / aplay │ ▼UCM2 层: alsaucm → HiFi.conf → cset 命令 │ ▼内核层: /dev/snd/controlC0 → mixer 控件 (OUT1 Switch, OUT2 Switch...)
Android 用 HAL (AIDL/tinyalsa) 管理通路,Linux 用 UCM2。两者最终操作的是同一套内核 mixer 控件,只是配置格式和组织方式不同。
二、架构:三层配置
RK730 的 ALSA 配置分为三层(源码位于 SDK 的 external/alsa-config/ 目录),从系统启动到运行时逐层生效:
启动阶段 (boot) alsactl init → alsa/init/rk730 ← 硬件 mixer 默认值运行阶段 (runtime) alsa-lib → alsa/cards/rk730-es7210.conf ← "default" PCM 映射音频服务 (runtime) PipeWire → alsa/ucm2/conf.d/rk730-es7210/ ← Jack 感知 + 设备切换
2.1 Init 层:开机初始化 mixer
alsa/init/rk730 — 系统启动时由 alsactl init 执行,设置 mixer 安全默认值:
CTL{reset}="mixer"# 重置所有 mixer 控件到默认CTL{name}="OUT1 Switch" CTL{value}="on"CTL{name}="OUT2 Switch" CTL{value}="on"CTL{name}="MIC1 Boost Volume" CTL{value}="2"CTL{name}="MIC2 Boost Volume" CTL{value}="2"RESULT="true"
入口:alsa/init/rockchip 是调度器,根据内核驱动名匹配:
CARDINFO{driver}=="rockchip-rk730", INCLUDE="rk730", GOTO="init_end"
当内核 ALSA 驱动上报名称为 rockchip-rk730 时,自动包含 rk730 脚本。
Init 层的作用是开机后立即设置硬件到已知状态,防止上次关机遗留的异常值导致无声或杂音。
2.2 Cards 层:定义默认 PCM 设备
alsa/cards/rk730-es7210.conf — 为应用提供 "default" 设备别名:
rk730-es7210.pcm.default {type hooks slave.pcm {type hw card $CARD# 指向真实硬件 card }}
任何 ALSA 应用均可通过 "default:CARD=rk730-es7210" 访问声卡,无需关心底层卡号。hooks 插件预留了自动路由逻辑的扩展点(当前为空)。
Cards 层使应用无需关心卡号,直接通过声卡名访问。卡片名由 cards 目录下的 .conf 文件名决定。
2.3 UCM2 层:场景切换(核心)
主配置:rk730-es7210.conf
Syntax 3SectionUseCase."HiFi" { File "HiFi.conf" Comment "Rockchip HiFi Codec RK730/ES7210."}
主配置文件声明 HiFi UseCase,具体配置指向 HiFi.conf。
三、HiFi.conf 详解
3.1 全局初始化:SectionVerb
SectionVerb { EnableSequence [ # 设置 SDI 映射顺序(Speaker/Default 模式) cset "name='SAI1 Receive PATH0 Source Select' From SDI1" cset "name='SAI1 Receive PATH1 Source Select' From SDI2" cset "name='SAI1 Receive PATH2 Source Select' From SDI0" # 默认:打开扬声器,关闭耳机 cset "name='OUT2 Switch' on" cset "name='OUT1 Switch' off" # 恢复硬件回环增益到 0dB(ES7210 ADC 的 PGA 设置) cset "name='PGA3_setting' 0" cset "name='PGA4_setting' 0" ] DisableSequence [ ]}
SectionVerb 在声卡打开时执行一次,设置初始状态:
- SDI 映射 —
SAI1 Receive PATHx Source Select 是 SoC SAI 驱动注册的控件,控制 I2S/TDM 接收通道的数据源映射。PATH0←SDI1、PATH1←SDI2、PATH2←SDI0 是"扬声器模式"的映射顺序 - 输出路由 — 打开 OUT2(扬声器),关闭 OUT1(耳机)——这是开机默认状态
- PGA 恢复 —
PGA3/4_setting 是 ES7210 ADC 的数字 PGA 增益,0 表示 0dB
SDI 映射说明:ES7210 是 4 通道 ADC,通过 I2S 多条 SDI 线传输数据。切换麦克风时需要重新映射 SDI 数据源,使正确的麦克风通道映射到对应的 ALSA 捕获通道。
3.2 扬声器设备
SectionDevice."Speaker" { Comment "Speaker Output" Value { PlaybackPriority 100 # 优先级:数字越大越优先 } EnableSequence [ # 关闭耳麦麦克风 cset "name='Headset Mic Switch' off" # 恢复 SDI 映射(Speaker 模式) cset "name='SAI1 Receive PATH0 Source Select' From SDI1" cset "name='SAI1 Receive PATH1 Source Select' From SDI2" cset "name='SAI1 Receive PATH2 Source Select' From SDI0" # 打开扬声器,关闭耳机 cset "name='OUT2 Switch' on" cset "name='OUT1 Switch' off" ] DisableSequence [ ]}
3.3 耳机设备
SectionDevice."Headphones" { Comment "Headphones Output" Value { PlaybackPriority 200 # 优先级高于 Speaker JackName "rk730-es7210 Headset" # 插拔检测 } EnableSequence [ # 打开耳麦麦克风 cset "name='Headset Mic Switch' on" # 切换 SDI 映射(Headset 模式) cset "name='SAI1 Receive PATH0 Source Select' From SDI0" cset "name='SAI1 Receive PATH1 Source Select' From SDI1" cset "name='SAI1 Receive PATH2 Source Select' From SDI2" # 打开耳机,关闭扬声器 cset "name='OUT1 Switch' on" cset "name='OUT2 Switch' off" ] DisableSequence [ ]}
两设备对比:
四、ES7210 vs ES7202 两种变体
alsa-config 仓库里同时提供了两种 RK730 的 UCM2 配置:
| | |
|---|
| ADC | | |
| SDI 线 | | |
| SDI 映射 | | |
| 麦克风 | | |
| Mic 控件 | Headset Mic Switch | Headset Mic Switch |
| 适用 | | EVB2 / Buildroot 嵌入式 Linux |
两者的播放部分完全一致(OUT1=耳机、OUT2=扬声器),差异只在录音的 SDI 映射。
五、Speaker ↔ Headphone 切换流程
1. 用户插入耳机 │2. 内核 Jack "rk730-es7210 Headset" 触发事件 │3. UCM2 检测到 JackName 匹配,切换设备: │ ├── Speaker.DisableSequence (空 — 由 Headphones 覆盖) │ └── Headphones.EnableSequence: ├── Headset Mic Switch → on (打开耳麦) ├── SDI 映射切换 (SDI0/SDI1/SDI2) ├── OUT1 Switch → on (打开耳机) └── OUT2 Switch → off (关闭扬声器) │4. 用户拔掉耳机 │5. UCM2 回退到 Speaker 设备: │ └── Speaker.EnableSequence: ├── Headset Mic Switch → off ├── SDI 映射恢复 (SDI1/SDI2/SDI0) ├── OUT2 Switch → on └── OUT1 Switch → off
六、UCM2 与 Android HAL 的对应关系
两者操作的是同一套内核 mixer 控件,只是封装方式不同:
同一个场景的三种写法
| | |
|---|
| UCM2 | cset "name='OUT2 Switch' on" | cset "name='OUT1 Switch' on" |
| AIDL HAL | <ctl name="OUT2 Switch" value="1"/> | <ctl name="OUT1 Switch" value="1"/> |
| TinyALSA HAL | .ctl_name = "OUT2 Switch", .int_val = {on} | .ctl_name = "OUT1 Switch", .int_val = {on} |
| 手动调试 | tinymix 'OUT2 Switch' 1 | tinymix 'OUT1 Switch' 1 |
架构对比
| | |
|---|
| 配置格式 | HiFi.conf | mixer_paths_*.xml |
| 设备定义 | SectionDevice."Speaker" | <path name="out_speaker"> |
| 场景切换 | EnableSequence | Route::applyAndUpdatePaths() |
| 触发方式 | | Framework setConnectedDevices() |
| 优先级 | PlaybackPriority | 由 Android AudioPolicyManager 管理 |
七、调试方法
7.1 确认配置加载
# 查看 UCM2 配置目录ls /usr/share/alsa/ucm2/conf.d/# 应看到: rk730-es7210/# 列出可用卡片alsaucm listcards
7.2 手动操作 UCM2
# 激活 HiFi UseCasealsaucm -c rk730-es7210 set _verb HiFi# 启用扬声器alsaucm -c rk730-es7210 set _enadev Speaker# 启用耳机(模拟插拔)alsaucm -c rk730-es7210 set _enadev Headphones# 查看当前激活的设备alsaucm -c rk730-es7210 get _enadev
7.3 播放测试
# 激活扬声器后播放alsaucm -c rk730-es7210 set _enadev Speakeraplay -D hw:1,0 -c 2 -f S16_LE -r 48000 test.wav# 切换到耳机后播放alsaucm -c rk730-es7210 set _enadev Headphonesaplay -D hw:1,0 -c 2 -f S16_LE -r 48000 test.wav
7.4 检查 mixer 控件状态
# 查看 OUT1/OUT2 当前状态amixer -c 1 cget name='OUT1 Switch'amixer -c 1 cget name='OUT2 Switch'# 查看 SDI 映射状态amixer -c 1 cget name='SAI1 Receive PATH0 Source Select'amixer -c 1 cget name='SAI1 Receive PATH1 Source Select'amixer -c 1 cget name='SAI1 Receive PATH2 Source Select'
八、添加新 Codec 的 ALSA 配置
以添加 es5678 Codec 为例,三层各需修改一个文件:
1. Init 层:创建初始化脚本
新建 alsa/init/es5678:
CTL{reset}="mixer"CTL{name}="DAC Switch" CTL{value}="on"RESULT="true"
在 alsa/init/rockchip 调度器中添加:
CARDINFO{driver}=="es5678", INCLUDE="es5678", GOTO="init_end"
2. Cards 层:创建卡片配置
新建 alsa/cards/es5678.conf:
es5678.pcm.default {type hw card $CARD}
3. UCM2 层:创建场景配置
新建 alsa/ucm2/conf.d/es5678/es5678.conf:
Syntax 3SectionUseCase."HiFi" { File "HiFi.conf" Comment "ES5678 HiFi Codec."}
新建 alsa/ucm2/conf.d/es5678/HiFi.conf:
SectionVerb { EnableSequence [ cset "name='DAC Switch' on" ]}SectionDevice."Speaker" { Value { PlaybackPriority 100 } EnableSequence [ cset "name='DAC Switch' on" cset "name='Speaker Volume' 60" ]}
控件名通过 amixer -c 1 contents 查看内核驱动实际注册的名称。
九、总结
| | | |
|---|
| init | | | alsa/init/rk730 |
| cards | | | alsa/cards/rk730-es7210.conf |
| UCM2 | | | alsa/ucm2/conf.d/rk730-es7210/ |
三层从底层到上层,各司其职。日常开发中添加新 Codec 时,三层都需要更新:init 层增加开机初始化脚本,cards 层增加默认 PCM 映射,UCM2 层增加场景切换配置。
本文是「Rockchip RK730 音频开发」系列文章。
SDK 路径:external/alsa-config/
设备路径:/usr/share/alsa/ucm2/conf.d/rk730-es7210/
示例平台:RK3572 SoC + RK730 Codec