8.声卡驱动05-自己实现alsa驱动-虚拟声卡-kcontrol

平台: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://codechina.csdn.net/u014056414/myalsa

后续会增加其他的功能,完成本文的提交是: 8.加一个kcontrol, 模拟音量控制

初学者可以按照此提交学习,以免新提交干扰。

你可能感兴趣的:(linux驱动觉醒之路,linux,嵌入式,alsa)