Linux ALSA 之十二:ALSA ASOC Kcontrol

2023-05-16

ALSA ASOC Kcontrol

  • 一、结构体 snd_kcontrol_new
  • 二、ASoC 系统定义的 kcontrol 宏
    • 2.1 SOC_SINGLE
    • 2.2 SOC_SINGLE_TLV
    • 2.3 SOC_DOUBLE
    • 2.4 Mixer 控件
    • 2.5 Mux 控件
    • 2.6 其他控件


一、结构体 snd_kcontrol_new

定义位于:include/sound/control.h

在前面"声卡之 Control 设备"一节中已经有介绍 Control 设备如何创建。通常,一个 kcontrol 代表着一个 mixer(混音器),或者是一个 mux(多路开关),又或者是一个音量控制器等等。从之前介绍中我们知道,定义一个 kcontrol 主要就是定义一个 snd_kcontrol_new 结构,为了方便讨论,这里再次给出它的定义:

struct snd_kcontrol_new {
	snd_ctl_elem_iface_t iface;	/* interface identifier */
	unsigned int device;		/* device/client number */
	unsigned int subdevice;		/* subdevice (substream) number */
	const unsigned char *name;	/* ASCII name of item */
	unsigned int index;		/* index of item */
	unsigned int access;		/* access rights */
	unsigned int count;		/* count of same elements */
	snd_kcontrol_info_t *info;
	snd_kcontrol_get_t *get;
	snd_kcontrol_put_t *put;
	union {
		snd_kcontrol_tlv_rw_t *c;
		const unsigned int *p;
	} tlv;
	unsigned long private_value;
};

我们知道,对于每个控件,我们需要定义一个和它对应的 snd_kcontrol_new 机构,这些 snd_kcontrol_new 结构会在声卡初始化阶段通过 snd_soc_add_card_controls() 函数(详细查看代码)注册到系统中,用户空间就可以通过 amixer 或 alsamixer 等工具查看和设定这些控件的状态。

snd_kcontrol_new 结构中,几个主要的字段是 get,put,private_value 等。get 回调函数用于获取该控件当前的状态值,而 put 回调函数则用于设置控件的状态值,而 private_value 字段则根据不同的控件类型有不同的意义,比如普通控件,private_value 字段可以用来定义该控件所对应的寄存器地址以及对应的控制位在寄存器中的位置信息。值得庆幸的是,ASoC 系统已经为我们准备了大量的宏定义,用于定义常用的控件,这些宏定义位于 include/sound/soc.h 中。

二、ASoC 系统定义的 kcontrol 宏

# Note:ASoC Kcontrol 控件中 put 回调函数的定义仅仅是利用用户参数设置在实际的 register 中。

下面分别讨论下如何用这些预设的宏定义来定义一些常用的控件:

2.1 SOC_SINGLE

SOC_SINGLE 应该算是最简单的控件了,这种控件只有一个控制量,比如一个开关,又或者是一个数值变量(如 Codec 中的某个频率,FIFO 大小等),该宏定义如下:

#define SOC_SINGLE(xname, reg, shift, max, invert) \
{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
	.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
	.put = snd_soc_put_volsw, \
	.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }

该宏定义参数分别如下:

参数含义
xname控件的名字
reg控件对应的寄存器
shift控件位在寄存器中的位移
max控件可设置的最大值
invert设定值是否逻辑取反
private_value定义如下 :

如定义所示,private_value 字段使用了一个新的宏定义:SOC_SINGLE_VALUE,定义如下:

#define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert, xautodisable) \
	((unsigned long)&(struct soc_mixer_control) \
	{.reg = xreg, .rreg = xreg, .shift = shift_left, \
	.rshift = shift_right, .max = xmax, .platform_max = xmax, \
	.invert = xinvert, .autodisable = xautodisable})

#define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert, xautodisable) \
	SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert, xautodisable)

如定义所示,这里实际上是定义了一个 soc_mixer_control 结构,然后把该结构的地址赋给了 private_value 字段,其定义如下:

/* mixer control */
struct soc_mixer_control {
	int min, max, platform_max;
	int reg, rreg;
	unsigned int shift, rshift;
	unsigned int sign_bit;
	unsigned int invert:1;
	unsigned int autodisable:1;
	struct snd_soc_dobj dobj;
};

