Linux ALSA 之十四:ASOC DAPM 之 Widget & Path & Route 分析

这篇具有很好参考价值的文章主要介绍了Linux ALSA 之十四:ASOC DAPM 之 Widget & Path & Route 分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


一、概述

在前面文章已经介绍了 DAPM 基本知识,了解了代表音频控件的 widget,并且系统中注册的各种 widget 需要互相连接在一起才能协调工作,故对于 ASOC Driver 中当使用到 DAPM 时需要定义 Widget 以及连接路径的 routes(ASOC DAPM Core 注册 Route 时会对连接的两个 widget 建立 path)。在本节中,将会从代码分析 DAPM Core 是如何注册 widget 以及注册 routes,主要有以下几个方面:
(1)如何注册 widget;(Including DAI Widget)
(2)如何连接两个 widget;
(3)一个 widget 的状态变化如何传递到整个音频路径中。

二、DAPM Context 介绍

参考 Link:linux-alsa详解11之DAPM详解4驱动中widget初始化
在讨论 widget 的注册之前,我们先了解另外一个概念:dapm context,直译过来的意思是 dapm 上下文,这个好像不好理解,其实我们可以这样理解:dapm 把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的 widget,每个域中的所有 widget 通常都处于同一个偏置电压级别上,而一个电源域就是一个 dapm context,通常会有以下几种 dapm context:
(1)属于 Codec 的 widget 位于一个 dapm context 中;
(2)属于 Platform 的 widget 位于一个 dapm context 中;
(3)属于整个声卡的 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_dapm_context 中用于设置偏置电压的重要成员为 set_bias_level 函数指针,一般 Codec 会在 snd_soc_component_driver 下定义 set_bias_level 函数,最后会添加到 Component->dapm->set_bias_level 中。其中 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,
};

(1)For Platform & Codec,snd_soc_dapm_context 是有直接内嵌在 snd_soc_component 结构体中;
(2)For Machine,snd_soc_dapm_context 是有直接内嵌在 snd_soc_card 结构体中。

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

三、Widget 的注册

参考链接同 “二、DAPM Context 介绍”
我们已经知道,一个 widget 用 snd_soc_dapm_widget 结构体来描述,通常,我们会根据音频硬件的组成,分别在声卡的 codec 驱动、platform 驱动和 machine 驱动定义一组 widget,这些 widget 用数组进行组织,我们一般会使用 dapm 框架提供的大量的辅助宏来定义这些 widget 数组,辅助宏的说明参与前一篇文章 “ASOC DAPM 简介 & Widget/Kcontrol 定义”。

3.1 Codec & Platform & Machine 中"显式" Widget 的注册

根据音频硬件的组成,通常在 Codec & Platform & Machine Driver 中都会各自定义一个 snd_soc_dapm_widget 数组,并且最终都会用过 snd_soc_dapm_new_controls(dapm,widgets,num_widgets) 进行注册显式 Widgets. (显式 Widget 指的是在 Driver 中直接定义的 Widget)

(1)For Platform:在定义 snd_soc_component_driver 时其 dapm_widgets 成员会指向定义的 Widgets 数组,在 Machine Driver 匹配成功后会在 soc_probe_link_components() 中调用 snd_soc_dapm_new_controls(dapm,conponent->driver->widgets,num_widgets) 来注册 Platform 显式 Widgets;
(2)For Codec:有两种方式注册显式 Widgets:
① 和 Platform 一样,将定义的 Widgets 填充到 Component 中;
② 第二种是在 snd_component_driver->probe 函数中会调用 snd_soc_dapm_new_controls(dapm,widgets,num_widgets) 来注册 Widgets,而在 Machine Driver 中匹配成功后同样是在 soc_probe_link_components() 中会调用 component->driver->probe(component) 函数,从而完成 Codec 显式 Widget 的注册;
(3)For Machine:在创建 snd_card_device 后紧接着会直接调用 snd_soc_dapm_new_controls(&card->dapm,card->dapm_widgets,num_widgets) 来注册 Machine 显式 Widgets。

如上所述,对于 Codec & Platform & Machine 注册 Widgets 最终都是通过 snd_soc_dapm_new_controls() 函数进行注册显式 Widgets,定义位于:/sound/soc/soc-dapm.c

