第3章:UCM配置文件结构:语法基础、Section划分、关键字段解析

好,咱们开始聊配置文件。说实话,我第一次接触UCM的配置文件时,心里是有点发怵的——满屏的括号、冒号、缩进,看着像JSON又像YAML,还有点像C语言的结构体。但用熟了之后你会发现,这玩意儿其实挺规整的。说白了,它就是一套用文本描述音频路由的“说明书”。

3.1 语法基础:别被花括号吓到

UCM的配置文件,全称叫 Use Case Manager 配置文件。它的语法核心就三个东西:Section、键值对、数组。嗯,就这些。

先看一个最简单的例子:

SectionVerb {
    EnableSequence [
        cset "name='Speaker' mixer.0 output_1_mux 0"
        cset "name='Speaker' mixer.0 output_2_mux 0"
    ]
    DisableSequence [
        cset "name='Speaker' mixer.0 output_1_mux 1"
        cset "name='Speaker' mixer.0 output_2_mux 1"
    ]
}

你看,SectionVerb 是一个Section的名字,花括号包起来的就是它的内容。里面 EnableSequence 和 DisableSequence 是键,后面的方括号 [] 里是数组,数组里每一条都是一个字符串命令。

关键点:

  • Section名必须顶格写,不能有缩进
  • 花括号 {} 必须单独占一行(我个人习惯这样,清晰)
  • 键值对用空格分隔,不是等号
  • 数组元素用方括号包裹,每个元素用双引号
  • 每条命令以 cset 开头,这是ALSA的控制命令

我在项目中遇到过有人把花括号写在同一行,结果解析器直接报错。嗯,UCM的解析器对格式要求挺严格的,少个换行都不行。

3.2 Section划分:五大核心模块

一个完整的UCM配置文件,通常由以下几个Section组成。我按使用频率排个序:

Section名称 作用 是否必须
SectionUseCase 定义音频场景(如播放音乐、通话) 是
SectionVerb 定义启用/禁用某个音频设备的操作 是
SectionDevice 定义物理音频设备(扬声器、麦克风等) 是
SectionModifier 定义音频效果(如EQ、音量控制) 否
SectionConf 全局配置(如采样率、缓冲区大小) 推荐有

你想想看,这五个Section其实对应了音频系统的五个维度:场景、动作、硬件、效果、全局参数。我刚开始学的时候,总是搞混 SectionVerb 和 SectionDevice 的区别。后来我总结了一句话:Verb是“怎么做”,Device是“用什么做”。

3.3 关键字段解析:每个Section里的核心参数

3.3.1 SectionUseCase 的关键字段

SectionUseCase."Music" {
    File "music.conf"
    Comment "Play music through speaker"
}

这里 File 字段指定了该场景对应的配置文件路径。注意,路径是相对于主配置文件的。我曾经踩过一个坑:把路径写成了绝对路径,结果换了个平台就找不到了。建议用相对路径。

Comment 字段是给人看的,解析器会忽略它。但别小看这个字段,团队协作时,写清楚注释能省很多沟通成本。

3.3.2 SectionVerb 的关键字段

SectionVerb {
    Value {
        TQ "0"
        CapturePCM "hw:0,0"
        PlaybackPCM "hw:0,0"
    }
    EnableSequence [
        cset "name='Master Playback Volume' 80%"
    ]
    DisableSequence [
        cset "name='Master Playback Volume' 0%"
    ]
}

这里 Value 块里定义了三个关键字段:

  • TQ:时间配额(Time Quota),0表示不限制。我一般设成0,除非有严格的功耗要求。
  • CapturePCM:录音使用的PCM设备。注意,这里填的是ALSA的hw设备名。
  • PlaybackPCM:播放使用的PCM设备。这两个字段很容易写反,我见过有人把Capture和Playback搞混,结果录音没声音,播放也没声音。

我曾经犯过的错: 在 EnableSequence 里忘了写 DisableSequence 的对应操作。结果场景切换时,上一个场景的设备没关掉,两个场景的音频流混在一起。嗯,从那以后我养成了习惯:写Enable的同时,一定把Disable也写好。

