ALSA

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 设备注册

这里写图片描述

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)调整的部件
以上三个部件在本章节中非常重要。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安德路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值