SM6225 linux-alsa详解(三)_control设备

转载于: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个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值