平台:ubuntu 16.04,kernel版本是4.15.0, 理论任何平台都可以,甚至是android,只要能编译通过。

需要完成的功能:一个codec里面可能有多个录音通道,如果要打开某一通道录音,需要怎么做?

目的:就像做数学题一样,看一遍答案,以为自己看懂了,就会了,非也,真到自己去做时,不一定能做出来。那就在自己的驱动里实现一遍。

本文只追求应用,不讲原理。想了解细节可以看官方文档或者看https://blog.csdn.net/droidphone/category_1118446.html。

widget是干嘛用的

widget也可以控制寄存器。那问题来了,有了kcontrol,为什么还要有widget?

widget是kcontrol的plus,可以理解为是kcontrol的进一步升级和封装,它同样是指音频系统中的某个部件,比如mixer,mux,输入输出引脚,电源供应器等等。

kcontrol还有以下几点不足:

  • 只能描述自身,无法描述各个kcontrol之间的连接关系;
  • 没有相应的电源管理机制;
  • 没有相应的时间处理机制来响应播放、停止、上电、下电等音频事件;
  • 为了防止pop-pop声,需要用户程序关注各个kcontrol上电和下电的顺序;
  • 当一个音频路径不再有效时,不能自动关闭该路径上的所有的kcontrol;

所以,widget诞生了。

像地图一样的codec

以下是全志A33 内部codec的模块图

像不像一幅地图。

当录音从mic1输入,经过各个mixer/mux,就像经过各个关卡一样,最终到达ADCL,模拟信号转换成数字信号。

条条大道通罗马,通往罗马的道路千万条,但是我们只需要走其中一条,没必要全部都走一遍;同样的,能到达ADCL的信号,可以是mic1、mic2或者其他的,但是当我只需要mic1输入时,其他路就不需要使能。需要被使能的这一条路,称之为complete path。

目的就是为了省电。

构造两条complete path

为更好理解dapm,在自己的虚拟声卡驱动里构造两条complete path。

先看看路线图,如下:

路线一:mic1 --> kcontrol(一个开关) --> mixer --> ADCL, 就是个录音

路线二:DACL --> HPOUTL --> speaker(功放), 就是个播放

下面看看代码实现

步骤一:定义widget

