----------------------------------------------------------------------------------------------------------------------------
开发板 :NanoPC-T4开发板eMMC :16GBLPDDR3 :4GB显示屏 :15.6英寸HDMI接口显示屏u-boot :2023.04linux :6.3----------------------------------------------------------------------------------------------------------------------------
Control是音频驱动中用来表示用户可操作的音频参数或功能的抽象。它可以是音量控制、混音控制(Mixer)、开关控制(Mux)等。Control 提供了一个统一的接口,使用户能够通过音频设备驱动程序来管理和调整音频参数。
ALSA CORE已经实现了Control中间层,在include/sound/control.h中定义了所有的Control API.,如果你要为你的Codec实现自己的控件,请在代码中包含该头文件。
需要注意的是:这里说的Control设备中的Control表示的是控制的意思;而后文提到的controls/control/kcontrol表示的是控件的意思,其主要实现控制声卡的音量,混音等一系列控制,可以理解为switch,
一、Control设备
1.1 创建Control设备
Control设备和PCM设备一样,都属于声卡下的逻辑设备。用户空间的应用程序通过alsa-lib访问该Control设备,读取或控制控件的控制状态,从而达到控制音频Codec进行各种Mixer等控制操作。
Control设备的创建过程大体上和PCM设备的创建过程相同。对于创建 PCM设备只需要在驱动初始化时主动调用snd_pcm_new函数创建,而Control设备则用snd_ctl_create创建。不过由于 snd_card_create函数中已经会调用 snd_ctl_create函数创建Control设备,故我们无需显示地创建Control设备,只要建立声卡,Control设备则被自动地创建。
我们来看一下snd_ctl_create到底做了什么。函数定义在sound/core/control.c;
/*
* create control core:
* called from init.c
*/
int snd_ctl_create(struct snd_card *card) // 传入声卡设备
{
static struct snd_device_ops ops = { // 声卡Control设备操作集
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
int err;
if (snd_BUG_ON(!card))
return -ENXIO;
if (snd_BUG_ON(card->number < 0 || card->number >= SNDRV_CARDS)) // 声卡设备编号无效
return -ENXIO;
snd_device_initialize(&card->ctl_dev, card); // 初始化card->ctl_dev控制设备,设置class为sound_class,parent为card->card_dev
dev_set_name(&card->ctl_dev, "controlC%d", card->number); // 为控制设备分配一个名词 controlC%d
err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); // 创建一个新的snd_device实例,并将添加到声卡设备的devices链表中
if (err < 0)
put_device(&card->ctl_dev);
return err;
}
这个函数主要只做了两件事情:
- 调用snd_device_initialize初始化声卡的控制设备,也就是card->ctrl_dev;这里设置控制设备的parent为声卡设备card->card_dev,class为sound_class;
- 调用snd_device_new为控制设备分配一个snd_device实例,并添加到声卡设备的逻辑设备链表devices中;
1.1.1 snd_device_initialize
snd_device_initialize函数定义在sound/core/init.c,用于初始化struct device 结构体的各种成员变量并分配合适的资源;
/**
* snd_device_initialize - Initialize struct device for sound devices
* @dev: device to initialize
* @card: card to assign, optional
*/
void snd_device_initialize(struct device *dev, struct snd_card *card)
{
device_initialize(dev);
if (card)
dev->parent = &card->card_dev;
dev->class = sound_class;
dev->release = default_release;
}
1.1.2 snd_ctl_dev_register
在注册声卡设备card时会遍历声卡设备的逻辑设备链表devices,并调用声卡逻辑设备操作集中的dev_register函数,对于Control设备也就是snd_ctl_dev_register函数;
我们最后来看一下Control设备的操作集:
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
这些回调函数都是定义在sound/core/control.c,以snd_ctl_dev_register函数为例,该回调函数建立了和用户空间应用程序(alsa-lib)通信所用的设备文件节点:/dev/snd/controlC%d;
/*
* registration of the control device
*/
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;
return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, // 注册Control设备
&snd_ctl_f_ops, card, &card->ctl_dev);
}
dev_free、dev_disconnnet我们不是特别关心,忽略就即可:
/*
* disconnection of the control device
*/
static int snd_ctl_dev_disconnect(struct snd_device *device)
{
struct snd_card *card = device->device_data;
struct snd_ctl_file *ctl;
read_lock(&card->ctl_files_rwlock); // 读写自旋锁
list_for_each_entry(ctl, &card->ctl_files, list) { // 遍历ctl_files链表
wake_up(&ctl->change_sleep);
kill_fasync(&ctl->fasync, SIGIO, POLL_ERR);
}
read_unlock(&card->ctl_files_rwlock); // 释放锁
return snd_unregister_device(&card->ctl_dev); // 卸载声卡逻辑设备
}
/*
* free all controls
*/
static int snd_ctl_dev_free(struct snd_device *device)
{
struct snd_card *card = device->device_data;
struct snd_kcontrol *control;
down_write(&card->controls_rwsem); // 获取读写信号量
while (!list_empty(&card->controls)) { // 遍历控制链表
control = snd_kcontrol(card->controls.next);
snd_ctl_remove(card, control);
}
up_write(&card->controls_rwsem); // 释放读写信号量
put_device(&card->ctl_dev);
return 0;
}
二、kcontrol
kcontrol其实是一种控件,其主要实现控制声卡的音量,混音等一系列控制,可以理解为switch。
kcontrol对应的数据结构是snd_kcontrol_new,这些snd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_component_controls函数注册到系统中,用户空间就可以通过amixer或alsamixer等工具查看和设定这些控件的状态。
实际上除了snd_kcontrol_new结构外,还有一个snd_kcontrol结构,snd_kcontrol_new更像是kcontrol的模板,而snd_kcontrol才是真正的kcontrol。
控件的创建步骤如下:(1)定义snd_kcontrol_new数组;
(2)通过snd_soc_add_component_controls根据snd_kcontrol_new数组创建并添加多个kcontrol到声卡card的controls链表;
2.1 数据结构
2.1.1 snd_kcontrol_new
struct snd_kcontrol_new定义在include/sound/control.h文件中:
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 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:控件的类型,alsa定义了几种类型(SNDDRV_CTL_ELEM_IFACE_XXX),常用的类型是 MIXER,当然也可以定义属于全局的 CARD 类型,也可以定义属于某类设备的类型,例如 HWDEP,PCMRAWMIDI,TIMER 等,这时需要在 device 和 subdevice 字段中指出声卡的设备逻辑编号;
- name :控件的名字,从ALSA 0.9.x开始,控件的名字是变得比较重要,因为控件的作用是按名字来归类的。ALSA 已经预定义了一些控件的名字,我们在后面的章节中会详细讨论;
- index:控件的在该声卡中的编号。如果声卡中有不止一个codec,每个codec中有相同名字的控件,这时我们可以通过index来区分这些控件。当index 为 0 时,则可以忽略这种区分策略;
- access:控件的访问类型。每一个 bit 代表一种访问类型,这些访问类型可以多个“或”运算组合在一起;
- private_value:根据不同的控件类型有不同的意义,比如对于普通的控件,private_value字段可以用来定义该控件所对应的寄存器的地址以及对应的控制位在寄存器中的位置信息;
- tlv:控件元数据;
- get:用于获取控件当前的状态值;
- put:用于设置控件的状态值;
2.1.2 关系图
为了更加清晰的了解struct snd_kcontrol_new、struct snd_kcontrol 等数据结构的关系,我们绘制了如下关系图: