ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

本文深入探讨了ALSA(Advanced Linux Sound Architecture)驱动中的DAPM(Dynamic Audio Power Management)机制,特别是如何在驱动程序中初始化和注册widget以及建立route。内容包括 dapm context 的概念,解释了电源域的组织方式,以及如何通过snd_soc_dapm_widget结构体创建和注册widget。文章以codec、platform和CPU DAI为例,展示了在不同驱动中注册widget的过程,并指出端点widget在音频路径中的重要性。
摘要由CSDN通过智能技术生成

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

1.如何注册widget
2.如何连接两个widget
3.一个widget的状态裱画如何传递到整个音频路径中


(1)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:

snd_soc_bias_level的取值范围是以下几种:
/*                                                                                                                                      
 * Bias levels                                                                                                                          
 *                                                                                                                                      
 * @ON:      Bias is fully on for audio playback and capture operations.                                                                
 * @PREPARE: Prepare for audio operations. Called before DAPM switching for                                                             
 *           stream start and stop operations.                                                                                          
 * @STANDBY: Low power standby state when no playback/capture operations are                                                            
 *           in progress. NOTE: The transition time between STANDBY and ON                                                              
 *           should be as fast as possible and no longer than 10ms.                                                                     
 * @OFF:     Power Off. No restrictions on transition times.                                                                            
 */                                                                                                                                     
enum snd_soc_bias_level {                                                                                                               
    SND_SOC_BIAS_OFF = 0,                                                                                                               
    SND_SOC_BIAS_STANDBY = 1,                                                                                                           
    SND_SOC_BIAS_PREPARE = 2,                                                                                                           
    SND_SOC_BIAS_ON = 3,                                                                                                                
};  
snd_soc_dapm_context被内嵌到代表codec、platform、card、dai的结构体中:

struct snd_soc_codec {
        ......
        /* dapm */
        struct snd_soc_dapm_context dapm;
        ......
};
 
struct snd_soc_platform {
        ......
        /* dapm */
        struct snd_soc_dapm_context dapm;
        ......
};
 
struct snd_soc_card {
        ......
        /* dapm */
        struct snd_soc_dapm_context dapm;
        ......
};
:
struct snd_soc_dai {
        ......
        /* dapm */
        struct snd_soc_dapm_widget *playback_widget;
        struct snd_soc_dapm_widget *capture_widget;
        struct snd_soc_dapm_context dapm;
        ......
};


代表widget结构snd_soc_dapm_widget中,有一个snd_soc_dapm_context结构指针,指向所属的codec、platform、card、或dai的dapm结构。同时,所有的dapm结构,通过它的list字段,链接到代表声卡的snd_soc_card结构的dapm_list链表头字段

/* dapm widget */                                                   
struct snd_soc_dapm_widget {                                                                                 
    struct list_head list;          
    struct snd_soc_dapm_context *dapm;
}

(2)创建和注册widget


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


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

struct snd_soc_codec_driver {
        ......        
        /* Default control and setup, added after probe() is run */
        const struct snd_kcontrol_new *controls;
        int num_controls;
        const struct snd_soc_dapm_widget *dapm_widgets;
        int num_dapm_widgets;
        const struct snd_soc_dapm_route *dapm_routes;
        int num_dapm_routes;
        ......
}


我们只要把我们定义好的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。我们看看实际的例子可能是这样的:


    
static const struct snd_soc_dapm_widget icdc_d3_dapm_widgets[] = {
/* ADC */
    SND_SOC_DAPM_ADC("ADC", "Capture" , SCODA_REG_AICR_ADC, 4, 1),
    SND_SOC_DAPM_MUX("ADC Mux", SCODA_REG_CR_ADC, 4, 1, &icdc_d3_adc_controls),                        /*0*/
    SND_SOC_DAPM_MICBIAS("MICBIAS", SCODA_REG_CR_MIC1, 5, 1),
    SND_SOC_DAPM_PGA("AMIC", SCODA_REG_CR_MIC1, 4, 1, NULL, 0),
    SND_SOC_DAPM_PGA("DMIC", SCODA_REG_CR_DMIC, 7, 0, NULL, 0),

    SND_SOC_DAPM_DAC("DAC", "Playback", SCODA_REG_AICR_DAC, 4, 1),
    SND_SOC_DAPM_PGA("DAC_MERCURY", SCODA_REG_CR_DAC, 4, 1, NULL, 0),

    SND_SOC_DAPM_SWITCH_E("VDIGITAL BYPASS", SND_SOC_NOPM, 0, 0, &icdc_d3_vdigital_bypass_controls,
            icdc_d3_vdigital_bypass_controls_event,
            SND_SOC_DAPM_POST_PMD|SND_SOC_DAPM_PRE_PMU),
    
/* PINS */
    SND_SOC_DAPM_INPUT("AIP"),
    SND_SOC_DAPM_INPUT("AIN"),
    SND_SOC_DAPM_INPUT("DMIC IN"),
    SND_SOC_DAPM_OUTPUT("DO_LO_PWM"),
    SND_SOC_DAPM_OUTPUT("DO_BO_PWM"),
};


