ALSA lib open分析
alsa-lib如何解析asound.conf
一.打开代码流程分析
无论是在录音还是在放音,都要打开一个PCM流,具体对应的函数原型为:
int snd_pcm_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode);
int snd_pcm_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode);
先解一下各个参数的函义:
- pcmp: 是带回的pcm流
- name是要找开的流即放音还是录音,放音对应AndroidPlayback_Speaker_normal,录音对应AndroidCapture。放音的字符串会因为当前使用设备和设备状况不一样而有所不同,不同之处在下划线后面那部分。录音则因为大多数板子成型后,都只有一个录音设备或者接口,因此录音串对应的就是AndroidCapture。对于linux就是放音的设备,比如hw:0,2
- stream对应流的类型,是放音(0)还是录音(1).
- mode是指设备打开模式,阻塞或非阻塞。
snd_pcm_open
//this function increases a reference counter so that the obtained tree won't be deleted until unreferenced by #snd_config_unref.
//这个函数增加了一个引用计数器,这样得到的树在未被引用之前不会被删除# snd_config_unref。
snd_config_update_ref(&top);
snd_pcm_open_noupdate(pcmp, top, name, stream, mode, 0);
if (snd_config_get_string(pcm_conf, &str) >= 0) //获取配置节点的字符串
snd_pcm_open_noupdate(pcmp, root, str, stream, mode, hop + 1);
else
snd_pcm_open_conf(pcmp, name, root, pcm_conf, stream, mode); //单独分析1
// 此函数将在配置树中查找对应的结点,然后将查找到的结点复制一份到pcm_conf中
snd_config_search_definition(root, "pcm", name, &pcm_conf);
//Decreases a reference counter of the given config tree, and eventually deletes the tree if all references are gone. This is the counterpart of #snd_config_unref.
//减少给定配置树的引用计数器,最终如果所有引用都已消失,则删除树。这是#snd_config_unref的对等项。
snd_config_unref(top);
单独分析1:
static const char *const build_in_pcms[] = {
"adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat",
"linear", "meter", "mulaw", "multi", "null", "empty", "plug", "rate", "route", "share",
"shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",
NULL
};
static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name, snd_config_t *pcm_root, snd_config_t *pcm_conf,
snd_pcm_stream_t stream, int mode)
//Searches for a node in a configuration tree.
* \code
* config {
* a 42 # "a"
* b { # "b"
* c "cee" # "b.c"
* d { # "b.d"
* e 2.71828 # "b.d.e"
* }
* }
* }
* \endcode
snd_config_search(pcm_conf, "type", &conf); //搜索config里面type的节点
snd_config_get_id(conf, &id); //获取ID
snd_config_get_string(conf, &str); //获取字符串
snd_config_search_definition(pcm_root, "pcm_type", str, &type_conf); //搜索pcm_type
snd_config_for_each(i, next, type_conf)
snd_config_t *n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0) //看看是否有comment和open的字符
if (strcmp(id, "comment") == 0)
if (strcmp(id, "open") == 0)
if (!open_name) //如果没有指定打开的设备
buf = malloc(strlen(str) + 32); //分配空间
open_name = buf;
sprintf(buf, "_snd_pcm_%s_open", str); //这里是这个是str是前面type获得的
if (!lib) ////如果前面没有指定lib
const char *const *build_in = build_in_pcms; //赋值字符串数组build_in_pcms
while (*build_in) //如果存在
if (!strcmp(*build_in, str)) //如果与字符串数组中的字符相等就跳出
break;
build_in++;
if (*build_in == NULL)
buf1 = malloc(strlen(str) + sizeof(ALSA_PLUGIN_DIR) + 32); //分配空间
lib = buf1;
sprintf(buf1, "%s/libasound_module_pcm_%s.so", ALSA_PLUGIN_DIR, str); //赋值plugin的so的路径
//加载相关的so
open_func = snd_dlobj_cache_get(lib, open_name, SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION), 1);
//打开动态库,如果是默认的plugin就打开这个/usr/lib64/libasound.so.2,否则就打开额外添加的/usr/lib/alsa-lib/libasound_module_pcm_pmaplug.so的plugin库
dlobj = snd_dlopen(lib, RTLD_NOW);,
func = snd_dlsym(dlobj, name, version); //解析来自动态库的符号,找到函数的地址,如果有的符号没有就会报错
return dlsym(handle, name);
c->lib = lib ? strdup(lib) : NULL;
c->name = strdup(name);
list_add_tail(&c->list, &pcm_dlobj_list); //把找到的函数加入到pcm_dlobj_list这个链表
if (open_func) //如果加载成功
//打开这个plugin,可能调用的函数:_snd_pcm_asym_open, _snd_pcm_plug_open,等,还有执行SND_PCM_PLUGIN_DEFINE_FUNC,这些都是从so库里面得到的,获取到函数的地址
open_func(pcmp, name, pcm_root, pcm_conf, stream, mode);
if ((*pcmp)->open_func)
snd_dlobj_cache_put(open_func); //释放解析动态库的缓存
else
(*pcmp)->open_func = open_func; //赋值
if (err >= 0)
snd_config_search(pcm_root, "defaults.pcm.compat", &tmp); //搜索defaults.pcm.compat这个节点
if (err >= 0)
if (snd_config_get_integer(tmp, &i) >= 0)
(*pcmp)->compat = 1;
char *str = getenv("LIBASOUND_COMPAT");
if (str && *str) //如果环境变量有配置LIBASOUND_COMPAT,置1
(*pcmp)->compat = 1;
snd_config_search(pcm_root, "defaults.pcm.minperiodtime", &tmp); //获取defaults.pcm.minperiodtime
snd_config_get_integer(tmp, &(*pcmp)->minperiodtime); //最小周期时间
二.实例分析
1.asound.conf内容
pcm.7input {
type plug
slave {
pcm "hw:0,2"
format S16_LE
rate 16000
}
}
pcm.pma {
type pmaplug
slave {
pcm 7input
}
input_channels 8
loopback 1
local 1
}
pcm.speech_route {
type route
slave.pcm pma
slave.channels 8
ttable{
0.0 1
0.1 1
0.2 1
0.3 1
0.4 1
0.5 1
0.6 1
0.7 1
}
}
pcm.speech {
type asym
capture.pcm speech_route
playback.pcm default
}
2.添加打印后的log
第一步
ALSA lib pcm.c:2567:(snd_pcm_open_conf) cxw conf id =type, str = asym
ALSA lib pcm.c:2613:(snd_pcm_open_conf) open_name = _snd_pcm_asym_open //获取到type asym
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = asym
............................................
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = asym
ALSA lib pcm.c:2637:(snd_pcm_open_conf) open_name lib = (null), open_name = 428883088
ALSA lib pcm.c:2641:(snd_pcm_open_conf) open_name name = speech
ALSA lib pcm_asym.c:84:(_snd_pcm_asym_open) open_name _snd_pcm_asym_open //调用到_snd_pcm_asym_open 函数
第二步
ALSA lib pcm.c:2567:(snd_pcm_open_conf) cxw conf id =type, str = route
ALSA lib pcm.c:2613:(snd_pcm_open_conf) open_name = _snd_pcm_route_open //获取到type route
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = route
................................
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = route
ALSA lib pcm.c:2637:(snd_pcm_open_conf) open_name lib = (null), open_name = 428883248
ALSA lib pcm.c:2641:(snd_pcm_open_conf) open_name name = speech_route
ALSA lib pcm_route.c:1293:(_snd_pcm_route_open) open_name _snd_pcm_route_open //调用到_snd_pcm_route_open 函数
第三步
ALSA lib pcm.c:2567:(snd_pcm_open_conf) cxw conf id =type, str = pmaplug
ALSA lib pcm.c:2613:(snd_pcm_open_conf) open_name = _snd_pcm_pmaplug_open //获取到type pmaplug
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = pmaplug
.................................
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = pmaplug
ALSA lib pcm.c:2631:(snd_pcm_open_conf) open_name buf1 = /usr/lib/alsa-lib/libasound_module_pcm_pmaplug.so
//加载libasound_module_pcm_pmaplug.so库,并调用SND_PCM_PLUGIN_DEFINE_FUNC
ALSA lib pcm.c:2637:(snd_pcm_open_conf) open_name lib = /usr/lib/alsa-lib/libasound_module_pcm_pmaplug.so, open_name = 428882960
ALSA lib pcm.c:2641:(snd_pcm_open_conf) open_name name = pma
pmaplug() name: pma stream: 1 mode: 0
snd_pcm_extplug_create
第四步
ALSA lib pcm.c:2567:(snd_pcm_open_conf) cxw conf id =type, str = plug
ALSA lib pcm.c:2613:(snd_pcm_open_conf) open_name = _snd_pcm_plug_open //找到type plug
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = plug
...................................
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = plug
ALSA lib pcm.c:2637:(snd_pcm_open_conf) open_name lib = (null), open_name = 428956384
ALSA lib pcm.c:2641:(snd_pcm_open_conf) open_name name = 7input
第五步
ALSA lib pcm.c:2567:(snd_pcm_open_conf) cxw conf id =type, str = hw
ALSA lib pcm.c:2613:(snd_pcm_open_conf) open_name = _snd_pcm_hw_open
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = hw
..........................................
ALSA lib pcm.c:2618:(snd_pcm_open_conf) open_name str = hw
ALSA lib pcm.c:2637:(snd_pcm_open_conf) open_name lib = (null), open_name = 429049088
ALSA lib pcm.c:2641:(snd_pcm_open_conf) open_name name = hw:0,2 //调用_snd_pcm_hw_open函数
分析结果:
代码介绍alsa.conf是一个递归的过程,根据type不同,调用不同的函数,逐步递归下去。
这是我们系统中snd_pcm_open的整个流程,虽然这里创建失败但是可以让我们清晰地认识流创建过程:它是一个分层的流建立过程。asym->route->pmaplug->plug->hw(pcm) 及asym->pmaplug->plug->hw函数层次调用过程。
三._snd_pcm_hw_open分析
Pcm_hw.c (z:\px30_linux\external\alsa-lib-1.1.5\src\pcm)
//Creates a new hw PCM
int _snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf,
snd_pcm_stream_t stream, int mode)
snd_config_for_each(i, next, conf) //遍历
n = snd_config_iterator_entry(i);
if (snd_config_get_id(n, &id) < 0)
if (snd_pcm_conf_generic_id(id))
if (strcmp(id, "card") == 0) //获取card
if (strcmp(id, "device") == 0) //获取device
if (strcmp(id, "subdevice") == 0) //获取subdevice
。。。。。。。。。。
if (strcmp(id, "rate") == 0) //获取rate
if (strcmp(id, "format") == 0) //获取format
if (strcmp(id, "channels") == 0) //获取channels
//打开相应的hw stream
snd_pcm_hw_open(pcmp, name, card, device, subdevice, stream, mode | (nonblock ? SND_PCM_NONBLOCK : 0),
0, sync_ptr_ioctl);
snd_ctl_hw_open(&ctl, NULL, card, 0)) //单独分析1,打开控制节点
sprintf(filename, filefmt, card, device); //pcmC%iD%ic,配置pcm节点
fd = snd_open_device(filename, fmode); //打开stream
fd = open(filename, fmode);
ioctl(fd, SNDRV_PCM_IOCTL_INFO, &info) //获取信息
return snd_pcm_hw_open_fd(pcmp, name, fd, sync_ptr_ioctl);
hw = calloc(1, sizeof(snd_pcm_hw_t));
hw->version = ver;
hw->card = info.card;
hw->device = info.device;
hw->subdevice = info.subdevice;
hw->fd = fd;
/* no restriction */
hw->format = SND_PCM_FORMAT_UNKNOWN;
hw->rate = 0;
hw->channels = 0;
ret = snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, info.stream, mode); //创建一个snd_pcm_t结构体
pcm->ops = &snd_pcm_hw_ops;
pcm->fast_ops = &snd_pcm_hw_fast_ops;
pcm->private_data = hw;
pcm->poll_fd = fd;
pcm->poll_events = info.stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
pcm->tstamp_type = tstamp_type;
map_status_and_control_data(pcm, !!sync_ptr_ioctl); //映射状态和控制数据
单独分析1
int snd_ctl_hw_open(snd_ctl_t **handle, const char *name, int card, int mode)
sprintf(filename, SNDRV_FILE_CONTROL, card); //赋值控制"controlC%i"
fd = snd_open_device(filename, fmode); //打开
fd = open(filename, fmode); //打开驱动节点
ioctl(fd, SNDRV_CTL_IOCTL_PVERSION, &ver) //控制的版本
hw->card = card;
hw->fd = fd;
hw->protocol = ver;
err = snd_ctl_new(&ctl, SND_CTL_TYPE_HW, name); //生成控制相关数据结构
ctl->ops = &snd_ctl_hw_ops;
ctl->private_data = hw;
ctl->poll_fd = fd;
四._snd_pcm_plug_open分析
int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf,
snd_pcm_stream_t stream, int mode)
snd_config_for_each(i, next, conf)
if (strcmp(id, "slave") == 0)
slave = n;
#ifdef BUILD_PCM_PLUGIN_ROUTE //可以设置路径相关的
if (strcmp(id, "ttable") == 0)
if (strcmp(id, "route_policy") == 0)
#ifdef BUILD_PCM_PLUGIN_RATE // //可以设置速率相关的
if (strcmp(id, "rate_converter") == 0)
//设置从设备的一些配置参数,这里会从alsa.conf里面获取
snd_pcm_slave_conf(root, slave, &sconf, 3, SND_PCM_HW_PARAM_FORMAT, SCONF_UNCHANGED, &sformat,
SND_PCM_HW_PARAM_CHANNELS, SCONF_UNCHANGED, &schannels, SND_PCM_HW_PARAM_RATE, SCONF_UNCHANGED, &srate);
err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
return snd_pcm_open_named_slave(pcmp, NULL, root, conf, stream, mode, parent_conf);
snd_pcm_open_conf(pcmp, name, root, conf, stream, mode); //前面有分析,获取相关的alsa.conf参数
//配置返回之后,就会到这里. 单独分析1
snd_pcm_plug_open(pcmp, name, sformat, schannels, srate, rate_converter, route_policy, ttable, ssize, cused, sused, spcm, 1);
单独分析1
int snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, int schannels, int srate,
const snd_config_t *rate_converter, enum snd_pcm_plug_route_policy route_policy, snd_pcm_route_ttable_entry_t *ttable,
unsigned int tt_ssize, unsigned int tt_cused, unsigned int tt_sused, snd_pcm_t *slave, int close_slave)
plug->sformat = sformat;
plug->schannels = schannels;
plug->srate = srate;
//将HW层的pcm流对象存放在PLUG层pcm流的私有数据成员中 ,slave就是那个指针
plug->gen.slave = plug->req_slave = slave;
plug->gen.close_slave = close_slave;
plug->route_policy = route_policy;
plug->ttable = ttable;
plug->tt_ssize = tt_ssize;
plug->tt_cused = tt_cused;
plug->tt_sused = tt_sused;
err = snd_pcm_new(&pcm, SND_PCM_TYPE_PLUG, name, slave->stream, slave->mode); //创建SND_PCM_TYPE_PLUG数据流
pcm->ops = &snd_pcm_plug_ops;
pcm->fast_ops = slave->fast_ops;
pcm->fast_op_arg = slave->fast_op_arg;
pcm->private_data = plug;
pcm->poll_fd = slave->poll_fd;
pcm->poll_events = slave->poll_events;
pcm->mmap_shadow = 1;
pcm->tstamp_type = slave->tstamp_type;
snd_pcm_link_hw_ptr(pcm, slave);
snd_pcm_link_appl_ptr(pcm, slave);
*pcmp = pcm;
五._snd_pcm_pmaplug_open 分析
因为有这个两个宏
#define SND_PCM_PLUGIN_ENTRY(name) _snd_pcm_##name##_open
SND_PCM_PLUGIN_DEFINE_FUNC(pmaplug) ->_snd_pcm_pmaplug_open
#define SND_PCM_PLUGIN_DEFINE_FUNC(plugin) \
int SND_PCM_PLUGIN_ENTRY(plugin) (snd_pcm_t **pcmp, const char *name,\
snd_config_t *root, snd_config_t *conf, \
snd_pcm_stream_t stream, int mode)
所有,下面这个就是
_snd_pcm_pmaplug_open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf, \
snd_pcm_stream_t stream, int mode)
snd_config_for_each(i, next, conf) //获取配置相关
if (strcmp(id, "slave") == 0)
slave = n;
(strcmp(id, "input_channels") == 0)
。。。。。。。
pmaplug->ext.version = SND_PCM_EXTPLUG_VERSION;
pmaplug->ext.name = "pmaplug"; //名字
pmaplug->ext.callback = &callbacks; //回调函数
pmaplug->ext.private_data = pmaplug; //私有
pmaplug->local = local; //私有的
pmaplug->loopback = loopback;
//私有的
//实例化,Create an extplug instance
snd_pcm_extplug_create(&pmaplug->ext, name, root, slave, stream, mode); //单独分析1
// mono out, user defined number in,设置
snd_pcm_extplug_set_param(&pmaplug->ext, SND_PCM_EXTPLUG_HW_CHANNELS, channels);
snd_pcm_extplug_set_slave_param(&pmaplug->ext, SND_PCM_EXTPLUG_HW_CHANNELS, channels);
// take double in and write signed 16 bit ints out, doubles are for PMA,设置
snd_pcm_extplug_set_param(&pmaplug->ext, SND_PCM_EXTPLUG_HW_FORMAT, SND_PCM_FORMAT_S16_LE);
snd_pcm_extplug_set_slave_param(&pmaplug->ext, SND_PCM_EXTPLUG_HW_FORMAT, SND_PCM_FORMAT_FLOAT64_LE);
*pcmp = pmaplug->ext.pcm;
//单独分析1
int snd_pcm_extplug_create(snd_pcm_extplug_t *extplug, const char *name,
snd_config_t *root, snd_config_t *slave_conf,
snd_pcm_stream_t stream, int mode)
snd_pcm_slave_conf(root, slave_conf, &sconf, 0);
snd_pcm_open_slave(&spcm, root, sconf, stream, mode, NULL); //之前有分析
ext = calloc(1, sizeof(*ext));
ext->data = extplug;
extplug->stream = stream;
ext->plug.read = snd_pcm_extplug_read_areas;
ext->plug.write = snd_pcm_extplug_write_areas;
ext->plug.undo_read = snd_pcm_plugin_undo_read_generic;
ext->plug.undo_write = snd_pcm_plugin_undo_write_generic;
//将plug层的pcm流对象存放在ext->plug层pcm流的私有数据成员中 ,spcm就是那个指针
ext->plug.gen.slave = spcm;
ext->plug.gen.close_slave = 1;
snd_pcm_new(&pcm, SND_PCM_TYPE_EXTPLUG, name, stream, mode); //创建一个SND_PCM_TYPE_EXTPLUG流
extplug->pcm = pcm;
pcm->ops = &snd_pcm_extplug_ops;
pcm->fast_ops = &snd_pcm_plugin_fast_ops;
pcm->private_data = ext;
pcm->poll_fd = spcm->poll_fd;
pcm->poll_events = spcm->poll_events;
snd_pcm_set_hw_ptr(pcm, &ext->plug.hw_ptr, -1, 0);
snd_pcm_set_appl_ptr(pcm, &ext->plug.appl_ptr, -1, 0);
六._snd_pcm_route_open分析
int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf,
snd_pcm_stream_t stream, int mode)
snd_config_for_each(i, next, conf)
snd_config_t *n = snd_config_iterator_entry(i);
if (strcmp(id, "slave") == 0)
slave = n;
if (strcmp(id, "ttable") == 0)
tt = n;
err = snd_pcm_slave_conf(root, slave, &sconf, 2, SND_PCM_HW_PARAM_FORMAT, 0, &sformat,
SND_PCM_HW_PARAM_CHANNELS, 0, &schannels);
determine_chmap(tt, &tt_chmap); //决定通道的map
snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
if (tt_chmap)
find_matching_chmap(spcm, tt_chmap, &chmap, &schannels);
_snd_pcm_route_determine_ttable(tt, &csize, &ssize, chmap);
_snd_pcm_route_load_ttable(tt, ttable, csize, ssize, &cused, &sused, schannels, chmap);
snd_pcm_route_open(pcmp, name, sformat, schannels, ttable, ssize, cused, sused, spcm, 1); //单独分析1
单独分析1
int snd_pcm_route_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, int schannels,
snd_pcm_route_ttable_entry_t *ttable, unsigned int tt_ssize, unsigned int tt_cused, unsigned int tt_sused,
snd_pcm_t *slave, int close_slave)
route = calloc(1, sizeof(snd_pcm_route_t));
snd_pcm_plugin_init(&route->plug);
route->sformat = sformat;
route->schannels = schannels;
route->plug.read = snd_pcm_route_read_areas;
route->plug.write = snd_pcm_route_write_areas;
route->plug.undo_read = snd_pcm_plugin_undo_read_generic;
route->plug.undo_write = snd_pcm_plugin_undo_write_generic;
route->plug.gen.slave = slave; //赋值slave指针
route->plug.gen.close_slave = close_slave;
route->plug.init = route_chmap_init;
snd_pcm_new(&pcm, SND_PCM_TYPE_ROUTE, name, slave->stream, slave->mode); //创建SND_PCM_TYPE_ROUTE的pcm
pcm->ops = &snd_pcm_route_ops;
pcm->fast_ops = &snd_pcm_plugin_fast_ops; //赋值操作函数
pcm->private_data = route;
pcm->poll_fd = slave->poll_fd;
pcm->poll_events = slave->poll_events;
pcm->tstamp_type = slave->tstamp_type;
snd_pcm_set_hw_ptr(pcm, &route->plug.hw_ptr, -1, 0);
snd_pcm_set_appl_ptr(pcm, &route->plug.appl_ptr, -1, 0);
err = route_load_ttable(&route->params, pcm->stream, tt_ssize, ttable, tt_cused, tt_sused);
七.总结
snd_pcm_open函数主要完成如下两步:
第一步:构建配置树
第二步:创建PCM流
第一步:构建配置树
第二步:创建PCM流
由此可见,在整个过程中创建了三种类型的pcm流对象,EXTPLUG,PLUG,HW
_snd_pcm_asym_open -->_snd_pcm_route_open--->_snd_pcm_pmaplug_open--->_snd_pcm_plug_open--->_snd_pcm_hw_open层层递进分析配置树
_snd_pcm_asym_open <--....................................<---_snd_pcm_plug_open<--_snd_pcm_hw_open层层回溯。
回溯过程依次创建了HW pcm-->PLUG pcm-->EXTPLUG pcm-->ROUTE pcm及和它们区配的私有数据及相关操作函数集,而且可以通过ROUTE层的pcm流,查找到EXTPLUG ,plug,hw的pcm.它们都被寄存在上层的private_data成员中。
_snd_pcm_asym_open -->_snd_pcm_route_open--->_snd_pcm_pmaplug_open--->_snd_pcm_plug_open--->_snd_pcm_hw_open层层递进分析配置树
_snd_pcm_asym_open <--....................................<---_snd_pcm_plug_open<--_snd_pcm_hw_open层层回溯。
回溯过程依次创建了HW pcm-->PLUG pcm-->EXTPLUG pcm-->ROUTE pcm及和它们区配的私有数据及相关操作函数集,而且可以通过ROUTE层的pcm流,查找到EXTPLUG ,plug,hw的pcm.它们都被寄存在上层的private_data成员中。
深入了解ALSA
alsa-lib如何解析asound.conf
Why asoundrc?
alsa lib中ttable相关学习
ALSA分析
Audio模拟多声道
Attachment List