Linux内核4.14版本——alsa框架分析(14)——DAPM(5)——注册widget、route、path

目录

1. dapm context

2. 创建和注册widget

2.1 codec驱动中注册

2.1.1 snd_soc_codec_driver定义静态数组

2.1.2 也可以在struct snd_soc_codec_driver的probe函数中注册

2.2 platform驱动中注册

2.3 machine驱动中注册 

3. 注册音频路径(route)

4. route怎么转换为path

5. dai widget

5.1 codec dai widget    

5.2 cpu dai widget  

6. 端点widget

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

如何注册widget
如何连接两个widget
一个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:

/* DAPM context */
struct snd_soc_dapm_context {
	enum snd_soc_bias_level bias_level;
	unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
	/* Go to BIAS_OFF in suspend if the DAPM context is idle */
	unsigned int suspend_bias_off:1;
	void (*seq_notifier)(struct snd_soc_dapm_context *,
			     enum snd_soc_dapm_type, int);

	struct device *dev; /* from parent - for debug */
	struct snd_soc_component *component; /* parent component */
	struct snd_soc_card *card; /* parent card */

	/* used during DAPM updates */
	enum snd_soc_bias_level target_bias_level;
	struct list_head list;

	int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);
	int (*set_bias_level)(struct snd_soc_dapm_context *dapm,
			      enum snd_soc_bias_level level);

	struct snd_soc_dapm_wcache path_sink_cache;
	struct snd_soc_dapm_wcache path_source_cache;

#ifdef CONFIG_DEBUG_FS
	struct dentry *debugfs_dapm;
#endif
};

      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的结构体中:

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

在新版本的kernel中被内嵌到各个component中。

/* SoC Audio Codec device */
struct snd_soc_codec {
	......
	/* component */
	struct snd_soc_component component;
};


/* SoC card */
struct snd_soc_card {
	.......

	/* lists of probed devices belonging to this card */
	struct list_head component_dev_list;

	struct list_head widgets;
	struct list_head paths;
	struct list_head dapm_list;
	struct list_head dapm_dirty;

	/* attached dynamic objects */
	struct list_head dobj_list;

	/* Generic DAPM context for the card */
	struct snd_soc_dapm_context dapm;
	......
};

struct snd_soc_platform {
	......
	struct snd_soc_component component;
};

/*
 * Digital Audio Interface runtime data.
 *
 * Holds runtime data for a DAI.
 */
struct snd_soc_dai {
	.......
	struct snd_soc_dapm_widget *playback_widget;
	struct snd_soc_dapm_widget *capture_widget;
	........
	/* parent platform/codec */
	struct snd_soc_codec *codec;
	struct snd_soc_component *component;
	.......
};

struct snd_soc_component {
	.......
	/*
	* DO NOT use any of the fields below in drivers, they are temporary and
	* are going to be removed again soon. If you use them in driver code the
	* driver will be marked as BROKEN when these fields are removed.
	*/

	/* Don't use these, use snd_soc_component_get_dapm() */
	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链表头字段。

2. 创建和注册widget

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

2.1 codec驱动中注册

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

struct snd_soc_codec_driver {
        ......        
	    struct snd_soc_component_driver component_driver;
        ......
}

/* component interface */
struct snd_soc_component_driver {
	const char *name;

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

2.1.1 snd_soc_codec_driver定义静态数组

