资源描述
手机事业部
ALSA框架介绍——音频通路
陈金泉 2011-3-3
内容:
1、简单介绍android音频状态与音频通路、codec。
2、通过ALSA的controls控制codec实现通路切换。
2.1、control
2.2、widget
2.3、audio_map
2.4、通路切换
2.5、alsa_amixer
1、简单介绍android音频状态与音频通路、codec、声卡注册
1.1、android音频状态与音频通路
Android的音频通路管理主要是在AudioPolicyManager中完成的,包括音量管理,音频策略(strategy)管理,输入输出设备管理。
Android音频模式状态图:
Android的音频模式状态包括:NORMAL、IN_CALL、RINGTONE。
AudioPolicyManager的mPhoneState成员变量记录了当前音频模式状态,在音频通路切换时的设备选择时会使用到。
在这3种音频模式状态下一共有10种音频数据流,定义如下:
VOICE_CALL = 0,
SYSTEM = 1,
RING = 2,
MUSIC = 3,
ALARM = 4,
NOTIFICATION = 5,
BLUETOOTH_SCO = 6,
ENFORCED_AUDIBLE = 7,
DTMF = 8,
TTS = 9,
Android把10种stream type归纳为4种路由策略,getStrategy(stream_type)根据stream_type,返回对应的routing_strategy值,也就是返回stream_type对应的路由策略。
AudioPolicyManager中有两个成员变量:mAvailableOutputDevices和mAvailableInputDevices,他们记录了当前可用的输入和输出设备。
getDeviceForStrategy()则结合routing_strategy、mPhoneState以及mAvailableOutputDevices或mAvailableInputDevices,返回可用的device。
所以android设置音频通路时会先根据getStrategy(stream_type)和getDeviceForStrategy()函数获取相应的输出、输入设备,然后通过setOutputDevice(mHardwareOutput, newDevice)函数调用到alsa_default.cpp中的s_route、s_open函数控制到底层。
s_open函数用会调用deviceName(handle, devices, mode),这个函数会根据Devices的值来组合成字符串。然后通过snd_pcm_open获取asound.conf中相应的pcm配置里面的controls,并对调用到codec驱动当中对codec进行配置。
例子:
Music(NORMAL) -> STRATEGY_MEDIA -> DEVICE_OUT_WIRED_HEADSET
-> AndroidPlayback_Headset_normal
Ring(RINGTONE) -> SONIFICATION -> DEVICE_OUT_SPEAKER 和 DEVICE_OUT_WIRED_HEADSET
-> AndroidPlayback_Speaker_Headset_ringtone
1.2、codec
Codec中文译名是编译码器,由英文编码器(coder)和译码器(decoder)两词的词头组成的缩略语。指的是数字通信中具有编码、译码功能的器件。
Wm8900芯片内部框图:
Wm8900内部包括有:ADC、DAC、Output Mixer、Input Mixer、Input PGA、LINEOUT2、HPOUT等
音频输入口:INPUT1、INPUT2、INPUT3、I2S_DAC
音频输入口:HPOUT、LINEOUT1、LINEOUT2、I2S_ADC
Codec输入输出选择、音量控制、各个部分的power会都会通过control、widgets注册到不同的list中供ALSA和用户使用。
2、通过ALSA的controls控制codec实现通路切换。
2.1、control
Codec中会把一部分功能的设置信息,以control的形式通过snd_soc_add_controls函数添加到声卡中,我们可以通过asound.conf去控制这些control。
如:
SOC_SINGLE("Left Input PGA Switch", WM8900_REG_LINVOL, 6, 1, 1),
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
(1)iface 字段定义了control 的类型,形式为SNDRV_CTL_ELEM_IFACE_XXX,通常是MIXER
(2)name 是名称标识字符串,control 的名称非常重要,因为control 的作用由名称来区分。name 定义的标准是“SOURCE DIRECTION FUNCTION”即“源、方向、功能”。
(3)info函数可以得到对应control的详细信息。
(4)get()函数用于得到control 的目前值并返回用户空间。
(5)put()函数用于从用户空间写入值,如果值被改变,该函数返回1,否则返回0;如果发生错误,该函数返回错误码。
(6)private_value保存了改control用于设置codec的详细信息。这些也就是与codec直接相关的信息。
reg表示寄存器地址;shift表示数据偏移量;max表示数据最大值;invert表示是否倒置。
所有的control都是通过 snd_soc_add_controls(codec, wm8900_snd_controls, ARRAY_SIZE(wm8900_snd_controls))函数添加到声卡中。
2.2、widget
Widget可以认为是codec内部各个部件,需要根据audio_map对应的path来调用。Widget的管理和控制都是在soc-dapm.c中去实现的。
通过snd_soc_dapm_new_controls函数添加到codec->dapm_widgets中。
SND_SOC_DAPM_PGA("LINEOUT1L PGA", WM8900_REG_POWER2, 8, 0, NULL, 0),
SND_SOC_DAPM_PGA会直接把widget对应的信息添加到codec->dapm_widgets中,包括id、reg、shift、max等。id用来区分不同的widget类型,不同的id控制方式也不同。
codec通过 snd_soc_add_controls和wm8900_add_widgets两个函数把来添加control、widgets到不同的list里。
wm8900_add_widgets中除了把wm8900_dapm_widgets结构体里面的所有widgets信息添加到list里,还通过snd_soc_dapm_add_routes函数把audio_map里的信息添加到list里。
2.3、audio_map
2.3.1、audio_map介绍
重点说下audio_map,因为audio_map是一个帮助我们切换通路的很重要的部分。我们可以根据audio_map来控制codec实现通路而不用去了解codec具体内部需要怎么配置寄存器来实现连接。这样就可以不用怎么去了解codec芯片。
audio_map的类型snd_soc_dapm_route定义为:
struct snd_soc_dapm_route {
const char *sink;
const char *control;
const char *source;
};
可以理解为:目的地,控制条件,源头。
Codec的通路(连接方式)非常多,以WM8900为例举个例子。从HP_L输出(目的地sink)的信号可能来自MIC、DAC(源头source),而这个取决于codec的配置(控制条件control)。
每个通路在codec内部又被分成了好几个部分,也就是前面说到的widgets,每个widget都可以做为一个数据流的源头或者目的地。
这些widgets可能的path都在audio_map罗列出来了。
如下是WM8900一部分audio_map:
/* Outputs */
{"LINEOUT1L", NULL, "LINEOUT1L PGA"},
{"LINEOUT1L PGA", NULL, "Left Output Mixer"},
{"LINEOUT1R", NULL, "LINEOUT1R PGA"},
{"LINEOUT1R PGA", NULL, "Right Output Mixer"},
{"LINEOUT2L PGA", NULL, "Left Output Mixer"},
{"LINEOUT2 LP", "Disabled", "LINEOUT2L PGA"},
{"LINEOUT2 LP", "Enabled", "Left Output Mixer"},
{"LINEOUT2L", NULL, "LINEOUT2 LP"},
{"LINEOUT2R PGA", NULL, "Right Output Mixer"},
{"LINEOUT2 LP", "Disabled", "LINEOUT2R PGA"},
{"LINEOUT2 LP", "Enabled", "Right Output Mixer"},
{"LINEOUT2R", NULL, "LINEOUT2 LP"},
{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},
{"Left Output Mixer", "AUX Bypass Switch", "AUX"},
{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
{"Left Output Mixer", "DACL Switch", "DACL"},
{"Right Output Mixer", "RINPUT3 Bypass Switch", "RINPUT3"},
{"Right Output Mixer", "AUX Bypass Switch", "AUX"},
{"Right Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
{"Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
{"Right Output Mixer", "DACR Switch", "DACR"},
{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
{"HP_L", NULL, "Headphone Amplifier"},
{"HP_R", NULL, "Headphone Amplifier"},
从audio_map中可以看出,对于LINEOUT2 LP,可以是Headphone Amplifier的source,也可以是Left Output Mixer的sink。
2.3.2、audio_map分析
对于每一个sink,都有可能有好几个source,也就是codec中一个节点(widget)可能有好几个其他节点(widgets)可以连接到这个节点,如Left Output Mixer在audio_map作为sink的情况包括:
{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},
{"Left Output Mixer", "AUX Bypass Switch", "AUX"},
{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},
{"Left Output Mixer", "DACL Switch", "DACL"},
这个时候audio_map的control就起了作用。目前我遇到的control只有三种情况,这个也是和widget的添加定义有关,也可以说是与id有,不同id对应path中的control不同:
(1)、NULL
如:
{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
这个时候这个只要LINEOUT2 LP作为sink时的path以及Headphone Amplifier 作为source时的path是连接的状态,那么这个path就自动连接起来。也就是ALSA中会先判断当前path的下游path和上游path是否已经连接。所以,在设置通路时,有这写path是要在所有path都连通的状态下才会全部都自动连接。
具体实现如果想了解可以看soc-dapm.c中的static int dapm_power_widgets(struct snd_soc_codec *codec, int event)函数。
(2)、字符串, source与switch组合而成。
如:
{"Left Output Mixer", "DACL Switch", "DACL"},
对于这中情况,在audio_map添加到list里时会处理成新的control添加到ALSA中,我们可以通过alsa_amixer命令看到这个control的存在:
numid=58,iface=MIXER,name='Left Output Mixer DACL Switch'
控制这个control时参数填入on或者off就可以打开和关闭这个path。
(3)、字符串,与source的字符串没有关系。
如:{"LINEOUT2 LP", "Enabled", "Left Output Mixer"},
那么这个时候只有当LINEOUT2 LP对应的control命令设置为Enabled,这样两个widgets就连接起来了。
所以我们要配置codec通路,实际上就是要根据每个codec的audio_map来控制ALSA controls。
2.4、通路切换
播放音乐通路路径图:
RK29_I2S -> DAC -> OUT MIXER -> LINEOUT2 -> HPOUT
要配置WM8900到播放音乐通路,那么我们就要知道WM8900播放音乐是的输入口和输出口。播放音乐使用的是I2S信号,所以输入口就是I2S的DAC,从芯片配合SDK电路我们可以知道输出口是HP_L与HP_R。
(1)、先看DACL,从audio_map中我们可以看出。
以DACL为源头的path只有Left Output Mixer。
{"Left Output Mixer", "DACL Switch", "DACL"},
(2)、Left Output Mixer作为源头,可以与之相连的widgets有一下三个:
{"LINEOUT1L PGA", NULL, "Left Output Mixer"},
{"LINEOUT2L PGA", NULL, "Left Output Mixer"},
{"LINEOUT2 LP", "Enabled", "Left Output Mixer"},
即可以到LINEOUT1L PGA、LINEOUT2L PGA、LINEOUT2 LP
(3)、同时,从audio_map中的下面列出的部分我们可以知道,要到HP_L只能通过LINEOUT2 LP。
{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
{"Headphone Amplifier", NULL, "LINEOUT2 LP"},
{"HP_L", NULL, "Headphone Amplifier"},
{"HP_R", NULL, "Headphone Amplifier"},
(4)、所以播放音乐的路径就确认了:
DACL-> Left Output Mixer ->LINEOUT2 LP-> Headphone Amplifier-> HP_L
(5)、路径的实现过程中,只有两个path需要设置:
{"LINEOUT2 LP", "Enabled", "Left Output Mixer"},
{"Left Output Mixer", "DACL Switch", "DACL"},
所以这部分在asound.conf中对应的控制:
{
name 'LINEOUT2 LP'
value Enabled
}
{
name 'Left Output Mixer DACL Switch'
value on
}
{
name 'Right Output Mixer DACR Switch'
value on
}
(6)、音量等的设置。
通过前面几个步骤,codec的路径就已经打通了,但是可能某些widgets初始音量值是mute或者增益非常小导致听不到声音。另外在ADC和DAC部分有可能会选择输入、输出是左右声道的选择也要注意。
所以在配置过程中要注意由snd_soc_add_controls函数添加到list的control命令了。
对于WM8900的播放音乐通路,还需要设置:
numid=26,iface=MIXER,name='Digital Playback Volume'
所以综上所述,配置WM8900播放音乐通路pcm.AndroidPlayback_Speaker_normal如下:
pcm.AndroidPlayback_ Headset _normal {
type hooks
slave.pcm {
type hw
card 0
device 0 # Must be of type "digital audio playback"
}
hooks.0 {
type ctl_elems
hook_args [
{
name 'Left DAC Source'
value Left
}
{
name 'Right DAC Source'
value Right
}
{
name 'LINEOUT2 LP'
value Enabled
}
{
name 'Left Output Mixer DACL Switch'
value on
}
{
name 'Right Output Mixer DACR Switch'
value on
}
{
name 'Digital Playback Volume'
value.0 85
value.1 85
}
]
}
}
2.5、alsa_amixer
alsa_amixer命令:该命令配置主要配置音频codec的mixer开关、mux对路选择、volume值等;
# alsa_amixer --help
Usage: amixer <options> [command]
Available options:
-h,--help this help
-c,--card N select the card
-D,--device N select the device, default 'default'
-d,--debug debug mode
-n,--nocheck do not perform range checking
-v,--version print version of this program
-q,--quiet be quiet
-i,--inactive show also inactive controls
-a,--abstract L select abstraction level (none or basic)
-s,--stdin Read and execute commands from stdin sequentially
Available commands:
scontrols show all mixer simple controls
scontents show contents of all mixer simple controls (default command)
sset sID P set contents for one mixer simple control
sget sID get contents for one mixer simple control
controls show all controls for given card
contents show contents of all controls for given card
cset cID P set control contents for one control
cget cID get control contents for one control
由于在配置wm8900过程中我只用到了alsa_amixer controls、alsa_amixer、alsa_amixer cset、alsa_amixer cget,所以下面我就介绍一下这四个命令的使用,另外也顺便提下alsa_alsactl。
2.5.1、alsa_amixer controls:
使用alsa_amixer controls可以列出当前alsa里面添加的controls的简单信息。
如(具体可以看alsa_amixer controls.txt):
numid=59,iface=MIXER,name='LINEOUT2 LP'
每个controls对应一个numid,至于numid的排序应该和添加的时候有关系吧。
2.5.2、alsa_amixer:
通过alsa_amixer命令,会列出,所有controls对应的详细信息。
例1(具体可以看alsa_amixer.txt):
Simple mixer control 'LINEOUT2 LP',0
Capabilities: enum
Items: 'Disabled' 'Enabled'
Item0: 'Disabled'
这个control对应的可选状态为:'Disabled' 'Enabled',当前状态为:'Disabled'
例2:
Simple mixer control 'Left Input PGA',0
Capabilities: volume volume-joined pswitch pswitch-joined penum
Playback channels: Mono
Capture channels: Mono
Limits: 0 - 31
Mono: 21 [68%] [9.00dB] Playback [on]
这个control对应的可选状态为:on、off,音量值范围:0~31,当前状态为:on,21
2.5.3、alsa_amixer cset:
通过alsa_amixer cset可以对control进行在线控制。
如输入:
alsa_amixer cset numid=25,iface=MIXER,name='DAC Invert Switch' 0,1
对DAC Invert Switch进行控制,关闭左声道,开启右声道。
回应:
numid=25,iface=MIXER,name='DAC Invert Switch'
; type=BOOLEAN,access=rw------,values=2
: values=off,on
状态已经变。
alsa_amixer cget
用于获取当前control的状态。
如输入:
alsa_amixer cget numid=25,iface=MIXER,name='DAC Invert Switch'
回应
numid=25,iface=MIXER,name='DAC Invert Switch'
; type=BOOLEAN,access=rw------,values=2
: values=off,on
获取'DAC Invert Switch'的情况。
2.5.4、alsa_alsactl
alsa_alsactl store:该命令生成/etc/asound.state文件,该文件显示当前ALSA中contols的配置情况,可以根据该文件检查codec的状态是否正确。
# cat /etc/asound.state
使用amixer命令可以通过串口直接配置codec通路切换;配置完成后可以通过alsa_alsactl生成/etc/asound.state,列出当前控制过的control列表,进而编写asound.conf。
本文档为瑞芯微电子手机事业部成员撰写及提供,不得用于工作之外的使用及交流。19
展开阅读全文