Linux Audio Alsa (2) alsa.conf
alsa.conf
Linux Audio Alsa (2) alsa.conf
最近看alsa系统,发现在alsa第一层借口下会有一层配置层,第一层接口通过配置参数调用动态库中指定的函数。这些函数才会去真正操作音频设备节点。
据说alsa.conf功能很强大,现在遇到的问题是,这个中间层对继续梳理alsa上层的调用过程造成了一些麻烦。所以,在这里吧alsa.conf相关的内容整理一哈。
alsa.conf的 内部结构
alsa.conf主要用来保存一些变量的值。为了完成这个功能,alsa.conf中有三种变量compound(符合类型),string和integer。
数组是一种compound,它的叶子节点是id为index(0,1,2…)的特殊节点,用@args标识的数组用来对一组compoud进行参数相关的解释。
@func是作为string类型出现的,之后在内部以func的方式进行扩展解释。
alsa.conf最终被解释为一棵树,它的节点是struct _snd_config结构,三种类型的变量都用此结构表示。
struct _snd_config {
char *id; //节点名
snd_config_type_t type; //节点类型
union {
long integer;
long long integer64;
char *string;
double real;
const void *ptr;
struct {
struct list_head fields; //作为compound时的子节点链表
int join;
} compound;
} u;
struct list_head list; //将会链如父节点链表中
snd_config_t *parent;
int hop;
};
如下为compound类型外部的赋值语句(在配置文件中第一次出现对该变量的赋值):
defaults.namehint.showall off
赋值语句格式是 var value(变量+space+值)。这里的值是一个字符串。首先构造一个id为defaults,type为compound类型的node(每个node都是struct _snd_config结构),namehint下一个compound节点,并且是defaults的子节点,showall是compound namehint的子节点,它是string类型的,它的值是off。
下面是整数类型的赋值
defaults.ctl.card 0
defaults是刚才构造的compound节点,添加新的ctl compound节点到defaults节点中,card是ctl下的integer类型的节点,它的值为0。
下面这种形式的赋值是一种别名(alias),但是在构造时,就是简单的字符串赋值
defaults.pcm.dmix.card defaults.pcm.card
card是string类型的节点,它的值是字符串defaults.pcm.card. 综上,string和integer类型的节点一定是叶子节点。
运行是构造的节点连接结构如下图
常见的compound结构如下,在pcm compound节点下,用标准方式构造hw compound节点。
pcm.hw {
@args [ CARD DEV SUBDEV ]//参数数组,得到@args compound,下面有id为0,值为CARD的string节点等
@args.CARD { //@args compound节点下有CARD compound节点
type string
default {
@func getenv //特殊字符串节点
vars [
ALSA_PCM_CARD
ALSA_CARD
]
default {
@func refer
name defaults.pcm.card
}
}
}
@args.DEV {
type integer
default {
@func igetenv
vars [
ALSA_PCM_DEVICE
]
default {
@func refer
name defaults.pcm.device
}
}
}
@args.SUBDEV {
type integer
default {
@func refer
name defaults.pcm.subdevice
}
}
type hw
card $CARD //字符串节点,它的值引用参数CARD
device $DEV
subdevice $SUBDEV
hint {
show {
@func refer
name defaults.namehint.extended
}
description "Direct hardware device without any conversions"
}
}
@args和@func
pcm.hw节点在运行过程中会被展开,最终得到展开后的节点。 @args指定该数组是将会作为参数来解释。@func的值val在运行时会调用snd_func_val函数。
hw {
type hw
card card_val
device dev_val
subdevice subdev_val
hint {
show show_val
description "Direct hardware device without any conversions"
}
}
card_val, dev_val和subdev_val根据@args获得,show_val由函数展开获得。
函数展开时如下
default {
@func refer
name defaults.pcm.card
}
展开时会调用snd_func_refer函数,根据name的值deaults.pcm.card,搜索全局获得对应的实际值返回作为default的值default_val.
变为
default {
@func getenv
vars [
ALSA_PCM_CARD
ALSA_CARD
]
default default_val
}
接下来调用snd_func_getenv函数,使用getenv获取vars中所列的两个环境变量,若都为空,则返回default的值作为最外层的default_val. 若有一个不为空,则返回值做为最外层的default_val.
此时根据@args的信息生成新的节点subs
subs {
CARD {
type string
default default_val
}
DEV {
type integer
default default_val
}
SUBDEV {
type integer
default default_val
}
}
最后只取default的作为最后的值
subs {
CARD default_val
DEV default_val
SUBDEV default_val
}
根据展开得到的新节点信息,hw就被展开为最后的形式。
@hooks
alsa conf中@hooks的关键字表示这个compoud节点是一个hook,hook用来加载指定路径的配置文件,解释这个文件并将相关的解释结果加入到父节点中。
@hooks [
{
func load
files [
{
@func concat
strings [
{ @func datadir }
"/alsa.conf.d/"
]
}
"/etc/asound.conf"
"~/.asoundrc"
]
errors false
}
]
snd_func_datadir 返回配置文件的目录,snd_func_concat函数将路径和下面的路径字符串链接作为返回值。此时files array中就会有三个节点。
调用snd_config_hook_load会被调用用来加载三个元素指定的配置文件。
处理hooks的函数是
int snd_config_hooks(snd_config_t *config, snd_config_t *private_data)
@hooks节点可以出现在任何compoud节点下。
alias
类似如下的字符串的赋值可以作为别名
pcm.surround21 cards.pcm.surround21 //pcm.surround21是cards.pcm.surround21的别名
指定pcm.surroud21, 最终就是指定使用cards.pcm.surround21节点。
设置别名必须以同一个父节点为基点,pcm和cards的父节点是root节点。
参数赋值
当指定-Dhw:0,1时,hw:0,1中hw是设备节点名称,:后边0,1给出的是参数值。 再根节点中查找hw节点,找到@args中的0号元素值,1号元素值,查找参数数组下以0号元素名称为id的节点,将0设为它的值,同样将1赋值给同名节点。
以@args小结所述的展开过程为例。在参数赋值后最终参数节点会被移除,并生成subs节点,它的值是:
subs {
CARD 0
DEV 1
SUBDEV default_val
}
后边在计算hw中节点的引用时, $CARD就是0, $DEV就是1.
重要函数
定义查找函数
int snd_config_search_definition(snd_config_t *config,
const char *base, const char *name,
snd_config_t **result)
{
snd_config_t *conf;
char *key;
const char *args = strchr(name, ':');
int err;
if (args) {
args++;
key = alloca(args - name);
memcpy(key, name, args - name - 1);
key[args - name - 1] = '\0';
} else {
key = (char *) name;
}
/*
* if key contains dot (.), the implicit base is ignored
* and the key starts from root given by the 'config' parameter
*/
snd_config_lock();
err = snd_config_search_alias_hooks(config, strchr(key, '.') ? NULL : base, key, &conf);
if (err < 0) {
snd_config_unlock();
return err;
}
err = snd_config_expand(conf, config, args, NULL, result);
snd_config_unlock();
return err;
}
引用查找和hook掉用
int snd_config_search_alias_hooks(snd_config_t *config,
const char *base, const char *key,
snd_config_t **result)
{
snd_config_t *res = NULL; \
char *old_key; \
int err, first = 1, maxloop = 1000; \
assert(config && key); \
while (1) { \
old_key = strdup(key); \
if (old_key == NULL) { \
err = -ENOMEM; \
res = NULL; \
break; \
} \
err = first && base ? -EIO : snd_config_searcha_hooks(config, config, key, &res); \
if (err < 0) { \
if (!base) \
break; \
err = snd_config_searchva_hooks(config, config, &res, base, key, NULL); \
if (err < 0) \
break; \
} \
if (snd_config_get_string(res, &key) < 0) \
break; \
assert(key); \
if (!first && (strcmp(key, old_key) == 0 || maxloop <= 0)) { \
if (maxloop == 0) \
SNDERR("maximum loop count reached (circular configuration?)"); \
else \
SNDERR("key %s refers to itself", key); \
err = -EINVAL; \
res = NULL; \
break; \
} \
free(old_key); \
first = 0; \
maxloop--; \
} \
free(old_key); \
if (!res) \
return err; \
if (result) \
*result = res; \
return 0; \
}
int snd_config_searchva_hooks(snd_config_t *root, snd_config_t *config,
snd_config_t **result, ...)
{
snd_config_t *n; \
va_list arg; \
assert(config); \
va_start(arg, result); \
while (1) { \
const char *k = va_arg(arg, const char *); \
int err; \
if (!k) \
break; \
err = snd_config_searcha_hooks(root, config, k, &n); \
if (err < 0) \
return err; \
config = n; \
} \
va_end(arg); \
if (result) \
*result = n; \
return 0; \
}
int snd_config_searcha_hooks(snd_config_t *root, snd_config_t *config, const char *key, snd_config_t **result)
{
snd_config_t *n; \
int err; \
const char *p; \
assert(config && key); \
while (1) { \
if (config->type != SND_CONFIG_TYPE_COMPOUND) { \
if (snd_config_get_string(config, &p) < 0) \
return -ENOENT; \
err = snd_config_searcha_hooks(root, root, p, &config); \
if (err < 0) \
return err; \
} \
{ err = snd_config_hooks(config, NULL); \
if (err < 0) \
return err; \
} \
p = strchr(key, '.'); \
if (p) { \
err = _snd_config_search(config, key, p - key, &n); \
if (err < 0) \
return err; \
config = n; \
key = p + 1; \
} else \
return _snd_config_search(config, key, -1, result); \
} \
}
virtual device and plugin
使用-D 可以指定使用的音频设备, 这里音频设备可以是hw:x,x 和virtual device。virtual device在alsa.conf 或者 asound.conf 定义。
操作音频设备时,有两类操作分别是record,playback 和 操作controls(mute unmute, set volume). 这样就对应两类设备PCM device 和ctl device. PCM device 和ctl device 都可以定义自己的virtual device
pcm.!default {
type hw
card 0
}
ctl.!default {
type hw
card 0
}
这样定义了default virtual device,!的作用是代替alsa 内部默认的default device。
virtual device 是通过定义自己的type 为不同的plugin的device. 如上type hw 指定default 是使用直接访问音频设备的hw plugin的功能。
有些virtual device 需要pcm salve device,这种virtual device 是在slave device 上进行功能扩展。
如
虚拟设备rate44100hz 进行自动转换samplerate
pcm_slave.slave_rate44100hz {
pcm "hw:0,0"
rate 44100
}
###define pcm virtual device rate44100hz
pcm.rate44100hz {
type plug
slave salve_rate44100hz
}
可以使用aplay 播放文件
aplay -D rate44100hz test.pcm
系统自带的plugin 有 hw, rate, plug,file,asym 等
参见: alsa plugin
外部提供的plugin
pulseaudio提供的的alsa plugin:
libasound_module_conf_pulse.so 提供解析type pulse 时的函数
libasound_module_ctl_pulse.so 提供pulseaudio接管alsa ctl 相关api的函数
libasound_module_pcm_pulse.so 提供pulseaudio结果alsa pcm相关
alsa的外部plugin和配置文件之间的名字关系规则: libasound_module_pcm_####.so libasound_module_ctl_####.so这里的#### 就是你再conf文件中pcm.xxxx 里所写的名字。
使用pulseaudio提供的plugin需要再配置加入如下virtual device的定义
pcm.pulse {
type pulse
}
ctl.pulse {
type pulse
}
此时可以通过-D pulse 使用virtual device
aplay -Dpulse foo.wav
amixer -D pulse
将pulse设置为default device
pcm.!default {
type pulse
hint.description "Default Audio Device"
}
ctl.!default {
type pulse
}
更多推荐
所有评论(0)