转: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. ThinkPHP -- 问题

    @.nginx下,找不到页面,如果然nginx支持pathinfo模式 原文:http://www.leixuesong.cn/1418 把文章的配置拷贝了下.

  3. Servlet Session 跟踪

    HTTP 是一种"无状态"协议,这意味着每次客户端检索网页时,客户端打开一个单独的连接到 Web 服务器,服务器会自动不保留之前客户端请求的任何记录. 但是仍然有以下三种方式来维持 ...

  4. 这样就可以修改MathType公式编号格式吗

    MathType公式编辑器与很多的软件都可以兼容,其中很多的用户在word上写论文的时候,都会用到MathType.特别是当公式比较多时,可以使用MathType公式编号功能来对公式进行自动编号.但公 ...

  5. python在linux中输出带颜色的文字的方法

    在开发项目过程中,为了方便调试代码,经常会向stdout中输出一些日志,默认的这些日志就直接显示在了终端中.而一般的应用服务器,第三方库,甚至服务器的一些通告也会在终端中显示,这样就搅乱了我们想要的信 ...

  6. http://localhost:8080请求用户名和密码。信息为:“XDB” 解决办法

    windows查看端口占用情况 cmd下 netstat -ano 查看端口和对应的服务 为2520 Oracle的服务 源博客: http://blog.163.com/jxguo_05/blog/ ...

  7. 46、PopWindow工具类

    <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http: ...

  8. 【BZOJ2199】[Usaco2011 Jan]奶牛议会 2-SAT

    [BZOJ2199][Usaco2011 Jan]奶牛议会 Description 由于对Farmer John的领导感到极其不悦,奶牛们退出了农场,组建了奶牛议会.议会以“每头牛 都可以获得自己想要 ...

  9. xampp怎么操作数据库mysql

    1.打开软件的主界面,打开Apache和MySQL,然后点击MySQL后面的admin.且我操作时,Apache,MySQL要启动,才打的开. 2.打开MySQL,报错. 09:00:23 [mysq ...

  10. springboot之修改内置tomcat配置项

    1.spring boot默认端口号是8080,如果要修改端口的话,只需要修改application.properties文件,在其中加入 例如: server.port=8081 2.在正常的项目中 ...