static const struct snd_kcontrol_new mic1_input_mixer[] = {SOC_DAPM_SINGLE("MIC1 Boost Switch", VCODEC_ADCL_REG, 0, 1, 0),
};static const struct snd_soc_dapm_widget vcodec_dapm_widgets[] = {SND_SOC_DAPM_INPUT("MIC1"),SND_SOC_DAPM_MIXER("ADCL Input", SND_SOC_NOPM, 0, 0,mic1_input_mixer,ARRAY_SIZE(mic1_input_mixer)),SND_SOC_DAPM_AIF_OUT_E("ADCL", "Capture", 0, VCODEC_ADCL_REG,8, 0,vcodec_capture_event,SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_AIF_IN_E("DACL", "Playback", 0, VCODEC_DAC_REG,0, 0,vcodec_playback_event,SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_OUTPUT("HPOUTL"),SND_SOC_DAPM_SPK("HpSpeaker", vcodec_hpspeaker_event),
};

这是个数组,成员是snd_soc_dapm_widget,通过各种宏定义来填充不同类型的widget;

  • SND_SOC_DAPM_INPUT:该widget对应一个输入引脚;一条完整的dapm音频路径,必然有起点和终点,我们把位于这些起点和终点的widget称之为端点widget。明显mic1是起点,ADCL是终点;

  • SND_SOC_DAPM_MIXER:该widget对应一个mixer控件,看一下宏定义:

    #define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \wcontrols, wncontrols)\
    {   .id = snd_soc_dapm_mixer, .name = wname, \SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \.kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
    

    参数说明:

    • id:表示该widget的类型,snd_soc_dapm_mixer就是一个mixer控件;
    • wname:该widget名称;
    • wreg:该widget对应的寄存器控制位,用于控制widget的电源状态的,没有就是设置成SND_SOC_NOPM;
    • winvert:设定值是否逻辑取反;
    • wcontrols:kcontrol,表示开关,一个mixer可能有多路输入,一个kcontrol表示一个开关,就是上图的m;
    • wncontrols:kcontrol个数;
  • SND_SOC_DAPM_AIF_OUT_E:对应一个数字音频输出接口,看一下宏定义:

    #define SND_SOC_DAPM_AIF_OUT_E(wname, stname, wslot, wreg, wshift, winvert, \wevent, wflags)             \
    {   .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \.event = wevent, .event_flags = wflags }
    

    stname必须要和codec dai_driver 的stream_name一样;

    心细如发的你肯定发现了这个宏后面带了“_E”后缀,相对没带“_E”后缀的多了wevent和wflags参数,wevent用于保存该widget的事件回调函数,同时,wflags用于保存该widget需要关心的dapm事件种类,只有wflags中相应的事件位被设置了的事件才会发到event回调函数中进行处理。dapm有哪些事件种类呢?

    事件类型 说明
    SND_SOC_DAPM_PRE_PMU widget要上电前发出的事件
    SND_SOC_DAPM_POST_PMU widget要上电后发出的事件
    SND_SOC_DAPM_PRE_PMD widget要下电前发出的事件
    SND_SOC_DAPM_POST_PMD widget要下电后发出的事件
    SND_SOC_DAPM_PRE_REG 音频路径设置之前发出的事件
    SND_SOC_DAPM_POST_REG 音频路径设置之后发出的事件
    SND_SOC_DAPM_WILL_PMU 在处理up_list链表之前发出的事件
    SND_SOC_DAPM_WILL_PMD 在处理down_list链表之前发出的事件
    SND_SOC_DAPM_PRE_POST_PMD SND_SOC_DAPM_PRE_PMD和
    SND_SOC_DAPM_POST_PMD的合并
  • SND_SOC_DAPM_OUTPUT:该widget对应一个输出引脚;

  • SND_SOC_DAPM_SPK:该widget对应一个扬声器;

步骤二:注册widgets

snd_soc_dapm_new_controls(dapm, vcodec_dapm_widgets,ARRAY_SIZE(vcodec_dapm_widgets));

widgets最终会加入到card的widgets链表

步骤三:设置route

static const struct snd_soc_dapm_route vcodec_dapm_routes[] = {/* Mic input route */{"ADCL Input", "MIC1 Boost Switch", "MIC1"},{"ADCL", NULL, "ADCL Input"},/* Headphone output route */{"HPOUTL", NULL, "DACL"},{"HpSpeaker", NULL, "HPOUTL"},};

一个widget是有输入和输出的,widget之间是可以动态地进行连接的,连接两个widget的“线”就是path,它把一个widget的输出端和另一个widget的输入端连接在一起;

那route是干嘛用的?可以理解为,route就是用来生成path的,route指定了输出端的widget和输入端的widget,然后生成连接两widget的path;

比如上面的数组,“ADCL Input”是目的widget,"MIC1 Boost Switch"是两个widget之间的开关(kcontrol),没有就是NULL,"MIC1"就是起始widget;

步骤四:注册routes

snd_soc_dapm_add_routes(dapm, vcodec_dapm_routes,ARRAY_SIZE(vcodec_dapm_routes));

snd_soc_dapm_add_routes()函数所做的事情,简单点来说就是,先找到route指定的目的widget、起始widget和开关(kcontrol),生成path,然后将path添加到card的paths链表。

怎么用

dapm要给一个widget上电的其中一个前提条件是:这个widget位于一条完整的音频路径上,而一条完整的音频路径的两头,必须是输入/输出引脚,或者是一个外部音频设备,又或者是一个处于激活状态的音频流widget;

以刚定义的widget和route为例,"MIC1"是输入端点widget, "ADCL"是输出端点widget,那么

"MIC1" --> "ADCL Input" --> "ADCL"就是一条complete path(完整的音频路径),如果这条complete path的开关(“MIC1 Boost Switch”)被应用使能了,在打开录音时,这条complete path上的所有widget都会被上电;

同理"DACL" --> "HPOUTL" --> "HpSpeaker"是一条complete path,因为这条complete path没有开关,所以在打开播放的时候,这条complete path上的所有widget都会被上电,当然了,widget设置的event函数也会在此时调用。反之,结束播放的时候,这条complete path上的所有widget都会被下电,widget设置的event函数也会被调用;

可以看看这篇文章:ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身

怎么知道什么时候给一条complete path上电/下电,当然是去扫描一下了,

以下几种情况可以触发dapm发起一次扫描操作:

  1. 声卡初始化阶段;
  2. 用户空间修改了kcontrol的配置值;
  3. pcm的打开或关闭;
  4. 驱动改变widget并把它加入到dapm_dirty链表。

测试

在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/1Subdevice #0: subdevice #0
card 1: mycodec [my-codec], device 0: MY-CODEC vcodec_dai-0 []Subdevices: 1/1Subdevice #0: subdevice #0

可见我们注册的是card 1,ubuntu本身有自己的声卡,再看看自己注册的controls

vbox@vbox-pc:~$ amixer -D hw:1 contents
numid=2,iface=MIXER,name='ADCL Input MIC1 Boost Switch'; type=BOOLEAN,access=rw------,values=1: values=on
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

诶,没看到注册widget,是注册失败了吗?并没有

可以看一下/sys/kernel/debug/asoc/my-codec/codec:vcodec.0/dapm目录

vbox@vbox-pc:~$ sudo ls -l /sys/kernel/debug/asoc/my-codec/codec\:vcodec\.0/dapm
total 0
-r--r--r-- 1 root root 0 11月 15 18:58 ADCL
-r--r--r-- 1 root root 0 11月 15 18:58 ADCL Input
-r--r--r-- 1 root root 0 11月 15 18:58 bias_level
-r--r--r-- 1 root root 0 11月 15 18:58 Capture
-r--r--r-- 1 root root 0 11月 15 18:58 DACL
-r--r--r-- 1 root root 0 11月 15 18:58 HPOUTL
-r--r--r-- 1 root root 0 11月 15 18:58 HpSpeaker
-r--r--r-- 1 root root 0 11月 15 18:58 MIC1
-r--r--r-- 1 root root 0 11月 15 18:58 Playback

ps:ubuntu下要root权限才能看到

可以去打开一下’ADCL Input MIC1 Boost Switch’

vbox@vbox-pc:~$ amixer -D hw:1 cset numid=2 on
numid=2,iface=MIXER,name='ADCL Input MIC1 Boost Switch'; type=BOOLEAN,access=rw------,values=1: values=on

你会发现,这个kcontrol的名字变了,定义时是"MIC1 Boost Switch",现在变成了"ADCL Input MIC1 Boost Switch";

如果此时正在录音,那么对应的complete path上的所有widget都会被上电,widget的event函数也会被调用;

看看以下log,会不会清楚一些

[ 2294.464437] vplat_pcm_open,line:235
[ 2294.464441] -vcodec_startup,line:193
[ 2294.464630] my_card_hw_params,rate:48000
[ 2294.464632] -vcodec_hw_params,line:205
[ 2294.464633] playback period size : 32768
[ 2294.464667] -vcodec_prepare,line:247
[ 2294.464697] -vcodec_playback_event,SND_SOC_DAPM_PRE_PMU
[ 2294.464700] -vcodec_reg_read,line:163,reg_data[2] = 0x0
[ 2294.464701] -vcodec_reg_write,line:171,reg=0x2,val=0x1
[ 2294.464703] -vcodec_hpspeaker_event,SND_SOC_DAPM_POST_PMU
[ 2294.466124] vplat_pcm_open,line:235
[ 2294.466127] -vcodec_startup,line:193
[ 2294.466251] my_card_hw_params,rate:48000
[ 2294.466253] -vcodec_hw_params,line:205
[ 2294.466254] capture period size : 32768
[ 2294.466289] -vcodec_prepare,line:247
[ 2294.466314] -vcodec_reg_read,line:163,reg_data[1] = 0x1
[ 2294.466315] -vcodec_reg_write,line:171,reg=0x1,val=0x101
[ 2294.466317] -vcodec_capture_event,SND_SOC_DAPM_POST_PMU
[ 2294.466336] -vcodec_prepare,line:247
[ 2294.466355] -vcodec_prepare,line:247
[ 2294.473240] -vcodec_trigger: catpure start
[ 2294.473243] capture running...
[ 2294.492381] -vcodec_trigger: playback start
[ 2294.492383] playback running...
[ 2304.140557] -vcodec_trigger:playback stop
[ 2304.140559] playback stop...
[ 2304.358123] -vcodec_shutdown,line:212
[ 2304.358126] vplat_pcm_close,line:248
[ 2304.358150] -vcodec_hpspeaker_event,SND_SOC_DAPM_PRE_PMD
[ 2304.358153] -vcodec_reg_read,line:163,reg_data[2] = 0x1
[ 2304.358155] -vcodec_reg_write,line:171,reg=0x2,val=0x0
[ 2304.358156] -vcodec_playback_event,SND_SOC_DAPM_POST_PMD
[ 2304.358178] -vcodec_trigger:catpure stop
[ 2304.358179] capture stop...
[ 2304.358235] -vcodec_shutdown,line:212
[ 2304.358236] vplat_pcm_close,line:248
[ 2304.358250] -vcodec_reg_read,line:163,reg_data[1] = 0x101
[ 2304.358252] -vcodec_reg_write,line:171,reg=0x1,val=0x1
[ 2304.358253] -vcodec_capture_event,SND_SOC_DAPM_POST_PMD

代码

代码位置:https://codechina.csdn.net/u014056414/myalsa

后续会增加其他的功能,完成本文的提交是: 9.加widget, 模拟codec的两条complete path

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

8.声卡驱动06-自己实现alsa驱动-虚拟声卡-widget相关推荐

  1. ubuntu14.04安装oss音频驱动,替换掉alsa驱动

    环境: ubuntu14.04 64位 前言: 由于不知道怎么操作alsa驱动下的音频设备,所以使用oss替换掉alsa.替换之后,就可以使用open函数打开"/dev/dsp"设 ...

  2. 虚拟服务器声卡,如何使用虚拟声卡?虚拟声卡安装教程!

    在没有声卡的机器上播放音频和视频文件时,将出现诸如"找不到音频设备"的提示,并且无法播放. 虚拟声卡是一种软件的名称,可用于在没有声卡的机器上实现诸如声音回放之类的功能. 如何使用 ...

  3. 用ALSA驱动声卡流程详解

    作者:北南南北 来自:LinuxSir.Org 提要:目前大多数发行版都已经支持主流声卡,声卡的驱动无非是用ALSA:本文主要讲述声卡驱动的流程:目的是帮助遇到声卡的驱动问题的弟兄来弄清楚解决问题的流 ...

  4. Linux ALSA驱动框架(一)--ALSA架构简介--声卡的创建

    (1)ALSA简介 (1) Native ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音.录音.控制 ALSA ...

  5. Linux中用ALSA驱动声卡流程详解

    一.什么是ALSA : Advanced Linux Sound Architecture 的简称为 ALSA ,译成中文的意思是 Linux 高级声音体系(这是我直译的,可能译的不对):一谈到体系就 ...

  6. 使用最新 ALSA 驱动解决 UBUNTU LINUX INTEL 集成声卡问题

    刚开始学习alsa驱动,刚开始装就出现了一大堆问题,所以在网上找解决方案,看到一篇不错的文章,转载以供大家分享. 目前用户所抱怨的 Ubuntu 系列的声卡问题,基本上归结为几类:一,找不到声音设备: ...

  7. arm linux alsa驱动使用 usb 声卡

    这里写目录标题 一:添加alsa驱动 二:添加alsa-lib和alsa-utils 三:录音测试 四:播放测试 五:alsa 库文件与头文件 一:添加alsa驱动 alsa内核配置选项: 二:添加a ...

  8. arm linux免驱usb声卡,arm linux利用alsa驱动并使用usb音频设备

    一.背景: arm linux的内核版本是3.13.0 二.准备工作 添加alsa驱动到内核中,也就是在编译内核的时候加入以下选项: 接下来就重新编译内核即可 三.交叉编译alsa-lib和alsa- ...

  9. linux系统声卡安装教程,Linux系统下如何安装声卡驱动?

    装了几次Linux OS,当然也装了几次声卡驱动,一般来说都是安装ALSA(Adcance Linux Sound Architecture)驱动,多装几次以后就会发现非常的简单的. 首先,先决条件, ...

  10. alsa 驱动介绍及user层到hw层文件ioctl操作流程分析

    您当前位置:首页 > php开源 > 综合技术 > alsa 驱动介绍 alsa 驱动介绍 来源:程序员人生   发布时间:2016-07-02 13:40:22 阅读次数:6838 ...

最新文章

  1. 用C语言编写:判断一个≥2的整型数是否存在于斐波那契数列中?
  2. 不要一辈子靠技术生存
  3. Dropout_layer.cpp(防止过拟合)
  4. 数据库(概念、语法、DBMS、SQL语言:创建数据库、表格,添加、修改、删除数据记录)...
  5. 腾讯AI开放平台的接口调用指南
  6. 在单页应用Vue中设置标题(title)
  7. ubunt16.04 安装3090显卡驱动 cuda cudnn pytorch
  8. 前端开发---ppt展示页面评论区展示
  9. python画柱形图把奇数年份也显示出来_python怎么输出数据中的奇数
  10. marker 头像 高德地图_高德地图上线马丽导航语音
  11. 进销存excel_Excel教程:教大家做简单的进销存
  12. 说课稿模板计算机,计算机系统的组成说课稿1模板.doc
  13. Excel金额大小写转换公式
  14. 如何使用Windows事件查看器和微软知识库解决问题
  15. Realtek 1296 (RTD1296) OpenWRT Android 双系统全功能开发板
  16. 数据平台作业调度系统详解-实践篇
  17. 丙烯颜料试用心得和丙烯绘画入门
  18. es6 filter() 数组过滤方法总结
  19. 似然函数的详细分析----似然函数的本质意义
  20. php中的时间函数(如何设置时区有4种方法)------与时间相关的函数 (time、date、m‘ktime、microtime(true)、strtotime)

热门文章

  1. jq 获取引入页面url_jQuery获取当前页面的URL信息
  2. 逻辑为基、数企赋能(NO.7)—非逻辑思维
  3. java 实现屏幕录像_用JAVA捕获屏幕、屏幕录像、播放
  4. oracle错误代码03113,Oracle ora-03113错误的处理
  5. 数据库MySQL数据查询---模糊查询(like和relike(或regexp))
  6. 前端安全问题及解决方案
  7. 对话系统最新综述II
  8. Aspose.PDF使用教程:使用 C# 创建多列 PDF 文档
  9. Xilinx IP核 之DDS
  10. 智鼎逻辑推理题及答案_校园招聘在线测试笔试题型的种类和解题技巧