转载于:https://www.cnblogs.com/xinghuo123/p/13149650.html
上节讲了PCM设备的注册和创建,以及怎么open pcm设备 并 调用 playback或者 capture的fops
这节我们来看control 设备
1、 control设备简介
Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等.对于Mixer(混音)来说,Control接口显得尤为重要,从ALSA 0.9.x版本开始,所有的mixer工作都是通过control接口的API来实现的。其实通俗的理解control设备的作用如音量的调节,开关等。<sound/control.h>定义了所有的Control API.如果你要为你的codec实现自己的controls,请在代码中包含该头文件。
可能很多人对control 控制什么不太了解,自我的理解是对音频数据的一些控制,比如说多路音频stream合成一路的Mixer(混音) 或者对一下音频数据的增益比如mic boost 调节音量大小的spk Volume 或者哪个通路的开关switch等
== 总的来说就是控制音频的stream 流 的方向,数据,通路 开关 翻译出来就是 mixer switch volume 等等控件,那具体是怎么控制 个人理解是在codec 驱动中 设置对应寄存器来控制通路 开关 以及操作数据流 等等 (可能不一定是在codec驱动中在alsalib hal层也可以操作数据)==
---- 纯属个人理解,方便我继续看下去 ,后面讲到如果不是,将会修改 —
放一个tinymix 的图方便理解我们操作switch volume 等等 我们可以通过tinymix 命令来调节音频
2 、control设备的建立
control设备和PCM设备一样,都属于声卡下的逻辑设备.用户空间的应用程序通过alsa-lib访问该Control设备,读取或控制control的控制状态,从而达到控制音频Codec进行各种Mixer等控制操作。
control设备创建函数snd_ctl_create,定义位于\sound\core\control.c
//sm6225_android13.0_r01_r003/target/kernel_platform/msm-kernel/sound/core/control.c
/*
* create control core:
* called from init.c
*/
int snd_ctl_create(struct snd_card *card)
{
static const struct snd_device_ops ops = {
.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);
dev_set_name(&card->ctl_dev, "controlC%d", card->number);
err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); //调用snd_device_new注册control设备
if (err < 0)
put_device(&card->ctl_dev);
return err;
}
control设备的创建过程大体上和pcm设备的创建过程相同,最终调用函数snd_device_new,拿到声卡逻辑设备的设备操作结构体(设备操作结构体定义以上函数中,最重要的还是注册函数),将该设备逻辑设备加入声卡结构体devices链表中
3、 control设备的注册
/*
* registration of the control device
*/
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;
struct snd_ctl_layer_ops *lops;
int err;
err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
&snd_ctl_f_ops, card, &card->ctl_dev);
if (err < 0)
return err;
down_read(&card->controls_rwsem);
down_read(&snd_ctl_layer_rwsem);
for (lops = snd_ctl_layer; lops; lops = lops->next)
lops->lregister(card);
up_read(&snd_ctl_layer_rwsem);
up_read(&card->controls_rwsem);
return 0;
}
还是和pcm设备注册一样,最终调用函数snd_register_device。
(1)获取声卡次设备snd_minor ,并将其赋值给全局声卡次设备变量snd_minor中
(2)获取次设备pcm的文件操作结构体,供用户层使用的api回调函数
control的文件操作结构体定义如下:
static const struct file_operations snd_ctl_f_ops =
{
.owner = THIS_MODULE,
.read = snd_ctl_read,
.open = snd_ctl_open,
.release = snd_ctl_release,
.llseek = no_llseek,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl,
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,
};
4、control设备的打开
用户程序需要打开control设备时,驱动程序通过snd_minors[]全局数组和此设备号,可以获得snd_ctl_f_ops结构中的各个回调函数,然后通过这些回调函数访问control中的信息和数据(最终会调用control的几个回调函数get,put,info)。具体参考pcm设备的打开。
还是根据open(/dev/snd/control0) 通过snd_open 加上设备路劲名获取到次设备好,并使用control的fops替换ops执行声卡control设备的文件操作结构体。
5、controls的定义
要自定义一个Control,我们首先要定义3各回调函数:info,get和put.然后,定义一个snd_kcontrol_new结构
1 static struct snd_kcontrol_new my_control __devinitdata = {
2 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
3 .name = "PCM Playback Switch",
4 .index = 0,
5 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
6 .private_value = 0xffff,
7 .info = my_control_info,
8 .get = my_control_get,
9 .put = my_control_put
10 };
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提供元数据.
== 这个name就是 我们在adb 中使用tinymix命令打印出来的 name ==
在高通SM6225中他使用的codec是WDC937X 他使用SOC_SINGLE_EXT 等一些宏
#define SND_SOC_BYTES(xname, xbase, xregs) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_bytes_info, .get = snd_soc_bytes_get, \
.put = snd_soc_bytes_put, .private_value = \
((unsigned long)&(struct soc_bytes) \
{.base = xbase, .num_regs = xregs }) }
可以看到大部分都是mixer
赋值给.name .interfance .idx 和.get .put 最重要的是实现.get和put函数
6 、Control的名字
control的名字需要遵循一些标准,通常可以分成3部分来定义control的名字:源–方向–功能.
1、源 可以理解为该control的输入端,alsa已经预定义了一些常用的源,例如:Master,PCM,CD,Line等等.
2、方向 代表该control的数据流向,例如:Playback,Capture,Bypass,Bypass Capture等等,也可以不定义方向,这时表示该Control是双向的 (playback和capture).
3、功能 根据control的功能,可以是以下字符串:Switch,Volume,Route等等
也有一些命名上的特例:
4、全局的capture和playback “Capture Source”,“Capture Volume”,“Capture Switch”,它们用于全局的capture source,switch和volume.同 理,“Playback Volume”,“Playback Switch”,它们用于全局的输出switch和volume.
5、Tone-controles 音调控制的开关和音量命名为:Tone Control - XXX,例如,“Tone Control - Switch”,“Tone Control - Bass”,“Tone Control - Center”.
6、3D controls 3D控件的命名规则:,“3D Control - Switch”,“3D Control - Center”,“3D Control - Space”.
7、Mic boost 麦克风音量加强控件命名为:“Mic Boost"或"Mic Boost(6dB)”.
7 创建Controls 控件
我们在第六节定义结构体 snd_kcontrol_new 定义了很多control控件有“HPHL voumle” 耳机左耳音量等等,但是我们只是定义了。我们还要把这结构体创建好。
当把以上讨论的内容都准备好了以后,我们就可以创建我们自己的control了.alsa-driver为我们提供了两个用于创建control的API:
snd_ctl_add()
我们可以用以下最简单的方式创建control:
1 err = snd_ctl_add(card, snd_ctl_new1(&wcd937x_snd_controls, chip));
2 if (err < 0)
3 return err;
这里wcd937x_snd_controls是我们SM6225 使用的codec wcd937x 驱动中 snd_kcontrol_new对象,chip对象将会被赋值在kcontrol->private_data字段,该字段可以在回调函数中访问.
snd_ctl_new1()会分配一个新的snd_kcontrol实例,并把my_control中相应的值复制到该实例中,所以,在定义my_control时,通常我们可以加上__devinitdata前缀.snd_ctl_add则把该control绑定到声卡对象card当中.
snd_ctl_new1
/**
* snd_ctl_new1 - create a control instance from the template
* @ncontrol: the initialization record
* @private_data: the private data to set
*
* Allocates a new struct snd_kcontrol instance and initialize from the given
* template. When the access field of ncontrol is 0, it's assumed as
* READWRITE access. When the count field is 0, it's assumes as one.
*
* Return: The pointer of the newly generated instance, or %NULL on failure.
*/
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
void *private_data)
{
struct snd_kcontrol *kctl;
unsigned int count;
unsigned int access;
int err;
if (snd_BUG_ON(!ncontrol || !ncontrol->info))
return NULL;
count = ncontrol->count;
if (count == 0)
count = 1;
access = ncontrol->access;
if (access == 0)
access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_VOLATILE |
SNDRV_CTL_ELEM_ACCESS_INACTIVE |
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK |
SNDRV_CTL_ELEM_ACCESS_LED_MASK |
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK);
err = snd_ctl_new(&kctl, count, access, NULL);
if (err < 0)
return NULL;
/* The 'numid' member is decided when calling snd_ctl_add(). */
kctl->id.iface = ncontrol->iface;
kctl->id.device = ncontrol->device;
kctl->id.subdevice = ncontrol->subdevice;
if (ncontrol->name) {
strscpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name));
if (strcmp(ncontrol->name, kctl->id.name) != 0)
pr_warn("ALSA: Control name '%s' truncated to '%s'\n",
ncontrol->name, kctl->id.name);
}
kctl->id.index = ncontrol->index;
kctl->info = ncontrol->info;
kctl->get = ncontrol->get;
kctl->put = ncontrol->put;
kctl->tlv.p = ncontrol->tlv.p;
kctl->private_value = ncontrol->private_value;
kctl->private_data = private_data;
return kctl;
}
EXPORT_SYMBOL(snd_ctl_new1);
以上就创建和定义好了一些控件control
但是每个控件都要实现对应功能。所以我们得实现get 和put函数和一下控件属性得定义
8、回调函数
5 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
6 .private_value = 0xffff,
7 .info = my_control_info,
8 .get = my_control_get,
9 .put = my_control_put
8.1 .info回调函数
nfo回调函数用于获取control的详细信息.它的主要工作就是填充通过参数传入的snd_ctl_elem_info对象,以下例子是一个具有单个元素的boolean型control的info回调:
int snd_soc_bytes_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct soc_bytes *params = (void *)kcontrol->private_value;
uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
uinfo->count = params->num_regs * component->val_bytes;
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_bytes_info);
type字段指出该control的值类型,值类型可以是BOOLEAN, INTEGER, ENUMERATED, BYTES,IEC958和INTEGER64之一
.count字段指出了改control中包含有多少个元素单元,比如,立体声的音量control左右两个声道的音量值,它的count字段等于2
.value字段是一个联合体(union),value的内容和control的类型有关.其中,boolean和integer类型是相同的.
alsa已经为我们实现了一些通用的info回调函数,例如:snd_ctl_boolean_mono_info(),snd_ctl_boolean_stereo_info()等等.
8.2 .get .put回调函数
我们来看
SOC_ENUM_EXT(“RX HPH Mode”, rx_hph_mode_mux_enum,
wcd937x_rx_hph_mode_get, wcd937x_rx_hph_mode_put),
这个函数得得get和put函数 这个看name应该是耳机 mode的选择的函数
static int wcd937x_rx_hph_mode_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component =
snd_soc_kcontrol_component(kcontrol);
struct wcd937x_priv *wcd937x = snd_soc_component_get_drvdata(component);
ucontrol->value.integer.value[0] = wcd937x->hph_mode;
return 0;
}
static int wcd937x_rx_hph_mode_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component =
snd_soc_kcontrol_component(kcontrol);
struct wcd937x_priv *wcd937x = snd_soc_component_get_drvdata(component);
u32 mode_val;
mode_val = ucontrol->value.enumerated.item[0];
dev_dbg(component->dev, "%s: mode: %d\n", __func__, mode_val);
if (mode_val == 0) {
dev_warn(component->dev, "%s:Invalid HPH Mode, default to class_AB\n",
__func__);
mode_val = 3; /* enum will be updated later */
}
wcd937x->hph_mode = mode_val;
return 0;
}
可以看到只设置和读取Mode值得函数
如上述例子所示,当control的值被改变时,put回调必须要返回1,如果值没有被改变,则返回0.如果发生了错误,则返回一个负数的错误号.
和get回调一样,当control的count大于1时,put回调也要处理多个control中的元素值.
**8.3 .access **
访问标识,该值表示该控件得访问类型 有可读写,自读 等等。。。
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的值.
9 元数据(Metadata)
很多mixer control需要提供以dB为单位的信息,我们可以使用DECLARE_TLV_xxx宏来定义一些包含这种信息的变量,然后把control的tlv.p字段指向这些变量,最后,在access字段中加上SNDRV_CTL_ELEM_ACCESS_TLV_READ标志,就像这样:
1 static DECLARE_TLV_DB_SCALE(db_scale_my_control, -4050, 150, 0);
2
3
4 static struct snd_kcontrol_new my_control __devinitdata = {
5 ...
6 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
7 SNDRV_CTL_ELEM_ACCESS_TLV_READ,
8 ...
9 .tlv.p = db_scale_my_control,
10 };
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个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据.