ALSA学习(4)——Control设备的创建

news2024/11/26 4:28:04

参考博客: https://blog.csdn.net/DroidPhone/article/details/6409983
(下面的内容基本是原博主的内容,我只是修改了一些格式之类的)

文章目录

  • 一、Control接口
  • 二、Controls的定义
  • 三、Control的名字
  • 四、访问标志(ACCESS Flags)
  • 五、回调函数
    • 5.1 info回调函数
    • 5.2 get回调函数
    • 5.3 put回调函数
  • 六、创建Controls
  • 七、元数据(Metadata)
  • 八、Control设备的建立

一、Control接口

Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等。对于Mixer(混音)来说,Control接口显得尤为重要,从ALSA 0.9.x版本开始,所有的mixer工作都是通过control接口的API来实现的。

ALSA已经为AC97定义了完整的控制接口模型,如果你的Codec芯片只支持AC97接口,你可以不用关心本节的内容。

<sound/control.h>定义了所有的Control API。如果你要为你的codec实现自己的controls,请在代码中包含该头文件。

二、Controls的定义

要自定义一个Control,我们首先要定义3各回调函数:info,get和put。然后,定义一个snd_kcontrol_new结构:

代码路径: kernel_4.14nclude\sound\control.h

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

iface :字段指出了control的类型,alsa定义了几种类型(SNDDRV_CTL_ELEM_IFACE_XXX),常用的类型是MIXER,当然也可以定义属于全局的CARD类型,也可以定义属于某类设备的类型,例如HWDEP,PCMRAWMIDI,TIMER等,这时需要在device和subdevice字段中指出卡的设备逻辑编号。

name: 字段是该control的名字,从ALSA 0.9.x开始,control的名字是变得比较重要,因为control的作用是按名字来归类的。ALSA已经预定义了一些control的名字,我们再Control Name一节详细讨论。

index:字段用于保存该control的在该卡中的编号。如果声卡中有不止一个codec,每个codec中有相同名字的control,这时我们可以通过index来区分这些controls。当index为0时,则可以忽略这种区分策略。

access: 字段包含了该control的访问类型。每一个bit代表一种访问类型,这些访问类型可以多个“或”运算组合在一起。

private_value:字段包含了一个任意的长整数类型值。该值可以通过info,get,put这几个回调函数访问。你可以自己决定如何使用该字段,例如可以把它拆分成多个位域,又或者是一个指针,指向某一个数据结构。
tlv字段为该control提供元数据

三、Control的名字

control的名字需要遵循一些标准,通常可以分成3部分来定义control的名字:源–方向–功能。
:可以理解为该control的输入端,alsa已经预定义了一些常用的源,例如:Master,PCM,CD,Line等等。
方向:代表该control的数据流向,例如:Playback,Capture,Bypass,Bypass Capture等等,也可以不定义方向,这时表示该Control是双向的(playback和capture)。
功能:根据control的功能,可以是以下字符串:Switch,Volume,Route等等。

举例:

static const struct snd_kcontrol_new capture_source_control = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
     // 方向
	.name = "Capture Source",
static const struct snd_kcontrol_new mute_control = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	// 源 + 功能
	.name = "Master Playback Switch",

四、访问标志(ACCESS Flags)

static const struct snd_kcontrol_new mute_control = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "Master Playback Switch",
	// 标志
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,

Access字段是一个bitmask,它保存了改control的访问类型。默认的访问类型是:SNDDRV_CTL_ELEM_ACCESS_READWRITE,表明该control支持读和写操作。如果access字段没有定义(.access==0),此时也认为是READWRITE类型。

如果是一个只读control,access应该设置为:SNDDRV_CTL_ELEM_ACCESS_READ,这时,我们不必定义put回调函数。类似地,如果是只写control,access应该设置为:SNDDRV_CTL_ELEM_ACCESS_WRITE,这时,我们不必定义get回调函数。

如果control的值会频繁地改变(例如:电平表),我们可以使用VOLATILE类型,这意味着该control会在没有通知的情况下改变,应用程序应该定时地查询该control的值

五、回调函数

5.1 info回调函数

info回调函数用于获取control的详细信息。它的主要工作就是填充通过参数传入的snd_ctl_elem_info对象,以下例子是一个具有单个元素的boolean型control的info回调:

static int snd_myctl_mono_info(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_info *uinfo)
{
    uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
    uinfo->count = 1;
    uinfo->value.integer.min = 0;
    uinfo->value.integer.max = 1;
    return 0;
}

ype字段指出该control的值类型,值类型可以是BOOLEAN, INTEGER, ENUMERATED, BYTES,IEC958和INTEGER64之一。count字段指出了改control中包含有多少个元素单元,比如,立体声的音量control左右两个声道的音量值,它的count字段等于2。value字段是一个联合体(union),value的内容和control的类型有关。其中,boolean和integer类型是相同的。

ENUMERATED类型有些特殊。它的value需要设定一个字符串和字符串的索引,请看以下例子:

static int snd_myctl_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
    static char *texts[4] = {
        "First", "Second", "Third", "Fourth"
    };
    uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
    uinfo->count = 1;
    uinfo->value.enumerated.items = 4;
    if (uinfo->value.enumerated.item > 3)
        uinfo->value.enumerated.item = 3;
    strcpy(uinfo->value.enumerated.name,
        texts[uinfo->value.enumerated.item]);
    return 0;
}

alsa已经为我们实现了一些通用的info回调函数,例如:snd_ctl_boolean_mono_info(),snd_ctl_boolean_stereo_info()等等。

5.2 get回调函数

该回调函数用于读取control的当前值,并返回给用户空间的应用程序。

static int snd_myctl_get(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct mychip *chip = snd_kcontrol_chip(kcontrol);
    ucontrol->value.integer.value[0] = get_some_value(chip);
    return 0;
}

value字段的赋值依赖于control的类型(如同info回调)。很多声卡的驱动利用它存储硬件寄存器的地址、bit-shift和bit-mask,这时,private_value字段可以按以下例子进行设置:

.private_value = reg | (shift << 16) | (mask << 24);

然后,get回调函数可以这样实现:

static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)

{
    int reg = kcontrol->private_value & 0xff;
    int shift = (kcontrol->private_value >> 16) & 0xff;
    int mask = (kcontrol->private_value >> 24) & 0xff;
    ....

    //根据以上的值读取相应寄存器的值并填入value中
}

如果control的count字段大于1,表示control有多个元素单元,get回调函数也应该为value填充多个数值。

5.3 put回调函数

put回调函数用于把应用程序的控制值设置到control中。

static int snd_myctl_put(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct mychip *chip = snd_kcontrol_chip(kcontrol);
    int changed = 0;
    if (chip->current_value !=
        ucontrol->value.integer.value[0]) {
        change_current_value(chip,
        ucontrol->value.integer.value[0]);
        changed = 1;
    }
    return changed;
}

如上述例子所示,当control的值被改变时,put回调必须要返回1,如果值没有被改变,则返回0。如果发生了错误,则返回一个负数的错误号。
和get回调一样,当control的count大于1时,put回调也要处理多个control中的元素值

六、创建Controls

当把以上讨论的内容都准备好了以后,我们就可以创建我们自己的control了。alsa-driver为我们提供了两个用于创建control的API:

snd_ctl_new1()
snd_ctl_add()
我们可以用以下最简单的方式创建control:

err = snd_ctl_add(card, snd_ctl_new1(&my_control, chip));
if (err < 0)
    return err;

在这里,my_control是一个之前定义好的snd_kcontrol_new对象,chip对象将会被赋值在kcontrol->private_data字段,该字段可以在回调函数中访问。

snd_ctl_new1()会分配一个新的snd_kcontrol实例,并把my_control中相应的值复制到该实例中,所以,在定义my_control时,通常我们可以加上__devinitdata前缀。snd_ctl_add则把该control绑定到声卡对象card当中。

七、元数据(Metadata)

很多mixer control需要提供以dB为单位的信息,我们可以使用DECLARE_TLV_xxx宏来定义一些包含这种信息的变量,然后把control的tlv.p字段指向这些变量,最后,在access字段中加上SNDRV_CTL_ELEM_ACCESS_TLV_READ标志,就像这样:

static DECLARE_TLV_DB_SCALE(db_scale_my_control, -4050, 150, 0);

static struct snd_kcontrol_new my_control __devinitdata = {
    ...
    .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
            SNDRV_CTL_ELEM_ACCESS_TLV_READ,
    ...
    .tlv.p = db_scale_my_control,
};

DECLARE_TLV_DB_SCALE宏定义的mixer control,它所代表的值按一个固定的dB值的步长变化。该宏的第一个参数是要定义变量的名字,第二个参数是最小值,以0.01dB为单位。第三个参数是变化的步长,也是以0.01dB为单位。如果该control处于最小值时会做出mute时,需要把第四个参数设为1。
DECLARE_TLV_DB_LINEAR宏定义的mixer control,它的输出随值的变化而线性变化。 该宏的第一个参数是要定义变量的名字,第二个参数是最小值,以0.01dB为单位。第二个参数是最大值,以0.01dB为单位。如果该control处于最小值时会做出mute时,需要把第二个参数设为TLV_DB_GAIN_MUTE。
这两个宏实际上就是定义一个整形数组,所谓tlv,就是Type-Lenght-Value的意思,数组的第0各元素代表数据的类型,第1个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据。

八、Control设备的建立