static struct snd_soc_codec_driver soc_codec_dev_icdc_d3_codec = {                                                                                                                                                       
    .dapm_widgets = icdc_d3_dapm_widgets,                                                                                               
    .num_dapm_widgets = ARRAY_SIZE(icdc_d3_dapm_widgets),                                                                               
                                                                                                       
                                                                                                      
}; 

static int icdc_d3_platform_probe(struct platform_device *pdev){

ret = snd_soc_register_codec(&pdev->dev,
                                 &soc_codec_dev_icdc_d3_codec, &icdc_d3_codec_dai, 1);

}


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

sound/soc/ingenic/asoc-board/phoenix_icdc.c
static int phoenix_dlv_dai_link_init(struct snd_soc_pcm_runtime *rtd)
{
err = snd_soc_dapm_new_controls(dapm, phoenix_dapm_widgets,
            ARRAY_SIZE(phoenix_dapm_widgets));


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

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


struct snd_soc_platform_driver {
        ......        
        /* Default control and setup, added after probe() is run */
        const struct snd_kcontrol_new *controls;
        int num_controls;
        const struct snd_soc_dapm_widget *dapm_widgets;
        int num_dapm_widgets;
        const struct snd_soc_dapm_route *dapm_routes;
        int num_dapm_routes;
        ......
}


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


struct snd_soc_card {
        ......
        /*
         * Card-specific routes and widgets.
         */
        const struct snd_soc_dapm_widget *dapm_widgets;
        int num_dapm_widgets;
        const struct snd_soc_dapm_route *dapm_routes;
        int num_dapm_routes;
        bool fully_routed;
        ......
}


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

(3)注册音频路径
系统中注册的各种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函数来完成音频路径的注册工作的。machine驱动,使用第二种方法注册路径信息:


static struct snd_soc_card phoenix = {
    .name = "phoenix",
    .owner = THIS_MODULE,
    .dai_link = phoenix_dais,
    .num_links = ARRAY_SIZE(phoenix_dais),
};

static int snd_phoenix_probe(struct platform_device *pdev)
{
    int ret = 0;
    phoenix.dev = &pdev->dev;
    codec_platform_data = (struct snd_codec_data *)phoenix.dev->platform_data;
    ret = snd_soc_register_card(&phoenix);
    if (ret)
        dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret);
    return ret;
}

static struct snd_soc_dai_link phoenix_dais[] = {
    [0] = {
        .name = "phoenix ICDC",
        .stream_name = "phoenix ICDC",
        .platform_name = "jz-asoc-aic-dma",
        .cpu_dai_name = "jz-asoc-aic-i2s",
        .init = phoenix_dlv_dai_link_init,
        .codec_dai_name = "icdc-d3-hifi",
        .codec_name = "icdc-d3",
        .ops = &phoenix_i2s_ops,
    },
}

static int phoenix_dlv_dai_link_init(struct snd_soc_pcm_runtime *rtd)
{
  err = snd_soc_dapm_new_controls(dapm, phoenix_dapm_widgets,
            ARRAY_SIZE(phoenix_dapm_widgets));
   
    /* Set up rx1950 specific audio path audio_mapnects */
    err = snd_soc_dapm_add_routes(dapm, audio_map,
            ARRAY_SIZE(audio_map));


}


static const struct snd_soc_dapm_widget phoenix_dapm_widgets[] = {
    SND_SOC_DAPM_HP("Headphone Jack", NULL),
    SND_SOC_DAPM_SPK("Speaker", phoenix_spk_power),
    SND_SOC_DAPM_MIC("Mic Buildin", NULL),
    SND_SOC_DAPM_MIC("DMic", NULL),
};


/* phoenix machine audio_map */
static const struct snd_soc_dapm_route audio_map[] = {
    /* ext speaker connected to DO_LO_PWM  */
    {"Speaker", NULL, "DO_LO_PWM"},

    /* mic is connected to AIP/N1 */
    {"MICBIAS", NULL, "Mic Buildin"},
    {"DMIC", NULL, "DMic"},

};

(4)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框架有关:


struct snd_soc_dai {
        ......
        struct snd_soc_dapm_widget *playback_widget;
        struct snd_soc_dapm_widget *capture_widget;
        struct snd_soc_dapm_context dapm;
        ......
}


dai由codec驱动和平台代码中的iis或pcm接口驱动注册,machine驱动负责找到snd_soc_dai_link中指定的一对cpu/codec dai,并把它们进行绑定。不管是cpu dai还是codec dai,通常会同时传输播放和录音的音频流的能力,所以我们可以看到,snd_soc_dai中有两个widget指针,分别代表播放流和录音流。这两个dai widget是何时创建的呢?下面我们逐一进行分析。


(4.1)codec dai widget    

首先,codec驱动在注册codec时,会传入该codec所支持的dai个数和记录dai信息的snd_soc_dai_driver结构指针
sound/soc/ingenic/icodec/icdc_d3.c 
static struct snd_soc_dai_driver  icdc_d3_codec_dai = {                                                                                 
    .name = "icdc-d3-hifi",                                                                                                             
    .playback = {                                                                                                                       
        .stream_name = "Playback",                                                                                                      
        .channels_min = 1,                                                                                                              
        .channels_max = 2,                                                                                                              
#if defined(CONFIG_SOC_4780)                                                                                                            
        .rates = SNDRV_PCM_RATE_8000_96000,                                                                                             
#else                                                                                                                                   
        .rates = SNDRV_PCM_RATE_8000_192000,                                                                                            
#endif                                                                                                                                  
        .formats = DLV4780_FORMATS,                                                                                                     
    },                                                                                                                                  
    .capture = {                                                                                                                        
        .stream_name = "Capture",                                                                                                       
        .channels_min = 1,                                                                                                              
        .channels_max = 2,                                                                                                              
#if defined(CONFIG_SOC_4780)                                                                                                            
        .rates = SNDRV_PCM_RATE_8000_96000,                                                                                             
#else                                                                                                                                   
        .rates = SNDRV_PCM_RATE_8000_192000,                                                                                            
#endif                                                                                                                                  
        .formats = DLV4780_FORMATS,                                                                                                     
    },                                                                                                                                  
    .ops = &icdc_d3_dai_ops,                                                                                                            
}; 

这回使得ASoc把codec的dai注册到系统中,并把这些dai都挂在全局链表变量dai_list中,然后,在codec被machine驱动匹配后,soc_probe_codec函数会被调用,他会通过全局链表变量dai_list查找所有属于该codec的dai,调用snd_soc_dapm_new_dai_widgets函数来生成该dai的播放流widget和录音流widget:


static int icdc_d3_platform_probe(struct platform_device *pdev)
{

ret = snd_soc_register_codec(&pdev->dev,
                                 &soc_codec_dev_icdc_d3_codec, &icdc_d3_codec_dai, 1);

}


static int soc_probe_codec(struct snd_soc_card *card,
               struct snd_soc_codec *codec)
{
    /* Create DAPM widgets for each DAI stream */
    list_for_each_entry(dai, &dai_list, list) {
        if (dai->dev != codec->dev)
            continue;

        snd_soc_dapm_new_dai_widgets(&codec->dapm, dai);
    }  

}

我们看看snd_soc_dapm_new_dai_widgets的代码:
int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
                 struct snd_soc_dai *dai)
{
    struct snd_soc_dapm_widget template;
    struct snd_soc_dapm_widget *w;

    WARN_ON(dapm->dev != dai->dev);

    memset(&template, 0, sizeof(template));
    template.reg = SND_SOC_NOPM;

        // 创建播放 dai widget                
    if (dai->driver->playback.stream_name) {
        template.id = snd_soc_dapm_dai_in;
        template.name = dai->driver->playback.stream_name;
        template.sname = dai->driver->playback.stream_name;
            
        dev_dbg(dai->dev, "ASoC: adding %s widget\n",
            template.name);
        
        w = snd_soc_dapm_new_control(dapm, &template);
        if (!w) {
            dev_err(dapm->dev, "ASoC: Failed to create %s widget\n",
                dai->driver->playback.stream_name);
        }

        w->priv = dai;
        dai->playback_widget = w;
    }

