alsa-lib如何解析asound.conf

1. 简介

无论是在录音还是在放音,都要打开一个PCM流,具体对应的函数原型为:

int snd_pcm_open(snd_pcm_t **pcmp, const char *name,  snd_pcm_stream_t stream, int mode);

 本文以录音为例介绍一下它的流程。录音时传入name==AndroidCapture
 先解一下各个参数的函义:

  • pcmp: 是带回的pcm流
  • name是要找开的流即放音还是录音,放音对应AndroidPlayback_Speaker_normal,录音对应AndroidCapture。放音的字符串会因为当前使用设备和设备状况不一样而有所不同,不同之处在下划线后面那部分。录音则因为大多数板子成型后,都只有一个录音设备或者接口,因此录音串对应的就是AndroidCapture
  • stream对应流的类型,是放音(0)还是录音(1).
  • mode是指设备打开模式,阻塞或非阻塞。

2. 打开流程(snd_pcm_open_conf)

       首先会根据传入的name,到配置树中查找对应的结点。 snd_config_search_definition(root, "pcm", name, &pcm_conf); 此函数将在配置树中查找对应的结点,然后将查找到的结点复制一份到
pcm_conf中。 之后,系统会调用snd_pcm_open_conf(pcmp, name, root, pcm_conf, stream, mode);进一步分析,pcm_conf即为刚才根据传入name到配置树查找到的结点。

 

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;
	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
	void *h = NULL;
	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;
	}
	err = snd_config_search(pcm_conf, "type", &conf);
	if (err < 0) {
		SNDERR("type is not defined");
		return err;
	}
	err = snd_config_get_id(conf, &id);
	if (err < 0) {
		SNDERR("unable to get id");
		return err;
	}
	err = snd_config_get_string(conf, &str);
	if (err < 0) {
		SNDERR("Invalid type for %s", id);
		return 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);
			goto _err;
		}
		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;
		}
	}
	if (!open_name) {
		buf = malloc(strlen(str) + 32);
		if (buf == NULL) {
			err = -ENOMEM;
			goto _err;
		}
		open_name = buf;
		sprintf(buf, "_snd_pcm_%s_open", str); //生成真正的open函数
	}
	if (!lib) {
		const char *const *build_in = 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);
			if (buf1 == NULL) {
				err = -ENOMEM;
				goto _err;
			}
			lib = buf1;
			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
	open_func = snd_dlobj_cache_lookup(open_name);
	if (open_func) {
		err = 0;
		goto _err;
	}
	h = snd_dlopen(lib, RTLD_NOW);
	if (h)
		open_func = snd_dlsym(h, open_name, SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION));
	err = 0;
	if (!h) {
		SNDERR("Cannot open shared library %s",
		       lib ? lib : "[builtin]");
		err = -ENOENT;
	} else if (!open_func) {
		SNDERR("symbol %s is not defined inside %s", open_name,
		       lib ? lib : "[builtin]");
		snd_dlclose(h);
		err = -ENXIO;
	}
       _err:
	if (err >= 0) {
		err = open_func(pcmp, name, pcm_root, pcm_conf, stream, mode);
		if (err >= 0) {
			if (h /*&& (mode & SND_PCM_KEEP_ALIVE)*/) {
				snd_dlobj_cache_add(open_name, h, open_func);
				h = NULL;
			}
			(*pcmp)->dl_handle = h;
			err = 0;
		} else {
			if (h)
				snd_dlclose(h);
		}
	}
	if (type_conf)
		snd_config_delete(type_conf);
	free(buf);
	free(buf1);
	return err;
}
  • snd_pcm_open_conf函数的主要功能:

        分析传递过来的配置树pcm_conf的type子结点对应的字符串:hooks,然后调用由type值(hooks)构成的字符串函数(_snd_pcm_hooks_open)的过程。在我们系统中后面还要调用此函数,而且在查找lib名时,都能在build_in_pcms中找到,所以对我们来说这个函数的主要功能就是查找pcm_conf子结点type对应的字符串<如plug>构成的函数。


       snd_pcm_open_conf会根据配置树的情况,获取子节点中名为type的结点(它是一个叶子结点),并获取它的值hooks,使用str变量存储。然后会在root对应配置树中查找名为pcm_type hooks的结点。查找成功后,会在查找到的结点中搜索所索要的lib,open_name字符串。(后面要打开)。 在本系统中没有找到pcm_type hooks的结点。那么lib和open_name均为空,下面就要对lib/open_name初始化,(如果找到pcm_type empyt结点则会用该结点的孩子结点初始化)。首先对lib初始化,首先查找str是否包含在:

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

     这个数组中,如果在,就将lib设为null.如果不在,设置lib为默认库
 sprintf(lib, "%s/libasound_module_pcm_%s.so", ALSA_PLUGIN_DIR, str); 即/usr/lib/alsa-lib/libasound_module_pcm_hooks.so;如果查找发现hooks在数组中,所以lib为null。下面对open_name初始化sprintf(open_name, "_snd_pcm_%s_open", open_name);即_snd_pcm_hooks_open函数。


为了搞清楚配置选项在代码中的表现形式,可用如下码查看,此函数在conf.c中定义:

int  show_snd_config(snd_config_t *config)
{ 
    assert(config);
    switch (config->type) {
    case SND_CONFIG_TYPE_COMPOUND:
        {
            int err;
            struct list_head *i;
            i = config->u.compound.fields.next;
            SNDERR("Start Compound---> node id =%s\n",config->id?config->id:"null");
            
            while (i != &config->u.compound.fields) {
                struct list_head *nexti = i->next;
                snd_config_t *leaf = snd_config_iterator_entry(i);
                
                err = show_snd_config(leaf);
                if (err < 0)
                    return err;
                
                i = nexti;
            }
            SNDERR("End Compound---> ---> node id =%s\n",config->id?config->id:"null");
            break;
        }
    case SND_CONFIG_TYPE_STRING:
        SNDERR("SND_CONFIG_TYPE_STRING---> leaf id=%s,str=%s\n",config->id?config->id:"null",config->u.string?config->u.string:"null");
        break;
    
    case SND_CONFIG_TYPE_INTEGER:
        SNDERR("SND_CONFIG_TYPE_ INTGER --->leaf id=%s,num=%ld\n",config->id?config->id:"null",config->u.integer); 
        break;
    
    case SND_CONFIG_TYPE_REAL:
        SNDERR("SND_CONFIG_TYPE_ REAL--->leaf id=%s,num=%f\n",config->id?config->id:"null",config->u.real);  
        break;
    
    case   SND_CONFIG_TYPE_INTEGER64:
        SNDERR("SND_CONFIG_TYPE_INTEGER64--->leaf id=%s,num=%ld\n",config->id?config->id:"null",config->u.integer64); 
        break;
    
    case SND_CONFIG_TYPE_POINTER:
        SNDERR("SND_CONFIG_TYPE_POINTER--->leaf id=%s,num=%p\n",config->id?config->id:"null",config->u.ptr); 
        break;
    
    default:
        break;
    }
    return 0;
}

    对于音频播放,其在asound.conf的配置项如下:

pcm.AndroidPlayback_Speaker_normal {
	type plug
	slave.pcm {
		type hw
		card 0
		device 0
	}
}

对于此节点,使用show_snd_config结果如下:

pcm/pcm.c:2280:(snd_pcm_open) ***2280:start:name:AndroidPlayback_Speaker_normal
pcm/pcm.c:2235:(snd_pcm_open_noupdate) ***Begin:2235:name=AndroidPlayback_Speaker_normal, stream type:0
pcm/pcm.c:2249:(snd_pcm_open_noupdate) 2222222 pcm_conf
conf.c:2504:(show_snd_config) Start Compound---> node id =AndroidPlayback_Speaker_normal
conf.c:2520:(show_snd_config) SND_CONFIG_TYPE_STRING---> leaf id=type,str=plug
conf.c:2504:(show_snd_config) Start Compound---> node id =slave
conf.c:2504:(show_snd_config) Start Compound---> node id =pcm          
conf.c:2520:(show_snd_config) SND_CONFIG_TYPE_STRING---> leaf id=type,str=hw
conf.c:2524:(show_snd_config) SND_CONFIG_TYPE_ INTGER --->leaf id=card,num=0
conf.c:2524:(show_snd_config) SND_CONFIG_TYPE_ INTGER --->leaf id=device,num=0
conf.c:2516:(show_snd_config) End Compound---> ---> node id =pcm      
conf.c:2516:(show_snd_config) End Compound---> ---> node id =slave     
conf.c:2516:(show_snd_config) End Compound---> ---> node id =AndroidPlayback_Speaker_normal
pcm/pcm.c:2260:(snd_pcm_open_noupdate) ***2260:name=AndroidPlayback_Speaker_normal,hop=0
pcm/pcm.c:2079:(snd_pcm_open_conf) ***Begin: 2079                
pcm/pcm.c:2100:(snd_pcm_open_conf) ***2100:id=type
pcm/pcm.c:2107:(snd_pcm_open_conf) ***2107:str=plug
pcm/pcm.c:2152:(snd_pcm_open_conf) ***2152:buf=_snd_pcm_plug_open
pcm/pcm.c:2156:(snd_pcm_open_conf) ***2156:str=plug                   
pcm/pcm.c:2177:(snd_pcm_open_conf) ***2177:open_name=_snd_pcm_plug_open
pcm/pcm.c:2199:(snd_pcm_open_conf) ***2199:cal open_func<_snd_pcm_plug_open>
pcm/pcm_plug.c:1227:(_snd_pcm_plug_open) ****Begin:1227
pcm/pcm.c:2079:(snd_pcm_open_conf) ***Begin: 2079
pcm/pcm.c:2100:(snd_pcm_open_conf) ***2100:id=type
pcm/pcm.c:2107:(snd_pcm_open_conf) ***2107:str=hw
pcm/pcm.c:2152:(snd_pcm_open_conf) ***2152:buf=_snd_pcm_hw_open
pcm/pcm.c:2156:(snd_pcm_open_conf) ***2156:str=hw                   
pcm/pcm.c:2177:(snd_pcm_open_conf) ***2177:open_name=_snd_pcm_hw_open
pcm/pcm.c:2199:(snd_pcm_open_conf) ***2199:call open_func<_snd_pcm_hw_open>
pcm/pcm_hw.c:1397:(_snd_pcm_hw_open) ***Begin:1397                   
pcm/pcm_hw.c:1272:(snd_pcm_hw_open) ***Begin:1272:err=0                
pcm/pcm_hw.c:1273:(snd_pcm_hw_open) ***1273:name=(null),card=0,device=0,subdevice=-1
control/control_hw.c:375:(snd_ctl_hw_open) ***375:card:0
pcm/pcm_hw.c:1129:(snd1_pcm_hw_open_fd) ***Begin:1129:ret=0            
pcm/pcm_hw.c:1237:(snd1_pcm_hw_open_fd) ***End:1237:ret=0              
pcm/pcm_hw.c:1327:(snd_pcm_hw_open) ***End:1327:ret=0
pcm/pcm_hw.c:1519:(_snd_pcm_hw_open) ***Begin:1519:err=0
pcm/pcm.c:2218:(snd_pcm_open_conf) ***END: 2218:err=0
pcm/pcm_plug.c:1328:(_snd_pcm_plug_open) ****End: 1328: err=0
pcm/pcm.c:2218:(snd_pcm_open_conf) ***END: 2218:err=0
pcm/pcm.c:2264:(snd_pcm_open_noupdate) ***End:2264:err=0


     所以与此节点对应的open函数为_snd_pcm_plug_open,通过函数sprintf(buf, "_snd_pcm_%s_open", str);生成。

 3. _snd_pcm_hooks_open <如_snd_pcm_plug_open>

 

