第四节:SectionDevice深入:Playback/Capture设备定义、硬件参数(hw_params)、软件参数(sw_params)
好,咱们继续往下走。上一节我们把SectionDevice的骨架搭起来了,这一节要往里面填肉——具体说说Playback和Capture设备怎么定义,以及那两个让人又爱又恨的参数块:hw_params和sw_params。
说实话,我刚开始接触UCM时,最头疼的就是这两个参数。明明都是描述设备行为的,为什么非要拆成两个?后来踩过几次坑才明白——这设计其实挺讲究的。
一、Playback与Capture设备定义
先看个最简单的例子。一个典型的音频Codec,通常会有播放和录音两条路径。在UCM里,我们用SectionDevice来分别描述它们。
SectionDevice."Speaker" {
Comment "内置扬声器播放设备"
EnableSequence [
cset "name='Speaker Playback Switch' 1"
cset "name='Speaker Playback Volume' 80"
]
DisableSequence [
cset "name='Speaker Playback Switch' 0"
]
Value {
PlaybackPriority 100
PlaybackPcm "hw:0,0"
}
}
SectionDevice."Internal Mic" {
Comment "内置麦克风录音设备"
EnableSequence [
cset "name='Internal Mic Capture Switch' 1"
cset "name='Internal Mic Capture Volume' 90"
]
DisableSequence [
cset "name='Internal Mic Capture Switch' 0"
]
Value {
CapturePriority 100
CapturePcm "hw:0,1"
}
}
看到没?Playback和Capture的定义结构几乎一样,区别就在Value块里的那几个关键字段。我个人习惯把Playback和Capture分开写,这样后期维护时一眼就能找到对应的设备。
关键点:一个SectionDevice可以同时包含Playback和Capture属性,但实际项目中我建议分开。混在一起容易出问题,尤其是当EnableSequence里需要分别控制播放和录音路径时。
二、硬件参数(hw_params)深入
hw_params描述的是硬件层面的能力。说白了,就是告诉音频系统:我这块Codec能支持哪些采样率、哪些位宽、哪些通道数。
我曾经在一个项目里遇到过这样的问题:明明Codec支持48kHz,但播放出来的声音就是变调了。查了半天,发现是hw_params里没配对,系统默认用了44.1kHz。嗯,这种低级错误,犯过一次就再也不会忘了。
SectionDevice."Speaker" {
Value {
PlaybackPriority 100
PlaybackPcm "hw:0,0"
# 硬件参数定义
PlaybackHwParams "format:S16_LE,S24_LE"
PlaybackHwParams "rate:44100,48000,96000"
PlaybackHwParams "channels:2"
PlaybackHwParams "period_size:1024"
PlaybackHwParams "buffer_size:4096"
}
}
这里每个参数的含义,我简单说一下:
| 参数 | 说明 | 常见取值 |
|---|---|---|
| format | 音频样本格式(位宽+字节序) | S16_LE, S24_LE, S32_LE |
| rate | 支持的采样率列表 | 8000, 16000, 44100, 48000 |
| channels | 声道数 | 1(单声道), 2(立体声) |
| period_size | 每次中断处理的帧数 | 1024, 512, 256 |
| buffer_size | 环形缓冲区总大小(帧数) | 4096, 8192, 16384 |
小技巧:period_size和buffer_size的关系要搞清楚。buffer_size通常是period_size的整数倍,我一般设4倍。比如period_size=1024,buffer_size=4096。这样既能保证低延迟,又不容易出现Xrun(缓冲区欠载/溢出)。
三、软件参数(sw_params)深入
sw_params和hw_params有什么区别?你想想看,hw_params是硬件的能力,sw_params是软件层面的策略。硬件能做什么,和软件想怎么做,这是两码事。
举个例子:你的Codec硬件支持S16_LE和S24_LE两种格式,这是hw_params告诉系统的。但具体用哪种格式来播放音乐,这是sw_params决定的。
SectionDevice."Speaker" {
Value {
PlaybackPriority 100
PlaybackPcm "hw:0,0"
# 硬件参数
PlaybackHwParams "format:S16_LE,S24_LE"
PlaybackHwParams "rate:44100,48000"
PlaybackHwParams "channels:2"
# 软件参数
PlaybackSwParams "format:S16_LE"
PlaybackSwParams "rate:48000"
PlaybackSwParams "channels:2"
PlaybackSwParams "start_threshold:1024"
PlaybackSwParams "stop_threshold:4096"
PlaybackSwParams "silence_threshold:0"
PlaybackSwParams "silence_size:0"
PlaybackSwParams "avail_min:512"
}
}
这里有几个sw_params参数,我得重点说说:
- start_threshold:缓冲区里有多少数据才开始播放。设小了容易卡顿,设大了延迟高。我一般设成period_size的值。
- stop_threshold:缓冲区空到什么程度就停止播放。通常设成buffer_size,表示完全空了才停。
- avail_min:应用程序最少要写入多少数据才能触发下一次写入。这个值设小了会增加CPU开销,设大了可能丢数据。
注意:sw_params里的format、rate、channels必须在hw_params支持的范围内。我曾经见过有人把sw_params的rate设成96000,但hw_params里根本没配这个值,结果音频服务直接崩溃。这种问题排查起来特别费劲,因为错误日志不会直接告诉你「参数不匹配」。
四、实战中的参数调优建议
好了,理论说完了,咱们聊聊实战。我这些年调过的设备少说也有几十款,总结了几条经验:
- 先配hw_params,再配sw_params。这个顺序别搞反了。hw_params是基础,基础没打好,上层怎么调都没用。
- period_size别设太小。有些人为了追求低延迟,把period_size设成64甚至32。结果呢?CPU占用率飙升,系统卡成PPT。除非你做的是专业音频设备,否则1024是个比较稳妥的起点。
- 多测试几种参数组合。同一个Codec,在不同的平台上表现可能完全不同。我习惯在调试阶段准备3-4套参数方案,逐一测试,选最优的那套。
- 别忘了DisableSequence。很多人只关注EnableSequence,忽略了关闭设备时的操作。不正确的关闭顺序可能导致下次打开设备时状态混乱。
核心总结:SectionDevice的Playback/Capture定义是UCM配置的基石。hw_params描述硬件能力,sw_params描述软件策略。两者配合好了,音频系统才能稳定高效地工作。记住一句话:硬件能做什么,软件想做什么,中间差的就是你写的UCM配置。
下一节我们会聊聊更复杂的场景——多路音频流的混音和路由。到时候你会发现,今天学的这些参数配置,到了那个场景下会变得更有意思。