android alsa分析之一

snd_pcm_open

最近闲了,将去年分析的alsa库分析一下,和大家共享一下 ,请多多指点。 里面有一部分链接,图片等有需要可联系我 哈

  硬 件: Arm
 软件: Android 2.1
 无论是在录音还是在放音,都要打开一个PCM流,具体对应的函数原型为:
 int snd_pcm_open(snd_pcm_t **pcmp, const char *name,  snd_pcm_stream_t stream, int mode);
 这里就开始分析一下它的流程。
 在开始之前读者最好先看一下alsa-lib是如何对conf文件进行解析的,后面就简单提一下,这只重点在snd_pcm_open的流程。
 本文以录音为例介绍一下它的流程。录音时传入name==AndroidCapture
 先解一下各个参数的函义:pcmp是带回的pcm流,name是要找开的流即放音还是录音。放音对应AndroidPlayback_Speaker_normal,
录音对应AndroidCapture.放音的字符串会因为当前使用设备和设备状况不一样而有所不同,不同之处在下划线后面那部分。录音则因为大多
数板子成型后,都只有一个录音设备或者接口,因为录音串对应的就是AndroidCapture.stream对应流的类型,是放音(0)还是录音(1).mode是
指设备打开模式,可以暂时不用管。
 打开流程首先会根据传入的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):
 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函数。
 在这里需要说明的,为什么查找到的串的库名取null,而没有找到的取 /usr/lib/alsa-lib/libasound_module_pcm_hooks.so.个人理解因为我们
alsa-lib源码是开源的,alsa-lib初始化是会加载进来的。没有找到的库名为 /usr/lib/alsa-lib/libasound_module_pcm_hooks.so,这个可能是根据
我们不同的硬件来实现的吧,只要我们实现这个库存的接口(库名)一致alas-lib就可以正确加载 。
 在我们的系统中包含对应的串hooks,所以lib为null.后续要开库时,传入库名为null,系统会返回一默认库名柄。
 下面就是打开库,并在库中查找对应的open_name.因为我们代码是开源的,当然就可以在alsa-lib源码中找到对应的open_name,此处为_snd_pcm_hooks_open.然后就是调用这个函数 open_func(pcmp, name, pcm_root, pcm_conf, stream, mode)。
 到此为至,简单来说snd_pcm_open_conf函数的主要功能,就是分析传递过来的配置树pcm_conf的type子结点对应的字符串:hooks,然后调
用由type值(hooks)构成的字符串函数(_snd_pcm_hooks_open)的过程。在我们系统中后面还要陆续调用此函数,而且在查找lib名时,都能在build_in_pcms中找到,所以对我们来说这个函数的主要功能就是查找pcm_conf子结点type对应的字符串构成的函数。
 
 int _snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf, snd_pcm_stream_t stream, int mode)
 这个函数主要完成三件事,在传入的配置树中查找子孩子ID为slave的结点,然后调用snd_pcm_open_slave和snd_pcm_hooks_open,最后再分析配置树
的是否有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_type_plug_open.
 函数_snd_pcm_type_plug_open会调 用snd_pcm_open_slave及snd_pcm_plug_open。
 在传入树中摘下这样的子树,调用snd_pcm_open_slave-->snd_pcm_open_named_slave-->snd_pcm_open_conf-->_snd_pcm_hw_open.下面简单介绍一下_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;
 //以下两函数可能是设置映射指针。读、写指针
 ret = snd_pcm_hw_mmap_status(pcm);
 ret = snd_pcm_hw_mmap_control(pcm);
 至此一个完整的类型为SND_PCM_TYPE_HW的pcm流对象已建立成功,但是整个函数流程并没有完成,层层深入的函数调用将要返回,此处完成后将返回snd_pcm_plug_open函数的调用,先看一下这个函数,代码不多
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)
{
 //这里传入的slave是刚刚我们创建的SND_PCM_TYPE_HW类型的pcm流对象
 snd_pcm_t *pcm;
 snd_pcm_plug_t *plug;
 int err;
 assert(pcmp && slave);
 //创建SND_PCM_TYPE_PLUG类型pcm流的私有数据对象
 plug = calloc(1, sizeof(snd_pcm_plug_t));
 if (!plug)
  return -ENOMEM;
 //根据传入参数和slave的相关值初始化plug成员
 plug->sformat = sformat;
 plug->schannels = schannels;
 plug->srate = srate;
 plug->rate_converter = rate_converter;
 //这里大家需要留意一下,后面还要介绍,知道这里有个赋值
 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;
 //创建SND_PCM_TYPE_PLUG类型的流对象
 err = snd_pcm_new(&pcm, SND_PCM_TYPE_PLUG, name, slave->stream, slave->mode);
 if (err < 0) {
  free(plug);
  return err;
 }
 //用plug对应的一组函数赋值给它的ops/fast_ops成员
 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->monotonic = slave->monotonic;
 snd_pcm_link_hw_ptr(pcm, slave);
 snd_pcm_link_appl_ptr(pcm, slave);
 *pcmp = pcm;
 return 0;
}
 这个函数完成后,我们仍然没有完成整个建立流程,此时会返回到_snd_pcm_hooks_open函数中调用snd_pcm_hooks_open,代码也不是很多
int snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t *slave, int close_slave)
{
 //这里的slave是类型为plug的pcm流对象
 snd_pcm_t *pcm;
 snd_pcm_hooks_t *h;
 unsigned int k;
 int err;
 assert(pcmp && slave);
 //分配私有数据对象
 h = calloc(1, sizeof(snd_pcm_hooks_t));
 if (!h)
  return -ENOMEM;
 //留意一下
 h->gen.slave = slave;
 h->gen.close_slave = close_slave;
 for (k = 0; k <= SND_PCM_HOOK_TYPE_LAST; ++k) {
  INIT_LIST_HEAD(&h->hooks[k]);
 }
 //创建流类型为SND_PCM_TYPE_HOOKS的pcm流对象。
 err = snd_pcm_new(&pcm, SND_PCM_TYPE_HOOKS, name, slave->stream, slave->mode);
 if (err < 0) {
  free(h);
  return err;
 }
 //用hooks对应的一组函数初始化ops/fast_ops
 pcm->ops = &snd_pcm_hooks_ops;
 pcm->fast_ops = &snd_pcm_hooks_fast_ops;
 //私有成员链接到pcm 对象中
 pcm->private_data = h;
 pcm->poll_fd = slave->poll_fd;
 pcm->poll_events = slave->poll_events;
 pcm->mmap_shadow = 1;
 pcm->monotonic = slave->monotonic;
 snd_pcm_link_hw_ptr(pcm, slave);
 snd_pcm_link_appl_ptr(pcm, slave);
 *pcmp = pcm;
 return 0;
}
 至此pcm流的创建过程,基本完成,可是还有最后一步_snd_pcm_hooks_open中的snd_pcm_hook_add_conf,因为我们的树中有hooks结点,所以必需调用这个函数进行处理。
 snd_pcm_hook_add_conf(rpcm, root, n);其中n对应的配置树是这颗树中的子树。snd_pcm_hook_add_conf函数功能和snd_pcm_open_conf函数功能类似,只要我们知道n这颗树信息,就可以很容易阅 读完它的代码。snd_pcm_hook_add_conf--->_snd_pcm_hook_ctl_elems_install.
 int _snd_pcm_hook_ctl_elems_install(snd_pcm_t *pcm, snd_config_t *conf)根据我们本次传入的子树,然后再来分析代码会变得很容易,slave为我们最后一次创建的HOOKS类型的pcm.
 本函数会调用snd_ctl_open(&ctl, ctl_name, 0);打开对应的ctl,下面看一下
