平台:ubuntu 16.04,kernel版本是4.15.0, 理论任何平台都可以,甚至是android,只要能编译通过。
需要完成的功能:前几篇文章完成了播放/录音功能,声卡驱动就这样完成了吗?某种意义上讲是完成了,但是如果需要控制音量怎么办?这里加一个kcontrol,实现音量控制功能。
目的:就像做数学题一样,看一遍答案,以为自己看懂了,就会了,非也,真到自己去做时,不一定能做出来。那就在自己的驱动里实现一遍。
本文只追求应用,不讲原理。想了解细节可以看官方文档或者看https://blog.csdn.net/droidphone/category_1118446.html。
步骤1:定义snd_kcontrol_new
static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -11925, 75, 0);
static const struct snd_kcontrol_new vcodec_codec_controls[] = {
SOC_DOUBLE_TLV("DAC volume", VCODEC_DAC_VOL_CTRL, DAC_VOL_L, DAC_VOL_R,
0xFF, 0, dac_vol_tlv),
};
这里定义了个数组,成员是snd_kcontrol_new。
SOC_DOUBLE_TLV是一个宏,作用就是填充snd_kcontrol_new,如下:
代码位置:include/sound/soc.h
#define SOC_DOUBLE_TLV(xname, reg, shift_left, shift_right, 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_DOUBLE_VALUE(reg, shift_left, shift_right, \
max, invert, 0) }
参数说明:
-
iface:控件类型,一般是SNDRV_CTL_ELEM_IFACE_XXX,对于mixer就是SNDRV_CTL_ELEM_IFACE_MIXER,对于不属于mixer的全局控制,使用CARD;如果关联到某类设备,则是PCM、RAWMIDI、TIMER或SEQUENCER;
-
name:控件的名字;
-
access:访问权限,SNDRV_CTL_ELEM_ACCESS_TLV_READ就是只读读权限,SNDRV_CTL_ELEM_ACCESS_READWRITE就是读写权限;
-
tlv.p:用来描述寄存器的设定值与它所代表的实际意义之间的映射关系,最常用的就是用于音量控件时,设定值与对应的dB值之间的映射关系,比如这里定义的:
static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -11925, 75, 0);
DECLARE_TLV_DB_SCALE是一个宏定义,它所代表的值按一个固定的dB值的步长变化,第一个参数是要定义变量的名字,第二个参数是最小值,以0.01dB为单位。第三个参数是变化的步长,也是以0.01dB为单位。如果该control处于最小值时会做出mute时,需要把第四个参数设为1;
什么意思呢?
寄存器的最小值对应是-119.25dB,寄存器每增加一,对应的dB数增加是0.75dB(0.01dB*75);由上可知,该控件可设置的最大值为0xFF,所以dB的数值范围是
-119.25dB
~-119.25+255*0.75dB
; -
info:一个回调函数,作用就是获取该控件的信息;
-
get:一个回调函数,作用就是获取控件对应寄存器的值;
-
put:一个回调函数,作用就是设置控件对应寄存器的值;
-
private_value:私有数据,这是给上面3个回调函数传参的,看一下它的参数:
-
reg:该控件对应的寄存器的地址;
-
shift_left:左声道控制位在寄存器中的位移;
-
shift_right:右声道控制位在寄存器中的位移;
-
max:控件可设置的最大值;
-
invert:设定值是否逻辑取反;
-
另外,SOC_DOUBLE_TLV是控制双声道音量,单声道可用SOC_SINGLE_TLV。
到这你可能会有以下问题:
问题一:我们实现的是虚拟声卡,哪来的寄存器?
没错,我们要实现的是不涉及硬件操作的虚拟声卡,但是kcontrol是要操作寄存器的,怎么办呢?这么办:
先定义一些寄存器,如下:
enum reg {
VCODEC_DAC_VOL_CTRL,
VCODEC_CTRL_NUM
};
然后定义个数组,如下:
static u32 reg_data[VCODEC_CTRL_NUM];
这个数据就是虚拟出来的寄存器了,u32类型占4字节,所以还是32位的寄存器。
问题二:snd_soc_get_volsw()/snd_soc_put_volsw()都不是我们自己定义的寄存器,怎么操作寄存器?
一般编解码芯片跟主控是通过I2C、USB等接口传递控制参数,asoc-core层是不知道的,也不关心,当它需要传递控制参数时,就调用codec的read/weite函数。所以snd_soc_get_volsw()/snd_soc_put_volsw()最终调用的是snd_soc_codec_driver里的read/write函数来操作寄存器, 定义如下:
static struct snd_soc_codec_driver soc_vcodec_drv = {
...
.read = vcodec_reg_read,
.write = vcodec_reg_write,
...
};
vcodec_reg_read/vcodec_reg_write实现对寄存器(reg_data)的读写操作。
步骤2:注册
ret = snd_soc_add_codec_controls(codec, vcodec_codec_controls,
ARRAY_SIZE(vcodec_codec_controls));
vcodec_codec_controls的成员最终会注册到card的controls链表中,看一下调用流程:
snd_soc_add_codec_controls() -->
snd_soc_add_component_controls -->
snd_soc_add_controls -->
for (i = 0; i < num_controls; i++) //for循环注册每一个control
const struct snd_kcontrol_new *control = &controls[i];
//将kcontrol注册到card的controls链表中,data就是codec
//snd_soc_cnew()函数将kcontrol_new转换成kcontrol
err = snd_ctl_add(card, snd_soc_cnew(control, data,
control->name, prefix));
}
步骤3:测试
在ubuntu下测试需要另外安装驱动,如下:
sudo insmod /lib/modules/4.15.0-112-generic/kernel/sound/core/snd-compress.ko
sudo insmod /lib/modules/4.15.0-112-generic/kernel/sound/core/snd-pcm-dmaengine.ko
sudo insmod /lib/modules/4.15.0-112-generic/kernel/sound/soc/snd-soc-core.ko
为什么是/lib/modules/4.15.0-112-generic/kernel/sound/core/
这个目录,请看前几篇文章。
然后安装我们的驱动
sudo insmod vplatform.ko
sudo insmod vcodec.ko
sudo insmod vmachine.ko
如果你是在开发板上测试,就不需要这么麻烦,直接安装驱动
安装完驱动之后,就可以看到自己的声卡,如下:
vbox@vbox-pc:~$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: I82801AAICH [Intel 82801AA-ICH], device 0: Intel ICH [Intel 82801AA-ICH]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: mycodec [my-codec], device 0: MY-CODEC vcodec_dai-0 []
Subdevices: 1/1
Subdevice #0: subdevice #0
可见我们注册的是card 1,ubuntu本身有自己的声卡,再看看自己注册的controls
vbox@vbox-pc:~$ amixer -D hw:1 contents
numid=1,iface=MIXER,name='DAC volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=255,step=0
: values=50,60
| dBscale-min=-119.25dB,step=0.75dB,mute=0
ps:如果你是在android下测试,就用tinymix命令。
同样可以使用以下命令看当前音量值
vbox@vbox-pc:~$ amixer -D hw:1 cget numid=1
numid=1,iface=MIXER,name='DAC volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=255,step=0
: values=50,60
| dBscale-min=-119.25dB,step=0.75dB,mute=0
设置一下音量
vbox@vbox-pc:~$ amixer -D hw:1 cset numid=1 30,40
numid=1,iface=MIXER,name='DAC volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=255,step=0
: values=30,40
| dBscale-min=-119.25dB,step=0.75dB,mute=0
就那么简单,完事。
代码
代码位置:https://gitcode.net/u014056414/myalsa/
后续会增加其他的功能,完成本文的提交是: 8.加一个kcontrol, 模拟音量控制
初学者可以按照此提交学习,以免新提交干扰。