DAPM之五:dapm机制深入分析(上)

哲学里有三个终极命题:你是谁?从何处来?到何处去?而对于dapm机制,我们可以这样提问:dapm是什么,起到何作用?dapm是如何建立的?dapm又是如何触发的?下面会就这三个问题进行分析。

首先是dapm是什么?这在DAPM之一:概述中提及了,就是音频电源动态管理。相信电源管理大家都不会陌生。dapm设计的目的就是只有需要时才打开必要的部件(widget),不需要时则关闭部件,达到省电的目的,这在便携设备中非常重要。这里“需要”的意思是音频播放录音等操作,“部件”的意思是ADC、DAC、PGA、、MIXER、SPK等等。除此之外,dapm还肩负一个重要角色,就是音频路径,关于这,相信在DAPM之二:audio paths与dapm kcontrol已经描述得足够详细了。因此,这篇仅对电源管理这块做一些分析。

我们先分析dapm模块(源码soc-dapm.c)中的一些重要函数:


dapm_update_bits

更新widget中的reg的值,一般可以认为控制widget的开关。


snd_soc_update_bits

更新指定寄存器reg、指定位mask的值。


dapm_set_pga

这个函数会在dapm触发时调用,主要是为了减少通电/断电PGA产生的POP音。如下详细分析了这个函数:

/* ramps the volume up or down to minimise pops before or after a
 * DAPM power event */
static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power)
{
	//取得该widget的kcontrol,这个kcontrol应该是用于调节PGA的音量的,请留意这点。
	//对于PGA widget,一般是单路输入,单路输出,带gain调整。
	const struct snd_kcontrol_new *k = widget->kcontrols; 

	//检查该widget是否为muted,从下面看来,当widget power up时,置muted=0,反之置1
	if (widget->muted && !power)
		return 0;
	if (!widget->muted && power)
		return 0;

	if (widget->num_kcontrols && k) {
		//以下操作取得kcontrol的reg、shift和mask等,用于调整该PGA widget的增益gain。
		struct soc_mixer_control *mc =
			(struct soc_mixer_control *)k->private_value;
		unsigned int reg = mc->reg;
		unsigned int shift = mc->shift;
		int max = mc->max;
		unsigned int mask = (1 << fls(max)) - 1;
		unsigned int invert = mc->invert;

		if (power) {
			int i;
			/* power up has happended, increase volume to last level */
			//power up widget,则将保存值恢复到widget。注意:这是一个逐步的过程,
			//从0至保存值saved_value逐步恢复,我想是这是为了减少POP。
			if (invert) {
				for (i = max; i > widget->saved_value; i--)
					snd_soc_update_bits(widget->codec, reg, mask, i);
			} else {
				for (i = 0; i < widget->saved_value; i++)
					snd_soc_update_bits(widget->codec, reg, mask, i);
			}
			widget->muted = 0; 
		} else {
			/* power down is about to occur, decrease volume to mute */
			//power down widget,保存widget当前的gain值,并逐步减少该widget的音量,直至0。
			int val = snd_soc_read(widget->codec, reg);
			int i = widget->saved_value = (val >> shift) & mask;
			if (invert) {
				for (; i < mask; i++)
					snd_soc_update_bits(widget->codec, reg, mask, i);
			} else {
				for (; i > 0; i--)
					snd_soc_update_bits(widget->codec, reg, mask, i);
			}
			widget->muted = 1;
		}
	}
	return 0;
}
这里有个建议:当遇到PGA部件时,最好按照规范来写这个dapm widget,该widget应包含gain调整的kcontrol,这样系统会减少通电/断电时可能带来的POP音。

小结:dapm触发时会调用dapm_set_pga,控制PGA widgets的音量逐步增大/减少,目的是减少通电/断电时可能带来的POP音。


is_connected_output_ep

1、is_connected_output_ep和is_connected_input_ep,及其endpoint

is_connected_output_ep挺有趣的,widget一路往后走,直至达到output endpoint,统计widget到endpoints的路径数目。同理is_connected_input_ep是取得widget连接到input endpoint的路径数目,但走的方向是相反的。目的是检查路径的完整性,保证通道是贯穿input endpoint和output endpoint的。

何为output endpoint?snd_soc_dapm_adc、snd_soc_dapm_aif_out、snd_soc_dapm_output、snd_soc_dapm_hp、snd_soc_dapm_spk这些类型的widgets就是。