int snd_ctl_open(snd_ctl_t **ctlp, const char *name, int mode)
{
 int err;
 assert(ctlp && name);
 err = snd_config_update();
 if (err < 0)
  return err;
 return snd_ctl_open_noupdate(ctlp, snd_config, name, mode);
}
 是否似曾相似,同样是到配置树中查找对应的结点。通过snd_config_search_definition找到这样配置树,然后调用snd_ctl_open_conf->_snd_ctl_hw_open.函数返回带回一个ctl控件。
 snd_ctl_open完成后调用snd_config_imake_pointer(&pcm_conf, "pcm_handle", pcm);创建一个结点信息,并点pcm赋值给结点的指针域。
 之后,会调用snd_sctl_build(&sctl, ctl, conf, pcm_conf, 0);
 int snd_sctl_build(snd_sctl_t **sctl, snd_ctl_t *handle, snd_config_t *conf, snd_config_t *private_data, int mode)
{
 //handle刚创建的ctl,conf传入_snd_pcm_hook_add_conf中的conf,private_data刚创建的指针结点。
 snd_sctl_t *h;
 snd_config_iterator_t i, next;
 int err;

 assert(sctl);
 assert(handle);
 assert(conf);
 *sctl = NULL;
 if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND)
  return -EINVAL;
 h = calloc(1, sizeof(*h));
 if (!h) {
  if (mode & SND_SCTL_NOFREE)
   return -ENOMEM;
  snd_ctl_close(handle);
  return -ENOMEM;
 }
 h->mode = mode;
 h->ctl = handle;
 INIT_LIST_HEAD(&h->elems);
 //在这里依次添加hook_args中的参数添加到h中。
 snd_config_for_each(i, next, conf) {
  snd_config_t *n = snd_config_iterator_entry(i);
  err = add_elem(h, n, private_data);
  if (err < 0) {
   free_elems(h);
   return err;
  }
 }
 *sctl = h;
 return 0;
}
 不幸的是根据我们的配置文件在这里添加的信息add_elem时会将每个参数信息通过调用snd_pcm_expand展开,我们的配置有问题,add_elem时会返回错误信息。
 这样错误信息会一层层返回,并释放掉刚才我们创建的那么多pcm对象和私有数据等。但是不要紧,我们可以从中分析知道snd_pcm_open函数的流程。
 稍后会对snd_pcm_open创建流程做一个小结,然后再说一下我们系统中pcm在后面是如何创建成功的。

 

 

 

 


 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android凭借其开源,良好的用户体验,高性价比,庞大的应用程序等优势迅速发展起来并保持强劲的发展势头。如今Android智能手机、Android平板电脑等移动产品已走入人们的日常生活,成为人们通讯、娱乐的重要工具。本设计以malata采用RK2918方案的Android平板电脑项目为背景,主要任务是设计和实现Android平板电脑的音频系统,为广大平板用户提供良好的音频体验。 ALSA(Advanced Linux Sound Architecture,高级Linux声音架构)在Linux操作系统上提供了音频和MIDI(Musical Instrument Digital Interface,音乐设备数字化接口)的支持。它的主要特性包括:高效地支持从消费类入门级声卡到专业级音频设备所有类型的音频接口,完全模块化的设计,支持对称多处理和线程安全,对OSS(Open Sound System,开放声音系统)的向后兼容,以及提供了用户空间的alsa-lib库来简化应用程序的开发[1]。基于ALSA的音频系统能够很好的适应硬件的多样性,因此Android设备厂商能够更灵活地根据不同需求选择不同的音频编解码芯片。ALC5625是一款高度集成低功耗高保真的带I2S/PCM接口并具有多路输入输出的音频编解码器,可满足本次设计的各项需求。 基于ALSA的Andorid音频系统拥有一个标准和健全的架构,自上而下由Audio应用程序、Audio Java框架层、Audio本地框架层、AudioFlinger、Audio硬件抽象层、alsa-lib和底层Audio驱动几个部分组成。本文分析音频系统架构的各个层次,并着重阐述ALSAAndroid音频系统中的应用以及Audio驱动的详细实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值