/**
 * snd_soc_dapm_new_controls - create new dapm controls
 * @dapm: DAPM context
 * @widget: widget array
 * @num: number of widgets
 *
 * Creates new DAPM controls based upon the templates.
 *
 * Returns 0 for success else error.
 */
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
	const struct snd_soc_dapm_widget *widget,
	int num)
{
	struct snd_soc_dapm_widget *w;
	int i;
	int ret = 0;

	mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
	for (i = 0; i < num; i++) {
		w = snd_soc_dapm_new_control_unlocked(dapm, widget);
		if (IS_ERR(w)) {
			ret = PTR_ERR(w);
			break;
		}
		widget++;
	}
	mutex_unlock(&dapm->card->dapm_mutex);
	return ret;
}

如上述代码所示,该函数实际上最终就是为每个 Widget 调用 snd_soc_dapm_new_control_unlocked() 函数来完成真正的注册,如下:

struct snd_soc_dapm_widget *
snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm,
			 const struct snd_soc_dapm_widget *widget)
{
	enum snd_soc_dapm_direction dir;
	struct snd_soc_dapm_widget *w;
	const char *prefix;
	int ret;

	//(1) 为 Widget 分配内存并拷贝内容
	if ((w = dapm_cnew_widget(widget)) == NULL)
		return ERR_PTR(-ENOMEM);

	//(2) 当 widget 为 regulator_supply/pinctrl/clock_supply 则填充特定字段
	switch (w->id) {
	case snd_soc_dapm_regulator_supply:
		w->regulator = devm_regulator_get(dapm->dev, w->name);
		if (IS_ERR(w->regulator)) {
			ret = PTR_ERR(w->regulator);
			goto request_failed;
		}

		if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
			ret = regulator_allow_bypass(w->regulator, true);
			if (ret != 0)
				dev_warn(dapm->dev,
					 "ASoC: Failed to bypass %s: %d\n",
					 w->name, ret);
		}
		break;
	case snd_soc_dapm_pinctrl:
		w->pinctrl = devm_pinctrl_get(dapm->dev);
		if (IS_ERR(w->pinctrl)) {
			ret = PTR_ERR(w->pinctrl);
			goto request_failed;
		}
		break;
	case snd_soc_dapm_clock_supply:
		w->clk = devm_clk_get(dapm->dev, w->name);
		if (IS_ERR(w->clk)) {
			ret = PTR_ERR(w->clk);
			goto request_failed;
		}
		break;
	default:
		break;
	}

	//(3) Widget Name 赋值
	prefix = soc_dapm_prefix(dapm);
	if (prefix)
		w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, widget->name);
	else
		w->name = kstrdup_const(widget->name, GFP_KERNEL);
	if (w->name == NULL) {
		kfree(w);
		return ERR_PTR(-ENOMEM);
	}

	//(4) 根据 widget->id 填充 is_ep/power_check 等字段
	switch (w->id) {
	case snd_soc_dapm_mic:
		w->is_ep = SND_SOC_DAPM_EP_SOURCE;
		w->power_check = dapm_generic_check_power;
		break;
	case snd_soc_dapm_input:
		if (!dapm->card->fully_routed)
			w->is_ep = SND_SOC_DAPM_EP_SOURCE;
		w->power_check = dapm_generic_check_power;
		break;
	case snd_soc_dapm_spk:
	case snd_soc_dapm_hp:
		w->is_ep = SND_SOC_DAPM_EP_SINK;
		w->power_check = dapm_generic_check_power;
		break;
	case snd_soc_dapm_output:
		if (!dapm->card->fully_routed)
			w->is_ep = SND_SOC_DAPM_EP_SINK;
		w->power_check = dapm_generic_check_power;
		break;
	case snd_soc_dapm_vmid:
	case snd_soc_dapm_siggen:
		w->is_ep = SND_SOC_DAPM_EP_SOURCE;
		w->power_check = dapm_always_on_check_power;
		break;
	case snd_soc_dapm_sink:
		w->is_ep = SND_SOC_DAPM_EP_SINK;
		w->power_check = dapm_always_on_check_power;
		break;

	case snd_soc_dapm_mux:
	case snd_soc_dapm_demux:
	case snd_soc_dapm_switch:
	case snd_soc_dapm_mixer:
	case snd_soc_dapm_mixer_named_ctl:
	case snd_soc_dapm_adc:
	case snd_soc_dapm_aif_out:
	case snd_soc_dapm_dac:
	case snd_soc_dapm_aif_in:
	case snd_soc_dapm_pga:
	case snd_soc_dapm_out_drv:
	case snd_soc_dapm_micbias:
	case snd_soc_dapm_line:
	case snd_soc_dapm_dai_link:
	case snd_soc_dapm_dai_out:
	case snd_soc_dapm_dai_in:
		w->power_check = dapm_generic_check_power;
		break;
	case snd_soc_dapm_supply:
	case snd_soc_dapm_regulator_supply:
	case snd_soc_dapm_pinctrl:
	case snd_soc_dapm_clock_supply:
	case snd_soc_dapm_kcontrol:
		w->is_supply = 1;
		w->power_check = dapm_supply_check_power;
		break;
	default:
		w->power_check = dapm_always_on_check_power;
		break;
	}

    //(5) 初始化 widget dirty 等链表头,并将 widget 挂在 card->widgets list 中
	w->dapm = dapm;
	INIT_LIST_HEAD(&w->list);
	INIT_LIST_HEAD(&w->dirty);
	list_add_tail(&w->list, &dapm->card->widgets);

	snd_soc_dapm_for_each_direction(dir) {
		INIT_LIST_HEAD(&w->edges[dir]);
		w->endpoints[dir] = -1;
	}

	/* machine layer sets up unconnected pins and insertions */
	w->connected = 1;
	return w;

