转:https://blog.csdn.net/wh_19910525/article/details/12749293

在用alsa_amixer controls时,除了我们之前提到的snd_soc_add_controls添加的kcontrols外,还有一些多出来的controls。其实多出来的那些都是属于dapm kcontrol,主要用于切换音频路径。

一、AUDIO PATHS OVERVIEW

以标准内核2.6.32的wm8900 codec为例。先看AUDIO PATHS OVERVIEW,红色线路是LINPUT1(Left Input) -> LEFT INPUT PGA -> LEFT INPUT MIXER -> LEFT OUTPUT MIXER -> LINEOUT1L,表示从LINPUT输入的信号通过这条路径送到LINEOUT输出,再具现一点,就是录音信号直接放到SPK播出。在这条路径上,有三个带+号的圆圈,那就是多路混合器mixer,用于切换输入源或将多个输入源混合输出。

土黄色部分为LEFT INPUT PGA:可选择LINPUT1、LINPUT2和LINPUT3;

绿色部分是LEFT INPUT MIXER:可选择INPUTPGA、LINPUT2、LINPUT3和AUX/LCOM;

蓝色部分是LEFT OUTPUT MIXER:可选择INPUTMIXER、LINPUT3、AUX/LCOM和LEFT DAC等。

配置声音通路时,主要是对mixer做切换输入源操作。如要实现Playback,则需要打通DAC -> OUTPUT MIXER -> LINEOUT通路。

二、配置声音通路

这里先绕开代码,先用alsa_amixer实际操作切换声音路径(以上图的红色路径为例),有个直观印象。

~ # alsa_amixer controls
numid=1,iface=MIXER,name='Mic Bias Level'
......省略......
numid=68,iface=MIXER,name='Left Input Mixer AUX Switch'
numid=69,iface=MIXER,name='Left Input Mixer Input PGA Switch'
numid=66,iface=MIXER,name='Left Input Mixer LINPUT2 Switch'
numid=67,iface=MIXER,name='Left Input Mixer LINPUT3 Switch'
numid=73,iface=MIXER,name='Left Input PGA LINPUT1 Switch'
numid=74,iface=MIXER,name='Left Input PGA LINPUT2 Switch'
numid=75,iface=MIXER,name='Left Input PGA LINPUT3 Switch'
numid=3,iface=MIXER,name='Left Input PGA Switch'
numid=2,iface=MIXER,name='Left Input PGA Volume'
numid=4,iface=MIXER,name='Left Input PGA ZC Switch'
numid=57,iface=MIXER,name='Left Output Mixer AUX Bypass Switch'
numid=60,iface=MIXER,name='Left Output Mixer DACL Switch'
numid=56,iface=MIXER,name='Left Output Mixer LINPUT3 Bypass Switch'
numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch'
numid=59,iface=MIXER,name='Left Output Mixer Right Input Mixer Switch'
......省略......
~ #

以上打印出所有的codec kcontrols,以之前对应的颜色区分了INPUT PGA、INPUT MIXER、OUTPUT MIXER三个mixer的路径选择。要打开红色通路,则进行如下操作:

  1. alsa_amixer cset numid=3,iface=MIXER,name='Left Input PGA Switch' 1
  2. alsa_amixer cset numid=73,iface=MIXER,name='Left Input PGA LINPUT1 Switch' 1
  3. alsa_amixer cset numid=69,iface=MIXER,name='Left Input Mixer Input PGA Switch' 1
  4. alsa_amixer cset numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch' 1
  5. alsa_amixer cset numid=43,iface=MIXER,name='LINEOUT1 Switch' 1

numid3 : 使能Left Input PGA;

numid73: 令Left Input PGA选择LINPUT1输入源;

numid69: 令Left Input Mixer选择Left Input PGA输入源;

numid58: 令Left Output Mixer选择Left Input Mixer输入源;

numid43: 使能LINEOUT1。

【这里仅以最基本的alsa-util来配置回路,在实际应用中,可自己实现alsa mixer或编写asound.conf虚拟出不同的devices,前者较典型见Android2.2的ALSAControl.cpp,后者在之后的章节会提一下。我偏向于asound.conf实现,灵活性较高,不同的codec改动asound.conf就行了。】

