Linux音频子系统(2) - ALSA Framework
了解ALSA架构1.概述 ALSA 是一个完全开放源码的音频驱动程序集,是由志愿者维护的开源项目,而 OSS 则是由公司提供的商业产品。ALSA 系统包括驱动包alsa-driver(集成在内核源码),开发包 alsa-libs,开发包插件 alsalibplugins,设置管理工具包 alsa-utils,其他声音相关处理小程序包alsa-tools,特殊音频固件支持包 alsa-fi...
- 了解ALSA架构
1.Advanced Linux Sound Architecture (ALSA)
The Advanced Linux Sound Architecture (ALSA) subsystem provides audio and MIDI capabilities to Linux systems, including a user space library to simplify application programming (alsa-lib) and support for the older Open Sound System (OSS) architecture through legacy compatibility modes. Specifically for system-on-chips, the architecture defines an ALSA system-on-chip (ASoC) layer which provides optimized support for embedded devices.
ALSA 是一个完全开放源码的音频驱动程序集,是由志愿者维护的开源项目,而 OSS 则是由公司提供的商业产品。ALSA 系统包括驱动包alsa-driver(集成在内核源码),开发包 alsa-libs,开发包插件 alsalibplugins,设置管理工具包 alsa-utils,其他声音相关处理小程序包alsa-tools,特殊音频固件支持包 alsa-firmware,OSS 接口兼容模拟层工具 alsa-oss 共 7 个子项目,其中只有 alsa-driver 是必须的。除了 alsa-driver,ALSA 包含在用户空间的 alsa-lib 函数库,具有更加友好的编程接口,并且完全兼容于 OSS,开发者可以通过这些高级 API 使用驱动,不必直接与内核驱动 API 进行交互。
ALSA 主要有如下特点:
- 支持多种声卡设备、
- 模块化的内核驱动程序 、
- 支持 SMP(对称多处理)和多线程、
- 提供应用开发函数库
- 兼容OSS应用程序
2.ALSA框架
-
User空间:主要由Alsa Libray API对应用程序提供统一的API接口,各个APP应用程序只要调用 alsa-lib 提供的 API接口来实现放音、录音、控制。现在提供了两套基本的库,tinyalsa是一个简化的alsa-lib库,现在Android的系统中主要使用它。
-
Kernel空间:
-
ASOC Core:是 ALSA 的标准框架,是 ALSA-driver 的核心部分,提供了各种音频设备驱动的通用方法和数据结构,为 Audio driver提供 ALSA Driver API
-
ALSA CORE:alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备。
-
Hardware Driver:音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec,提供的 ALSA Driver API 和相应音频设备的初始化及工作流程,实现具体的功能组件,这也是驱动开发人员需要具体实现的部分;
-
Codec类: Codec即编解码芯片的驱动,此Codec驱动是和平台无关,包含的功能有: 音频的控制接口,音频读写IO接口,以及DAPM的定义等。如果需要的话,此Codec类可以在BT,FM,MODEM模块中不做修改的使用。因此Codec就是一个可重复使用的模块,同一个Codec在不同的SOC中可以使用。
-
Platform类: 可以理解为某款SOC平台,平台驱动中包括音频DMA引擎驱动,数字接口驱动(I2S, AC97, PCM)以及该平台相关的任何音频DSP驱动。同样此Platform也可以重用,在不同的Machine中可以直接重复使用。
-
Machine类: Machine可以理解为是一个桥梁,用于在Codec和Platform之间建立联系。此Machine指定了该机器使用那个Platform,那个Codec,最终会通过Machine建立两者之间的联系。
-
-
2.1.Hardware Driver三者关系:
-
Platform
指某款soc平台的音频模块,比如qcom,omap,amlogic,atml等等。platform又可细分为二个部分:- cpu dai:在嵌入式系统里面通常指soc的i2s,pcm总线控制器,负责把音频数据从I2S tx FIFO搬运到codec(playback,capture则相反)。cpu_dai通过 snd_soc_register_dai()来注册。注:DAI是Digital Audio Interface的简称,分为cpu_dai和codec_dai,这两者通过i2s/pcm总线连接;AIF是Audio Interface母的简称,嵌入式系统中一般是I2S和PCM接口。
- PCM dma:负责把dma buffer中的音频数据搬运到i2s tx fifo。值得留意的是:某些情形下是不需要dma操作的,比如modem和codec直连,因为modem本身已经把数据送到fifo了,这时只需要启动codec_dai接收数据即可;该情形下,machine驱动dai_link中需要设定.platform_name = “snd_soc_dummy”,这是虚拟dma驱动,实现见sound/soc/soc-utils.c. 音频dma驱动通过 snd_soc_register_platform()来注册,故也常用platform来指代音频dma驱动(这里的platform需要与soc platfrom区分开)。
-
Codec:对于回放来说,userspace送过来的音频数据是经过采样量化的数字信号,在codec经过DAC转换成模拟信号然后输出到外放或耳机,这样你就可以听到声音了,codec字面意思是编解码器,但芯片(codec)里面的功能部件很多,常见的有AIF,DAC,ADC,Mixer,PGA,line-in,line-out,有些高端的codec芯片还有EQ,DSP,SRC,DRC,AGC,Echo-Canceller,Noise-Suppression等部件。
-
Machine:指某款机器,通过配置dai_link把cpu_dai,codec_dai,modem_dai各个音频接口给链结成一条条音频链路,然后注册snd_soc_card.和上面两个不一样,platform和codec驱动一般是可以重用的,而machine有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:Soc Platform与Codec的差异;DAIs之间的链结方式;通过某个GPIO打开Amplifier;通过某个GPIO检测耳机插拔;使用某个时钟如MCLK/External-OSC作为i2s,CODEC的时钟源等等。
2.2.以s3c24xx为例:
Machine:
s3c24xx_uda134x.c
该文件是设备模型建立要执行的第一个文件。它联系了以上三个 文 件,导致了以 上三文件中的初始化函数的调用执行。填充了结构体 s3c24xx_uda134x_ops, s3c24xx_uda134x_dai_link,snd_soc_s3c24xx_uda134x, s3c24xx_uda134x, s3c24xx_uda134x_snd_devdata。添加了平台设备 s3c24xx_uda134x_snd_device到内 核。注册了与平台设备"s3c24xx_uda134x"(移植时需要手动添加,还需要添加的是该设 备的platform_data)相匹配 的驱动 s3c24xx_uda134x_driver。
Platform:
s3c24xx-i2s.c :该文件主要实现了配置cpu上iis接口寄存器的一些操作函数,填充了结构 体 s3c24xx_i2s_dai。
dma.c
Codec:
uda134x.c
该文件主要实现了对编解码芯片uda1341寄存器的设置,声音调节,静音设 置等操 作函数。填 充了 结 构 体 uda134x_dai。该文件还实现了一些重要的初始化,比如 创 建结构体类型为snd_card的card实例,创建pcm实例等。
3.ALSA核心层
核心层为用户空间提供逻辑设备接口, 同时为驱动提供接口来驱动硬件设备, 主要位于sound/core目录下。
分析是谁调用snd_register_device_for_dev函数来注册sound设备的。
3.1.字符设备注册
sound/core/sound.c:
static int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
if (register_chrdev(major, "alsa", &snd_fops)) {
snd_printk(KERN_ERR "unable to register native major device number %d/n", major);
return -EIO;
}
if (snd_info_init() < 0) {
unregister_chrdev(major, "alsa");
return -ENOMEM;
}
snd_info_minor_register();
return 0;
}
注册了主设备号为116的字符设备文件,文件操作是snd_fops,snd_open接口比较重要,snd_open接口通过文件节点inode得到了次设备号,通过snd_minors数组得到对应的声音逻辑设备的文件操作,调用对应的open接口,并调用fops_put替换成了对应的逻辑设备的文件操作(snd_minors里维护)。
snd_info_init创建proc目录下的asound,并在该目录下分别建立version EVM card0 cards device pcm timers,用来查看相关的信息。
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
}
alsa驱动设备文件结构:
$ cd /dev/snd
$ ls -l
crw-rw----+ 1 root audio 116, 8 2011-02-23 21:38 controlC0
crw-rw----+ 1 root audio 116, 4 2011-02-23 21:38 midiC0D0
crw-rw----+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D0c
crw-rw----+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D0p
crw-rw----+ 1 root audio 116, 5 2011-02-23 21:38 pcmC0D1p
crw-rw----+ 1 root audio 116, 3 2011-02-23 21:38 seq
crw-rw----+ 1 root audio 116, 2 2011-02-23 21:38 timer
- controlC0 :用于声卡的控制,例如通道选择,混音,麦克风的控制等
- midiC0D0:用于播放midi音频
- pcmC0D0c:用于录音的pcm设备
- pcmC0D0p:用于播放的pcm设备
- seq :音序器
- timer :定时器
其中, C0D0代表的是声卡0中的设备0, pcmC0D0c最后一个c代表capture, pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。
/proc/asound
dr-xr-xr-x 6 root root 0 Oct 8 00:04 card0
-r–r–r– 1 root root 0 Oct 8 00:04 cards
-r–r–r– 1 root root 0 Oct 8 00:04 devices
-r–r–r– 1 root root 0 Oct 8 00:04 modules
dr-xr-xr-x 2 root root 0 Oct 8 00:04 oss
-r–r–r– 1 root root 0 Oct 8 00:04 pcm
dr-xr-xr-x 2 root root 0 Oct 8 00:04 seq
-r–r–r– 1 root root 0 Oct 8 00:04 timers
-r–r–r– 1 root root 0 Oct 8 00:04 version
- cards : 可显示系统中存在多少个声卡
- card0 : 代表某个声卡
- devices : 可显示系统中存在多少个逻辑设备
/proc/asound/card0:该节点提供该card的一些info.
-r–r–r– 1 root root 0 Oct 7 19:57 audiopci
dr-xr-xr-x 2 root root 0 Oct 7 19:57 codec97#0
-r–r–r– 1 root root 0 Oct 7 19:57 id
-r–r–r– 1 root root 0 Oct 7 19:57 midi0
dr-xr-xr-x 3 root root 0 Oct 7 19:57 pcm0c
dr-xr-xr-x 3 root root 0 Oct 7 19:57 pcm0p
dr-xr-xr-x 3 root root 0 Oct 7 19:57 pcm1p
3.2 数据结构
该层包含的主要数据结构包括:
- snd_card 表示一个声卡实例, 包含多个声卡设备
- snd_device 表示一个声卡设备部件
- snd_pcm 表示一个PCM设备, 声卡设备的一种, 用于播放和录音
- snd_control 表示Control设备, 声卡设备的一种, 用于控制声卡
- snd_pcm_str 表示PCM流, 分为playback和capture
- snd_pcm_substream PCM子流, 用于音频的播放或录制
- snd_pcm_ops PCM流操作集
snd_card主要字段如下:
struct snd_card {
int number; /* 索引 */
char id[16]; /* 标识符 */
char driver[16]; /* 驱动名称 */
char shortname[32]; /* 短名 */
char longname[80]; /* 名字 */
void *private_data; /* 声卡私有数据*/
void (*private_free) (struct snd_card *); /* 私有数据释放回调 */
struct list_head devices; /* 该声卡下所有设备*/
struct list_head controls; /* 该声卡下所有控制设备*/
struct list_head files_list; /* 声卡管理文件 */
struct device *dev; /* 声卡相关的device */
struct device card_dev; /* 用于sysfs, 代表该声卡 */
bool registered; /* 是否注册标记 */
};
snd_device主要字段如下:
struct snd_device {
struct list_head list; /* 所有注册的声卡设备链表 */
struct snd_card *card; /* 设备所属声卡 */
enum snd_device_state state; /* 设备状态*/
enum snd_device_type type; /* 设备类型*/
void *device_data; /* 指向具体的声卡设备, 如snd_pcm */
struct snd_device_ops *ops; /* 设备操作集*/
};
snd_pcm主要字段如下:
struct snd_pcm {
struct snd_card *card; /* 该PCM设备所属声卡*/
struct list_head list; /* 所有注册的PCM设备链表 */
int device; /* PCM索引 */
unsigned int info_flags; /* SNDRV_PCM_INFO_ */
char id[64]; /* PCM设备标识 */
char name[80]; /* PCM设备名 */
struct snd_pcm_str streams[2]; /* 指向PCM设备的capture(1)和playback(0)流 */
void *private_data; /* PCM设备私有数据*/
void (*private_free) (struct snd_pcm *); /* 私有数据释放回调 */
};
3.3 APIs
该层主要接口如下:
/* 创建和初始化声卡结构体 */
int snd_card_new(struct device *parent, int idx, const char *xid, struct module *, int extra_size, struct snd_card **card_ret);
/* 释放声卡结构体 */
int snd_card_free(struct snd_card * card);
/* 注册声卡 */
int snd_card_register(struct snd_card * card);
/* 创建声卡设备部件, 通常由snd_pcm_new和snd_card_new自动完成 */
int snd_device_new(struct snd_card *, enum snd_device_type type, void *device_data, struct snd_device_ops *ops);
/* 注册声卡设备部件, 通常由snd_card_register自动完成 */
int snd_device_register(struct snd_card *card, void *device_data);
/* 创建PCM设备 */
int snd_pcm_new(struct snd_card *, const char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm);
/* 创建PCM流, 通常snd_pcm_new会自动创建capture和playback两个PCM流 */
int snd_pcm_new_stream(struct snd_pcm * pcm, int stream, int substream_count);
/* 设置PCM设备操作集 */
void snd_pcm_set_ops(struct snd_pcm *, int direction, const struct snd_pcm_ops *ops);
4.card的创建与注册
struct snd_card可以说是整个ALSA音频驱动最顶层的一个结构, 整个声卡的软件逻辑结构开始于该结构, 几乎所有与声音相关的逻辑设备都是在snd_card的管理之下, 声卡驱动的第一个动作通常就是创建一个snd_card结构体。
4.1.snd_card_new
1. 分配snd_card+extra_size空间大小
2. 如果extra_size大于0,将private_data指向extra_size所在首地址
3. 如果指定了xid, 将其拷贝至snd_card::id中, 即声卡标识符
4. 根据idx获取可用的声卡索引并赋值给snd_card::number
5. 分别将parent、module赋值给snd_card::dev、snd_card::module
6. 初始化链表snd_card::devices、snd_card::controls、snd_card::ctl_files、snd_card::files_list
7. 调用device_initialize()初始化snd_card::card_dev, 并设置snd_card::card_dev相关成员变量, 用于sysfs
8. 调用snd_ctl_create()创建控制接口
8.1 调用snd_device_initialize初始化snd_card::ctl_dev, 并设置相关成员变量, 用于sysfs
8.2 调用snd_device_new(SNDRV_DEV_CONTROL, ops)创建声卡控制设备部件
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
9. 调用snd_info_card_create()创建proc对应文件系统
4.2.snd_card_register
函数的主要目的是注册card下面挂载的所有device; 另外只有该函数成功返回后, 用户空间才能通过control interface来访问底层。
4.3.struct snd_device
一个struct snd_device用于描述一个逻辑设备,每个逻辑设备都有一个对应的snd_device_ops, 我们在新增一个逻辑设备时, 需要为此设备准备好该数据结构。
4.3.1.snd_device_new
该API用于创建一个逻辑设备, 主要内容是分配一个struct snd_device空间, 初始化相关字段, 并把该逻辑设备添加到card->devices链表下。注意在添加新设备到card->devices链表时, 采用的是有序插入方式: type小的在前、type大的在后。
4.3.2.snd_device_register
一般在注册声卡时(snd_card_register)会自动调用此处的API; 不过也可以在card注册完毕后, 在手动调用此API注册一个新的逻辑设备。
该API的实现很简单: 首先回调snd_device_ops->dev_register, 然后把核心层的状态标记为SNDRV_DEV_REGISTERED。
refer to
- https://www.cnblogs.com/hzl6255/p/9979377.html
- https://cloud.tencent.com/developer/article/1603864
- https://cloud.tencent.com/developer/article/1541259
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)