何为input endpoint?snd_soc_dapm_dac、snd_soc_dapm_aif_in、snd_soc_dapm_input、snd_soc_dapm_vmid、snd_soc_dapm_mic这些类型的widgets就是。

如果连接到output endpoint的路径数目为0或者连接到input endpoint的路径数目为0,那么说明该widget没有构成任意合法的完整的音频路径,因此也就应该关闭该widget;如果该widget有连接,则需要打开该widget。在dapm_power_widgets里详细分析。【实话说,代码注释是这样,但往下走到dapm_power_widgets太复杂了,我暂时未能完全分析。】

2、path->connect

连接状态是根据path->connect来判断的,改变mixer/mux部件的path->connect有两个地方:

(1)、在dapm kcontrols建立时,会检查mixer/mux的连接状态,具体见dapm_set_path_status:判定这个path对应的source是否被选中了,是则置p->connect = 1,否则置p->connect = 0。这个过程在<DAPM之二:audio paths与dapm kcontrol>有具体的描述。

(2)、上层通过alsa_amixer或其他来操作dapm kcontrols时,会更新widget路径的连接状态path->connect,简略流程如下:

amixer-应用层[alsa_amixer cset name='Left Output Mixer Left Input Mixer Switch' 1]  
  |->snd_ctl_ioctl-系统调用  
       |->snd_ctl_elem_write_user-内核钩子函数  
            |->snd_ctl_elem_wirte-  
                 |->snd_ctl_find_id-遍历kcontrol链表找到name字段匹配的kctl  
                 |->kctl->put()-调用kctl的成员函数put()  
                      |->snd_soc_dapm_put_volsw
                           |->dapm_mixer_update_power
                              更新path->connect 

pga、adc、dac、input、output等部件初始化path->connect=1,hp、spk、mic等部件初始化path->connect=0,具体见snd_soc_dapm_add_route函数。

3、path的相关链表

每一个mixer/mux widget都有数个path,path的数目与mixer/mux widget的kcontrols数目是一致的,有N个kcontrols,就可以构成N个path。每个path都会通过list_add(&path->list, &codec->dapm_paths)添加到链表codec->dapm_paths上;每个path,都会通过INIT_LIST_HEAD(&path->list_sink)创建一个path->list_sink节点,然后通过list_add(&path->list_sink, &wsink->sources)将节点添加到链表wsink->sources上;每个path,都会通过INIT_LIST_HEAD(&path->list_source)创建一个path->list_source节点,然后通过list_add(&path->list_source, &wsource->sinks)将节点添加到链表wsource->sink上。每个path都会挂到source widget和sink widget上的链表上,因此可通过widget->sources找到该widget作为sources的所有paths,通过widget->sinks找到该widget作为sinks的所有paths。

path的建立过程分析见DAPM之二:audio paths与dapm kcontrol,这里为了方便理解,画了一个示意图。其实这些应该要放到之前章节的,写得有点乱了,后面会整理一个有条理的文档出来。


4、is_connected_output_ep流程

理解以上三点,应该可以探讨is_connected_output_ep的流程了。is_connected_input_ep类似的。图解一下:


图中浅绿色为input endpoint,浅蓝色为output endpoint。如红色线路对于Input Mixer,它如何找到到达output endpoint的路径呢?

(1)、首先会找到Input Mixer->sinks,发现有两个分别是ADC和Output Mixer;

(2)、检查Input Mixer到ADC这个path是否连接,接着检查到ADC是属于snd_soc_dapm_adc类型的widget,返回1,因为这个widget属于output endpoint类型并且是连接上的;

(3)、检查Input Mixer到Output Mixer这个path是否连接的,如果不是则跳出;如果是则继续往下走,由于这个widget不属于output endpoint,继续找Output Mixer->sinks,有两个分别是HP和SPK;

(4)、再检查到HP是连接到Output Mixer,而且HP也属于output endpoint,因此Input Mixer->Output Mixer->HP这个通道也是贯穿output endpoint的。

以上是is_connected_output_ep的过程,至于is_connected_input_ep,则遍历的方向是相反的(即widget->sources),往前遍历到MIC(input endpoint)。当然已走过的path会置walked标识,不会重复检查。

只有is_connected_output_ep和is_connected_input_ep同时不为0(即往前能到input endpoint,往后能到output endpoint),才认为widget位于一条完整合法的音频通道中。

/*
 * Recursively check for a completed path to an active or physically connected
 * output widget. Returns number of complete paths.
 */