从其定义可以看出 soc_mixer_control 是控件特征的真正描述者,它确定了该控件对应寄存器的地址,位移值,最大值和是否逻辑取反等特性,控件的 put/get 回到函数则会借助该结构来访问实际的寄存器。例如可以看看这 get 回调函数的定义:位于 sound/soc/soc-ops.c

/**
 * snd_soc_get_volsw - single mixer get callback
 * @kcontrol: mixer control
 * @ucontrol: control element information
 *
 * Callback to get the value of a single mixer control, or a double mixer
 * control that spans 2 registers.
 *
 * Returns 0 for success.
 */
int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
	struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);

	//通过 kcontrol->private_value 获取到 soc_mixer_control
	struct soc_mixer_control *mc =
		(struct soc_mixer_control *)kcontrol->private_value;
	unsigned int reg = mc->reg;
	unsigned int reg2 = mc->rreg;
	unsigned int shift = mc->shift;
	unsigned int rshift = mc->rshift;
	int max = mc->max;
	int min = mc->min;
	int sign_bit = mc->sign_bit;
	unsigned int mask = (1 << fls(max)) - 1;
	unsigned int invert = mc->invert;
	int val;
	int ret;

	if (sign_bit)
		mask = BIT(sign_bit + 1) - 1;

	ret = snd_soc_read_signed(component, reg, mask, shift, sign_bit, &val);
	if (ret)
		return ret;

	ucontrol->value.integer.value[0] = val - min;
	if (invert)
		ucontrol->value.integer.value[0] =
			max - ucontrol->value.integer.value[0];

	if (snd_soc_volsw_is_stereo(mc)) {
		if (reg == reg2)
			ret = snd_soc_read_signed(component, reg, mask, rshift,
				sign_bit, &val);
		else
			ret = snd_soc_read_signed(component, reg2, mask, shift,
				sign_bit, &val);
		if (ret)
			return ret;

		ucontrol->value.integer.value[1] = val - min;
		if (invert)
			ucontrol->value.integer.value[1] =
				max - ucontrol->value.integer.value[1];
	}

	return 0;
}

从上述代码一目了然,从 private_value 字段取出 soc_mixer_kcontrol 结构,利用该结构的信息访问对应的寄存器,返回相应的值。

2.2 SOC_SINGLE_TLV

该宏是 SOC_SINGLE 的一种扩展,主要用于定义那些有增益控制的控件,例如音量控制器,EQ均衡器等等,定义如下:

#define SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
		 SNDRV_CTL_ELEM_ACCESS_READWRITE,\
	.tlv.p = (tlv_array), \
	.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
	.put = snd_soc_put_volsw, \
	.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }

从它的定义可以看出,用于设置寄存器信息的 private_value 字段的定义和 SOC_SINGLE 是一样的,甚至 put、get 回到函数也是使用同一套,唯一不同的是增加了一个 tlv_arry 参数,并把它赋值给了 tlv.p 字段。用户空间可以通过声卡的 control 设备发起以下两种 ioctl 来访问 tlv 字段所指向的数组:

#define SNDRV_CTL_ELEM_ACCESS_TLV_READ		(1<<4)	/* TLV read is possible */
#define SNDRV_CTL_ELEM_ACCESS_TLV_WRITE		(1<<5)	/* TLV write is possible */
#define SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE	(SNDRV_CTL_ELEM_ACCESS_TLV_READ|SNDRV_CTL_ELEM_ACCESS_TLV_WRITE)
#define SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND	(1<<6)	/* TLV command is possible */

通常,tlv_arry 用来描述寄存器的设定值与它代表的实际意义之间的映射关系,最常用的就是用于音量控件时,设定值与对应的 dB 值之间的映射关系,如下以 wm8960.c 为例:
在这里插入图片描述
DECLARE_TLV_DB_SCALE 用于定义一个 dB 值映射的 tlv_array,上述的例子标明,该 tlv 的类型是 SNDRV_CTL_TLV_DB_SCALE,寄存器的最小值为 -15dB,寄存器每增加一个单位值,对应的 dB 数值是增加 3dB(0.01dB* 300),而由接下来的 SOC_SINGLE_TLV 定义可以看出,我们定义了一个 boost 控件,寄存器地址都是 WM8960_MIXER1,控制位是第 4 bit,最大值是 7,即对应的范围为 min:0(-15dB),max:7(6dB).

