第三章:核心语法详解——Value/Sequence/Tuple的定义、布尔值与字符串处理、Include机制
好,咱们进入第三章。说实话,UCM 的语法看起来简单,但坑不少。我见过太多人把 Value 和 Sequence 搞混,结果音频路由死活不对。今天咱们就把这几个核心概念掰开揉碎讲清楚。
3.1 Value:最基础的配置单元
Value 是什么?说白了就是一个键值对。你告诉系统:「这个属性等于那个值」。比如:
Value ["JackDetection"] "Jack" "Present"
这行代码的意思是:把 JackDetection 这个属性,在 Jack 设备上,设置为 Present。嗯,就这么简单。
但要注意,Value 的值可以是数字、字符串、布尔值。我刚开始学的时候,以为只能写字符串,结果踩了个坑——有一次我需要配置一个采样率参数,写成:
Value ["SampleRate"] "48000"
系统死活不认。后来才发现,数字应该直接写,不加引号:
Value ["SampleRate"] 48000
3.2 Sequence:有序的配置集合
Sequence 就有点意思了。它是一组有序的 Value,用来描述一个完整的配置序列。你想想看,音频设备初始化时,往往需要按顺序执行多个操作——先设置采样率,再配置通道,最后启动传输。Sequence 就是干这个的。
举个例子:
Sequence ["CaptureSequence"] {
Value ["Format"] "S16_LE"
Value ["Channels"] 2
Value ["Rate"] 48000
Value ["Enable"] true
}
我在项目中遇到过一个问题:有个客户说他的麦克风采集不到声音。我排查了半天,发现是 Sequence 里的顺序写反了——先 Enable 了设备,然后才设置 Format。你想想看,设备都启动了,你再改格式,它根本不认。所以顺序很重要。
3.3 Tuple:成对出现的配置
Tuple 是成对出现的配置项。它和 Value 的区别在于:Value 是一个属性对应一个值,Tuple 是两个值必须一起出现。比如:
Tuple ["JackControl"] "Headphone" "Volume"
这里 "Headphone" 和 "Volume" 是成对出现的,缺一不可。我曾经见过有人只写了一个值,结果系统直接报语法错误。嗯,Tuple 就是这么严格。
Tuple 最常见的用途是描述「设备-控制」对。比如:
| Tuple 示例 | 含义 |
|---|---|
| Tuple ["JackControl"] "Mic" "Mute" | 麦克风的静音控制 |
| Tuple ["JackControl"] "Speaker" "Volume" | 扬声器的音量控制 |
| Tuple ["JackControl"] "LineIn" "Gain" | 线路输入的增益控制 |
3.4 布尔值与字符串处理
布尔值在 UCM 里只有两个:true 和 false。注意,必须是小写。我见过有人写 True、TRUE、甚至 1,统统不认。
Value ["Mute"] true
Value ["Enable"] false
字符串处理这块,有个小技巧:字符串可以包含转义字符。比如路径里有空格,你需要用反斜杠转义:
Value ["DeviceName"] "USB\ Audio\ Device"
为什么要这样?因为 UCM 的解析器会把空格当作分隔符。你不转义,它就把一个字符串拆成两个了。我曾经因为这个 bug 调试了一下午,最后发现是文件名里有个空格。
3.5 Include 机制:模块化你的配置
Include 机制是我个人最喜欢的特性。它允许你把一个 UCM 文件拆分成多个小文件,然后用 Include 把它们组合起来。就像写代码一样,模块化管理。
语法很简单:
Include "common/audio-format.conf"
Include "codecs/aac.conf"
Include "devices/speaker.conf"
Include 的路径是相对于当前文件的。我建议把所有公共配置放在一个 common 目录下,把设备相关的放在 devices 目录下。这样结构清晰,后期维护也方便。
为什么要用 Include?我给你讲个真实案例。有一次我负责一个项目,有 5 款不同的音频设备,它们的采样率配置完全一样。如果我把采样率配置在每个文件里写一遍,那改起来就太痛苦了。后来我用 Include 把公共配置抽出来,改一处就全改了。省心。
3.6 实战:组合使用这些语法
咱们来看一个完整的例子,把今天学的都用上:
# 主配置文件
Include "common/defines.conf"
SectionDevice "Speaker" {
Value ["JackType"] "Speaker"
Value ["Priority"] 100
Sequence ["InitSequence"] {
Value ["Format"] "S24_LE"
Value ["Channels"] 2
Value ["Rate"] 96000
Value ["Enable"] true
}
Tuple ["JackControl"] "Speaker" "Volume"
Tuple ["JackControl"] "Speaker" "Mute"
}
这个配置做了几件事:
- 通过 Include 引入了公共定义
- 定义了一个 Speaker 设备
- 用 Sequence 描述了初始化流程
- 用 Tuple 声明了可用的控制接口
你想想看,如果没有 Sequence,你得写多少行 Value?如果没有 Include,公共配置得重复多少次?这就是 UCM 语法的设计哲学——用最少的代码,表达最完整的配置。
好了,第三章就到这里。下一章咱们讲 Condition 和 Verb 的用法,那是 UCM 里最灵活的部分。到时候我会分享一个我踩过的坑——条件判断写错了,导致整个音频路由失效。嗯,敬请期待。