linux alsa-lib snd_pcm_open函数源码分析(四)
欢迎直接到博客
linux alsa-lib snd_pcm_open函数源码分析(四)
系列文章其他部分:
linux alsa-lib snd_pcm_open函数源码分析(一)
linux alsa-lib snd_pcm_open函数源码分析(二)
linux alsa-lib snd_pcm_open函数源码分析(三)
linux alsa-lib snd_pcm_open函数源码分析(五)
linux alsa-lib snd_pcm_open函数源码分析(六)
snd_pcm_open_noupdate
实际执行起来要比我们单纯分析代码复杂的多,
因为函数同样时采用了多层嵌套递归的方式,特别是如果使用了alsa的多个插件,
执行过程会更下复杂。
总体的思想是读取配置树,根据配置文件插件类型拼接出函数符号,然后从动态库中查找函数来执行,
在这些函数中会一层一层的往下调用,最终调用的硬件层。同时每一层调用返回的设备都会以链表的形式链接起来。
最终返回的snd_pcm_t句柄中,实际包是所有插件句柄的最外层入口,通过此入口可以依次查找到所有的插件句柄。
使用不同的插件,函数的执行流程也会不同,为了方便说明插件的使用流程同时又不至于太过复杂,
这里以一个非常简单的重采样插件为例,其他插件同理,只不过更多的递归与嵌套调用。
pcm.rate16k {
type plug
slave {
pcm "hw:0,0"
rate 16000
}
}
1. snd_pcm_open_noupdate
此函数才是snd_pcm_open
主要的执行函数,前半部分snd_config_update_ref
其实主要是用来更新配置树,
在这部分中,根据配置的参数调用相应的函数,层层递进最终打开硬件设备。
static int snd_pcm_open_noupdate(snd_pcm_t **pcmp, snd_config_t *root,
const char *name, snd_pcm_stream_t stream,
int mode, int hop)
{
int err;
snd_config_t *pcm_conf;
const char *str;
//从pcm下查找name配置节点
err = snd_config_search_definition(root, "pcm", name, &pcm_conf);
if (err < 0) {
SNDERR("Unknown PCM %s", name);
return err;
}
//获取string类型节点,由于上面配置文家没有string类型节点此处结果<0
if (snd_config_get_string(pcm_conf, &str) >= 0)
err = snd_pcm_open_noupdate(pcmp, root, str, stream, mode,
hop + 1);
else {
//只是进行了conf->hop = hop的设置,具体作用会在后面递归时用到,用作对递归层数的限制
snd_config_set_hop(pcm_conf, hop);
//这里是重要入口,打开配置文件,在打开配置文件过程中逐渐执行了所有的函数
//详细见下文分析
err = snd_pcm_open_conf(pcmp, name, root, pcm_conf, stream, mode);
}
snd_config_delete(pcm_conf);
return err;
}
1.1. snd_pcm_open_conf
此函数是打开设备过程中最重要的函数入口,所有的插件系统都是从此处进入,
根据配置树中节点的类型拼接出函数符号,在动态库中查找到函数并调用。
打开的插件最终都会通过链表的形式链接起来,最终返回的pcmp即是所有设备链表的入口。
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)
{
const char *str;
char *buf = NULL, *buf1 = NULL;
int err;
snd_config_t *conf, *type_conf = NULL, *tmp;
snd_config_iterator_t i, next;
const char *id;
const char *lib = NULL, *open_name = NULL;
//声明函数指针
//这个地方用函数指针,是因为通过配置参数能查找到不同的函数
int (*open_func)(snd_pcm_t **, const char *,
snd_config_t *, snd_config_t *,
snd_pcm_stream_t, int) = NULL;
#ifndef PIC
extern void *snd_pcm_open_symbols(void);
#endif
//判断是否是复合类型,通常第一次执行都是复合类型,
//非复合类型比如浮点数,整数,字符串等
if (snd_config_get_type(pcm_conf) != SND_CONFIG_TYPE_COMPOUND) {
char *val;
id = NULL;
snd_config_get_id(pcm_conf, &id);
val = NULL;
snd_config_get_ascii(pcm_conf, &val);
SNDERR("Invalid type for PCM %s%sdefinition (id: %s, value: %s)", name ? name : "", name ? " " : "", id, val);
free(val);
return -EINVAL;
}
//查找type,根据我们传入的配置参数,有type节点,值为plug
err = snd_config_search(pcm_conf, "type", &conf);
if (err < 0) {
SNDERR("type is not defined");
return err;
}
//id即是type
err = snd_config_get_id(conf, &id);
if (err < 0) {
SNDERR("unable to get id");
return err;
}
//获取type节点的字符串,即为plug
err = snd_config_get_string(conf, &str);
if (err < 0) {
SNDERR("Invalid type for %s", id);
return err;
}
//在pcm_root节点,也就是总配置树下查找base为pcm_type,名称为plug的节点
//可见pcm_type也是alsa 插件的一种内置语法,实际搜索没有,此处err为负数错误码
err = snd_config_search_definition(pcm_root, "pcm_type", str, &type_conf);
if (err >= 0) {
//顺便分析下如果找到了怎么处理
//首先判断是否为复合类型,通常是复合类型
if (snd_config_get_type(type_conf) != SND_CONFIG_TYPE_COMPOUND) {
SNDERR("Invalid type for PCM type %s definition", str);
err = -EINVAL;
goto _err;
}
//然后遍历查找到的这个pcm_type节点
//之所以获取id,comment,lib这些,是因为pcm_type节点的语法如下:
//pcm_type.NAME {
// [lib STR] # Library file (default libasound.so)
// [open STR] # Open function (default _snd_pcm_NAME_open)
// [redirect { # Redirect this PCM to an another
// [filename STR] # Configuration file specification
// name STR # PCM name specification
// }]
//}
snd_config_for_each(i, next, type_conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id;
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "comment") == 0)
continue;
if (strcmp(id, "lib") == 0) {
err = snd_config_get_string(n, &lib);
if (err < 0) {
SNDERR("Invalid type for %s", id);
goto _err;
}
continue;
}
if (strcmp(id, "open") == 0) {
err = snd_config_get_string(n, &open_name);
if (err < 0) {
SNDERR("Invalid type for %s", id);
goto _err;
}
continue;
}
SNDERR("Unknown field %s", id);
err = -EINVAL;
goto _err;
}
}
//由于上面pcm_type找不到,此处open_name为null,进入分支
if (!open_name) {
//分配内存
buf = malloc(strlen(str) + 32);
if (buf == NULL) {
err = -ENOMEM;
goto _err;
}
open_name = buf;
//此处根据传入的配置树中的参数生成不同的buf
//本例中传入的str为plug,则此处buf即为 _snd_pcm_plug_open
sprintf(buf, "_snd_pcm_%s_open", str);
}
//pcm_type为null时,lib自然为null
if (!lib) {
//build_in_pcms中保存着内置插件的名字,比如dmix,plug等,
//它的具体定义在alsa-lib/src/pcm/pcm.c中
const char *const *build_in = build_in_pcms;
//此处循环的目的时判断输入的类型是否在内置的插件范围中
while (*build_in) {
if (!strcmp(*build_in, str))
break;
build_in++;
}
//build_in的最后一个元素为NULL,若执行到此,说明不再内置插件中
if (*build_in == NULL) {
//分配字符串的内存空间
buf1 = malloc(strlen(str) + sizeof(ALSA_PLUGIN_DIR) + 32);
if (buf1 == NULL) {
err = -ENOMEM;
goto _err;
}
lib = buf1;
//从这段代码可以发现,
//若不是内置插件时,用户应把动态库以这种规则命名并放置到ALSA_PLUGIN_DIR目录下
//命名应该以libasound_module_pcm_%s.so的格式
sprintf(buf1, "%s/libasound_module_pcm_%s.so", ALSA_PLUGIN_DIR, str);
}
}
#ifndef PIC
snd_pcm_open_symbols(); /* this call is for static linking only */
#endif
//打开动态库,最终根据名字返回函数
//具体实现见下文
//此处传入的名字为_snd_pcm_plug_open,最终返回的就是_snd_pcm_plug_open,
open_func = snd_dlobj_cache_get(lib, open_name,
SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION), 1);
if (open_func) {
//执行_snd_pcm_plug_open,
//详细见下文分析
err = open_func(pcmp, name, pcm_root, pcm_conf, stream, mode);
if (err >= 0) {
if ((*pcmp)->open_func) {
/* only init plugin (like empty, asym) */
//放入链表中,下次可直接从链表获取
snd_dlobj_cache_put(open_func);
} else {
(*pcmp)->open_func = open_func;
}
err = 0;
} else {
snd_dlobj_cache_put(open_func);
}
} else {
err = -ENXIO;
}
if (err >= 0) {
//查找其他节点,对本例来说这些统统找不到
err = snd_config_search(pcm_root, "defaults.pcm.compat", &tmp);
if (err >= 0) {
long i;
if (snd_config_get_integer(tmp, &i) >= 0) {
if (i > 0)
(*pcmp)->compat = 1;
}
} else {
char *str = getenv("LIBASOUND_COMPAT");
if (str && *str)
(*pcmp)->compat = 1;
}
//找不到
err = snd_config_search(pcm_root, "defaults.pcm.minperiodtime", &tmp);
if (err >= 0)
snd_config_get_integer(tmp, &(*pcmp)->minperiodtime);
err = 0;
}
_err:
if (type_conf)
snd_config_delete(type_conf);
free(buf);
free(buf1);
return err;
}
1.2 snd_dlobj_cache_get
从动态库中查找函数,并把查找到的函数添加到链表中。
void *snd_dlobj_cache_get(const char *lib, const char *name,
const char *version, int verbose)
{
struct list_head *p;
struct dlobj_cache *c;
void *func, *dlobj;
snd_dlobj_lock();
list_for_each(p, &pcm_dlobj_list) {
c = list_entry(p, struct dlobj_cache, list);
//查看链表中是否有与要查找的库名字一致的库
if (c->lib && lib && strcmp(c->lib, lib) != 0)
continue;
if (!c->lib && lib)
continue;
if (!lib && c->lib)
continue;
//查看链表中是否有与要查找的函数名字一致的函数
//如果找到则直接返回函数指针
if (strcmp(c->name, name) == 0) {
c->refcnt++;
func = c->func;
snd_dlobj_unlock();
return func;
}
}
//打开动态库,本质是对C库函数dlopen的包装
//分析见下文
dlobj = snd_dlopen(lib, RTLD_NOW);
if (dlobj == NULL) {
if (verbose)
SNDERR("Cannot open shared library %s",
lib ? lib : "[builtin]");
snd_dlobj_unlock();
return NULL;
}
//在上面的动态库中查找符号,返回函数
//本质是对c库函数dlsym的包装,详细分析见下文
func = snd_dlsym(dlobj, name, version);
if (func == NULL) {
if (verbose)
SNDERR("symbol %s is not defined inside %s",
name, lib ? lib : "[builtin]");
goto __err;
}
c = malloc(sizeof(*c));
if (! c)
goto __err;
c->refcnt = 1;
c->lib = lib ? strdup(lib) : NULL;
c->name = strdup(name);
if ((lib && ! c->lib) || ! c->name) {
free((void *)c->name);
free((void *)c->lib);
free(c);
__err:
snd_dlclose(dlobj);
snd_dlobj_unlock();
return NULL;
}
c->dlobj = dlobj;
c->func = func;
//加入链表,方便下次查找时不需要再打开动态库,直接从链表中找到
list_add_tail(&c->list, &pcm_dlobj_list);
snd_dlobj_unlock();
//最终返回的是函数指针
return func;
}
1.2.1 snd_dlopen
对c库函数dlopen的包装,用于打开某个动态库
void *snd_dlopen(const char *name, int mode)
{
#ifndef PIC //不走此分支
if (name == NULL)
return &snd_dlsym_start;
#else
#ifdef HAVE_LIBDL
if (name == NULL) {
static const char * self = NULL;
if (self == NULL) {
Dl_info dlinfo;
//此处获取当前函数的信息,
//这时认为要查找的库跟当前函数在一个库中
if (dladdr(snd_dlopen, &dlinfo) > 0)
//默认情况下self = "/usr/lib64/libasound.so.2"
self = dlinfo.dli_fname;
}
name = self;
}
#endif
#endif
#ifdef HAVE_LIBDL
/*
* Handle the plugin dir not being on the default dlopen search
* path, without resorting to polluting the entire system namespace
* via ld.so.conf.
*/
//handle为null
void *handle = NULL;
char *filename;
if (name && name[0] != '/') {
//名字当中第一个字符不是'/',则认为是相对路径
//一通操作把相对路径转换为绝对路径
filename = malloc(sizeof(ALSA_PLUGIN_DIR) + 1 + strlen(name) + 1);
strcpy(filename, ALSA_PLUGIN_DIR);
strcat(filename, "/");
strcat(filename, name);
handle = dlopen(filename, mode);
free(filename);
}
if (!handle)
//最终通过C库的dlopen打开动态库
handle = dlopen(name, mode);
return handle;
#else
return NULL;
#endif
}
1.2.2 snd_dlsym
对C库dlsym的包装,根据符号查找函数
void *snd_dlsym(void *handle, const char *name, const char *version)
{
int err;
#ifndef PIC
if (handle == &snd_dlsym_start) {
/* it's the funny part: */
/* we are looking for a symbol in a static library */
struct snd_dlsym_link *link = snd_dlsym_start;
while (link) {
if (!strcmp(name, link->dlsym_name))
return (void *)link->dlsym_ptr;
link = link->next;
}
return NULL;
}
#endif
#ifdef HAVE_LIBDL
#ifdef VERSIONED_SYMBOLS
//如果定义了版本要验证版本
if (version) {
err = snd_dlsym_verify(handle, name, version);
if (err < 0)
return NULL;
}
#endif
//最终通过dlsym返回函数句柄
return dlsym(handle, name);
#else
return NULL;
#endif
}
函数通过配置文件拼接并查找到_snd_pcm_plug_open函数,之后执行,
看似一个普通的函数执行,实际是个非常重要的函数入口,从此打开的alsa的插件系统。
露出了插件系统的冰山一角。
后续的文章会继续对插件的加载做详细分析
原文地址:https://blog.csdn.net/i826056899/article/details/143834699
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!