        // 创建录音 dai widget
    if (dai->driver->capture.stream_name) {
        template.id = snd_soc_dapm_dai_out;
        template.name = dai->driver->capture.stream_name;
        template.sname = dai->driver->capture.stream_name;

dev_dbg(dai->dev, "ASoC: adding %s widget\n",
            template.name);
        
        w = snd_soc_dapm_new_control(dapm, &template);
        if (!w) {
            dev_err(dapm->dev, "ASoC: Failed to create %s widget\n",
                dai->driver->playback.stream_name);
        }

        w->priv = dai;
        dai->playback_widget = w;
    }

    if (dai->driver->capture.stream_name) {
        template.id = snd_soc_dapm_dai_out;
        template.name = dai->driver->capture.stream_name;
        template.sname = dai->driver->capture.stream_name;

        dev_dbg(dai->dev, "ASoC: adding %s widget\n",
            template.name);

        w = snd_soc_dapm_new_control(dapm, &template);
        if (!w) {
            dev_err(dapm->dev, "ASoC: Failed to create %s widget\n",
                dai->driver->capture.stream_name);
        }

        w->priv = dai;
        dai->capture_widget = w;
    }

    return 0;
}


(4.2)cpu dai widget    

回到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:


static struct snd_soc_dai_ops jz_i2s_dai_ops = {
    .startup    = jz_i2s_startup,
    .trigger    = jz_i2s_trigger,                                                                                                       
    .hw_params  = jz_i2s_hw_params,                                                                                                     
    .shutdown   = jz_i2s_shutdown,                                                                                                      
    .set_fmt    = jz_set_dai_fmt,                                                                                                       
    .set_sysclk = jz_set_sysclk,                                                                                                        
    .set_clkdiv = jz_set_clkdiv,                                                                                                        
};                                                                                                                                      
                                                                                                                                        
#define jz_i2s_suspend  NULL                                                                                                            
#define jz_i2s_resume   NULL                                                                                                            
static struct snd_soc_dai_driver jz_i2s_dai = {                                                                                         
        .probe   = jz_i2s_probe,                                                                                                        
        .suspend = jz_i2s_suspend,                                                                                                      
        .resume  = jz_i2s_resume,                                                                                                       
        .playback = {                                                                                                                   
            .channels_min = 1,                                                                                                          
            .channels_max = 2,                                                                                                          
            .rates = JZ_I2S_RATE,                                                                                                       
            .formats = JZ_I2S_FORMATS,                                                                                                  
        },                                                                                                                              
        .capture = {                                                                                                                    
            .channels_min = 2,                                                                                                          
            .channels_max = 2,                                                                                                          
            .rates = JZ_I2S_RATE,                                                                                                       
            .formats = JZ_I2S_FORMATS,                                                                                                  
        },                                                                                                                              
        .ops = &jz_i2s_dai_ops,                                                                                                         
};                                                                                                                                      
    

sound/soc/ingenic/asoc-v13/asoc-i2s-v13.c
static int jz_i2s_platfrom_probe(struct platform_device *pdev)
{
   ret = snd_soc_register_component(&pdev->dev, &jz_i2s_component,
                     &jz_i2s_dai, 1);

}


sound/soc/soc-core.c
static int soc_probe_platform(struct snd_soc_card *card,
                           struct snd_soc_platform *platform)
{
        int ret = 0;
        const struct snd_soc_platform_driver *driver = platform->driver;
        struct snd_soc_dai *dai;
 
        ......
 
        if (driver->dapm_widgets)
                snd_soc_dapm_new_controls(&platform->dapm,
                        driver->dapm_widgets, driver->num_dapm_widgets);
 
        /* Create DAPM widgets for each DAI stream */
        list_for_each_entry(dai, &dai_list, list) {
                if (dai->dev != platform->dev)
                        continue;
 
                snd_soc_dapm_new_dai_widgets(&platform->dapm, dai);
        }
 
        platform->dapm.idle_bias_off = 1;
 
        ......
 
        if (driver->controls)
                snd_soc_add_platform_controls(platform, driver->controls,
                                     driver->num_controls);
        if (driver->dapm_routes)
                snd_soc_dapm_add_routes(&platform->dapm, driver->dapm_routes,
                                        driver->num_dapm_routes);
        ......
 
        return 0;
}


从上面的代码我们也可以看出,在上面的”创建和注册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的纽带,细节我们要留到下一篇文章中来阐述了。


(5)端点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就会认为它是一条完整音频路径的开始和结束,从而结束一次扫描动作。

转至:http://blog.csdn.net/droidphone

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值