Control设备和PCM设备一样,都属于声卡下的逻辑设备。用户空间的应用程序通过alsa-lib访问该Control设备,读取或控制control的控制状态,从而达到控制音频Codec进行各种Mixer等控制操作。

Control设备的创建过程大体上和PCM设备的创建过程相同。详细的创建过程可以参考本博的另一篇文章:Linux音频驱动之三:PCM设备的创建。下面我们只讨论有区别的地方。

我们需要在我们的驱动程序初始化时主动调用snd_pcm_new()函数创建pcm设备,而control设备则在snd_card_create()内被创建,snd_card_create()通过调用snd_ctl_create()函数创建control设备节点。所以我们无需显式地创建control设备,只要建立声卡,control设备被自动地创建。

和pcm设备一样,control设备的名字遵循一定的规则:controlCxx,这里的xx代表声卡的编号。我们也可以通过代码正是这一点,下面的是snd_ctl_dev_register()函数的代码:

/*
 * registration of the control device
 */
static int snd_ctl_dev_register(struct snd_device *device)
{
	struct snd_card *card = device->device_data;
	int err, cardnum;
	char name[16];

	if (snd_BUG_ON(!card))
		return -ENXIO;
	cardnum = card->number;
	if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
		return -ENXIO;
        /* control设备的名字 */
	sprintf(name, "controlC%i", cardnum);
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
				       &snd_ctl_f_ops, card, name)) < 0)
		return err;
	return 0;
}

snd_ctl_dev_register()函数会在snd_card_register()中,即声卡的注册阶段被调用。注册完成后,control设备的相关信息被保存在snd_minors[]数组中,用control设备的此设备号作索引,即可在snd_minors[]数组中找出相关的信息。注册完成后的数据结构关系可以用下图进行表述:
在这里插入图片描述
用户程序需要打开control设备时,驱动程序通过snd_minors[]全局数组和此设备号,可以获得snd_ctl_f_ops结构中的各个回调函数,然后通过这些回调函数访问control中的信息和数据(最终会调用control的几个回调函数get,put,info)。详细的代码我就不贴了,大家可以读一下代码:/sound/core/control.c。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1342936.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

运维分享|MySQL的字符集(一)

&#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端工程师 &#x1f3c6; 近期荣誉&#xff1a;华为云云享专家、阿里云专家博主、 &#x1f525; 三连支持&#xff1a;欢迎 ❤️关注、&#x1f44d;点赞、&…

Servlet获取前端请求的参数和中文乱码的解决方案

目录 1.Servlet获取前端请求的参数 1.1创建jsp 1.2构建servlet实例 1.3配置web.xml 2.中文乱码的解决方案 2.1请求时候的乱码问题 2.2响应时候中文乱码的问题 学好Servlet必须紧紧围绕着请求和响应这两个概念。 下面开始写在请求的时候前端带数据到servlet里面&#xff…

DP进阶之路——01背包问题

题目链接&#xff1a;题目页面 小明是一位科学家&#xff0c;他需要参加一场重要的国际科学大会&#xff0c;以展示自己的最新研究成果。他需要带一些研究材料&#xff0c;但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等&#xff0c;它们各自占据不…

【数据结构】C语言实现双链表的基本操作

双链表及其基本操作的实现 导言一、单链表与双链表二、双链表类型的创建三、双链表的初始化四、双链表的创建五、双链表的遍历六、双链表的查找七、双链表的插入八、双链表的删除结语 导言 大家好&#xff0c;很高兴又和大家见面啦&#xff01;&#xff01;&#xff01; 经过…

SpringBoot+modbus4j实现ModebusTCP通讯读取数据

场景 Windows上ModbusTCP模拟Master与Slave工具的使用&#xff1a; Windows上ModbusTCP模拟Master与Slave工具的使用-CSDN博客 Modebus TCP Modbus由MODICON公司于1979年开发&#xff0c;是一种工业现场总线协议标准。 1996年施耐德公司推出基于以太网TCP/IP的Modbus协议&…

Linux - 设置虚拟机和主机IP在同一网段(桥接)

1.查看主机ip地址等相关信息。 ipconfig -all 2.设置虚拟网络编辑器 打开虚拟网络编辑器 设置虚拟网络编辑器&#xff0c;设置为桥接模式。&#xff08;记得以管理员方式打开VMware&#xff09;。 3.修改虚拟机网卡文件 查看虚拟机ip,我们的目标是将其修改为与主机同一网段…

JAVA电商平台 免 费 搭 建 B2B2C商城系统 多用户商城系统 直播带货 新零售商城 o2o商城 电子商务 拼团商城 分销商城

在数字化时代&#xff0c;电商行业正经历着前所未有的变革。鸿鹄云商的saas云平台以其独特的架构和先进的理念&#xff0c;为电商行业带来了全新的商业模式和营销策略。该平台涉及多个平台端&#xff0c;包括平台管理、商家端、买家平台、微服务平台等&#xff0c;涵盖了pc端、…

