zoukankan      html  css  js  c++  java
  • ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

    前几篇文章我们从dapm的数据结构入手,了解了代表音频控件的widget,代表连接路径的route以及用于连接两个widget的path。之前都是一些概念的讲解以及对数据结构中各个字段的说明,从本章开始,我们要从代码入手,分析dapm的详细工作原理:

    • 如何注册widget
    • 如何连接两个widget
    • 一个widget的状态裱画如何传递到整个音频路径中
    /*****************************************************************************************************/
    声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
    /*****************************************************************************************************/
     

    dapm context


    在讨论widget的注册之前,我们先了解另一个概念:dapm context,直译过来的意思是dapm上下文,这个好像不好理解,其实我们可以这么理解:dapm把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的widget,每个域中的所有widget通常都处于同一个偏置电压级别上,而一个电源域就是一个dapm context,通常会有以下几种dapm context:
    • 属于codec中的widget位于一个dapm context中
    • 属于platform的widget位于一个dapm context中
    • 属于整个声卡的widget位于一个dapm context中
    对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,我们可以方便地对同一组widget进行统一的偏置电压管理,ASoc用snd_soc_dapm_context结构来表示一个dapm context:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. struct snd_soc_dapm_context {  
    2.         enum snd_soc_bias_level bias_level;  
    3.         enum snd_soc_bias_level suspend_bias_level;  
    4.         struct delayed_work delayed_work;  
    5.         unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */  
    6.   
    7.         struct snd_soc_dapm_update *update;  
    8.   
    9.         void (*seq_notifier)(struct snd_soc_dapm_context *,  
    10.                              enum snd_soc_dapm_type, int);  
    11.   
    12.         struct device *dev; /* from parent - for debug */  
    13.         struct snd_soc_codec *codec; /* parent codec */  
    14.         struct snd_soc_platform *platform; /* parent platform */  
    15.         struct snd_soc_card *card; /* parent card */  
    16.   
    17.         /* used during DAPM updates */  
    18.         enum snd_soc_bias_level target_bias_level;  
    19.         struct list_head list;  
    20.   
    21.         int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);  
    22.   
    23. #ifdef CONFIG_DEBUG_FS  
    24.         struct dentry *debugfs_dapm;  
    25. #endif  
    26. };  
    snd_soc_bias_level的取值范围是以下几种:
    • SND_SOC_BIAS_OFF
    • SND_SOC_BIAS_STANDBY
    • SND_SOC_BIAS_PREPARE
    • SND_SOC_BIAS_ON
    snd_soc_dapm_context被内嵌到代表codec、platform、card、dai的结构体中:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. struct snd_soc_codec {  
    2.         ......  
    3.         /* dapm */  
    4.         struct snd_soc_dapm_context dapm;  
    5.         ......  
    6. };  
    7.   
    8. struct snd_soc_platform {  
    9.         ......  
    10.         /* dapm */  
    11.         struct snd_soc_dapm_context dapm;  
    12.         ......  
    13. };  
    14.   
    15. struct snd_soc_card {  
    16.         ......  
    17.         /* dapm */  
    18.         struct snd_soc_dapm_context dapm;  
    19.         ......  
    20. };  
    21. :  
    22. struct snd_soc_dai {  
    23.         ......  
    24.         /* dapm */  
    25.         struct snd_soc_dapm_widget *playback_widget;  
    26.         struct snd_soc_dapm_widget *capture_widget;  
    27.         struct snd_soc_dapm_context dapm;  
    28.         ......  
    29. };  
    代表widget结构snd_soc_dapm_widget中,有一个snd_soc_dapm_context结构指针,指向所属的codec、platform、card、或dai的dapm结构。同时,所有的dapm结构,通过它的list字段,链接到代表声卡的snd_soc_card结构的dapm_list链表头字段。

    创建和注册widget


    我们已经知道,一个widget用snd_soc_dapm_widget结构体来描述,通常,我们会根据音频硬件的组成,分别在声卡的codec驱动、platform驱动和machine驱动中定义一组widget,这些widget用数组进行组织,我们一般会使用dapm框架提供的大量的辅助宏来定义这些widget数组,辅助宏的说明请参考前一偏文章:ALSA声卡驱动中的DAPM详解之三:如何定义各种widget

    codec驱动中注册    我们知道,我们会通过ASoc提供的api函数snd_soc_register_codec来注册一个codec驱动,该函数的第二个参数是一个snd_soc_codec_driver结构指针,这个snd_soc_codec_driver结构需要我们在codec驱动中显式地进行定义,其中有几个与dapm框架有关的字段:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. struct snd_soc_codec_driver {  
    2.         ......          
    3.         /* Default control and setup, added after probe() is run */  
    4.         const struct snd_kcontrol_new *controls;  
    5.         int num_controls;  
    6.         const struct snd_soc_dapm_widget *dapm_widgets;  
    7.         int num_dapm_widgets;  
    8.         const struct snd_soc_dapm_route *dapm_routes;  
    9.         int num_dapm_routes;  
    10.         ......  
    11. }  

    我们只要把我们定义好的snd_soc_dapm_widget结构数组的地址和widget的数量赋值到dapm_widgets和num_dapm_widgets字段即可,这样,经过snd_soc_register_codec注册codec后,在machine驱动匹配上该codec时,系统会判断这两个字段是否被赋值,如果有,它会调佣dapm框架提供的api来创建和注册widget,注意这里我说还要创建这个词,你可能比较奇怪,既然代表widget的snd_soc_dapm_widget结构数组已经在codec驱动中定义好了,为什么还要在创建?事实上,我们在codec驱动中定义的widget数组只是作为一个模板,dapm框架会根据该模板重新申请内存并初始化各个widget。我们看看实际的例子可能是这样的:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {  
    2.         ......  
    3.         SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, NULL, 0),  
    4.         SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),  
    5.         SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),  
    6.         ......  
    7. };  
    8.   
    9. static struct snd_soc_codec_driver soc_codec_dev_wm8993 = {  
    10.         .probe =        codec_xxx_probe,  
    11.         ......  
    12.         .dapm_widgets =       &wm8993_dapm_widgets[0],  
    13.         .num_dapm_widgets =      ARRAY_SIZE(wm8993_dapm_widgets),  
    14.         ......  
    15. };   
    16.   
    17. static int codec_wm8993_i2c_probe(struct i2c_client *i2c,  
    18.                             const struct i2c_device_id *id)  
    19. {  
    20.         ......  
    21.         ret = snd_soc_register_codec(&i2c->dev,  
    22.                         &soc_codec_dev_wm8993, &wm8993_dai, 1);  
    23.         ......  
    24. }  

    上面这种注册方法有个缺点,有时候我们为了代码的清晰,可能会根据功能把不同的widget定义成多个数组,但是snd_soc_codec_driver中只有一个dapm_widgets字段,无法设定多个widget数组,这时候,我们需要主动在codec的probe回调中调用dapm框架提供的api来创建这些widget:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static int wm8993_probe(struct snd_soc_codec *codec)  
    2. {  
    3.         ......  
    4.         snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,  
    5.                                   ARRAY_SIZE(wm8993_dapm_widgets));  
    6.         ......  
    7. }  

    实际上,对于第一种方法,snd_soc_register_codec内部其实也是调用snd_soc_dapm_new_controls来完成的。后面会有关于这个函数的详细分析。

    platform驱动中注册    和codec驱动一样,我们会通过ASoc提供的api函数snd_soc_register_platform来注册一个platform驱动,该函数的第二个参数是一个snd_soc_platform_driver结构指针,snd_soc_platform_driver结构中同样也包含了与dapm相关的字段:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. struct snd_soc_platform_driver {  
    2.         ......          
    3.         /* Default control and setup, added after probe() is run */  
    4.         const struct snd_kcontrol_new *controls;  
    5.         int num_controls;  
    6.         const struct snd_soc_dapm_widget *dapm_widgets;  
    7.         int num_dapm_widgets;  
    8.         const struct snd_soc_dapm_route *dapm_routes;  
    9.         int num_dapm_routes;  
    10.         ......  
    11. }  

    要注册platform级别的widget,和codec驱动一样,只要把定义好的widget数组赋值给dapm_widgets和num_dapm_widgets字段即可,snd_soc_register_platform函数注册paltform后,当machine驱动匹配上该platform时,系统会自动完成创建和注册的工作。同理,我们也可以在platform驱动的probe回调函数中主动使用snd_soc_dapm_new_controls来完成widget的创建工作。具体的代码和codec驱动是类似的,这里就不贴了。

    machine驱动中注册    有些widget可能不是位于codec中,例如一个独立的耳机放大器,或者是喇叭功放等,这种widget通常需要在machine驱动中注册,通常他们的dapm context也从属于声卡(snd_soc_card)域。做法依然和codec驱动类似,通过代表声卡的snd_soc_card结构中的几个dapm字段完成:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. struct snd_soc_card {  
    2.         ......  
    3.         /* 
    4.          * Card-specific routes and widgets. 
    5.          */  
    6.         const struct snd_soc_dapm_widget *dapm_widgets;  
    7.         int num_dapm_widgets;  
    8.         const struct snd_soc_dapm_route *dapm_routes;  
    9.         int num_dapm_routes;  
    10.         bool fully_routed;  
    11.         ......  
    12. }  

    只要把定义好的widget数组和数量赋值给dapm_widgets指针和num_dapm_widgets即可,注册声卡使用的api:snd_soc_register_card(),也会通过snd_soc_dapm_new_controls来完成widget的创建工作。

    注册音频路径


    系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过snd_soc_dapm_route结构来定义,关于如何用snd_soc_dapm_route结构来定义路径信息,请参考:ALSA声卡驱动中的DAPM详解之三:如何定义各种widget中的"建立widget和route"一节的内容。通常,所有的路径信息会用一个snd_soc_dapm_route结构数组来定义。和widget一样,路径信息也分别存在与codec驱动,machine驱动和platform驱动中,我们一样有两种方式来注册音频路径信息:
    • 通过snd_soc_codec_driver/snd_soc_platform_driver/snd_soc_card结构中的dapm_routes和num_dapm_routes字段;
    • 在codec、platform的的probe回调中主动注册音频路径,machine驱动中则通过snd_soc_dai_link结构的init回调函数来注册音频路径;
    两种方法最终都是通过调用snd_soc_dapm_add_routes函数来完成音频路径的注册工作的。以下的代码片段是omap的pandora板子的machine驱动,使用第二种方法注册路径信息:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static const struct snd_soc_dapm_widget omap3pandora_in_dapm_widgets[] = {  
    2.         SND_SOC_DAPM_MIC("Mic (internal)", NULL),  
    3.         SND_SOC_DAPM_MIC("Mic (external)", NULL),  
    4.         SND_SOC_DAPM_LINE("Line In", NULL),  
    5. };  
    6.   
    7. static const struct snd_soc_dapm_route omap3pandora_out_map[] = {  
    8.         {"PCM DAC", NULL, "APLL Enable"},  
    9.         {"Headphone Amplifier", NULL, "PCM DAC"},  
    10.         {"Line Out", NULL, "PCM DAC"},  
    11.         {"Headphone Jack", NULL, "Headphone Amplifier"},  
    12. };  
    13.   
    14. static const struct snd_soc_dapm_route omap3pandora_in_map[] = {  
    15.         {"AUXL", NULL, "Line In"},  
    16.         {"AUXR", NULL, "Line In"},  
    17.   
    18.         {"MAINMIC", NULL, "Mic (internal)"},  
    19.         {"Mic (internal)", NULL, "Mic Bias 1"},  
    20.   
    21.         {"SUBMIC", NULL, "Mic (external)"},  
    22.         {"Mic (external)", NULL, "Mic Bias 2"},  
    23. };  
    24. static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd)  
    25. {  
    26.         struct snd_soc_codec *codec = rtd->codec;  
    27.         struct snd_soc_dapm_context *dapm = &codec->dapm;  
    28.         int ret;  
    29.   
    30.         /* All TWL4030 output pins are floating */  
    31.         snd_soc_dapm_nc_pin(dapm, "EARPIECE");  
    32.         ......  
    33.         //注册kcontrol控件  
    34.         ret = snd_soc_dapm_new_controls(dapm, omap3pandora_out_dapm_widgets,  
    35.                                 ARRAY_SIZE(omap3pandora_out_dapm_widgets));  
    36.         if (ret < 0)  
    37.                 return ret;  
    38.         //注册machine的音频路径  
    39.         return snd_soc_dapm_add_routes(dapm, omap3pandora_out_map,  
    40.                 ARRAY_SIZE(omap3pandora_out_map));  
    41. }  
    42.   
    43. static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd)  
    44. {  
    45.         struct snd_soc_codec *codec = rtd->codec;  
    46.         struct snd_soc_dapm_context *dapm = &codec->dapm;  
    47.         int ret;  
    48.   
    49.         /* Not comnnected */  
    50.         snd_soc_dapm_nc_pin(dapm, "HSMIC");  
    51.         ......  
    52.         //注册kcontrol控件  
    53.         ret = snd_soc_dapm_new_controls(dapm, omap3pandora_in_dapm_widgets,  
    54.                                 ARRAY_SIZE(omap3pandora_in_dapm_widgets));  
    55.         if (ret < 0)  
    56.                 return ret;  
    57.         //注册machine音频路径  
    58.         return snd_soc_dapm_add_routes(dapm, omap3pandora_in_map,  
    59.                 ARRAY_SIZE(omap3pandora_in_map));  
    60. }  
    61.   
    62. /* Digital audio interface glue - connects codec <--> CPU */  
    63. static struct snd_soc_dai_link omap3pandora_dai[] = {  
    64.         {  
    65.                 .name = "PCM1773",  
    66.                 ......  
    67.                 .init = omap3pandora_out_init,  
    68.         }, {  
    69.                 .name = "TWL4030",  
    70.                 .stream_name = "Line/Mic In",  
    71.                 ......  
    72.                 .init = omap3pandora_in_init,  
    73.         }  
    74. };  


    dai widget


    上面几节的内容介绍了codec、platform以及machine级别的widget和route的注册方法,在dapm框架中,还有另外一种widget,它代表了一个dai(数字音频接口),关于dai的描述,请参考:Linux ALSA声卡驱动之七:ASoC架构中的Codec。dai按所在的位置,又分为cpu dai和codec dai,在硬件上,通常一个cpu dai会连接一个codec dai,而在machine驱动中,我们要在snd_soc_card结构中指定一个叫做snd_soc_dai_link的结构,该结构定义了声卡使用哪一个cpu dai和codec dai进行连接。在Asoc中,一个dai用snd_soc_dai结构来表述,其中有几个字段和dapm框架有关:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. struct snd_soc_dai {  
    2.         ......  
    3.         struct snd_soc_dapm_widget *playback_widget;  
    4.         struct snd_soc_dapm_widget *capture_widget;  
    5.         struct snd_soc_dapm_context dapm;  
    6.         ......  
    7. }  
    dai由codec驱动和平台代码中的iis或pcm接口驱动注册,machine驱动负责找到snd_soc_dai_link中指定的一对cpu/codec dai,并把它们进行绑定。不管是cpu dai还是codec dai,通常会同时传输播放和录音的音频流的能力,所以我们可以看到,snd_soc_dai中有两个widget指针,分别代表播放流和录音流。这两个dai widget是何时创建的呢?下面我们逐一进行分析。

    codec dai widget    

    首先,codec驱动在注册codec时,会传入该codec所支持的dai个数和记录dai信息的snd_soc_dai_driver结构指针:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static struct snd_soc_dai_driver wm8993_dai = {  
    2.         .name = "wm8993-hifi",  
    3.         .playback = {  
    4.                 .stream_name = "Playback",  
    5.                 .channels_min = 1,  
    6.                 .channels_max = 2,  
    7.                 .rates = WM8993_RATES,  
    8.                 .formats = WM8993_FORMATS,  
    9.                 .sig_bits = 24,  
    10.         },  
    11.         .capture = {  
    12.                  .stream_name = "Capture",  
    13.                  .channels_min = 1,  
    14.                  .channels_max = 2,  
    15.                  .rates = WM8993_RATES,  
    16.                  .formats = WM8993_FORMATS,  
    17.                  .sig_bits = 24,  
    18.          },  
    19.         .ops = &wm8993_ops,  
    20.         .symmetric_rates = 1,  
    21. };  
    22.   
    23. static int wm8993_i2c_probe(struct i2c_client *i2c,  
    24.                             const struct i2c_device_id *id)  
    25. {  
    26.         ......  
    27.         ret = snd_soc_register_codec(&i2c->dev,  
    28.                         &soc_codec_dev_wm8993, &wm8993_dai, 1);  
    29.         ......  
    30. }  
    这回使得ASoc把codec的dai注册到系统中,并把这些dai都挂在全局链表变量dai_list中,然后,在codec被machine驱动匹配后,soc_probe_codec函数会被调用,他会通过全局链表变量dai_list查找所有属于该codec的dai,调用snd_soc_dapm_new_dai_widgets函数来生成该dai的播放流widget和录音流widget:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static int soc_probe_codec(struct snd_soc_card *card,  
    2.                            struct snd_soc_codec *codec)  
    3. {  
    4.         ......  
    5.         /* Create DAPM widgets for each DAI stream */  
    6.         list_for_each_entry(dai, &dai_list, list) {  
    7.                 if (dai->dev != codec->dev)  
    8.                         continue;  
    9.   
    10.                 snd_soc_dapm_new_dai_widgets(&codec->dapm, dai);  
    11.         }  
    12.         ......  
    13. }  
    我们看看snd_soc_dapm_new_dai_widgets的代码:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,  
    2.                                  struct snd_soc_dai *dai)  
    3. {                 
    4.         struct snd_soc_dapm_widget template;  
    5.         struct snd_soc_dapm_widget *w;  
    6.           
    7.         WARN_ON(dapm->dev != dai->dev);  
    8.           
    9.         memset(&template, 0, sizeof(template));  
    10.         template.reg = SND_SOC_NOPM;  
    11.         // 创建播放 dai widget                  
    12.         if (dai->driver->playback.stream_name) {  
    13.                 template.id = snd_soc_dapm_dai_in;  
    14.                 template.name = dai->driver->playback.stream_name;  
    15.                 template.sname = dai->driver->playback.stream_name;  
    16.                           
    17.                 w = snd_soc_dapm_new_control(dapm, &template);  
    18.           
    19.                 w->priv = dai;  
    20.                 dai->playback_widget = w;  
    21.         }  
    22.         // 创建录音 dai widget  
    23.         if (dai->driver->capture.stream_name) {  
    24.                 template.id = snd_soc_dapm_dai_out;  
    25.                 template.name = dai->driver->capture.stream_name;  
    26.                 template.sname = dai->driver->capture.stream_name;  
    27.   
    28.                 w = snd_soc_dapm_new_control(dapm, &template);  
    29.    
    30.                 w->priv = dai;  
    31.                 dai->capture_widget = w;  
    32.         }  
    33.   
    34.         return 0;  
    35. }  
    分别为Playback和Capture创建了一个widget,widget的priv字段指向了该dai,这样通过widget就可以找到相应的dai,并且widget的名字就是snd_soc_dai_driver结构的stream_name。

    cpu dai widget    
    这里顺便说一个小意外,昨天晚上手贱,执行了一下git pull,版本升级到了3.12 rc7,结果发现ASoc的代码有所变化,于是稍稍纠结了一下,用新的代码继续还是恢复之前的3.10 rc5?经过查看了一些变化后,发现还是新的版本改进得更合理,现在决定,后面的内容都是基于3.12 rc7了。如果大家发现后面贴的代码和之前贴的有差异的地方,自己比较一下这两个版本的代码吧!
    回到cpu dai,以前的内核版本由驱动通过snd_soc_register_dais注册,新的版本中,这个函数变为了soc-core的内部函数,驱动改为使用snd_soc_register_component注册,snd_soc_register_component函数再通过调用snd_soc_register_dai/snd_soc_register_dais来完成实际的注册工作。和codec dai widget一样,cpu dai widget也发生在machine驱动匹配上相应的platform驱动之后,soc_probe_platform会被调用,在soc_probe_platform函数中,通过比较dai->dev和platform->dev,挑选出属于该platform的dai,然后通过snd_soc_dapm_new_dai_widgets为cpu dai创建相应的widget:
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static int soc_probe_platform(struct snd_soc_card *card,  
    2.                            struct snd_soc_platform *platform)  
    3. {  
    4.         int ret = 0;  
    5.         const struct snd_soc_platform_driver *driver = platform->driver;  
    6.         struct snd_soc_dai *dai;  
    7.   
    8.         ......  
    9.   
    10.         if (driver->dapm_widgets)  
    11.                 snd_soc_dapm_new_controls(&platform->dapm,  
    12.                         driver->dapm_widgets, driver->num_dapm_widgets);  
    13.   
    14.         /* Create DAPM widgets for each DAI stream */  
    15.         list_for_each_entry(dai, &dai_list, list) {  
    16.                 if (dai->dev != platform->dev)  
    17.                         continue;  
    18.   
    19.                 snd_soc_dapm_new_dai_widgets(&platform->dapm, dai);  
    20.         }  
    21.   
    22.         platform->dapm.idle_bias_off = 1;  
    23.   
    24.         ......  
    25.   
    26.         if (driver->controls)  
    27.                 snd_soc_add_platform_controls(platform, driver->controls,  
    28.                                      driver->num_controls);  
    29.         if (driver->dapm_routes)  
    30.                 snd_soc_dapm_add_routes(&platform->dapm, driver->dapm_routes,  
    31.                                         driver->num_dapm_routes);  
    32.         ......  
    33.   
    34.         return 0;  
    35. }  
    从上面的代码我们也可以看出,在上面的”创建和注册widget“一节提到的第一种方法,即通过给snd_soc_platform_driver结构的dapm_widgets和num_dapm_widgets字段赋值,ASoc会自动为我们创建所需的widget,真正执行创建工作就在上面所列的soc_probe_platform函数中完成的,普通的kcontrol和音频路径也是一样的原理。反推回来,codec的widget也是一样的,在soc_probe_codec中会做同样的事情,只是我上面贴出来soc_probe_codec的代码里没有贴出来,有兴趣的读者自己查看一下它的代码即可。
    花了这么多篇幅来讲解dai widget,好像现在看来它还没有什么用处。嗯,不要着急,实际上dai widget是一条完整dapm音频路径的重要元素,没有她,我们无法完成dapm的动态电源管理工作,因为它是音频流和其他widget的纽带,细节我们要留到下一篇文章中来阐述了。

    端点widget


    一条完整的dapm音频路径,必然有起点和终点,我们把位于这些起点和终点的widget称之为端点widget。以下这些类型的widget可以成为端点widget:

    codec的输入输出引脚

    • snd_soc_dapm_output
    • snd_soc_dapm_input
    外接的音频设备
    • snd_soc_dapm_hp
    • snd_soc_dapm_spk
    • snd_soc_dapm_line
    音频流(stream domain)
    • snd_soc_dapm_adc
    • snd_soc_dapm_dac
    • snd_soc_dapm_aif_out
    • snd_soc_dapm_aif_in
    • snd_soc_dapm_dai_out
    • snd_soc_dapm_dai_in
    电源、时钟和其它
    • snd_soc_dapm_supply
    • snd_soc_dapm_regulator_supply
    • snd_soc_dapm_clock_supply
    • snd_soc_dapm_kcontrol
    当声卡上的其中一个widget的状态发生改变时,从这个widget开始,dapm框架会向前和向后遍历路径上的所有widget,判断每个widget的状态是否需要跟着变更,到达这些端点widget就会认为它是一条完整音频路径的开始和结束,从而结束一次扫描动作。至于代码的分析,先让我歇一会......,我会在后面的文章中讨论。
  • 相关阅读:
    python12306抢票
    函数、迭代器、生成器、装饰器
    类(面向对象、增删改查、继承、多态、封装、反射)
    js循环、异常、函数
    js引入、注释、事件
    天融信护网面试
    Java URL处理
    Java多线程编程
    Java网络编程
    Java序列化
  • 原文地址:https://www.cnblogs.com/Ph-one/p/6297537.html
Copyright © 2011-2022 走看看