ALSA简介
ALSA(Advanced Linux Sound Architecture(高级Linux声音体系)的缩写)是为声卡提供驱动的Linux内核组件,以替代原先的OSS(开放声音系统)。ALSA除了像OSS那样提供一组内核驱动程序模块以外,还专门为简化应用程序的编写提供了相应的库函数,与OSS提供的基于ioctl的原始编程接口相比,ALSA函数库使用起来要更加方便一点。
驱动包alsa-driver 开发包
alsa-libs 开发包插件
alsa-libplugins 设置管理工具包
alsa-utils 其他声音相关处理小程序包
alsa-tools 特殊音频固件支持包
alsa-firmware
OSS接口兼容模拟层工具alsa-oss.
ALSA设备文件结构
alsa驱动的设备文件结构:/dev/snd/
crw-rw—-+ 1 root audio 116, 8 2011-02-23 21:38 controlC0crw-rw—-+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D0c
crw-rw—-+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D1c
crw-rw—-+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D2c crw-rw—-+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D0p
crw-rw—-+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D1p
crw-rw—-+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D2p
crw-rw—-+ 1 root audio 116, 3 2011-02-23 21:38 seqcrw-rw—-+ 1 root audio 116, 2 2011-02-23 21:38 timer
controlC0 –> 用于声卡的控制,例如通道选择,混音,麦克风的控制等
pcmC0D0c –〉 用于录音的pcm设备
pcmC0D0p –〉 用于播放的pcm设备
seq –〉 音序器
timer –〉 定时器
其中,C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback。
ALSA音频设备注册
设备的注册本身非常简单,复杂的是这个设备的drvdata,drvdata里面包含了三部分。
machine主要是关于cpu这边。
platform是关于平台级别的。
codec就是与我们所用的音频codec相关的
整个音频驱动的架构特点,就是从alsa层进入——>内核alsa层接口->core层,这里再调用上面说的三个方面的函数来处理,先是cpu级别的,再是platform的,再是codec级别的。
ALSA Soc_probe
这个函数本身架构很简单,和前面说的逻辑一样,先调用了cpu级别的probe,再是codec级别的,最后是platform。
对上主要是注册设备节点,以及这些设备节点对应的流的创建;
对下主要是读写函数的设置,codec本身的dai设置,初始化寄存器的设置,最重要的就是后面的control的创建和门的创建了
Codec_dev->probe
第一部分就是创建卡和流,对于alsa驱动来说,是先分成卡0,卡1…,然后对于每一个卡的每一个cpu支持的dai(digit audio interface)也就是pcm接口 或者i2S接口等都要建立对应的流,一个dai有可能包含两个流,一个是录的一个是play的,
第二部分就是control的创建,这个函数比较简单,就是把已经定义好的controls加入到card的controls列表中去;
第三部分就是门的创建了,这个函数也是很清楚,就是把codec对应的门都加入到codec->dapm_widgets列表中。
Card
snd_card可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd_card的管理之下,声卡驱动的第一个动作通常就是创建一个snd_card结构体。
第一步,创建snd_card的一个实例
struct snd_card *card;
int err;
err = snd_card_create(index, id, THIS_MODULE, 0, &card);
//index 一个整数值,该声卡的编号
//id 字符串,声卡的标识符
//第四个参数 该参数决定在创建snd_card实例时,需要同时额外分配的私有数据的大小,
// 该数据的指针最终会赋值给snd_card的private_data数据成员
//card 返回所创建的snd_card实例的指针
第二步,创建声卡的芯片专用数据
声卡的专用数据主要用于存放该声卡的一些资源信息,例如中断资源、io资源、dma资源等。可以有两种创建方法
第三步,设置Driver的ID和名字
第四步,创建声卡的功能部件(逻辑设备)
例如PCM,Mixer,MIDI等
第五步,注册声卡
PCM
PCM简介
PCM是英文Pulse-code modulation的缩写,中文译名是脉冲编码调制。现实生活中,人耳听到的声音是模拟信号,PCM就是要把声音从模拟转换成数字信号的一种技术,他的原理简单地说就是利用一个固定的频率对模拟信号进行采样,采样后的信号在波形上看就像一串连续的幅值不一的脉冲,把这些脉冲的幅值按一定的精度进行量化,这些量化后的数值被连续地输出、传输、处理或记录到存储介质中,所有这些组成了数字音频的产生过程。
PCM信号的两个重要指标是采样频率和量化精度。
目前,CD音频的采样频率通常为44100Hz,量化精度是16bit。通常,播放音乐时,应用程序从存储介质中读取音频数据(MP3、WMA、AAC……),经过解码后,最终送到音频驱动程序中的就是PCM数据,反过来,在录音时,音频驱动不停地把采样所得的PCM数据送回给应用程序,由应用程序完成压缩、存储等任务。
所以,音频驱动的两大核心任务就是playback和capture。
playback: 如何把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频
capture: 把mic拾取到得模拟信号,经过采样、量化,转换为PCM信号送回给用户空间的应用程序
PCM中间层
每个声卡最多可以包含4个pcm的实例,每个pcm实例对应一个pcm设备文件一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。
pcm中间层几个重要的结构
snd_pcm是挂在snd_card下面的一个snd_device
snd_pcm中的字段:
streams[2],该数组中的两个元素指向两个snd_pcm_str结构,分别代表playback stream和capture stream
snd_pcm_str中的substream字段,指向snd_pcm_substream结构
snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是他的ops(snd_pcm_ops)字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理。
它的runtime字段则指向snd_pcm_runtime结构, snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数
新建一个pcm
alsa-driver的中间层已经为我们提供了新建pcm的api:
int snd_pcm_new(struct snd_card *card, const char *id, int device, int playback_count, int capture_count, struct snd_pcm ** rpcm);
//参数device 表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始。
//参数playback_count 表示该pcm将会有几个playback substream。
//参数capture_count 表示该pcm将会有几个capture substream。
另一个用于设置pcm操作函数接口的api:
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops);
snd_card_create
pcm是声卡下的一个设备(部件),所以第一步是要创建一个声卡
snd_pcm_new
调用该api创建一个pcm,才该api中会做以下事情
如果有,建立playback stream,相应的substream也同时建立
如果有,建立capture stream,相应的substream也同时建立
调用snd_device_new()把该pcm挂到声卡中,参数ops中的dev_register字段指向了函数snd_pcm_dev_register,这个回调函数会在声卡的注册阶段被调用。
snd_pcm_set_ops
设置操作该pcm的控制/操作接口函数,参数中的snd_pcm_ops结构中的函数通常就是我们驱动要实现的函数
snd_card_register
注册声卡,在这个阶段会遍历声卡下的所有逻辑设备,并且调用各设备的注册回调函数,对于pcm,就是第二步提到的snd_pcm_dev_register函数,该回调函数建立了和用户空间应用程序(alsa-lib)通信所用的设备文件节点:/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc
打开pcm设备
open一个pcm设备时,将会调用snd_fops的open回调函数,我们先看看snd_fops的定义:
snd_pcm_hw_params流程分析
Control
Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关。对于Mixer(混音)来说,Control接口显得尤为重要.所有的mixer工作都是通过control接口的API来实现的。
要自定义一个Control,我们首先要定义3各回调函数:info,get和put。然后,定义一个snd_kcontrol_new结构:
static struct snd_kcontrol_new my_control __devinitdata = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Switch",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.private_value = 0xffff,
.info = my_control_info,
.get = my_control_get,
.put = my_control_put
};
static const struct snd_kcontrol_new aif1adc1l_mix[] = {
SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING,
1, 1, 0),
SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING,
0, 1, 0),
};
iface字段指出了control的类型,alsa定义了几种类型(SNDDRV_CTL_ELEM_IFACE_XXX),常用的类型是MIXER,当然也可以定义属于全局的CARD类型,也可以定义属于某类设备的类型,例如HWDEP,PCMRAWMIDI,TIMER等,这时需要在device和subdevice字段中指出卡的设备逻辑编号。
name字段是该control的名字。
index字段用于保存该control的在该卡中的编号 。
access字段包含了该control的访问类型 。
private_value字段包含了一个任意的长整数类型值。该值可以通过info,get,put这几个回调函数访问 。
回调函数
info回调函数用于获取control的详细信息。它的主要工作就是填充通过参数传入的snd_ctl_elem_info对象,以下例子是一个具有单个元素的boolean型control的info回调:
static int snd_myctl_mono_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
该回调函数用于读取control的当前值,并返回给用户空间的应用程序。
static int snd_myctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct mychip *chip = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = get_some_value(chip);
return 0;
}
该回调函数用于读取control的当前值,并返回给用户空间的应用程序。
static int snd_myctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct mychip *chip = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = get_some_value(chip);
return 0;
}
Control设备的建立
Control设备和PCM设备一样,都属于声卡下的逻辑设备。用户空间的应用程序通过alsa-lib访问该Control设备,读取或控制control的控制状态,从而达到控制音频Codec进行各种Mixer等控制操作。
Control设备的创建过程大体上和PCM设备的创建过程相同,我们需要在我们的驱动程序初始化时主动调用snd_pcm_new()函数创建pcm设备,而control设备则在snd_card_create()内被创建,snd_card_create()通过调用snd_ctl_create()函数创建control设备节点。所以我们无需显式地创建control设备,只要建立声卡,control设备被自动地创建。和pcm设备一样,control设备的名字遵循一定的规则:controlCxx,这里的xx代表声卡的编号 。
DAPM
dapm的简单描述:动态音频电源管理(DAPM)用来使得任何时候便携Linux设备都最小化音频子系统的功耗,而且它独立于其它内核电源管理,容易与其他电源管理系统模块共存。dapm的切换根据设备内的音频流活动(捕获/回放)和混音器设置来决定的。
AUDIO PATHS实现
知道声音通路如何配置后,回到驱动代码实现。音频路径功能与普通的kcontrol差不多,但写法稍微复杂一点,以路径‘Left Output Mixer Left Input Mixer Switch’为例说明,具体寄存器配置请参考wm8994数据手册
定义controls
static const struct snd_kcontrol_new wm8994_loutmix_controls[] = {
SOC_DAPM_SINGLE(“LINPUT3 Bypass Switch”, WM8994_REG_LOUTMIXCTL1, 7, 1, 0),
SOC_DAPM_SINGLE(“AUX Bypass Switch”, WM8994_REG_AUXOUT_CTL, 7, 1, 0),
SOC_DAPM_SINGLE(“Left Input Mixer Switch”, WM8994_REG_BYPASS1, 7, 1, 0),
SOC_DAPM_SINGLE(“Right Input Mixer Switch”, WM8994_REG_BYPASS2, 3, 1, 0),
SOC_DAPM_SINGLE(“DACL Switch”, WM8994_REG_LOUTMIXCTL1, 8, 1, 0),
};
定义dapm widget
static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = {
SND_SOC_DAPM_MIXER(“Left Input Mixer”, WM8994_REG_POWER2, 5, 0, wm8994_linmix_controls,
ARRAY_SIZE(wm8994_linmix_controls)),
SND_SOC_DAPM_MIXER(“Left Output Mixer”, WM8994_REG_POWER3, 3, 0, wm8994_loutmix_controls,
ARRAY_SIZE(wm8994_loutmix_controls)),
};
注:dapm widget是分类型的,不同的类型的widget用不同的dapm宏定义,如Mixer类型用SND_SOC_DAPM_MIXER,PGA类型用SND_SOC_DAPM_PGA等等。关于widget类型解释,
Audio DAPM widgets fall into a number of types:-
Mixer - Mixes several analog signals into a single analog signal.
Mux - An analog switch that outputs only one of many inputs.
PGA - A programmable gain amplifier or attenuation widget.
ADC - Analog to Digital Converter
DAC - Digital to Analog Converter
Switch - An analog switch
Input - A codec input pin
Output - A codec output pin
Headphone - Headphone (and optional Jack)
Mic - Mic (and optional Jack)
Line - Line Input/Output (and optional Jack)
Speaker - Speaker
Supply - Power or clock supply widget used by other widgets.
Pre - Special PRE widget (exec before all others)
Post - Special POST widget (exec after all others)
Mixer :允许多个输入源混合成一个输出
Mux :多路选择器,多个输入,但只能选择一路作为输出
PGA :单路输入,单路输出,带增益(gain)调整的部件
以上三个部件在本章节中非常重要。