Linux ALSA
Linux音频子系统是Linux内核中最为庞大和复杂的组件之一,笔者在学习时也走了很多弯路,同时也建议学习者最好能够结合具体code并动手实践。
下面几个博主的博客对我帮助很大,希望能够对大家有所帮助。
一、ALSA简介
1.1 Linux Audio发展
Linux Audio最初是各个Unix厂商定制自己的音频API,不具备可移植性并且只具备录音与放音功能。
OSSv3是Linux下原始的声音系统并集成在内核里。OSS提供了MIDI等更多功能并且具有了一定的可移植性,音频处理应用程序只要按照OSS的API来编写,在移植到其他平台时只需重新编译即可。
OSSv4在2002年OSS成为商业软件时它地位被ALSA所取代,ALSA是为声卡提供驱动的Linux内核组件,开始于为1998年Gravis Ultrasound所开发的驱动,它一直作为一个单独的软件包开发,直到2002年他被引进入 linux内核的开发版本 (2.5.4-2.5.5)1。从2.6 版本开始ALSA成为Linux内核中默认的标准音频驱动程序集,OSS则被标记为废弃。
1.2 ALSA
ALSA是Advanced Linux Sound Architecture,高级Linux声音架构的简称,它在Linux操作系统上提供了音频和MIDI(Musical Instrument Digital Interface,音乐设备数字化接口)的支持。在2.6系列内核中,ALSA已经成为默认的声音子系统,用来替换2.4系列内核中的OSS(Open Sound System,开放声音系统)。
ALSA的主要特性包括:高效地支持从消费类入门级声卡到专业级音频设备所有类型的音频接口,完全模块化的设计, 支持对称多处理(SMP)和线程安全,对OSS的向后兼容,以及提供了用户空间的alsa-lib库来简化应用程序的开发。
- ALSA Application:tinyplay/tinycap/tinymix,这些用户程序直接调用 alsa 用户库接口来实现放音、录音、控制
- ALSA Library API:alsa 用户库接口,如 tinyalsa和alsa-lib
- ALSA CORE:ALSA 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC)
- ASoC CORE:ASoC 是建立在标准 ALSA CORE 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系,由三大部分组成,分别是 Machine、Platform、Codec
- Hardware Driver:音频硬件设备驱动,由各个芯片厂商提供。
1.3 ALSA vs OSS
- 两个声音系统不能同时驱动声卡
- OSS不支持混音
- ALSA支持混音,具有声卡共享的特性
ALSA除了像OSS那样提供了一组内核驱动程序模块之外,还专门为简化应用程序的编写提供了相应的函数库ALSA-Lib
,与OSS提供的基于ioctl
的原始编程接口相比,ALSA-Lib
使用起来要更加方便。利用该函数库,开发人员可以方便快捷的开发出自己的应用程序,细节则留给函数库内部处理。当然ALSA也提供了类似于OSS的系统接口libaoss
,不过ALSA的开发者建议应用程序开发者使用音频函数库而不是驱动程序的API。
1.4 ALSA设备文件与驱动代码文件
1.4.1 设备文件
Linux系统中所有资源都是以文件的形式存在的,ALSA的设备文件在/dev/snd/
目录下,ALSA设备文件功能如下:
设备文件 | 功能 |
---|---|
controlC0 | 控制声卡(通道选择、混音、增益等) |
pcmC0D0c | capture |
pcmC0D0p | playback |
seq | 音序器 |
timer | 定时器 |
C0D0代表声卡0中的设备0。
1.4.2 驱动代码
Linux内核中ALSA驱动在/sound
目录下:
子路径 | |
---|---|
core | ALSA驱动核心层代码 |
core/oss | 模拟旧的oss架构的PCM和Mixer |
core/seq | 音序器相关代码 |
include | ALSA驱动公共头文件目录,需要到处给用户空间的用户程序使用 |
drivers | 与CPU、bus架构无关的公用代码 |
i2c | I2C控制代码 |
pci | PCI声卡代码 |
isa | ISA声卡代码 |
soc | ASoC代码 |
soc/codecs | 针对ASoC的各种codec的驱动实现,与平台无关 |
二、ALSA CORE
2.1 声卡
snd_card
可以说是整个ALSA音频驱动最顶层的一个结构,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在snd_card
的管理之下,声卡驱动的第一个动作通常就是创建一个snd_card
结构体。
struct snd_card {
int number; /* number of soundcard (index to
snd_cards) */
char id[16]; /* id string of this card */
char driver[16]; /* driver name */
char shortname[32]; /* short name of this soundcard */
char longname[80]; /* name of this soundcard */
char mixername[80]; /* mixer name */
char components[128]; /* card components delimited with
space */
struct module *module; /* top-level module */
void *private_data; /* private data for soundcard */
void (*private_free) (struct snd_card *card); /* callback for freeing of
private data */
struct list_head devices; /* devices */
······
}
ALSA驱动核心层通过snd_card_new()
/snd_card_create()
来创建一个snd_card
。在kernel 2.6.32之后,snd_card_new()
已经被snd_card_create()
取代。
int snd_card_create(int idx,const char *xid,struct module *module,int extra_size,struct snd_card **card_ret)
//idx表示该声卡的索引,xid是声卡的字符串标识符,module通常设置为THIS_MODULE,extra_size表示声卡私有数据的大小,card_ret返回的是新创建的snd_card对象
snd_card_create()
创建并初始化一个snd_card
对象,实际上执行了如下操作。
- 调用
kzalloc()
分配sizeof(*card) + extra size
大小的空间给snd_card
结构体指针card;判断参数xid是否为空,如果不为空,则调用snd_info_check_reserved_word()
检查给定ID是否是系统保留的字符串,如果不是,则将xid复制到card->id
- 如果参数idx小于0,则试图在存放声卡模块名称的数组slots中寻找匹配项,如果找到,则将下标赋值给idx,如果没找到,则在slots中寻找空项,将空项的下标赋值给idx。在这个过程中用到一个名为
snd_cards_lock
的锁,它实际上是一个无符号整型数,用于记录被分配的idx。如果一个idx被分配,就利用snd_cards_lock|=1<<idx
把它记录下来。 - 将idx赋值给
card->number
,将 module赋值给card->module
,并初始化card->devices、card-> controls
等成员。 - 调用
snd_ctl_create()
为声卡创建SNDRV_DEV_CONTROL
类型的逻辑设备,并将新创建的设备挂在card> devices
链表上。 - 调用
snd_info_card_create()
为声卡创建proc文件系统下的接口,这里主要为声卡在/proc/asound
目录中创建一个cardX目录作为声卡特定文件的根目录,其中的X由card->number
的值决定。 - 将声卡私有数据指针
cad->private_data
指向(char*)card + sizeof(struct snd_card)
的位置,并返回card。 - 在调用
snd_card_new()
创建并初始化snd_card
对象后,声卡驱动通常还需要为snd_card
的driver、 shortname、 longname等成员赋值,然后调用核心层提供的相关接口为声卡创建并添加更多的逻辑设备。
2.2 逻辑设备
在ALSA核心层中,一个逻辑设备由snd_device
结构描述,定义如下:
struct snd_device(){
struct list_head list; //注册逻辑设备链表的入口项
struct snd_card *card; //所属声卡设备
snd_device_state_t state; //设备状态
snd_device_type_t type; //设备类型
void *device_data; //具体设备结构,例如对于PCM设备就是snd_pcm
struct snd_device_ops *ops; //设备操作,包括设备注册、断开和释放
}
snd_device_new()
可以用来创建逻辑设备,但是一般不会直接调用该函数。
创建逻辑设备函数 | 作用 |
---|---|
snd_pcm_new() | 创建SNDRV_DEV_PCM 类型的设备并加入声卡的devices链表中 |
snd_rawmidi_new() | 创建SNDRV_DEV_RAWMIDI 类型的设备并加入声卡的devices链表中 |
snd_ctl_new() | 创建SNDRV_DEV_CONTROL 类型的设备并加入声卡的devices链表中 |
snd_timer_new() | 创建SNDRV_DEV_TIMER 类型的设备并加入声卡的devices链表中 |
snd_card_proc_new() | 创建SNDRV_DEV_INFO 类型的设备并加入声卡的devices链表中,同时在/proc/asound 中的声卡根目录下产生相应的信息文件 |
snd_jack_new() | 创建SNDRV_DEV_JACK 类型的设备并加入声卡的devices链表中 |
向声卡 添加必要的逻辑设备后,驱动调用snd_card_register()
来完成声卡的注册。
int snd_card_register(struct snd_card *card)
//card表示要注册的声卡
2.2.1 PCM
ALSA驱动实现了用于管理和操作PCM设备的功能子层,PCM子层使用snd_pcm
来描述一个PCM逻辑设备,该结构在include/sound/pcm.h
中定义。PCM子层的最终目的是向声卡注册PCM逻辑设备
struct snd_pcm {
struct snd_card *card; /* 该PCM设备所属声卡*/
struct list_head list; /* 所有注册的PCM设备链表 */
int device; /* PCM索引 */
unsigned int info_flags; /* SNDRV_PCM_INFO_ */
unsigned short dev_class;
unsigned short dev_subclass;
char id[64]; /* PCM设备标识 */
char name[80]; /* PCM设备名 */
struct snd_pcm_str streams[2];//PCM的playback和capture stream
struct mutex open_mutex;
wait_queue_head_t open_wait;
void *private_data;//private_data一般为芯片专用信息
void (*private_free) (struct snd_pcm *pcm);//用来释放private_data
bool internal; /* pcm is for internal use only */
bool nonatomic; /* whole PCM operations are in non-atomic context */
#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
struct snd_pcm_oss oss;
#endif
};
2.2.2 CONTROL
声卡控制项为用户空间的应用程序(alsa-lib)提供了访问和控制codec中丰富的开关和调节器的途径。
在include/sound/control.h
中,定义了声卡控制项相关的结构和操作,snd_kcontrol
用于描述一个具体的控制项。
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const unsigned char *name; /* ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
三、ASOC
ASoC--ALSA System on Chip ,是建立在标准ALSA驱动层上,为了更好地支持嵌入式处理器和移动设备中的音频Codec的一套软件体系。在ASoc出现之前,内核对于SoC中的音频已经有部分的支持,不过会有一些局限性:
- Codec驱动与SoC CPU的底层耦合过于紧密,这种不理想会导致代码的重复,例如,仅是wm8731的驱动,当时Linux中有分别针对4个平台的驱动代码。
- 音频事件没有标准的方法来通知用户,例如耳机、麦克风的插拔和检测,这些事件在移动设备中是非常普通的,而且通常都需要特定于机器的代码进行重新对音频路劲进行配置。
- 当进行播放或录音时,驱动会让整个codec处于上电状态,这对于PC没问题,但对于移动设备来说,这意味着浪费大量的电量。同时也不支持通过改变过取样频率和偏置电流来达到省电的目的。
ASoC为声卡注册一个名称是“soc- audio”的平台设备,然后将由snd_soc_device
结构描述的声卡设备实例设置为该平台设备的驱动数据。可以认为snd_soc_device
是整个ASoC数据结构的基础,由它开始,引出一系列描述ASoC架构下各个功能组件及其特性、操作的数据结构。snd_soc_device
结构引出了snd_soc_card
和 soc_soc_codec
两个结构,然后snd_soc_card
又引出了snd_ soc_platform
、snd_soc_dai_link
结构。如上所述,整个ASoC框架被划分为Machine驱动、 Platform驱动和 Codec驱动三大部分。从这些数据结构看来,snd_codec_device
和snd_soc_card
代表 Machine驱动,snd_soc_platform
代表 Platforn驱动,snd_soc_codec
和snd_soc_codec_device
代表 Codec驱动,snd_soc_dai_link
则负责连接 Platforn驱动和 Codec驱动。
四、ALSA初始化及Playback流程
Playback流程请查看下属链接文档:Linux_ALSA声卡驱动原理分析 提取码:jifr