3.3.3 SectionDevice 的关键字段

SectionDevice."Speaker" {
    Comment "Speaker output"
    SupportedDevice [
        "Speaker"
    ]
    ConflictingDevice [
        "Headphone"
    ]
    EnableSequence [
        cset "name='Speaker Switch' on"
    ]
    DisableSequence [
        cset "name='Speaker Switch' off"
    ]
}

这里有两个字段值得注意:

  • SupportedDevice:当前设备支持哪些物理设备。说白了,就是告诉系统“我这个场景能用哪些硬件”。
  • ConflictingDevice:冲突设备列表。比如插上耳机时,扬声器应该静音。这个字段就是干这个的。

我建议你在写 ConflictingDevice 时,把互斥关系列全。比如扬声器和耳机互斥,麦克风和扬声器在某些场景下也互斥。漏掉一个,就可能出现“耳机响着,扬声器也响着”的尴尬情况。

3.3.4 SectionModifier 的关键字段

SectionModifier."EQ" {
    Comment "Equalizer for speaker"
    EnableSequence [
        cset "name='EQ Curve' 0 1 2 3 2 1 0"
    ]
    DisableSequence [
        cset "name='EQ Curve' 0 0 0 0 0 0 0"
    ]
}

Modifier说白了就是音频效果器。它的字段和Verb很像,但多了个 Type 字段(虽然上面没写)。常见的Type有 Volume、EQ、DRC(动态范围压缩)。

小技巧: 如果你不确定某个Modifier的Type怎么写,可以先用 Volume 试试。因为Volume是最通用的,几乎所有音频硬件都支持。等调通了再换成具体的效果器。

3.3.5 SectionConf 的关键字段

SectionConf {
    SampleRate "48000"
    BufferSize "1024"
    PeriodSize "256"
    Format "S16_LE"
}

这个Section一般放在配置文件最前面。它定义了全局的音频参数:

  • SampleRate:采样率,常见的有44100、48000、96000。我建议统一用48000,兼容性最好。
  • BufferSize:缓冲区大小,单位是帧。值越小延迟越低,但CPU占用越高。1024是个比较平衡的值。
  • PeriodSize:周期大小,一般设为BufferSize的1/4。这个值影响中断频率。
  • Format:音频格式,S16_LE是16位有符号小端格式,最通用。

嗯,这里要注意:SectionConf里的参数是全局生效的。如果你在某个场景里想用不同的采样率,得在对应的SectionVerb里单独覆盖。我一般不在SectionConf里写死,而是留个注释,方便后期调优。

3.4 一个完整的配置文件示例

好了,咱们把上面这些拼起来,看一个完整的例子。这个例子定义了一个“播放音乐”的场景:

SectionConf {
    SampleRate "48000"
    BufferSize "1024"
    PeriodSize "256"
    Format "S16_LE"
}

SectionUseCase."Music" {
    File "music.conf"
    Comment "Play music through speaker"
}

SectionVerb {
    Value {
        TQ "0"
        PlaybackPCM "hw:0,0"
    }
    EnableSequence [
        cset "name='Master Playback Volume' 80%"
    ]
    DisableSequence [
        cset "name='Master Playback Volume' 0%"
    ]
}

SectionDevice."Speaker" {
    SupportedDevice [
        "Speaker"
    ]
    ConflictingDevice [
        "Headphone"
    ]
    EnableSequence [
        cset "name='Speaker Switch' on"
    ]
    DisableSequence [
        cset "name='Speaker Switch' off"
    ]
}

你看,整个结构其实很清晰:先定义全局参数,再定义场景,然后定义动作,最后定义设备。我习惯按这个顺序写,不容易漏东西。

总结一下这章的核心:

  • UCM配置文件由Section组成,每个Section有特定的作用
  • 语法上注意花括号、缩进、数组格式
  • 关键字段要理解其含义,特别是 EnableSequence 和 DisableSequence 的配对
  • 写配置文件时,先搭框架,再填细节

下一章咱们会聊怎么调试这些配置文件。说实话,写配置不难,调配置才是真正考验人的地方。到时候我会分享几个我踩过的坑,保证让你少走弯路。