static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
{
	struct snd_soc_dapm_path *path;
	int con = 0;

	if (widget->id == snd_soc_dapm_supply)
		return 0;

	//如果widget类型是adc或aif_out,则到达endpoint,置widget->endpoint路径合法。
	switch (widget->id) {
	case snd_soc_dapm_adc:
	case snd_soc_dapm_aif_out:
		if (widget->active)
			return 1;
	default:
		break;
	}

	//如果widget类型是output、hp、spk等,则到达endpoint,置widget->endpoint路径合法。
	if (widget->connected) {
		/* connected pin ? */
		if (widget->id == snd_soc_dapm_output && !widget->ext)
			return 1;

		/* connected jack or spk ? */
		if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||
		    (widget->id == snd_soc_dapm_line && !list_empty(&widget->sources)))
			return 1;
	}

	//这里比较难以理解,dapm的链表很复杂,后面具体提一下,但不保证能表达清楚。
	//这里递归遍历widget->sink,直至到达一个endpoint或者遍历完毕,过程中检查path连接状态。
	//is_connected_output_ep是将widget作为source往前递归找到output endpoint;
	//is_connected_input_ep是将widget作为sink往后递归找到input endpoint。
	list_for_each_entry(path, &widget->sinks, list_source) {
		//这条路径已走过,找下一个path
		if (path->walked)
			continue;

		if (path->sink && path->connect) {
			path->walked = 1;
			con += is_connected_output_ep(path->sink);
		}
	}

	return con;
}
首先要理解INIT_LIST_HEAD、list_add和list_for_each_entry等Linux kernel链表操作,这里不多提。对于list_for_each_entry(path, &widget->sinks, list_source),链表头head是widget->sinks,member是list_source,这是结构体struct snd_soc_dapm_path中的一个成员。

注意3中,path建立时链表的操作:list_add(&path->list_source, &wsource->sinks),这里将节点path->list_source添加到链表wsource->sinks中,那么如何理解wsource->sinks呢?我认为是记录连接到wsource上的每一个sink(同理wsink->sources用于记录连接到wsink上的每一个source)。因此,得到一个widget,无论角度往前遍历还是往后遍历,都可找到所有的paths。

小结:这部分写得太多了,因为细节来说的确比较复杂(我改了又改,现在还不能保证细节上是否还有错的地方),要掌握endpoint的概念、path连接状态是如何更新的、什么是完整的音频路径complete paths,难点是path的链表分析。但原理是简单的,看以上的图解就比较清晰了。这两个函数检查一个widget是否同时贯穿input endpoint和output endpoint,是则认为该widget要power up。


dapm_generic_check_power

/* Generic check to see if a widget should be powered.
 */
static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
{
	int in, out;

	in = is_connected_input_ep(w);
	dapm_clear_walk(w->codec);
	out = is_connected_output_ep(w);
	dapm_clear_walk(w->codec);
	return out != 0 && in != 0;
}
检查一个widget是否需要打开,调用is_connected_input_ep检查该widget是否连接到input endpoint,is_connected_output_ep检查该widget是否连接到output endpoint。一个widget只有同时连接到input endpoint和output endpoint,才认为这个widget需要打开。

这其实是一系列的函数,分别是dapm_generic_check_power、dapm_adc_check_power、dapm_dac_check_power、dapm_supply_check_power,视widget的类型使用。如mixer/mux的widget,有w->power_check = dapm_generic_check_power;adc/aif_out的widget,有w->power_check = dapm_adc_check_power。具体见snd_soc_dapm_new_widgets。


dapm_generic_apply_power

核心函数,开关指定的widget,dapm机制绕来绕去最终就是为了控制widget的开和关。但是原理是非常简单的,复杂的是其他部分,如检查widget是否贯穿input/out endpoint、power up/down list顺序等前期处理细节。

/* Standard power change method, used to apply power changes to most
 * widgets.
 */
