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
}
Logo

更多推荐