ALSA 用例配置。参考 ALSA 用例配置 来了解更详细信息。
ALSA 用例配置
用例配置文件使用 配置文件 语法来定义静态配置树。该树在运行时根据配置树中的条件和动态变量进行评估(修改)。使用 用例接口 API 解析结果并将其导出到应用程序。
配置目录和主文件名查找
查找路径在 ucm.conf 文件中描述。配置结构看起来像下面这样:
UseCasePath.path1 {
Directory "conf.virt.d"
File "${OpenName}.conf"
}
UseCasePath.path2 {
Directory "external"
File "${OpenName}.conf"
}
现代 Linux 中,ucm.conf 文件通常位于*/usr/share/alsa/ucm2/ucm.conf*,该文件结构大体如下:
#
# This is the toplevel file included from the alsa-lib.
#
# It allows to add extra lookups for the old kernels or so.
#
# You may specify the directory (relative to the toplevel) and
# the master configuration file which defines the verbs.
#
#
# Syntax version is reset for the master configuration file.
#
Syntax 3
Define.V1 "" # non-empty string to enable ucm v1 paths
Define.V2Module yes # empty string to disable
Define.V2Name yes # empty string to disable
If.driver {
Condition {
Type String
Empty "${CardNumber}"
}
True {
#
# The probed path for no-hw-card:
#
# ucm2/${OpenName}/${OpenName}.conf
#
UseCasePath {
legacy {
Directory "${OpenName}"
File "${OpenName}.conf"
}
}
}
False {
#
# The probed path when hw-card is found:
#
# ucm2/${KernelModule}/${KernelModule}.conf
# ucm2/${CardDriver}/${CardLongName}.conf
# ucm2/${CardDriver}/${CardDriver}.conf
#
If.V2Module {
Condition {
Type String
Empty "${var:V2Module}"
}
False {
Define.KernelModulePath "class/sound/card${CardNumber}/device/driver"
Define.KernelModule "${sys:$KernelModulePath}"
UseCasePath.module {
Directory "module"
File "${var:KernelModule}.conf"
}
}
}
If.V2Name {
Condition {
Type String
Empty "${var:V2Name}"
}
False.UseCasePath {
longname {
Directory "${CardDriver}"
File "${CardLongName}.conf"
}
driver {
Directory "${CardDriver}"
File "${CardDriver}.conf"
}
}
}
}
}
If.V1 {
Condition {
Type String
Empty "${var:V1}"
}
False.If.v1_driver {
Condition {
Type String
Empty "${CardNumber}"
}
True {
#
# The probed path for no-hw-card:
#
# ucm/${OpenName}/${OpenName}.conf
#
UseCasePath.v1_legacy {
Version 1
Directory "${OpenName}"
File "${OpenName}.conf"
}
}
False {
#
# The ucm v1 probed path when hw-card is found:
#
# ucm/${CardLongName}/${CardLongName}.conf
# ucm/${CardName}/${CardName}.conf or \
# ucm/${OpenName}/${OpenName}.conf
#
UseCasePath.v1_longname {
Version 1
Directory "${CardLongName}"
File "${CardLongName}.conf"
}
If.v1_hw {
Condition {
Type String
Haystack "${OpenName}"
Needle "hw:"
}
True.UseCasePath.v1_cardnamme {
Version 1
Directory "${CardName}"
File "${CardName}.conf"
}
False.UseCasePath.v1_openname {
Version 1
Directory "${OpenName}"
File "${OpenName}.conf"
}
}
}
}
}
UCM 主配置文件
每个声卡都有一个主声卡文件,它列出声卡支持的所有用例 verbs,如:
# Example master file for blah sound card
# By Joe Blogs <joe@bloggs.org>
Syntax 6
# Use Case name for user interface
Comment "Nice Abstracted Soundcard"
# The file is divided into Use case sections. One section per use case verb.
SectionUseCase."Voice Call" {
File "voice_call_blah"
Comment "Make a voice phone call."
}
SectionUseCase."HiFi" {
File "hifi_blah"
Comment "Play and record HiFi quality Music."
}
# Define Value defaults
ValueDefaults {
PlaybackChannels 4
CaptureChannels 4
}
# Define boot / initialization sequence
# This sequence is skipped, when the soundcard was already configured by system
# (alsactl configuration was already created). The purpose is to not alter
# ALSA card controls which may be modified by user after initial settings.
BootSequence [
cset "name='Master Playback Switch',index=2 0,0"
cset "name='Master Playback Volume',index=2 25,25"
msleep 50
cset "name='Master Playback Switch',index=2 1,1"
cset "name='Master Playback Volume',index=2 50,50"
]
# Define fixed boot sequence
# This sequence is always executed on boot (hotplug).
FixedBootSequence [
cset "name='Something to toggle' toggle"
]
UCM verb 配置文件
verb 配置文件定义设备,修饰符和初始化序列。它有点像声音配置文件。
# Example Use case verb section for Voice call blah
# By Joe Blogs <joe@blogs.com>
# verb global section
SectionVerb {
# enable and disable sequences are compulsory
EnableSequence [
disdevall "" # run DisableSequence for all devices
]
DisableSequence [
cset "name='Power Save' on"
]
# Optional transition verb
TransitionSequence."ToCaseName" [
disdevall "" # run DisableSequence for all devices
msleep 1
]
# Optional TQ and device values
Value {
TQ HiFi
PlaybackChannels 6
}
}
# Each device is described in new section. N devices are allowed
SectionDevice."Headphones" {
SupportedDevice [
"x"
"y"
]
# or (not both)
ConflictingDevice [
"x"
"y"
]
EnableSequence [
...
]
DisableSequence [
...
]
TransitionSequence."ToDevice" [
...
]
Value {
PlaybackVolume "name='Master Playback Volume',index=2"
PlaybackSwitch "name='Master Playback Switch',index=2"
PlaybackPCM "hw:${CardId},4"
}
}
# Each modifier is described in new section. N modifiers are allowed
SectionModifier."Capture Voice" {
Comment "Record voice call"
SupportedDevice [
"x"
"y"
]
# or (not both)
ConflictingDevice [
"x"
"y"
]
EnableSequence [
...
]
DisableSequence [
...
]
TransitionSequence."ToModifierName" [
...
]
# Optional TQ and ALSA PCMs
Value {
TQ Voice
CapturePCM "hw:${CardId},11"
PlaybackMixerElem "Master"
PlaybackVolume "name='Master Playback Volume',index=2"
PlaybackSwitch "name='Master Playback Switch',index=2"
}
}
序列图
序列命令
命令名称 | 描述 |
---|---|
enadev2 ARG | 执行设备启用序列 |
disdev2 ARG | 执行设备禁用序列 |
disdevall “” | 为 verb 中的所有设备执行设备禁用序列 |
cdev ARG | 为 ALSA 控制设备名调用 snd_ctl_open() |
cset ARG | ALSA 控制设置 - snd_ctl_ascii_elem_id_parse() + snd_ctl_ascii_value_parse() |
cset-new ARG | 创建新的 ALSA 用户控制元素 - snd_ctl_ascii_elem_id_parse() + 描述 |
ctl-remove ARG | 移除 ALSA 用户控制元素 - snd_ctl_ascii_elem_id_parse() |
sysw ARG | 写入 sysfs 树 |
usleep ARG | 休眠指定的微妙数 |
msleep ARG | 休眠指定的毫妙数 |
exec ARG | 执行指定的命令 (不通过 shell - man execv) |
shell ARG | 执行指定的命令 (使用 shell - man system) |
cfg-save ARG | 将 LibraryConfig 保存到文件 |
# Examples
cset "name='PCM Playback Volue',index=2 99"
cset-new "name='Bool2' type=bool,count=2 1,0"
cset-new "name='Enum' type=enum,labels='L1;L2;L3' 'L2'"
ctl-remove "name='Bool2'"
sysw "-/class/sound/ctl-led/speaker/card${CardNumber}/attach:Speaker Channel Switch"
usleep 10
exec "/bin/echo hello"
shell "set"
cfg-save "/tmp/test.conf:+pcm"
这些命令用在用例管理的配置文件里,而不是 alsaucm 命令行工具。
名称
参考带有 SND_USE_CASE_VERB 前缀的宏,如 SND_USE_CASE_VERB_HIFI 来了解已知 verb 的完整列表。
#define SND_USE_CASE_VERB_ANALOG_RADIO "FM Analog Radio"
#define SND_USE_CASE_VERB_DIGITAL_RADIO "FM Digital Radio"
#define SND_USE_CASE_VERB_HIFI "HiFi"
#define SND_USE_CASE_VERB_HIFI_LOW_POWER "HiFi Low Power"
#define SND_USE_CASE_VERB_INACTIVE "Inactive"
#define SND_USE_CASE_VERB_IP_VOICECALL "Voice Call IP"
#define SND_USE_CASE_VERB_VOICE "Voice"
#define SND_USE_CASE_VERB_VOICE_LOW_POWER "Voice Low Power"
#define SND_USE_CASE_VERB_VOICECALL "Voice Call"
参考带有 SND_USE_CASE_DEV 前缀的宏,如 SND_USE_CASE_DEV_SPEAKER 来了解已知设备的完整列表。
#define SND_USE_CASE_DEV_BLUETOOTH "Bluetooth"
#define SND_USE_CASE_DEV_DIRECT "Direct"
#define SND_USE_CASE_DEV_EARPIECE "Earpiece"
#define SND_USE_CASE_DEV_HANDSET "Handset"
#define SND_USE_CASE_DEV_HDMI "HDMI"
#define SND_USE_CASE_DEV_HEADPHONES "Headphones"
#define SND_USE_CASE_DEV_HEADSET "Headset"
#define SND_USE_CASE_DEV_LINE "Line"
#define SND_USE_CASE_DEV_MIC "Mic"
#define SND_USE_CASE_DEV_NONE "None"
#define SND_USE_CASE_DEV_SPDIF "SPDIF"
#define SND_USE_CASE_DEV_SPEAKER "Speaker"
#define SND_USE_CASE_DEV_USB "USB"
如果存在多个同名的设备,则应在这些名称中添加数字后缀,如 HDMI1,HDMI2,HDMI3 等等。不允许有数字间隙。带数字的名称必须是连续的。为了提高可读性,可以在名称和索引之间放置空格(如“Line 1”)。为了此目的,设备名称 “Line 1” 和 “Line1” 是相同的。
如果 EnableSequence/DisableSequence 控制硬件中的独立路径,则还建议分割播放和捕获 UCM 设备并使用数字后缀。示例用例:使用笔记本电脑中的集成麦克风,而不是耳机中的麦克风。
设备的优先级由优先级值决定(值越高 = 优先级越高)。
参考带有 SND_USE_CASE_MOD 前缀的宏,如 SND_USE_CASE_MOD_ECHO_REF 来了解已知修饰符的完整列表。
#define SND_USE_CASE_MOD_CAPTURE_MUSIC "Capture Music"
#define SND_USE_CASE_MOD_CAPTURE_VOICE "Capture Voice"
#define SND_USE_CASE_MOD_ECHO_REF "Echo Reference"
#define SND_USE_CASE_MOD_PLAY_MUSIC "Play Music"
#define SND_USE_CASE_MOD_PLAY_TONE "Play Tone"
#define SND_USE_CASE_MOD_PLAY_VOICE "Play Voice"
启动(alsactl)
FixedBootSequence 在每次启动时执行。BootSequence 仅当声卡的配置缺失时才会执行。目的是让用户修改音量或开关等配置。alsactl 确保持久性(将控制状态存储到 /var 树并在下次启动时加载以前的状态)。
设备音量
预计应用程序将处理音量设置。如果设备导出了硬件音量(MixerElem 或 Volume/Switch 值),不建议在 Enable / Disable 序列中为 verbs 或设备的音量设置设定固定值。默认的音量设置应该在 BootSequence 中设置。这种方案的目的是,允许用户使用 alsactl 声卡状态管理覆盖默认值。
检查清单:
- 在 BootSequence 中设置默认音量
- Verb 的 EnableSequence 应确保所有设备都已关闭(混音器路径),以避免同时使用设备 - 先前的状态未知(请参阅 disdevall 和 disdev2 命令或创建新的自定义命令序列)
动态配置树
评估顺序可能看起来和从用户的角度看到的有点不同。首先,解析标准 alsa-lib 配置树。顶层的所有其它层都与这棵树一起工作。它可能会将配置块从所在的配置文件移动到树内部。
Example configuration | Parsed static tree | Identical static tree
----------------------------+-------------------------+-------------------------------
If.1 { | If { | If.1.True.Define.VAR "A"
True.Define.VAR "A" | 1.True.Define.VAR "A" | If.2.True.Define.VAR "C"
} | 2.True.Define.VAR "C" | Define.VAR "B"
Define.VAR "B" | } |
If.2 { | Define.VAR "B" |
True.Define.VAR "C" | |
} | |
即使一个或两个条件都被评估为真,变量 VAR 也将始终被评估为 B,因为第一个 If 块位于非嵌套的 Define 之前。在文本配置解析器中第二个 If 块被附加到第一个 If 块(配置树中的 Define 之前)中。
语法
除非另有说明,否则使用语法版本 4。
Syntax 4
包含
有两种方法可以包含其它配置文件。
静态包含
静态包含继承自标准 alsa-lib 配置语法。它可以放在配置文件的任何位置。搜索路径由根 alsa 配置路径(通常为 /usr/share/alsa 的 ucm2 目录)组成。
<some/path/file.conf> # include file using the search path
</absolute/path/file.conf> # include file using the absolute path
惰性包含
惰性包含在运行时进行评估。根路径是 ucm2 树。绝对包含将 ucm2 绝对路径附加到指定路径。相对包含是相对于包含 Include 配置块的文件而言的。
配置树评估
静态配置树的评估按照特定顺序进行(见下表)。当动态配置树发生变化时,将重新启动评估序列以评估所有可能的更改(新的 Define 或 Include 或 If 块)。
评估顺序 | 配置块 | 评估重启 |
---|---|---|
1 | Define | No |
2 | Include | Yes |
3 | If | Yes |
替换
配置树中的动态树标识符和指定值被替换。替换字符串如下表所示。
替换字符串 | 值 |
---|---|
${OpenName} | 原始 UCM 声卡名称(传给 snd_use_case_mgr_open()) |
${ConfLibDir} | 库顶层配置目录(如 /usr/share/alsa) |
${ConfTopDir} | 顶层 UCM 配置目录(如 /usr/share/alsa/ucm2) |
${ConfDir} | 声卡的 UCM 配置目录(如 /usr/share/alsa/ucm2/conf.d/USB-Audio) |
${ConfName} | 配置名称(如 USB-Audio.conf) |
${CardNumber} | 真实的 ALSA 声卡号(或者对于虚拟 UCM 声卡为空字符串) |
${CardId} | ALSA 声卡标识符(参见 snd_ctl_card_info_get_id()) |
${CardDriver} | ALSA 声卡驱动(参见 snd_ctl_card_info_get_driver()) |
${CardName} | ALSA 声卡名称(参见 snd_ctl_card_info_get_name()) |
${CardLongName} | ALSA 声卡长名称(参见 snd_ctl_card_info_get_longname()) |
${CardComponents} | ALSA 声卡组件(参见 [snd_ctl_card_info_get_components()](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#ga2181aa6bf919fd1342b259d2f3af755b “Get the sound cards “components” property from the given info object.”)) |
${env:} | 环境变量 |
${sys:} | sysfs 文件 的内容 |
${var:} | UCM 解析器变量(使用 Define 块创建) |
${eval:} | 计算表达式,如 ($var+2)/3 [Syntax 5] |
${find-card:} | 查找声卡 - 参考 查找声卡替换 部分 |
${find-device:} | 查找设备 - 参考 查找设备替换 部分 |
声卡相关的这些部分信息,可以通过 alsa-utils 包里的 amixer 工具来查看,如:
$ amixer info
Card default 'pulse'/'PulseAudio'
Mixer name : 'PulseAudio'
Components : ''
Controls : 4
Simple ctrls : 2
amixer 工具实现 info 操作的 info()
函数定义 (位与 alsa-utils/amixer/amixer.c) 如下:
static int info(void)
{
int err;
snd_ctl_t *handle;
snd_mixer_t *mhandle;
snd_ctl_card_info_t *info;
snd_ctl_elem_list_t *clist;
snd_ctl_card_info_alloca(&info);
snd_ctl_elem_list_alloca(&clist);
if ((err = snd_ctl_open(&handle, card, 0)) < 0) {
error("Control device %s open error: %s", card, snd_strerror(err));
return err;
}
if ((err = snd_ctl_card_info(handle, info)) < 0) {
error("Control device %s hw info error: %s", card, snd_strerror(err));
return err;
}
printf("Card %s '%s'/'%s'\n", card, snd_ctl_card_info_get_id(info),
snd_ctl_card_info_get_longname(info));
printf(" Mixer name : '%s'\n", snd_ctl_card_info_get_mixername(info));
printf(" Components : '%s'\n", snd_ctl_card_info_get_components(info));
if ((err = snd_ctl_elem_list(handle, clist)) < 0) {
error("snd_ctl_elem_list failure: %s", snd_strerror(err));
} else {
printf(" Controls : %i\n", snd_ctl_elem_list_get_count(clist));
}
snd_ctl_close(handle);
if ((err = snd_mixer_open(&mhandle, 0)) < 0) {
error("Mixer open error: %s", snd_strerror(err));
return err;
}
if (smixer_level == 0 && (err = snd_mixer_attach(mhandle, card)) < 0) {
error("Mixer attach %s error: %s", card, snd_strerror(err));
snd_mixer_close(mhandle);
return err;
}
if ((err = snd_mixer_selem_register(mhandle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) {
error("Mixer register error: %s", snd_strerror(err));
snd_mixer_close(mhandle);
return err;
}
err = snd_mixer_load(mhandle);
if (err < 0) {
error("Mixer load %s error: %s", card, snd_strerror(err));
snd_mixer_close(mhandle);
return err;
}
printf(" Simple ctrls : %i\n", snd_mixer_get_count(mhandle));
snd_mixer_close(mhandle);
return 0;
}
amixer info 输出的信息的格式如下:
Card ${Card} '${CardId}'/'${CardLongName}'
Mixer name : 'PulseAudio'
Components : '${CardComponents}'
Controls : 4
Simple ctrls : 2
${CardComponents} 信息由声卡驱动程序返回,如 Intel 的 ALC 5651 声卡的驱动 (位于 sound/soc/intel/boards/bytcr_rt5651.c):
snprintf(byt_rt5651_components, sizeof(byt_rt5651_components),
"cfg-spk:%s cfg-mic:%s%s",
(byt_rt5651_quirk & BYT_RT5651_MONO_SPEAKER) ? "1" : "2",
mic_name[BYT_RT5651_MAP(byt_rt5651_quirk)],
(byt_rt5651_quirk & BYT_RT5651_HP_LR_SWAPPED) ?
" cfg-hp:lrswap" : "");
byt_rt5651_card.components = byt_rt5651_components;
#if !IS_ENABLED(CONFIG_SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES)
snprintf(byt_rt5651_long_name, sizeof(byt_rt5651_long_name),
"bytcr-rt5651-%s-spk-%s-mic%s",
(byt_rt5651_quirk & BYT_RT5651_MONO_SPEAKER) ?
"mono" : "stereo",
mic_name[BYT_RT5651_MAP(byt_rt5651_quirk)],
(byt_rt5651_quirk & BYT_RT5651_HP_LR_SWAPPED) ?
"-hp-swapped" : "");
byt_rt5651_card.long_name = byt_rt5651_long_name;
#endif
特殊的整个字符串替换
替换字符串 | 值 |
---|---|
${evali:} | 计算表达式,如 ($var+2)/3 [Syntax 6];目标节点将是整数;仅在 LibraryConfig 子树中替换 |
查找声卡替换
这种替换查找 ALSA 声卡并返回适当的标识符或声卡号(参见返回参数)。
使用示例:
${find-card:field=name,regex='^acp$',return=number}
参数:
参数 | 描述 |
---|---|
return | 返回值类型 (id, number),默认值为 id |
field | 查找的字段 (id, driver, name, longname, mixername, components) |
regex | 字段匹配的 regex 字符串 |
查找设备替换
使用示例:
${find-device:type=pcm,field=name,regex='DMIC'}
参数 | 描述 |
---|---|
type | 设备类型 (pcm) |
stream | stream 类型 (playback, capture),默认为 playback |
field | 查找的字段 (id, name, subname) |
regex | 字段匹配的 regex 字符串 |
变量定义
可以使用 Define 或 DefineRegex 块来定义和改变变量。Define 块如下所示:
Define {
variable1 "a"
variable2 "b"
}
DefineRegex 允许提取子字符串,例如:
DefineRegex.rval {
Regex "(hello)|(regex)"
String "hello, it's my regex"
}
结果将存储到变量 rval1 中作为 hello,存储到变量 rval2 中作为 regex(每个匹配的子字符串都存储到带有序列号后缀的单独变量中)。
例如可以使用 ${var:rval1}
引用替换变量。
宏
宏是为 Syntax 版本 6 添加的。DefineMacro 定义新的宏如下:
DefineMacro.macro1 {
Define.a "${var:__arg1}"
Define.b "${var:__other}"
# Device or any other block may be defined here...
}
宏中的参数被称为带有双下划线名称前缀的变量(如*_ variable*)。DefineMacro 子树中的配置块总是在实例化时计算(包括参数和变量)。
宏可以使用下列方式实例化(扩展):
# short version
Macro.id1.macro1 "arg1='something 1',other='other x'"
# long version
Macro.id1.macro1 {
arg1 'something 1'
other 'other x'
}
第二个标识符(示例中的 id1)必须是唯一的,但其内容被忽略。它仅仅区分子树中的项(允许一个宏有多个实例)。
条件
配置树计算支持条件 - If 块。每个 If 块必须定义一个 Condition 块和 True 或 False 块或两者。True 或 False 块将在计算 Condition 时合并到父树(If 块定义的地方)。
示例:
If.uniqueid {
Condition {
Type String
Haystack "abcd"
Needle "a"
}
True {
Define.a a
define.b b
}
}
True(Type AlwaysTrue)
仅执行 True 块。它可以被用于改变计算顺序,就像 配置树 段中解释的那样。
字符串是空 (Type String)
字段 | 描述 |
---|---|
Empty | 字符串 |
字符串相等 (Type String)
字段 | 描述 |
---|---|
String1 | 字符串 |
String2 | 字符串中的子字符串 |
子字符串存在 (Type String)
字段 | 描述 |
---|---|
Haystack | 字符串 |
Needle | 字符串中的子字符串 |
Regex 匹配 (Type RegexMatch)
字段 | 描述 |
---|---|
String | 字符串 |
Regex | regex 表达式(扩展的 posix,忽略大小写) |
ALSA 控制元素存在 (Type ControlExists)
字段 | 描述 |
---|---|
Device | ALSA 控制设备(参考 snd_ctl_open()) |
Control | ASCII 形式的控制项(使用 snd_ctl_ascii_elem_id_parse() 解析) |
ControlEnum | 枚举控制项的值(可选) |
示例:
If.fmic {
Condition {
Type ControlExists
Control "name='Front Mic Playback Switch'"
}
True {
...
}
}
变体
为了避免在配置更改很少的情况下许多配置文件的重复,而提供了变体扩展。变体是为 语法 版本 6 添加的。
下面的示例将创建两个 verbs - “HiFi” 和 “HiFi 7.1”,它们的 “Speaker” 设备具有不同的播放通道数(2 和 8)。
示例(主配置文件):
SectionUseCase."HiFi" {
File "HiFi.conf"
Variant."HiFi" {
Comment "HiFi"
}
Variant."HiFi 7+1" {
Comment "HiFi 7.1"
}
}
示例(verb 配置文件 - HiFi.conf):
SectionDevice."Speaker" {
Value {
PlaybackChannels 2
}
Variant."HiFi 7+1".Value {
PlaybackChannels 8
}
}
原文。
Done.