static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w)
{
	int ret;

	/* call any power change event handlers */
	if (w->event)
		pr_debug("power %s event for %s flags %x\n",
			 w->power ? "on" : "off",
			 w->name, w->event_flags);

	//在开关widget前,先执行标识为SND_SOC_DAPM_PRE_PMU/SND_SOC_DAPM_PRE_PMD的event
	//回调函数分支。event flag定义见dapm.h。

	/* power up pre event */
	if (w->power && w->event &&
	    (w->event_flags & SND_SOC_DAPM_PRE_PMU)) {
		ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU);
		if (ret < 0)
			return ret;
	}

	/* power down pre event */
	if (!w->power && w->event &&
	    (w->event_flags & SND_SOC_DAPM_PRE_PMD)) {
		ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD);
		if (ret < 0)
			return ret;
	}

	//关闭widget前,如果该widget属于PGA部件,调用dapm_set_pga控制pga部件音量逐步减少,
	//减少部件断电时产生的POP音。
	/* Lower PGA volume to reduce pops */
	if (w->id == snd_soc_dapm_pga && !w->power)
		dapm_set_pga(w, w->power);

	//操作widget开启或关闭。
	dapm_update_bits(w);

	//开启widget后,如果该widget属于PGA部件,调用dapm_set_pga控制pga部件音量逐步增大,
	//减少部件断电时产生的POP音。
	/* Raise PGA volume to reduce pops */
	if (w->id == snd_soc_dapm_pga && w->power)
		dapm_set_pga(w, w->power);

	//在开关widget后,执行标识为SND_SOC_DAPM_POST_PMU/SND_SOC_DAPM_POST_PMD的event
	//回调函数分支。event flag定义见dapm.h。
	
	/* power up post event */
	if (w->power && w->event &&
	    (w->event_flags & SND_SOC_DAPM_POST_PMU)) {
		ret = w->event(w,
			       NULL, SND_SOC_DAPM_POST_PMU);
		if (ret < 0)
			return ret;
	}

	/* power down post event */
	if (!w->power && w->event &&
	    (w->event_flags & SND_SOC_DAPM_POST_PMD)) {
		ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD);
		if (ret < 0)
			return ret;
	}

	return 0;
}


dapm_seq_insert

/* Insert a widget in order into a DAPM power sequence. */
static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget,
			    struct list_head *list,
			    int sort[])
{
	struct snd_soc_dapm_widget *w;

	list_for_each_entry(w, list, power_list)
		if (dapm_seq_compare(new_widget, w, sort) < 0) {
			list_add_tail(&new_widget->power_list, &w->power_list);
			return;
		}

	list_add_tail(&new_widget->power_list, list);
}

这个函数的作用是:将widget按照一定的顺序插入到链表list中。在power up/down时,为了减少可能由此产生的POP音,必须按照一定的顺序来操作这些widgets,这个顺序就是由dapm_seq_insert来指定的。

power down时,顺序如下:

static int dapm_down_seq[] = {
	[snd_soc_dapm_pre] = 0,
	[snd_soc_dapm_adc] = 1,
	[snd_soc_dapm_hp] = 2,
	[snd_soc_dapm_spk] = 2,
	[snd_soc_dapm_pga] = 4,
	[snd_soc_dapm_mixer_named_ctl] = 5,
	[snd_soc_dapm_mixer] = 5,
	[snd_soc_dapm_dac] = 6,
	[snd_soc_dapm_mic] = 7,
	[snd_soc_dapm_micbias] = 8,
	[snd_soc_dapm_mux] = 9,
	[snd_soc_dapm_value_mux] = 9,
	[snd_soc_dapm_aif_in] = 10,
	[snd_soc_dapm_aif_out] = 10,
	[snd_soc_dapm_supply] = 11,
	[snd_soc_dapm_post] = 12,
};
power up时,顺序如下:
static int dapm_up_seq[] = {
	[snd_soc_dapm_pre] = 0,
	[snd_soc_dapm_supply] = 1,
	[snd_soc_dapm_micbias] = 2,
	[snd_soc_dapm_aif_in] = 3,
	[snd_soc_dapm_aif_out] = 3,
	[snd_soc_dapm_mic] = 4,
	[snd_soc_dapm_mux] = 5,
	[snd_soc_dapm_value_mux] = 5,
	[snd_soc_dapm_dac] = 6,
	[snd_soc_dapm_mixer] = 7,
	[snd_soc_dapm_mixer_named_ctl] = 7,
	[snd_soc_dapm_pga] = 8,
	[snd_soc_dapm_adc] = 9,
	[snd_soc_dapm_hp] = 10,
	[snd_soc_dapm_spk] = 10,
	[snd_soc_dapm_post] = 11,
};

可以看出,这个顺序是合理的,符合音频depop常规原理。


总结:dapm机制分析上篇就到这里吧,介绍了一些重要的子函数细节,对于dapm机制提及不多,下篇会涉及到dapm的建立和触发过程。其实就我个人而言,最难理解的就是这些细节了。为了尽量消除POP,dapm_set_pga负责pga部件的depop,dapm_seq_insert负责组织widgets power up/down操作次序;power_check系列函数用于检查指定widget是否需要power up,由此又涉及到endpoint、complete paths等概念。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值