三、AUDIO PATHS代码实现

知道声音通路如何配置后,回到驱动代码实现。音频路径功能与普通的snd_kcontrol差不多,但写法稍微复杂一点,以'Left Output Mixer Left Input Mixer Switch'为例说明。

1、实现Left Output Mixer的输入源选择:

  1. static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {
  2. SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),
  3. SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),
  4. SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),
  5. SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),
  6. SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),
  7. };

2、添加名为Left Output Mixer的widget:

  1. static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = {
  2. /* Externally visible pins */
  3. ......省略......
  4. /* Input */
  5. ......省略......
  6. SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0,
  7. wm8900_linmix_controls,
  8. ARRAY_SIZE(wm8900_linmix_controls)),
  9. ......省略......
  10. /* Output */
  11. ......省略......
  12. SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,
  13. wm8900_loutmix_controls,
  14. ARRAY_SIZE(wm8900_loutmix_controls)),
  15. ......省略......
  16. };

【MIXER:多个输入源混合成一个输出,用SND_SOC_DAPM_MIXER定义这个widget,类型为snd_soc_dapm_mixer;
  MUX:多路选择器,多路输入,但只能选择一路作为输出,用SND_SOC_DAPM_MUX定义这个widget,类型为snd_soc_dapm_mux;
  PGA:单路输入,单路输出,带gain调整的部件,用SND_SOC_DAPM_PGA定义这个widget,类型为snd_soc_dapm_pga。】