      我们只要把我们定义好的snd_soc_dapm_widget结构数组的地址和widget的数量赋值到dapm_widgets和num_dapm_widgets字段即可。

static const struct snd_soc_codec_driver soc_codec_dev_es8316 = {
	.probe		= es8316_probe,
	.idle_bias_off	= true,

	.component_driver = {
		.controls		= es8316_snd_controls,
		.num_controls		= ARRAY_SIZE(es8316_snd_controls),
		.dapm_widgets		= es8316_dapm_widgets,
		.num_dapm_widgets	= ARRAY_SIZE(es8316_dapm_widgets),
		.dapm_routes		= es8316_dapm_routes,
		.num_dapm_routes	= ARRAY_SIZE(es8316_dapm_routes),
	},
};

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

/* Common DAPM widgets */
static const struct snd_soc_dapm_widget uda134x_dapm_widgets[] = {
	SND_SOC_DAPM_INPUT("VINL1"),
	SND_SOC_DAPM_INPUT("VINR1"),
	SND_SOC_DAPM_INPUT("VINL2"),
	SND_SOC_DAPM_INPUT("VINR2"),
	SND_SOC_DAPM_OUTPUT("VOUTL"),
	SND_SOC_DAPM_OUTPUT("VOUTR"),
};
 
static const struct snd_soc_codec_driver soc_codec_dev_uda134x = {
	.probe =        uda134x_soc_probe,
	.set_bias_level = uda134x_set_bias_level,
	.suspend_bias_off = true,

	.component_driver = {
		.dapm_widgets		= uda134x_dapm_widgets,
		.num_dapm_widgets	= ARRAY_SIZE(uda134x_dapm_widgets),
		.dapm_routes		= uda134x_dapm_routes,
		.num_dapm_routes	= ARRAY_SIZE(uda134x_dapm_routes),
	},
}; 
 
static int uda134x_codec_probe(struct platform_device *pdev)
{
    .............
	return snd_soc_register_codec(&pdev->dev,
			&soc_codec_dev_uda134x, &uda134x_dai, 1);
}

      最终分析codec有关的静态widgets被放在了struct snd_soc_codec的codec_dai->component->driver里面。前面kcontrol已经介绍过了。

2.1.2 也可以在struct snd_soc_codec_driver的probe函数中注册

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

static int wm8993_probe(struct snd_soc_codec *codec)
{
        ......
        snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,
                                  ARRAY_SIZE(wm8993_dapm_widgets));
        ......
}

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

static int uda134x_soc_probe(struct snd_soc_codec *codec)	
{
    ret = snd_soc_dapm_new_controls(dapm, widgets, num_widgets);
	if (ret) {
		printk(KERN_ERR "%s failed to register dapm controls: %d",
			__func__, ret);
		return ret;
	}
    ................
}

2.2 platform驱动中注册

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

struct snd_soc_component_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字段即可,devm_snd_soc_register_component函数注册paltform后,当machine驱动匹配上该platform时,系统会自动完成创建和注册的工作。同理,我们也可以在platform驱动的probe回调函数中主动使用snd_soc_dapm_new_controls来完成widget的创建工作。具体的代码和codec驱动是类似的,这里就不贴了。

2.3 machine驱动中注册 

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

struct snd_soc_card {
        ......
	/*
	 * Card-specific routes and widgets.
	 * Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in.
	 */
	const struct snd_soc_dapm_widget *dapm_widgets;
	int num_dapm_widgets;
	const struct snd_soc_dapm_route *dapm_routes;
	int num_dapm_routes;
	const struct snd_soc_dapm_widget *of_dapm_widgets;
	int num_of_dapm_widgets;
	const struct snd_soc_dapm_route *of_dapm_routes;
	int num_of_dapm_routes;
	bool fully_routed;
        ......
}

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

soc_probe_component
	snd_soc_dapm_new_controls
		snd_soc_dapm_new_control_unlocked
	snd_soc_dapm_new_dai_widgets
		snd_soc_dapm_new_control_unlocked
		
soc_probe_link_dais
	soc_link_dai_widgets
		snd_soc_dapm_new_pcm
			snd_soc_dapm_new_control_unlocked

snd_soc_dapm_new_widgets

3. 注册音频路径(route)