/**
 * \brief Creates a new Plug PCM
 * \param pcmp Returns created PCM handle
 * \param name Name of PCM
 * \param root Root configuration node
 * \param conf Configuration node with Plug PCM description
 * \param stream Stream type
 * \param mode Stream mode
 * \retval zero on success otherwise a negative error code
 * \warning Using of this function might be dangerous in the sense
 *          of compatibility reasons. The prototype might be freely
 *          changed in future.
 */
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_iterator_t i, next;
	int err;
	snd_pcm_t *spcm;
	snd_config_t *slave = NULL, *sconf;
	snd_config_t *tt = NULL;
	enum snd_pcm_plug_route_policy route_policy = PLUG_ROUTE_POLICY_DEFAULT;
	snd_pcm_route_ttable_entry_t *ttable = NULL;
	unsigned int csize, ssize;
	unsigned int cused, sused;
	snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
	int schannels = -1, srate = -1;
	const snd_config_t *rate_converter = NULL;

    SNDERR("****Begin:%d\n",__LINE__);
	snd_config_for_each(i, next, conf) {
		snd_config_t *n = snd_config_iterator_entry(i);
		const char *id;
		if (snd_config_get_id(n, &id) < 0)
			continue;
		if (snd_pcm_conf_generic_id(id))
			continue;
		if (strcmp(id, "slave") == 0) {
			slave = n;
			continue;
		}
#ifdef BUILD_PCM_PLUGIN_ROUTE
		if (strcmp(id, "ttable") == 0) {
			route_policy = PLUG_ROUTE_POLICY_NONE;
			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
				SNDERR("Invalid type for %s", id);
				return -EINVAL;
			}
			tt = n;
			continue;
		}
		if (strcmp(id, "route_policy") == 0) {
			const char *str;
			if ((err = snd_config_get_string(n, &str)) < 0) {
				SNDERR("Invalid type for %s", id);
				return -EINVAL;
			}
			if (tt != NULL)
				SNDERR("Table is defined, route policy is ignored");
			if (!strcmp(str, "default"))
				route_policy = PLUG_ROUTE_POLICY_DEFAULT;
			else if (!strcmp(str, "average"))
				route_policy = PLUG_ROUTE_POLICY_AVERAGE;
			else if (!strcmp(str, "copy"))
				route_policy = PLUG_ROUTE_POLICY_COPY;
			else if (!strcmp(str, "duplicate"))
				route_policy = PLUG_ROUTE_POLICY_DUP;
			continue;
		}
#endif
#ifdef BUILD_PCM_PLUGIN_RATE
		if (strcmp(id, "rate_converter") == 0) {
			rate_converter = n;
			continue;
		}
#endif
		SNDERR("Unknown field %s", id);
		return -EINVAL;
	}
	if (!slave) {
		SNDERR("slave is not defined");
		return -EINVAL;
	}
	err = 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);
	if (err < 0){
        SNDERR("****End: %d: err=%d\n",__LINE__,err);
		return err;
    }
#ifdef BUILD_PCM_PLUGIN_ROUTE
	if (tt) {
		err = snd_pcm_route_determine_ttable(tt, &csize, &ssize);
		if (err < 0) {
			snd_config_delete(sconf);
            SNDERR("****End: %d: err=%d\n",__LINE__,err);
			return err;
		}
		ttable = malloc(csize * ssize * sizeof(*ttable));
		if (ttable == NULL) {
			snd_config_delete(sconf);
            SNDERR("****End: %d: err=%d\n",__LINE__,err);
			return err;
		}
		err = snd_pcm_route_load_ttable(tt, ttable, csize, ssize, &cused, &sused, -1);
		if (err < 0) {
			snd_config_delete(sconf);
            SNDERR("****End: %d: err=%d\n",__LINE__,err);
			return err;
		}
	}
#endif
	
#ifdef BUILD_PCM_PLUGIN_RATE
	if (! rate_converter)
		rate_converter = snd_pcm_rate_get_default_converter(root);
#endif

	err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
	snd_config_delete(sconf);
	if (err < 0){
        SNDERR("****End: %d: err=%d\n",__LINE__,err);
		return err;
    }
	err = snd_pcm_plug_open(pcmp, name, sformat, schannels, srate, rate_converter,
				route_policy, ttable, ssize, cused, sused, spcm, 1);
	if (err < 0)
		snd_pcm_close(spcm);
    
    SNDERR("****End: %d: err=%d\n",__LINE__,err);
	return err;
}