2.3 SOC_DOUBLE

与 SOC_SINGLE 相对应,区别是 SOC_SINGLE 只控制一个变量,而 SOC_DOUBLE 则可以同时控制一个寄存器中控制两个相似的变量,最常用的就是用于一些立体声的控件,我们需要同时对左右声道进行控制,因为多了一个声道,参数也就相应地多了一个 shift 位移值,其定义如下:

#define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \
{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
	.info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \
	.put = snd_soc_put_volsw, \
	.private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \
					  max, invert, 0) }

SOC_DOUBLE_R:与 SOC_DOUBLE 类似,对于左右声道的控制寄存器不一样的情况,使用 SOC_DOUBLE_R 来定义,参数中需要指定两个寄存器地址,定义如下:

#define SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert) \
{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
	.info = snd_soc_info_volsw, \
	.get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \
	.private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \
					    xmax, xinvert) }

SOC_DOUBLE_TLV:与 SOC_SINGLE_TLV 对应的立体声版本,通常用于立体声音量控件的定义。
SOC_DOUBLE_R_TLV:左右声道有独立寄存器控制的 SOC_DOUBLE_TLV 版本。

2.4 Mixer 控件

用于音频通道的路由控制,由多个输入和一个输出组成,多个输入可以自由地混合在一起,形成混合后的输出:
在这里插入图片描述
对于 Mixer 控件,我们可以认为是多个简单控件的组合,通常,我们会为 mixer 的每个输入端都单独定义一个简单控件来控制该路输入的开启和关闭,反映在代码上,就是定义一个 soc_kcontrol_new 数组,以 wm8960.c 举例:
在这里插入图片描述
以上这个 mixer 使用寄存器 WM8960_LOUTMIX 的第 8 位,WM8960_LOUTMIX 的第 7 位,WM8960_BYPASS1 的第7 位来分别控制 3 个输入端的开启和关闭。

2.5 Mux 控件

与 mixer 控件类似,也是多个输入端和一个输出端的组合控件,与 mixer 控件不同的是,mux 控件的多个输入端同时只能有一个被选中。因此,mux 控件所对应的寄存器,通常可以设定一段连续的数值,每个不同的数值对应不同的输入端被打开,与上述的 mixer 控件不同,ASoC 用 soc_enum 结构来描述 mux 控件的寄存器信息,定义如下:

/* enumerated kcontrol */
struct soc_enum {
	int reg;
	unsigned char shift_l;
	unsigned char shift_r;
	unsigned int items;
	unsigned int mask;
	const char * const *texts;
	const unsigned int *values;
	unsigned int autodisable:1;
	struct snd_soc_dobj dobj;
};

两个寄存器地址和位移字段:reg,shift_l,shift_r,用于描述左右声道的控制寄存器信息。字符串数组指针 texts 用于描述每个输入端对应的名字,values 字段则指向一个数组,该数组定义了寄存器可以选择的值,每个值对应一个输入端,如果 value 是一组连续的值,通常我们可以忽略 values 参数。
其中,soc_enum 结构需要使用辅助宏 SOC_ENUM_SINGLE 来定义,该宏声明如下:

#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xitems, xtexts) \
{	.reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
	.items = xitems, .texts = xtexts, \
	.mask = xitems ? roundup_pow_of_two(xitems) - 1 : 0}

#define SOC_ENUM_SINGLE(xreg, xshift, xitems, xtexts) \
	SOC_ENUM_DOUBLE(xreg, xshift, xshift, xitems, xtexts)

上述宏仅仅是填充 soc_enum 变量,但是对于定义好的 soc_enum 结构变量的话,我们还需要定义 Mux 控件的 snd_kcontrol_new,需要使用 SOC_ENUM 宏,定义如下:

#define SOC_ENUM(xname, xenum) \
{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
	.info = snd_soc_info_enum_double, \
	.get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \
	.private_value = (unsigned long)&xenum }

由定义看出,会使用 private_value 字段记录 soc_enum 结构变量,不过几个回调函数变了,我们看看 get 回调对应的 snd_soc_get_enum_double 函数:

/**
 * snd_soc_get_enum_double - enumerated double mixer get callback
 * @kcontrol: mixer control
 * @ucontrol: control element information
 *
 * Callback to get the value of a double enumerated mixer.
 *
 * Returns 0 for success.
 */