      系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过snd_soc_dapm_route结构来定义,关于如何用snd_soc_dapm_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函数来完成音频路径的注册工作的。以下的代码片段是文件rt5651.c,使用第一种方法注册路径信息:

static const struct snd_soc_dapm_route rt5651_dapm_routes[] = {
	......
	{"IN1P", NULL, "LDO"},
	{"RECMIXL", "BST1 Switch", "BST1"},
	{"Stereo1 ADC L1 Mux", "DD MIX", "DD MIXL"},
	......
}
static struct snd_soc_codec_driver soc_codec_dev_rt5651 = {
	.dapm_routes = rt5651_dapm_routes,
}
snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5651,rt5651_dai, ARRAY_SIZE(rt5651_dai));
	/*machin之后最终导致该函数被调用*/
	static int snd_soc_instantiate_card(struct snd_soc_card *card)
		soc_probe_link_components(card, i, order);
			soc_probe_component(card, component);
				snd_soc_dapm_add_routes(dapm, component->dapm_routes,component->num_dapm_routes);
					snd_soc_dapm_add_route(dapm, route);	

      在继续分析snd_soc_dapm_add_route函数之前,我们先猜测一下。
      大家肯定注意到了很多的snd_soc_dapm_route,都使用了NULL,如:

	{"IN1P", NULL, "LDO"},

      如果不是为NULL的

	{"RECMIXR", "INR1 Switch", "INR1 VOL"},//MIX类型
	{"Stereo1 ADC L1 Mux", "DD MIX", "DD MIXL"},//MUX类型

      实际只有两种类型,及MIX类型与MUX类型(从命名我们可以看出)。左边的为sink widget,中间为kcontrol(如果不为NULL),右边为soure widget。

      其中,通过上小节的学习我们知道kcontrol分为3种:
      1.sink,NULL,soure:其表示他们肯定是连接在一起的,由他们构造出来的path,其成员connect一直为1。
      2.sink widget为mixer,如:

	{"RECMIXR", "INR1 Switch", "INR1 VOL"},
	{"RECMIXR", "BST3 Switch", "BST3"},
	{"RECMIXR", "BST2 Switch", "BST2"},
	{"RECMIXR", "BST1 Switch", "BST1"},//MIX类型

其描述如下

      可以看到这四个ptch他们的source是不一样的,但是他们的sink是一样的,都为RECMIL。从上我们知道一共需要4个kcontrol,四个为通道选择的kcontrol,分别对应寄存器MXC的3,2,1,5,位。并且这些kcontrol还会对RECMXL的寄存器MX3B进行设定。他们共同组成一个mixer widger。
      3.sink widget为为MUX,即如sink为mux widget类型:

       其中MUX widget包含了MUX本身信息,以及一个snd_kcontrol,我们设置snd_kcontrol为不同的值时,其导通不同的ptch。

      从上面我们总结出了3中route,那么他是怎么转换真path的呢?

4. route怎么转换为path

我们现在继续分析snd_soc_dapm_add_route(dapm, route)函数:

snd_soc_dapm_add_route
	snd_soc_dapm_add_path
		widgets[SND_SOC_DAPM_DIR_IN] = wsource;
		widgets[SND_SOC_DAPM_DIR_OUT] = wsink;
		path->connected = connected;
		
		/* connect static paths */
		if (control == NULL) {
			path->connect = 1;
		} else {

我们先做个简单的总结:

route{"sink","kcontrol","source"}

转化为path的过程:

1.找到sink,kcontrol,source
2.构造path。
3.如果需要设置connect则设置connect。

      我们称kcontrol为NULL的path称为static path。如果kcontrol不为NULL则称为动态path。对于动态路径:

static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,const char *control,int (*connected)(struct snd_soc_dapm_widget *source,struct snd_soc_dapm_widget *sink))
	if (control == NULL) { //静态路径
		path->connect = 1;
	}
	else
	{
		//根据不同的id去设置相应的状态,通过读取寄存器,再设置connect的状态
		switch (wsource->id) {
			case snd_soc_dapm_demux:
				ret = dapm_connect_mux(dapm, path, control, wsource);
				if (ret)
					goto err;
				break;
			default:
				break;
			}
	
			switch (wsink->id) {
			case snd_soc_dapm_mux:
				ret = dapm_connect_mux(dapm, path, control, wsink);
				if (ret != 0)
					goto err;
				break;
			case snd_soc_dapm_switch:
			case snd_soc_dapm_mixer:
			case snd_soc_dapm_mixer_named_ctl:
				ret = dapm_connect_mixer(dapm, path, control);
				if (ret != 0)
					goto err;
				break;
			default:
				break;
			}
	}