合并区间(LeetCode 56)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路参考文献 1.问题描述 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输…

虚拟机域环境的搭建

开始准备两台虚拟机Windows Server 2016和Windows10. Windows Server 2016的配置&#xff1a; 1.要用Administrator管理员&#xff0c;首先创建一个Administrator管理员 在此就可以创建一个新用户 然后退出登录就可以了。 2.开始环境的搭建 第一步&#xff1a; 第二步&…

Qt篇——QwtPainter::drawPie绘制扇形

QwtPainter::drawPie(QPainter *painter, const QRectF &rect, int startAngle, int angle); 一、参数含义&#xff1a; painter&#xff1a; 重绘函数中的painter对象 rect&#xff1a; 要绘制扇形的圆的外切矩形。 startAngle: 要绘制的扇形的起始角 …

【计算机毕业设计】SSM游戏点评网站

项目介绍 本项目分为前后台&#xff0c;前台为普通用户登录&#xff0c;后台为管理员登录&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,管理员管理,网站用户管理,游戏资讯管理,游戏类型管理,城市信息管理,竞技场管理,游戏信息管理,游戏评价信息管理等功能。…

深眸科技创新工业AI视觉系统,与机械臂协同工作实现视觉引导功能

工业AI视觉系统&#xff1a;工业AI视觉系统能够在工业环境中进行缺陷检测、视觉分拣、物流供包、拆码垛、工业上料等应用。 随着国内工业企业不断进步和发展&#xff0c;传统机器视觉无法满足企业对复杂操作流程的需求&#xff0c;多数制造企业对于机器视觉系统的需求增长。而…

苹果Mac电脑甘特图管 EasyGantt最新 for mac

EasyGantt提供直观的界面&#xff0c;让用户能够轻松创建具有时间轴视图的甘特图。你可以添加并排列任务、设置任务的开始和结束日期、调整任务之间的依赖关系等。 任务管理&#xff1a;软件允许你添加、编辑和删除任务&#xff0c;设定任务的优先级和状态&#xff0c;并为每个…

Openstack开启虚拟化嵌套

好久没写东西了&#xff0c;前两天我准备在虚机上装一个vmware 的虚机&#xff0c;结果失败了&#xff0c;提示如下&#xff0c;由于我是虚机上安装虚机&#xff0c;我的宿主机肯定是开启了vt-x和vt-d的 查了一些资料&#xff0c;这个需要打开nested,先看看nested返回是否为Y&a…

PicGo+GitHub搭建免费图床

PicGoGitHub搭建免费图床 步骤 1: 安装 PicGo步骤 2:创建图床仓库步骤 3: 配置 GitHub Token步骤 4: 配置 PicGo步骤 5: 上传图片步骤 6: 访问图片 使用 GitHub 作为图床的优势在于免费、稳定且具有版本控制功能&#xff0c;特别适合个人博客、小型项目等。PicGo作为一个开源的…

无论男孩女孩都要尽情打扮

这款柔软又细腻的开衫外套 上身体验感很不错的哈 舒适软糯百搭还透气&#xff0c;抗起球的面料 黑灰两色简单大方 胸前加上了流行的刺绣设计&#xff0c;可爱又精致 单穿内搭都可&#xff0c;现在天气还比较冷 外面可以套个羽绒服之类的 时尚叠穿风&#xff0c;韩系范儿…

c语言-位操作符练习题

文章目录 前言一、n&(n-1)的运用场景(n为整数)二、&1 和 >>的应用场景总结 前言 本篇文章介绍利用c语言的位操作符解决一些练习题&#xff0c;目的是掌握各个位操作符的使用和应用场景。 表1.1为c语言中的位操作符 操作符含义&按位与|按位或^按位异或~按位…

CSS 缩减中心动画

<template><!-- mouseenter"startAnimation" 表示在鼠标进入元素时触发 startAnimation 方法。mouseleave"stopAnimation" 表示在鼠标离开元素时触发 stopAnimation 方法。 --><!-- 容器元素 --><div class"container" mou…

HarmonyOS云开发基础认证【题库答案】

HarmonyOS应用开发者基础认证【题库答案】 HarmonyOS应用开发者高级认证【题库答案】 一、判断题 应用架构的演进依次经历了微服务架构、单体架构、Serverless架构等阶段。&#xff08;错&#xff09;鸿蒙应用可以使用Arkts开发&#xff08;对&#xff09;认证服务在绑定微信账…

python/selenium/jenkins整合

1、新建python项目&#xff0c;专门写selenium代码&#xff0c;建议用pytest框架写。 2、把代码上传到代码库中。 3、环境配置&#xff1a; 3.1 在跑jenkins的机器上配置好python环境&#xff0c;需要python --version能在任何地方运行&#xff08;配置好系统环境变量&#…