int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
	struct snd_ctl_elem_value *ucontrol)
{
	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
	unsigned int val, item;
	unsigned int reg_val;
	int ret;

	ret = snd_soc_component_read(component, e->reg, &reg_val);
	if (ret)
		return ret;
	val = (reg_val >> e->shift_l) & e->mask;
	item = snd_soc_enum_val_to_item(e, val);
	ucontrol->value.enumerated.item[0] = item;
	if (e->shift_l != e->shift_r) {
		val = (reg_val >> e->shift_r) & e->mask;
		item = snd_soc_enum_val_to_item(e, val);
		ucontrol->value.enumerated.item[1] = item;
	}

	return 0;
}

从上述代码一目了然,从 private_value 字段取出 soc_enum 结构,利用该结构的信息访问对应的寄存器,返回相应的 item.

下面我们先看看如何定义一个 mux控件:
① 定义 mux 控件每个输入端对应的字符串 texts 和 values 数组,以下的例子因为 values 是连续的,所以不用定义:
在这里插入图片描述
② 利用 ASoC 提供的辅助宏定义 soc_enum 结构变量,用于描述寄存器:
在这里插入图片描述
③ 利用 ASoC 提供的辅助宏,定义 snd_kcontrol_new 结构,该结构最后用于注册 mux 控件:
在这里插入图片描述
以上几步定义了一个叫 ADC Data Output Select 的 mux 控件,该控件具有四个输入选择,分别是代表选择的是 LR/LL/RR/RL,用寄存器 WM8960_ADDCTL1 控制。

以下是另外几个常用于定义 mux 控件的宏:

  • SOC_VALUE_ENUM_SINGLE 用于定义带 values 字段的 soc_enum 宏;
  • SOC_VALUE_ENUM_DOUBLE SOC_VALUE_ENUM_SINGLE 对应的立体声版本。

2.6 其他控件

其实,除了以上介绍的几种常用控件,ASoC 还为我们提供了另外一些控件定义辅助宏,详细参考:include/sound/soc.h.
这里列举几个:需要自己定义 get 和 put 回调时,可以使用以下这些带 EXT 的版本:

  • SOC_SINGLE_EXT
  • SOC_DOUBLE_EXT
  • SOC_SINGLE_EXT_TLV
  • SOC_DOUBLE_EXT_TLV
  • SOC_DOUBLE_R_EXT_TLV
  • SOC_ENUM_EXT
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux ALSA 之十二:ALSA ASOC Kcontrol 的相关文章