其会根据寄存器实际的值,然后设置connect的值,如dapm_connect_mixer函数:

/* connect mixer widget to its interconnecting audio paths */
static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,struct snd_soc_dapm_path *path, const char *control_name)
	dapm_set_mixer_path_status(path, i);
		soc_dapm_read(p->sink->dapm, reg, &val);
			p->connect = !!val;

我们再次回到snd_soc_dapm_add_path函数:

static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,const char *control,int (*connected)(struct snd_soc_dapm_widget *source,struct snd_soc_dapm_widget *sink))
	/*把path放入链表*/
	list_add(&path->list, &dapm->card->paths);
	list_add(&path->list_node[dir], &widgets[dir]->edges[dir]);

      添加到里面去,就可以通过dapm->card->paths查询所有的path了,其最终会形成多条complite path,这个在后续为大家讲解。

      从上面的分析,我们知道snd_soc_dapm_add_route其没有设置kcontrol,其设置是在snd_soc_dapm_new_control_unlocked函数中,把widget添加到dapm->card->widgets,然后在通过dapm_create_or_share_kcontrol把widget中的kcontrol添加到card->controls之中,

5. dai widget

      上面几节的内容介绍了codec、platform以及machine级别的widget和route的注册方法,在dapm框架中,还有另外一种widget,它代表了一个dai(数字音频接口),关于dai的描述,请参考前面的文章。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是何时创建的呢?下面我们逐一进行分析,新的版本中所有的widget注册到驱动时,都变成了struct snd_soc_component这点要注意

5.1 codec dai widget    

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

static struct snd_soc_dai_driver wm8993_dai = {
        .name = "wm8993-hifi",
        .playback = {
                .stream_name = "Playback",
                .channels_min = 1,
                .channels_max = 2,
                .rates = WM8993_RATES,
                .formats = WM8993_FORMATS,
                .sig_bits = 24,
        },
        .capture = {
                 .stream_name = "Capture",
                 .channels_min = 1,
                 .channels_max = 2,
                 .rates = WM8993_RATES,
                 .formats = WM8993_FORMATS,
                 .sig_bits = 24,
         },
        .ops = &wm8993_ops,
        .symmetric_rates = 1,
};
 
static int wm8993_i2c_probe(struct i2c_client *i2c,
                            const struct i2c_device_id *id)
{
        ......
        ret = snd_soc_register_codec(&i2c->dev,
                        &soc_codec_dev_wm8993, &wm8993_dai, 1);
        ......
}

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

	/* probe all components used by DAI links on this card */
	for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
			order++) {
		list_for_each_entry(rtd, &card->rtd_list, list) {
			ret = soc_probe_link_components(card, rtd, order);
			if (ret < 0) {
				dev_err(card->dev,
					"ASoC: failed to instantiate card %d\n",
					ret);
				goto probe_dai_err;
			}
		}
	}