request_failed:
	if (ret != -EPROBE_DEFER)
		dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n",
			w->name, ret);

	return ERR_PTR(ret);
}

驱动中定义的 Widget 只是模板,该函数首先为该 Widget 重新分配内存,并将模板的内容拷贝过来,然后根据 widget->id 初始化一些重要字段,如 is_ep(如 mic/input/vmid/siggen=>EP_SOURCE,spk/hp/output/sink=>EP_SINK) [Note:在后面 route 时会根据情况修改 is_ep 参数(详见后面)]、power_check 函数指针,填充 snd_soc_dapm_context 代表该 widget 属于哪一个域,以及初始化 list、dirty list、edges[2] list、endpoint[2] 等,然后将 widget 挂在 card->widgets list 中,最终将 w->connected=1.

Widget 类型和对应的 power_check 回调函数设置如下:
Linux ALSA 之十四:ASOC DAPM 之 Widget & Path & Route 分析
综上,显式 Widgets 的注册函数 snd_soc_dapm_new_controls() 主要完成功能如下:

  • 为每个 Widget 分配内存,并将 Driver 中定义的 Widget 拷贝到其参数;(重新分配并拷贝 widget 参数)
  • 设置 power_check 回调函数,当音频路径发生变化时,power_check 回调会被调用,用于检查该 widget 的电源状态是否需要更新
  • 将每个 widget 挂在 card->widgets list 中。

至此,widget 已经正确地被创建并初始化,而且被挂在声卡的 widgets 链表中,以后我们就可以通过 card->widgets 链表来遍历所有的 widgets.

3.2 DAI Widgets 的注册

3.3 端点 Widgets

四、Path & Routes 的注册(完整路径)

参考 Link:linux-alsa详解13之DAPM详解6音频路径route

4.1 注册 “显式” Routes

上面小节的内容介绍了 Codec、Platform 以及 Machine 级别的显式 Widget 的注册方法,在 dapm 框架中,还有另外一种 widget,它代表了一个 dai(数字音频接口),dai 按所在位置又分为 cpu daicodec dai.对于 cpu dai 经常会区分 FE & BE,其中 FE 不会连接 codec dai,而 BE 则会连接 codec dai,而在 Machine Driver 中,我们要在 snd_soc_card 结构中指定一个叫做 snd_soc_dai_link 的结构,其定义了声卡使用哪一个 cpu dai 和 codec dai 进行连接

在 Platform Driver & Codec Driver 中,当调用 snd_soc_register_component(dai) 时会将其所定义的所有 dai 注册到 component 中,即定义一个对应的 snd_soc_dai,并将这些 dai 都挂在 component->dai_list 中。在 Machine 驱动调用声卡初始化函数 snd_soc_instantiate_card() 中,Platform & Codec Component 被 Machine 匹配后,后面会调用 soc_probe_component() 函数,最后会遍历 component->dai_list 中所有的 dai 去调用 snd_soc_dapm_new_dai_widgets(dapm,dai) 进行注册 cpu & codec dai widgets.

4.2 DAI Widgets Connect Internal Widgets

4.3 CPU DAI Widgets Connect Codec DAI Widgets

五、DAPM Kcontrol 的创建

参考 Link:linux-alsa详解12之DAPM详解5dapm kcontrol文章来源地址https://www.toymoban.com/news/detail-460995.html

5.1 dapm mixer kcontrol

5.2 dapm mux/demux kcontrol

5.3 dapm pga kcontrol