这个函数主要完成三件事:

    1)在传入的配置树中查找子孩子ID为slave的结点

    2)然后调用snd_pcm_open_slave和snd_pcm_hooks_open

    3)最后再分析配置树是否有hooks结点,有的话调用snd_pcm_hook_add_conf。

    为了方便大家理解,还是先看一下传入_snd_pcm_hooks_open配置树的情况, 先来看一下snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);函数,在这里sconf即为slave结点中ID为pcm对应的配置树。root对应的是总的配置树(系统启动时会根据配置文件asound.conf/alsa-lib.conf加载构建)。

      snd_pcm_open_slave-->

         snd_pcm_open_named_slave-->

                 snd_pcm_open_noupdate

      由此可见snd_pcm_open_slave仍然会走到snd_pcm_open_noupdate这个函数处。然后会根据传入的参数name=default,在总的配置树中查找pcm default结点。snd_config_search_definition最终根据pcm default串查找对应的树。之后会调用 snd_pcm_open_conf(pcmp, name, root, pcm_conf, stream, mode)前面已经详细介过本函数了,本次调用只是传递过来的配置树不一样。

       snd_pcm_open_conf(pcmp, name, root, pcm_conf, stream, mode)根据当前传入树pcm_conf的情况会调用如下函数_snd_pcm_empty_open。这个函数会从当前传入树取下slave孩子结点的pcm结点对应的配置树做为根结点,并调用 snd_pcm_open_named_slave.
      snd_pcm_open_named_slave-->

          snd_pcm_open_conf

      可以看出又回到了snd_pcm_open_conf函数,只是传入的树不一样。snd_pcm_open_conf会调用_snd_pcm_plug_open.
     函数_snd_pcm_plug_open会调 用snd_pcm_open_slave及snd_pcm_plug_open。
     在传入树中有这样的子树,调用

      snd_pcm_open_slave-->

          nd_pcm_open_named_slave-->

               snd_pcm_open_conf-->

                  _snd_pcm_hw_open.

4. _snd_pcm_hw_open

     _snd_pcm_hw_open-->snd_pcm_hw_open,此函数中会根据树中的card号码,调用snd_ctl_hw_open打开/dev/snd/ControlC0,然后再打开/dev/snd/pcmC0D0c,完成后调用snd_ctl_close关闭/dev/snd/ControlC0.并调用snd_pcm_hw_open_fd,传入fd为打开的/dev/snd/pcmC0D0c.该函数完成PCM流的创建,首先创建pcm的私有数据hw (snd_pcm_hw_t结构),然后用参数(参数获取是通过ioctl命令发送到fd,然后内核将请求信息带回最后赋值给hw的成员中)初始化它,接着创建pcm(_snd_pcm结构),并将hw赋值给pcm->private_data成员。最后再调用snd_pcm_hw_mmap_status/snd_pcm_hw_mmap_control两个函数完成映射。需要注意在这里pcm有一个类型,本处创建的pcm类型为SND_PCM_TYPE_HW.
 对pcm的初始化代码如下:

//创建类型为SND_PCM_TYPE_HW的pcm流,并完成一些基本初始化。
 ret = snd_pcm_new(&pcm, SND_PCM_TYPE_HW, name, info.stream, mode);
 if (ret < 0) {
  free(hw);
  close(fd);
  return ret;
 }
 //初始化ops函数集
 pcm->ops = &snd_pcm_hw_ops;
 //初始化fast_ops函数集
 pcm->fast_ops = &snd_pcm_hw_fast_ops;
 pcm->private_data = hw;//如上所述私有数据赋值
 pcm->poll_fd = fd;/dev/snd/pcmC0D0c
 pcm->poll_events = info.stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN;
 pcm->monotonic = monotonic;


5. 配置树

配置树如下图所示:


转自:http://blog.chinaunix.net/uid-27134408-id-3272774.html

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(ALSA)