// 最终会调用
	list_for_each_entry(dai, &component->dai_list, list) {
		ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
		if (ret != 0) {
			dev_err(component->dev,
				"Failed to create DAI widgets %d\n", ret);
			goto err_probe;
		}
	}

      我们看看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;

	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_unlocked(dapm, &template);
		if (IS_ERR(w)) {
			int ret = PTR_ERR(w);

			/* Do not nag about probe deferrals */
			if (ret != -EPROBE_DEFER)
				dev_err(dapm->dev,
				"ASoC: Failed to create %s widget (%d)\n",
				dai->driver->playback.stream_name, ret);
			return ret;
		}
		if (!w) {
			dev_err(dapm->dev, "ASoC: Failed to create %s widget\n",
				dai->driver->playback.stream_name);
			return -ENOMEM;
		}

		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_unlocked(dapm, &template);
		if (IS_ERR(w)) {
			int ret = PTR_ERR(w);

			/* Do not nag about probe deferrals */
			if (ret != -EPROBE_DEFER)
				dev_err(dapm->dev,
				"ASoC: Failed to create %s widget (%d)\n",
				dai->driver->playback.stream_name, ret);
			return ret;
		}
		if (!w) {
			dev_err(dapm->dev, "ASoC: Failed to create %s widget\n",
				dai->driver->capture.stream_name);
			return -ENOMEM;
		}

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

	return 0;
}

      分别为Playback和Capture创建了一个widget,widget的priv字段指向了该dai,这样通过widget就可以找到相应的dai,并且widget的名字就是 snd_soc_dai_driver结构的stream_name。

5.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_link_components函数会被调用,他会通过全局链表变量dai_list查找所有属于该codec的dai。

      从上面的代码我们也可以看出,在上面的”创建和注册widget“一节提到的第一种方法,即通过给snd_soc_platform_driver结构的dapm_widgets和num_dapm_widgets字段赋值,ASoc会自动为我们创建所需的widget,最终都添加到components链表中,最终创建他。
       花了这么多篇幅来讲解dai widget,好像现在看来它还没有什么用处。嗯,不要着急,实际上dai widget是一条完整dapm音频路径的重要元素,没有她,我们无法完成dapm的动态电源管理工作,因为它是音频流和其他widget的纽带,细节我们要留到下一篇文章中来阐述了。

6. 端点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就会认为它是一条完整音频路径的开始和结束,从而结束一次扫描动作。至于代码的分析,先让我歇一会......,我会在后面的文章中讨论。
 

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ALSA(Advanced Linux Sound Architecture)是Linux内核中的音频驱动框架,它提供了一套完整的音频处理流程,包括音频采集、音频处理和音频播放等功能。ALSA框架的设计目标是提供一个高效、灵活、可靠的音频处理框架,让开发人员能够方便地开发音频应用程序。 ALSA框架的核心包括以下几个组件: 1. 驱动程序:驱动程序是ALSA框架的核心组件,负责管理音频设备硬件,并提供音频数据输入输出的接口。ALSA驱动程序一般由硬件厂商或开源社区开发,可以通过内核模块的形式加载到Linux内核中。 2. 应用程序接口:ALSA框架提供了一套完整的应用程序接口,包括ALSA库和ALSA命令行工具。ALSA库提供了一组API,让开发人员能够方便地访问ALSA驱动程序提供的音频数据输入输出接口。ALSA命令行工具则提供了一组命令行工具,让用户能够方便地对音频设备进行配置和管理。 3. 中间件:ALSA框架还提供了一些中间件组件,如MIDI子系统、混音器子系统等,用于提供更高级的音频处理功能。 ALSA框架的音频处理流程如下: 1. 音频采集:当音频设备接收到音频信号时,ALSA驱动程序将音频信号采集到内存中,并通过DMA(直接内存访问)将音频数据写入音频缓冲区。 2. 音频处理:ALSA驱动程序将音频信号从音频缓冲区读取到内存中,然后对音频数据进行处理。音频处理包括音频格式转换、音频采样率转换、音频混音等处理。 3. 音频播放:ALSA驱动程序将处理后的音频数据从内存中读取到音频缓冲区,并通过DMA将音频数据传输到音频设备中进行播放。 总之,ALSA框架提供了一套完整的音频处理流程,让开发人员能够方便地开发音频应用程序,并提供了一组API和命令行工具,方便用户对音频设备进行配置和管理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值