3、搭建音频路径:

  1. static const struct snd_soc_dapm_route audio_map[] = {
  2. /* Inputs */
  3. ......省略......
  4. /* Outputs */
  5. ......省略......
  6. {"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
  7. ......省略......
  8. };

前面的"Left Output Mixer"表示操作目的对象sink,对应widgets中的名为Left Output Mixer的widget;

中间的"Left Input Mixer Switch"表示操作行为control,对应wm8900_loutmix_controls中的名为Left Input Mixer Switch的kcontrol;

后面的"Left Input Mixer"表示操作源对象source,对应widgets中的名为Left Input Mixer的widget。

合起来的意思:通过动作"Left Input Mixer Switch"将输入源"Left Input Mixer"送到目的混合器"Left Output Mixer"输出。

这里我的解析有点拗口,直接看dapm.txt更清晰一点:

  1. e.g., from the WM8731 output mixer (wm8731.c)
  2. The WM8731 output mixer has 3 inputs (sources)
  3. 1. Line Bypass Input
  4. 2. DAC (HiFi playback)
  5. 3. Mic Sidetone Input
  6. Each input in this example has a kcontrol associated with it (defined in example
  7. above) and is connected to the output mixer via it's kcontrol name. We can now
  8. connect the destination widget (wrt audio signal) with it's source widgets.
  9. /* output mixer */
  10. {"Output Mixer", "Line Bypass Switch", "Line Input"},
  11. {"Output Mixer", "HiFi Playback Switch", "DAC"},
  12. {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
  13. So we have :-
  14. Destination Widget  <=== Path Name <=== Source Widget
  15. Or:-
  16. Sink, Path, Source
  17. Or :-
  18. "Output Mixer" is connected to the "DAC" via the "HiFi Playback Switch".
  19. When there is no path name connecting widgets (e.g. a direct connection) we
  20. pass NULL for the path name.

注意:dapm kcontrol名称 = 目的对象sink名称 + 操作行为control名称,即'Left Output Mixer Left Input Mixer Switch',操作源对象source名称被忽略。之后深入源码分析。

4、将kcontrols、widgets和route串联起来:

  1. static int wm8900_add_widgets(struct snd_soc_codec *codec)
  2. {
  3. snd_soc_dapm_new_controls(codec, wm8900_dapm_widgets,
  4. ARRAY_SIZE(wm8900_dapm_widgets));
  5. snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
  6. snd_soc_dapm_new_widgets(codec);
  7. return 0;
  8. }

四、dapm kcontrol

好了,如果仅仅是为了实现audio paths的话,了解以上应该是足够的了。但人生的追求不应该那么简单,我们要从更根源处剖析问题,以后再审视相似的问题时,站的高度也不同。

snd_soc_dapm_route的定义:

  1. /*
  2. * DAPM audio route definition.
  3. *
  4. * Defines an audio route originating at source via control and finishing
  5. * at sink.
  6. */
  7. struct snd_soc_dapm_route {
  8. const char *sink;
  9. const char *control;
  10. const char *source;
  11. };

sink为目的对象名称,control为操作行为名称,source为源对象名称。

为方便起见,先定义一些名词:source为输入源,在widgets中定义,如"Left Input Mixer";sources为输入源选择,如wm8900_loutmix_controls;control为操作行为,具体在sources数组中定义,如"Left Input Mixer Switch";sink为目的混合器,在widgets中定义,如"Left Output Mixer";route为音频路径,连接source、control和sink,如{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}。

1、snd_soc_dapm_new_controls()函数进行widget内存分配、链表初始化工作,比较简单,按下不表。

2、snd_soc_dapm_add_routes(),该函数的注释值得一看:snd_soc_dapm_add_routes - Add routes between DAPM widgets. Connects 2 dapm widgets together via a named audio path. The sink is the widget receiving the audio signal, whilst the source is the sender of the audio signal.

以MIXER类型snd_soc_dapm_mixer为例,简单介绍调用过程:

  1. snd_soc_dapm_add_routes
  2. -->snd_soc_dapm_add_route [添加一个snd_soc_dapm_mixer类型的route]
  3. -->分配snd_soc_dapm_path内存,初始化这个path,令path->sink=sink,path->source=source
  4. dapm_connect_mixer
  5. -->检查操作行为control是否存在:从sources中找到name匹配的control
  6. path->name = control.name;接着将这个path的sink、source和list插入到各自的链表中
  7. (注:path三大要素中的两个都已经得到即sink和source,差kcontrol。)
  8. dapm_set_path_status
  9. -->判定指定source是否被选择了,是则置p->connect = 1;否则置p->connect = 0

以上的核心部分是path的建立和链表插入操作,如果在sink->kcontrols中找不到name相匹配的kcontrol,则这个route无效,不创建path。这里path->name为找到的sink->kcontrol.name。

【其实dapm_set_path_status挺困惑的,path的connect理应包含两个操作:1是指定source的选择,2是sink本身的使能。但这里connect状态仅仅是检查source是否选择而已,不检查指定sink是否使能,看起来不太合理。后面会解决这个疑问。这里我理解可能还有些偏差或有遗漏的地方。】

3、snd_soc_dapm_new_widgets()

  1. snd_soc_dapm_new_widgets
  2. -->遍历dapm_widgets链表,找到为switch、mixer类型的widget
  3. w->power_check = dapm_generic_check_power;
  4. dapm_new_mixer
  5. -->/* add dapm control with long name.
  6. * for dapm_mixer this is the concatenation of the
  7. * mixer and kcontrol name.
  8. * for dapm_mixer_named_ctl this is simply the
  9. * kcontrol name.
  10. */
  11. snd_soc_dapm_mixer dapm kcontrol:path->long_name = sink->name + kcontrol->name
  12. snd_soc_dapm_mixer_named_ctl dapm kcontrol:path->long_name = kcontrol->name
  13. snd_soc_cnew为path分配一个kcontrol,置这个kcontrol的name为path->long_name
  14. snd_ctl_add添加这个dapm kcontrol
  15. dapm_power_widgets
  16. -->Scan each dapm widget for complete audio path.A complete path is a route that has valid endpoints i.e.:-

经过snd_soc_dapm_new_widgets(),终于为snd_soc_dapm_mixer类型的widget建立用于route切换的dapm kcontrol,使得alsa_amixer可以通过控制这些dapm kcontrol来达到音频通路切换的目的。

注:SND_SOC_DAPM_MIXER和SND_SOC_DAPM_MIXER_NAMED_CTL建立的widget仅体现在dapm kcontrol的名字上面,前者为sink->name + kcontrol->name,后者简单的为kcontrol->name。

4、snd_soc_dapm_path[补充]

  1. /* dapm audio path between two widgets */
  2. struct snd_soc_dapm_path {
  3. char *name;
  4. char *long_name;
  5. /* source (input) and sink (output) widgets */
  6. struct snd_soc_dapm_widget *source;
  7. struct snd_soc_dapm_widget *sink;
  8. struct snd_kcontrol *kcontrol;
  9. /* status */
  10. u32 connect:1;  /* source and sink widgets are connected */
  11. u32 walked:1;   /* path has been walked */
  12. struct list_head list_source;
  13. struct list_head list_sink;
  14. struct list_head list;
  15. };

以上的过程分析非常简略,其实一切都是围绕path展开的。可以把重点放在path的分析上面,搞懂path数据,基本就能理解这个dapm kcontrol的一切了。总的来说:

path->name = sink->kcontrol[i].name,上例是"Left Input Mixer Switch";

path->long_name = sink->name + sink->kcontrol[i].name,上例是"Left Output Mixer Left Input Mixer Switch";

path->source = source,上例是名为"Left Input Mixer"的widget;

path->sink = sink,上例是名为"Left Output Mixer"的widget;

path->connect:通道connect状态,根据sink->kcontrol[i]判断。

path->kcontrol = snd_soc_cnew(&w->kcontrols[i], w, path->long_name);可见path->kcontrol对应sink->kcontrol[i],但名为path->long_name。因此上层可以通过path->long_name找到对应的sink->kcontrol[i]。

五、如何触发path connect

dapm kcontrol的触发很大程度上可以参考snd_kcontrol探究,但更为复杂。当上层触发dapm kcontrol时,会做两个重要动作:1是切换音频通路,这与普通的kcontrol做法基本一致;2是使能dapm widget(power up/down),这就是分歧之处。

回到<三、AUDIO PATHS代码实现>复习一下"Left Input Mixer Switch"的kcontrol写法:SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0)。

1、SOC_DAPM_SINGLE宏定义:

  1. /* dapm kcontrol types */
  2. #define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) /
  3. {   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, /
  4. .info = snd_soc_info_volsw, /
  5. .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, /
  6. .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }

SOC_SINGLE宏定义:

  1. #define SOC_SINGLE(xname, reg, shift, max, invert) /
  2. {   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, /
  3. .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,/
  4. .put = snd_soc_put_volsw, /
  5. .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }

从这里就可以看出,dapm kcontrol跟普通的kcontrol不同之处,以put为例:

dapm kcontrol:.put = snd_soc_dapm_put_volsw

kcontrol:.put = snd_soc_put_volsw

2、snd_soc_dapm_put_volsw

  1. snd_soc_dapm_put_volsw
  2. -->dapm_mixer_update_power
  3. -->snd_kcontrol_chip
  4. -->找到dapm kcontrol所在的widget(也就是操作目的对象sink)
  5. -->snd_soc_test_bits
  6. -->Tests a register with a new value and checks if the new value is different from the old value.
  7. dapm_power_widgets
  8. -->power up/down对象widget,更底层可追溯到dapm_seq_run_coalesced
  9. 检查是否有widget->event [这里不分析Event的情况,继续往下走]
  10. snd_soc_update_bits
  11. -->根据dapm kcontrol:SOC_DAPM_SINGLE定义的reg、shift和max设置音频通路,方法与普通的kcontrol一样

这是底层方法差异,往上应该没必要说了,与snd_kcontrol探究一致。

linux音频 DAPM之二:audio paths与dapm kcontrol的更多相关文章

  1. Linux音频编程(二)声卡介绍

    一.声卡 1.声卡是audio interface,它含有hardware buffer,而这个hardware buffer是在声卡里面,不是内存.声卡的缓存是环状的,则ALSA中是将数据分成连续的 ...

  2. linux音频alsa-uda134x驱动文档阅读之一转自http://blog.csdn.net/wantianpei/article/details/7817293

    前言 目前,linux系统常用的音频驱动有两种形式:alsa oss alsa:现在是linux下音频驱动的主要形式,与简单的oss兼容.oss:过去的形式而我们板子上的uda1341用的就是alsa ...

  3. Linux音频编程指南

    Linux音频编程指南 虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有着非常丰富的媒体功能,本文就是以多媒体应用中最基本的声音为对象,介绍如何在Linux平台下开发实际的音频应用程序 ...

  4. Linux音频编程指南(转)

    转自: http://www.ibm.com/developerworks/cn/linux/l-audio/ Linux音频编程指南 虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有 ...

  5. Linux学习之CentOS(二十二)--单用户模式下修改Root用户的密码

    在上一篇随笔里面详细讲解了Linux系统的启动过程 (Linux学习之CentOS(二十一)--Linux系统启动详解),我们知道Linux系统的启动级别一共有6种级别,通过 /etc/inittab ...

  6. Linux音频编程

    1. 背景 在<Jasper语音助理介绍>中, 介绍了Linux音频系统, 本文主要介绍了Linux下音频编程相关内容. 音频编程主要包括播放(Playback)和录制(Record), ...

  7. Linux音频驱动-ALSA概述

    概述 ALSA(Advanced Linux Sound Architecture)是linux上主流的音频结构,在没有出现ALSA架构之前,一直使用的是OSS(Open Sound System)音 ...

  8. Linux shell脚本编程(二)

    Linux shell脚本编程(二) 练习:求100以内所有偶数之和; 使用至少三种方法实现; 示例1: #!/bin/bash # declare -i sum=0 #声明一个变量求和,初始值为0 ...

  9. 【jquery】一款不错的音频播放器——Amazing Audio Player

    前段时间分享了一款视频播放器,点击这里.今天介绍一款不错的音频播放器——Amazing Audio Player. 介绍: Amazing Audio Player 是一个使用很方便的 Windows ...

随机推荐

  1. linux 免密登录

    ssh-keygen -t rsa -P "" ssh-copy-id -i ~/.ssh/id_rsa.pub root@服务器地址

  2. 如何居中div?

    如何居中div? 水平居中 1 //给div设置一个宽度,然后添加margin:0 auto属性 2 3 div{ 4 width:200px; 5 margin:0 auto; 6 } 让绝对定位的 ...

  3. python cookies 爬虫处理

    Cookie Cookie 是指某些网站服务器为了辨别用户身份和进行Session跟踪,而储存在用户浏览器上的文本文件,Cookie可以保持登录信息到用户下次与服务器的会话. Cookie原理 HTT ...

  4. Yii2的redis扩展

    在应用的时候需要先对yii2进行扩展安装 如果装有composer直接运行 php composer.phar require --prefer-dist yiisoft/yii2-redis 当然也 ...

  5. scikit-learn:3.4. Model persistence

    參考:http://scikit-learn.org/stable/modules/model_persistence.html 训练了模型之后,我们希望能够保存下来,遇到新样本时直接使用已经训练好的 ...

  6. 【转】ATL提供的所有转换宏

    在头文件<atlconv.h>中定义了ATL提供的所有转换宏,如: A2CW (LPCSTR) -> (LPCWSTR) A2W     (LPCSTR) -> (LPWSTR ...

  7. 41个Web开发者必须收藏的JavaScript实用技巧

    1. 将彻底屏蔽鼠标右键 oncontextmenu=”window.event.returnValue=false” < table border oncontextmenu=return(f ...

  8. NetWorkUtil

    import java.io.IOException; import java.io.UnsupportedEncodingException; import org.apache.http.Http ...

  9. JPA概述以及它和Hibernate之间的关系

    http://www.cnblogs.com/Kevin-ZhangCG/p/8996491.html 一.JPA概述以及它和Hibernate之间的关系 1.1.Hibernate 概述 JPA J ...

  10. Linux 进程间通信(一)(经典IPC:消息队列、信号量、共享存储)

    有3种称作XSI IPC的IPC:消息队列.信号量.共享存储.这种类型的IPC有如下共同的特性. 每个内核中的IPC都用一个非负整数标志.标识符是IPC对象的内部名称,为了使多个合作进程能够在同一IP ...