到了这里,关于Linux ALSA 之十四:ASOC DAPM 之 Widget & Path & Route 分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux 内核 ASoC DMA 引擎驱动程序

    Linux 内核 ASoC 框架,在概念上将嵌入式音频系统拆分为多个可复用的组件驱动程序,包括 Codec 类驱动程序、平台类驱动程序和机器类驱动程序。在实现上,机器类驱动程序用 struct snd_soc_card 和 struct snd_soc_dai_link 结构描述,属于平台类驱动程序的 DMA 引擎驱动程序由 struct snd

    2024年02月11日
    浏览(46)
  • Kubernetes(K8s)从入门到精通系列之十四:安装工具

    Kubernetes 命令行工具 kubectl, 让你可以对 Kubernetes 集群运行命令。 你可以使用 kubectl 来部署应用、监测和管理集群资源以及查看日志。 kind 让你能够在本地计算机上运行 Kubernetes。 使用这个工具需要你安装 Docker 或者 Podman。 与 kind 类似,minikube 是一个工具, 能让你在本地运

    2024年02月14日
    浏览(44)
  • Uncaught Error: [vue-router] “path“ is required in a route configuration 的解决方案

    其一、报错的代码信息为: Uncaught Error: [vue-router] “path“ is required in a route configuration 中文翻译: 未捕获的错误:[vue-router]“路径”在路由配置中是必需的 其二、报错的页面显示为: 看到提示,肯定是 vue-router 的 path 的原因报错; 其一、就是配置路由的时候 多加了一个花

    2024年02月10日
    浏览(47)
  • router.addRoute()报错 Uncaught Error: [vue-router] “path” is required in a route configuration

    后端动态传递路由,前端通过router.addRoute()添加时报错。Uncaught Error: [vue-router] “path” is required in a route configuration(未捕获错误:[vue-router]:\\\"路径\\\"在路由配置中是必需的) 查看官方的接口文档:Vue Router _addRoute接口,等于说 addRoute() 实际接收的是一个类型为 RouteRecordRaw

    2024年02月11日
    浏览(67)
  • Linux ALSA 之三:简单的 ALSA Driver 实现

    本节主要根据例子来描述撰写简单的 Alsa Driver 实现步骤,其中涉及的 Alsa Api 具体功能基本已经在Linux ALSA 之二:ALSA 声卡与设备中已经描述。 在 dummy.c 中直接 module_init() 入口函数中注册 Platform Device Platform Driver,匹配后进入 platform_driver 的 probe() 函数。 在前面 driver device 匹配

    2023年04月17日
    浏览(31)
  • 【密码算法 之十四】非对称算法,ECC椭圆曲线算法 之 ECDSA、ECDH、SM2、SM9等

      ECC(Elliptic Curve Cryptography),就是椭圆曲线密码算法,它是基于椭圆曲线数学理论实现的一种非对称加密算法。相比RSA,ECC优势是可以使用更短的密钥,来实现与RSA相当或更高的安全,RSA加密算法也是一种非对称加密算法,在公开密钥加密和电子商业中RSA被广泛使用。据

    2024年02月13日
    浏览(35)
  • Linux alsa 音频 录制与播放

    ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构。 在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为咱们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,便可以完成对底层音频硬件的控制。 aplay -l 显示实际声卡序号 查看声卡:

    2024年02月16日
    浏览(36)
  • 【音频应用】Linux之ALSA音频应用编程

    上文: 【音频驱动】Linux之ALSA声卡、WAV文件相关概念 使用alsa-libs和alsa-utils实现.wav格式文件的播放与录制,了解Linux中声卡的应用层设备节点。介绍了使用alsa-libs应用编程步骤。 高级Linux声音体系结构(ALSA)为Linux操作系统提供音频和MIDI功能。 ALSA具有以下显著特征: 高效支持所

    2024年02月05日
    浏览(44)
  • 【C++】开源:Linux端ALSA音频处理库

    😏 ★,° :.☆( ̄▽ ̄)/$: .°★ 😏 这篇文章主要介绍Linux端ALSA音频处理库。 无专精则不能成,无涉猎则不能通。。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更新不迷路🥞 项目Github地址: https://github.com/alsa-project/alsa-lib ALSA(Adva

    2024年02月15日
    浏览(46)
  • Flutter源码分析笔记:Widget类源码分析

    Flutter源码分析笔记 Widget类源码分析 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.net Email: 291148484@163.com. Shenzhen China Address of this article: https://blog.csdn.net/qq_28550263/article/details/132259681 【介绍】:本文记录阅读与分析Flutter源码 - Widget类源码分析。 Widget类是Flu

    2024年02月12日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包