随机推荐

  • ssh服务和日志的基本常识

    查看网桥 brctl show 创建链接 ln s mnt dir file mnt lianjie 1 查看进程 gnome system monltor ps 当前shell的进程 A 所有的进程 a shell中所有的进程 xff0c
  • ESP8266基础详细使用

    前言 刚买一块ESP8266 琢磨一天才弄明白怎么使用 xff0c 小白第一次弄这个确实不太友好 xff0c 这里记录一下 xff0c 怕以后自己用到又忘了 xff0c 在物联网这一方面ESP8266还是特别实用的 材料准备 淘宝 xff0
  • nvm安装流程

    nvm nvm是管理node版本的工具 一般我们会负责多个项目 xff0c 不同项目有不同版本的node环境 xff0c 此时就需要nvm对node版本进行切换处理 1 首先卸载node 2 nvm下载 安装包下载地址 xff1a http
  • 树莓派 raspbian (各版本)换国内源

    xff08 看到师兄的博客后感觉还行 xff0c 所以自己也来写下 xff0c 第一次写 xff0c 所以不会编排 xff0c 有什么错误希望被指出 xff0c 谢谢 xff09 相信来寻找换源的人都和一样知道为什么要换国内更新源了吧 xf
  • 树莓派4b的i2c配置及wiringPi通信

    一 配置i2c设备 1 xff09 在终端中操作 xff0c 输入指令 sudo raspi config 2 xff09 然后会出现设置界面 xff0c 然后跟着如下图片操作 第一项 xff1a Change User Password
  • 树莓派驱动之设备树覆盖

    一 前言 由于是初学者 xff0c 所以对于一些操作需要记录下方便自己查找 附上 xff1a 树莓派设备树官网 我只从官网上了解到一点点内容 xff0c 还有许多没看懂的和还没学的 一个常规的Arm Linux设备树 xff0c 主要是由源
  • ConcurrentHashMap 常用方法

    void clear 从该映射中移除所有映射关系 boolean containsKey Object key 测试指定对象是否为此表中的键 boolean containsValue Object value 如果此映射将一个或多个键映射
  • .vscode中常用的配置文件

    文件 一 安装常用插件二 c cpp properties json文件三 settings json文件 一 安装常用插件 根据自己需要安装相应的插件 xff1a span class token number 1 span span c
  • 操作系统相关知识

    目录 一 嵌入式操作系统二 实时操作系统 xff08 RTOS xff09 三 Freertos 操作系统四 Linux xff08 操作系统 xff09 五 Linux 和 FreeRTOS操作系统的区别 xff08 面试中被问到 xff
  • 结构体对齐(全)

    目录 一 结构体对齐规则二 结构体位域对齐规则 一 结构体对齐规则 1 第一个成员在与结构体偏移量为0的地址处 xff1b 2 其他成员变量要与自身类型的整数倍地址处对齐 xff1b 3 结构体总大小为要与 处理器字节数与成员类型所占字节数
  • C语言自我实现模块化打印log

    在一个嵌入式稍微大些的工程中实现模块化控制打印输出信息是很有必要的 xff0c 下面是模仿别人的实现的模块化打印 xff0c 需要时可以根据下面的实现代码去修改满足自己所需要的 xff01 xff01 xff01 span class to
  • 卸载ibus后无法进入桌面的解决方法

    过程 一 复现现象 xff1a 二 复现原因 xff1a 三 解决方法 xff1a 重新安装ubuntu桌面 一 复现现象 xff1a 开机进入 Ubuntu xff0c 输入密码成功后一直卡在这个页面 xff0c 无法进入 ubuntu
  • Linux内核之 printk 打印

    Linux内核之 printk 打印 前言一 printk 介绍1 printk 消息级别2 内核 printk 文件 二 调整打印级别1 在 menuconfig 中修改2 在系统中修改 xff08 常用 xff09 三 使用示例四 查看
  • Linux ALSA 之四:Tinyalsa->Alsa Driver Flow分析

    Tinyalsa gt Alsa Driver Flow 一 概述二 Tinyalsa2 1 tinypcminfo2 2 tinymix2 3 tinyplay2 4 tinycap 三 Tinyalsa gt alsa driver f
  • Linux ALSA 之十:ALSA ASOC Machine Driver

    ALSA ASOC Machine Driver 一 Machine 简介二 ASoC Machine Driver2 1 Machine Driver 的 Platform Driver amp Platform Device 驱动模型2
  • Linux ALSA 之十一:ALSA ASOC Path 完整路径追踪

    ALSA ASOC Path 完整路径追踪 一 ASoc Path 简介二 ASoc Path 完整路径2 1 tinymix 设置2 2 完整路径 route 一 ASoc Path 简介 如前面小节所描述 xff0c ASoc 中 Ma
  • 【STM32】关于Clion+STM32cubeMX环境搭建过程中所遇到的一些问题·其一

    目录 一 前言 二 正文 stm32 cubemx的安装 建立工程 配置openOCD进行编译烧录 关于烧录失败的事 三 小结 四 参考文章 一 前言 近日在网上冲浪时无意间发现了稚晖君在知乎发布的一篇关于如何使用clion 43 stm3
  • 端口扫描 1

    TCP端口扫描是通过SYN数据包进行的 xff0c 用于扫描目标机器的端口上是否存在程序监听 xff0c 通常意义上 xff0c 普通个人机器上的某个端口如果有程序监听的话 xff0c 那么它一般是系统漏洞 由于TCP是一个有连接的可靠协议
  • linux Ubuntu终端快捷键

    终端操作快捷键 Tab 自动补全 Ctrl 43 a 光标移动到开始位置 Ctrl 43 e 光标移动到最末尾 Ctrl 43 k 删除此处至末尾的所有内容 Ctrl 43 u 删除此处至开始的所有内容 Ctrl 43 d 删除当前字符 C
  • Linux ALSA 之十二:ALSA ASOC Kcontrol

    ALSA ASOC Kcontrol 一 结构体 snd kcontrol new二 ASoC 系统定义的 kcontrol 宏2 1 SOC SINGLE2 2 SOC SINGLE TLV2 3 SOC DOUBLE2 4 Mixer