当前位置:   article > 正文

alsa-lib如何解析asound.conf_asound.conf怎么被解析?

asound.conf怎么被解析?

1. 简介

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

  1. 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到配置树查找到的结点。

 

  1. static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name,
  2. snd_config_t *pcm_root, snd_config_t *pcm_conf,
  3. snd_pcm_stream_t stream, int mode)
  4. {
  5. const char *str;
  6. char *buf = NULL, *buf1 = NULL;
  7. int err;
  8. snd_config_t *conf, *type_conf = NULL;
  9. snd_config_iterator_t i, next;
  10. const char *id;
  11. const char *lib = NULL, *open_name = NULL;
  12. int (*open_func)(snd_pcm_t **, const char *,
  13. snd_config_t *, snd_config_t *,
  14. snd_pcm_stream_t, int) = NULL;
  15. #ifndef PIC
  16. extern void *snd_pcm_open_symbols(void);
  17. #endif
  18. void *h = NULL;
  19. if (snd_config_get_type(pcm_conf) != SND_CONFIG_TYPE_COMPOUND) {
  20. char *val;
  21. id = NULL;
  22. snd_config_get_id(pcm_conf, &id);
  23. val = NULL;
  24. snd_config_get_ascii(pcm_conf, &val);
  25. SNDERR("Invalid type for PCM %s%sdefinition (id: %s, value: %s)", name ? name : "", name ? " " : "", id, val);
  26. free(val);
  27. return -EINVAL;
  28. }
  29. err = snd_config_search(pcm_conf, "type", &conf);
  30. if (err < 0) {
  31. SNDERR("type is not defined");
  32. return err;
  33. }
  34. err = snd_config_get_id(conf, &id);
  35. if (err < 0) {
  36. SNDERR("unable to get id");
  37. return err;
  38. }
  39. err = snd_config_get_string(conf, &str);
  40. if (err < 0) {
  41. SNDERR("Invalid type for %s", id);
  42. return err;
  43. }
  44. err = snd_config_search_definition(pcm_root, "pcm_type", str, &type_conf);
  45. if (err >= 0) {
  46. if (snd_config_get_type(type_conf) != SND_CONFIG_TYPE_COMPOUND) {
  47. SNDERR("Invalid type for PCM type %s definition", str);
  48. goto _err;
  49. }
  50. snd_config_for_each(i, next, type_conf) {
  51. snd_config_t *n = snd_config_iterator_entry(i);
  52. const char *id;
  53. if (snd_config_get_id(n, &id) < 0)
  54. continue;
  55. if (strcmp(id, "comment") == 0)
  56. continue;
  57. if (strcmp(id, "lib") == 0) {
  58. err = snd_config_get_string(n, &lib);
  59. if (err < 0) {
  60. SNDERR("Invalid type for %s", id);
  61. goto _err;
  62. }
  63. continue;
  64. }
  65. if (strcmp(id, "open") == 0) {
  66. err = snd_config_get_string(n, &open_name);
  67. if (err < 0) {
  68. SNDERR("Invalid type for %s", id);
  69. goto _err;
  70. }
  71. continue;
  72. }
  73. SNDERR("Unknown field %s", id);
  74. err = -EINVAL;
  75. goto _err;
  76. }
  77. }
  78. if (!open_name) {
  79. buf = malloc(strlen(str) + 32);
  80. if (buf == NULL) {
  81. err = -ENOMEM;
  82. goto _err;
  83. }
  84. open_name = buf;
  85. sprintf(buf, "_snd_pcm_%s_open", str); //生成真正的open函数
  86. }
  87. if (!lib) {
  88. const char *const *build_in = build_in_pcms;
  89. while (*build_in) {
  90. if (!strcmp(*build_in, str))
  91. break;
  92. build_in++;
  93. }
  94. if (*build_in == NULL) {
  95. buf1 = malloc(strlen(str) + sizeof(ALSA_PLUGIN_DIR) + 32);
  96. if (buf1 == NULL) {
  97. err = -ENOMEM;
  98. goto _err;
  99. }
  100. lib = buf1;
  101. sprintf(buf1, "%s/libasound_module_pcm_%s.so", ALSA_PLUGIN_DIR, str);
  102. }
  103. }
  104. #ifndef PIC
  105. snd_pcm_open_symbols(); /* this call is for static linking only */
  106. #endif
  107. open_func = snd_dlobj_cache_lookup(open_name);
  108. if (open_func) {
  109. err = 0;
  110. goto _err;
  111. }
  112. h = snd_dlopen(lib, RTLD_NOW);
  113. if (h)
  114. open_func = snd_dlsym(h, open_name, SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION));
  115. err = 0;
  116. if (!h) {
  117. SNDERR("Cannot open shared library %s",
  118. lib ? lib : "[builtin]");
  119. err = -ENOENT;
  120. } else if (!open_func) {
  121. SNDERR("symbol %s is not defined inside %s", open_name,
  122. lib ? lib : "[builtin]");
  123. snd_dlclose(h);
  124. err = -ENXIO;
  125. }
  126. _err:
  127. if (err >= 0) {
  128. err = open_func(pcmp, name, pcm_root, pcm_conf, stream, mode);
  129. if (err >= 0) {
  130. if (h /*&& (mode & SND_PCM_KEEP_ALIVE)*/) {
  131. snd_dlobj_cache_add(open_name, h, open_func);
  132. h = NULL;
  133. }
  134. (*pcmp)->dl_handle = h;
  135. err = 0;
  136. } else {
  137. if (h)
  138. snd_dlclose(h);
  139. }
  140. }
  141. if (type_conf)
  142. snd_config_delete(type_conf);
  143. free(buf);
  144. free(buf1);
  145. return err;
  146. }
  • 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是否包含在:

  1. static const char *const build_in_pcms[] = {
  2. "adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat",
  3. "linear", "meter", "mulaw", "multi", "null", "empty", "plug", "rate", "route", "share",
  4. "shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",
  5. NULL
  6. };

     这个数组中,如果在,就将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中定义:

  1. int show_snd_config(snd_config_t *config)
  2. {
  3. assert(config);
  4. switch (config->type) {
  5. case SND_CONFIG_TYPE_COMPOUND:
  6. {
  7. int err;
  8. struct list_head *i;
  9. i = config->u.compound.fields.next;
  10. SNDERR("Start Compound---> node id =%s\n",config->id?config->id:"null");
  11. while (i != &config->u.compound.fields) {
  12. struct list_head *nexti = i->next;
  13. snd_config_t *leaf = snd_config_iterator_entry(i);
  14. err = show_snd_config(leaf);
  15. if (err < 0)
  16. return err;
  17. i = nexti;
  18. }
  19. SNDERR("End Compound---> ---> node id =%s\n",config->id?config->id:"null");
  20. break;
  21. }
  22. case SND_CONFIG_TYPE_STRING:
  23. SNDERR("SND_CONFIG_TYPE_STRING---> leaf id=%s,str=%s\n",config->id?config->id:"null",config->u.string?config->u.string:"null");
  24. break;
  25. case SND_CONFIG_TYPE_INTEGER:
  26. SNDERR("SND_CONFIG_TYPE_ INTGER --->leaf id=%s,num=%ld\n",config->id?config->id:"null",config->u.integer);
  27. break;
  28. case SND_CONFIG_TYPE_REAL:
  29. SNDERR("SND_CONFIG_TYPE_ REAL--->leaf id=%s,num=%f\n",config->id?config->id:"null",config->u.real);
  30. break;
  31. case SND_CONFIG_TYPE_INTEGER64:
  32. SNDERR("SND_CONFIG_TYPE_INTEGER64--->leaf id=%s,num=%ld\n",config->id?config->id:"null",config->u.integer64);
  33. break;
  34. case SND_CONFIG_TYPE_POINTER:
  35. SNDERR("SND_CONFIG_TYPE_POINTER--->leaf id=%s,num=%p\n",config->id?config->id:"null",config->u.ptr);
  36. break;
  37. default:
  38. break;
  39. }
  40. return 0;
  41. }

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

  1. pcm.AndroidPlayback_Speaker_normal {
  2. type plug
  3. slave.pcm {
  4. type hw
  5. card 0
  6. device 0
  7. }
  8. }

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

  1. pcm/pcm.c:2280:(snd_pcm_open) ***2280:start:name:AndroidPlayback_Speaker_normal
  2. pcm/pcm.c:2235:(snd_pcm_open_noupdate) ***Begin:2235:name=AndroidPlayback_Speaker_normal, stream type:0
  3. pcm/pcm.c:2249:(snd_pcm_open_noupdate) 2222222 pcm_conf
  4. conf.c:2504:(show_snd_config) Start Compound---> node id =AndroidPlayback_Speaker_normal
  5. conf.c:2520:(show_snd_config) SND_CONFIG_TYPE_STRING---> leaf id=type,str=plug
  6. conf.c:2504:(show_snd_config) Start Compound---> node id =slave
  7. conf.c:2504:(show_snd_config) Start Compound---> node id =pcm
  8. conf.c:2520:(show_snd_config) SND_CONFIG_TYPE_STRING---> leaf id=type,str=hw
  9. conf.c:2524:(show_snd_config) SND_CONFIG_TYPE_ INTGER --->leaf id=card,num=0
  10. conf.c:2524:(show_snd_config) SND_CONFIG_TYPE_ INTGER --->leaf id=device,num=0
  11. conf.c:2516:(show_snd_config) End Compound---> ---> node id =pcm
  12. conf.c:2516:(show_snd_config) End Compound---> ---> node id =slave
  13. conf.c:2516:(show_snd_config) End Compound---> ---> node id =AndroidPlayback_Speaker_normal
  14. pcm/pcm.c:2260:(snd_pcm_open_noupdate) ***2260:name=AndroidPlayback_Speaker_normal,hop=0
  15. pcm/pcm.c:2079:(snd_pcm_open_conf) ***Begin: 2079
  16. pcm/pcm.c:2100:(snd_pcm_open_conf) ***2100:id=type
  17. pcm/pcm.c:2107:(snd_pcm_open_conf) ***2107:str=plug
  18. pcm/pcm.c:2152:(snd_pcm_open_conf) ***2152:buf=_snd_pcm_plug_open
  19. pcm/pcm.c:2156:(snd_pcm_open_conf) ***2156:str=plug
  20. pcm/pcm.c:2177:(snd_pcm_open_conf) ***2177:open_name=_snd_pcm_plug_open
  21. pcm/pcm.c:2199:(snd_pcm_open_conf) ***2199:cal open_func<_snd_pcm_plug_open>
  22. pcm/pcm_plug.c:1227:(_snd_pcm_plug_open) ****Begin:1227
  23. pcm/pcm.c:2079:(snd_pcm_open_conf) ***Begin: 2079
  24. pcm/pcm.c:2100:(snd_pcm_open_conf) ***2100:id=type
  25. pcm/pcm.c:2107:(snd_pcm_open_conf) ***2107:str=hw
  26. pcm/pcm.c:2152:(snd_pcm_open_conf) ***2152:buf=_snd_pcm_hw_open
  27. pcm/pcm.c:2156:(snd_pcm_open_conf) ***2156:str=hw
  28. pcm/pcm.c:2177:(snd_pcm_open_conf) ***2177:open_name=_snd_pcm_hw_open
  29. pcm/pcm.c:2199:(snd_pcm_open_conf) ***2199:call open_func<_snd_pcm_hw_open>
  30. pcm/pcm_hw.c:1397:(_snd_pcm_hw_open) ***Begin:1397
  31. pcm/pcm_hw.c:1272:(snd_pcm_hw_open) ***Begin:1272:err=0
  32. pcm/pcm_hw.c:1273:(snd_pcm_hw_open) ***1273:name=(null),card=0,device=0,subdevice=-1
  33. control/control_hw.c:375:(snd_ctl_hw_open) ***375:card:0
  34. pcm/pcm_hw.c:1129:(snd1_pcm_hw_open_fd) ***Begin:1129:ret=0
  35. pcm/pcm_hw.c:1237:(snd1_pcm_hw_open_fd) ***End:1237:ret=0
  36. pcm/pcm_hw.c:1327:(snd_pcm_hw_open) ***End:1327:ret=0
  37. pcm/pcm_hw.c:1519:(_snd_pcm_hw_open) ***Begin:1519:err=0
  38. pcm/pcm.c:2218:(snd_pcm_open_conf) ***END: 2218:err=0
  39. pcm/pcm_plug.c:1328:(_snd_pcm_plug_open) ****End: 1328: err=0
  40. pcm/pcm.c:2218:(snd_pcm_open_conf) ***END: 2218:err=0
  41. 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>

 

  1. /**
  2. * \brief Creates a new Plug PCM
  3. * \param pcmp Returns created PCM handle
  4. * \param name Name of PCM
  5. * \param root Root configuration node
  6. * \param conf Configuration node with Plug PCM description
  7. * \param stream Stream type
  8. * \param mode Stream mode
  9. * \retval zero on success otherwise a negative error code
  10. * \warning Using of this function might be dangerous in the sense
  11. * of compatibility reasons. The prototype might be freely
  12. * changed in future.
  13. */
  14. int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name,
  15. snd_config_t *root, snd_config_t *conf,
  16. snd_pcm_stream_t stream, int mode)
  17. {
  18. snd_config_iterator_t i, next;
  19. int err;
  20. snd_pcm_t *spcm;
  21. snd_config_t *slave = NULL, *sconf;
  22. snd_config_t *tt = NULL;
  23. enum snd_pcm_plug_route_policy route_policy = PLUG_ROUTE_POLICY_DEFAULT;
  24. snd_pcm_route_ttable_entry_t *ttable = NULL;
  25. unsigned int csize, ssize;
  26. unsigned int cused, sused;
  27. snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
  28. int schannels = -1, srate = -1;
  29. const snd_config_t *rate_converter = NULL;
  30. SNDERR("****Begin:%d\n",__LINE__);
  31. snd_config_for_each(i, next, conf) {
  32. snd_config_t *n = snd_config_iterator_entry(i);
  33. const char *id;
  34. if (snd_config_get_id(n, &id) < 0)
  35. continue;
  36. if (snd_pcm_conf_generic_id(id))
  37. continue;
  38. if (strcmp(id, "slave") == 0) {
  39. slave = n;
  40. continue;
  41. }
  42. #ifdef BUILD_PCM_PLUGIN_ROUTE
  43. if (strcmp(id, "ttable") == 0) {
  44. route_policy = PLUG_ROUTE_POLICY_NONE;
  45. if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
  46. SNDERR("Invalid type for %s", id);
  47. return -EINVAL;
  48. }
  49. tt = n;
  50. continue;
  51. }
  52. if (strcmp(id, "route_policy") == 0) {
  53. const char *str;
  54. if ((err = snd_config_get_string(n, &str)) < 0) {
  55. SNDERR("Invalid type for %s", id);
  56. return -EINVAL;
  57. }
  58. if (tt != NULL)
  59. SNDERR("Table is defined, route policy is ignored");
  60. if (!strcmp(str, "default"))
  61. route_policy = PLUG_ROUTE_POLICY_DEFAULT;
  62. else if (!strcmp(str, "average"))
  63. route_policy = PLUG_ROUTE_POLICY_AVERAGE;
  64. else if (!strcmp(str, "copy"))
  65. route_policy = PLUG_ROUTE_POLICY_COPY;
  66. else if (!strcmp(str, "duplicate"))
  67. route_policy = PLUG_ROUTE_POLICY_DUP;
  68. continue;
  69. }
  70. #endif
  71. #ifdef BUILD_PCM_PLUGIN_RATE
  72. if (strcmp(id, "rate_converter") == 0) {
  73. rate_converter = n;
  74. continue;
  75. }
  76. #endif
  77. SNDERR("Unknown field %s", id);
  78. return -EINVAL;
  79. }
  80. if (!slave) {
  81. SNDERR("slave is not defined");
  82. return -EINVAL;
  83. }
  84. err = snd_pcm_slave_conf(root, slave, &sconf, 3,
  85. SND_PCM_HW_PARAM_FORMAT, SCONF_UNCHANGED, &sformat,
  86. SND_PCM_HW_PARAM_CHANNELS, SCONF_UNCHANGED, &schannels,
  87. SND_PCM_HW_PARAM_RATE, SCONF_UNCHANGED, &srate);
  88. if (err < 0){
  89. SNDERR("****End: %d: err=%d\n",__LINE__,err);
  90. return err;
  91. }
  92. #ifdef BUILD_PCM_PLUGIN_ROUTE
  93. if (tt) {
  94. err = snd_pcm_route_determine_ttable(tt, &csize, &ssize);
  95. if (err < 0) {
  96. snd_config_delete(sconf);
  97. SNDERR("****End: %d: err=%d\n",__LINE__,err);
  98. return err;
  99. }
  100. ttable = malloc(csize * ssize * sizeof(*ttable));
  101. if (ttable == NULL) {
  102. snd_config_delete(sconf);
  103. SNDERR("****End: %d: err=%d\n",__LINE__,err);
  104. return err;
  105. }
  106. err = snd_pcm_route_load_ttable(tt, ttable, csize, ssize, &cused, &sused, -1);
  107. if (err < 0) {
  108. snd_config_delete(sconf);
  109. SNDERR("****End: %d: err=%d\n",__LINE__,err);
  110. return err;
  111. }
  112. }
  113. #endif
  114. #ifdef BUILD_PCM_PLUGIN_RATE
  115. if (! rate_converter)
  116. rate_converter = snd_pcm_rate_get_default_converter(root);
  117. #endif
  118. err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
  119. snd_config_delete(sconf);
  120. if (err < 0){
  121. SNDERR("****End: %d: err=%d\n",__LINE__,err);
  122. return err;
  123. }
  124. err = snd_pcm_plug_open(pcmp, name, sformat, schannels, srate, rate_converter,
  125. route_policy, ttable, ssize, cused, sused, spcm, 1);
  126. if (err < 0)
  127. snd_pcm_close(spcm);
  128. SNDERR("****End: %d: err=%d\n",__LINE__,err);
  129. return err;
  130. }

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

    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的初始化代码如下:

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


5. 配置树

配置树如下图所示:


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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/326058
推荐阅读
相关标签
  

闽ICP备14008679号