1. ALSA架构概览:音频子系统在Linux内核中的位置
大家好,我是老李。做嵌入式Linux音频驱动开发也有些年头了。今天咱们来聊聊ALSA——这个在Linux世界里几乎统治了音频领域的家伙。
说实话,我刚接触Linux音频时,也被那一堆术语搞得头晕。什么OSS、ALSA、PCM、DMA...嗯,别急,咱们一步步来。
1.1 音频子系统在Linux内核中的位置
先问个问题:音频子系统在内核里到底算老几?
Linux内核是个庞然大物,音频子系统属于字符设备驱动这一层。它位于内核的中间位置——上面是用户空间的应用程序(比如你播放音乐用的mpg123、aplay),下面是具体的硬件(Codec芯片、DMA控制器、I2S总线)。
我画了个图,帮你理解这个层次关系:
你看,用户空间的应用程序通过ALSA库(libasound)发起调用,经过系统调用陷入内核,然后由ALSA核心层调度,最终交给硬件驱动去操作真实的音频设备。
关键点:ALSA在内核中属于sound/core目录,它不直接操作硬件,而是提供一套统一的框架。真正的硬件操作由底层的SoC平台驱动和Codec驱动完成。
1.2 ALSA与OSS的对比——为什么ALSA赢了?
老工程师可能还记得OSS(Open Sound System)。我入行那会儿,OSS还是主流。但后来ALSA全面取代了它。为什么?
我列个表,你一看就明白:
| 对比项 | OSS | ALSA |
|---|---|---|
| 设计理念 | 简单粗暴,每个音频设备对应一个设备文件 | 层次化、模块化设计,支持复杂拓扑 |
| 设备文件 | /dev/dsp、/dev/mixer |
/dev/snd/pcmC0D0p、/dev/snd/controlC0 |
| 多路音频 | 不支持,一个设备一次只能被一个进程使用 | 原生支持,通过dmix插件实现多路混音 |
| API复杂度 | 简单,ioctl为主 | 复杂但功能强大,提供完整API |
| 内核集成 | 独立子系统 | 从2.6内核开始成为标准音频子系统 |
| 硬件支持 | 有限,商业驱动居多 | 广泛,大量开源驱动 |
| 现状 | 已被废弃,仅用于兼容旧硬件 | Linux标准音频框架 |
说白了,OSS就像个老式收音机——只能一个人听,换台还得拧旋钮。ALSA则像个现代音响系统——支持多路输入输出,还能做各种音效处理。
我的经验:如果你在维护老项目,可能还会碰到OSS代码。但新项目千万别碰OSS了。我曾经接手过一个用OSS的项目,改到ALSA费了不少劲。嗯,那滋味不好受。
1.3 ALSA的软件分层架构
ALSA的分层设计,说白了就是「各司其职」。咱们从上往下捋:
1.3.1 用户空间层
- ALSA库(libasound):这是用户空间的核心。应用程序通过它来操作音频设备。它封装了底层的系统调用,提供了
snd_pcm_open()、snd_pcm_writei()这些友好的API。 - ALSA插件系统:比如dmix(多路混音)、dsnoop(多路录音)、rate(采样率转换)。这些插件让ALSA变得非常灵活。
- 工具集:
aplay、arecord、amixer、alsamixer等。调试时我经常用它们。
1.3.2 内核空间层
- ALSA核心(sound/core):提供框架和通用功能。包括PCM设备管理、Control接口、Timer定时器等。
- SoC层(sound/soc):针对嵌入式系统的ASoC(ALSA System on Chip)框架。把驱动拆成了三部分——Platform驱动、Codec驱动、Machine驱动。这个后面会详细讲。
- 硬件驱动:具体的I2S控制器驱动、DMA驱动、Codec芯片驱动。
1.3.3 硬件层
- Codec芯片(音频编解码器)
- I2S/PCM总线
- DMA控制器
- 扬声器、麦克风等物理器件
记住这个分层:用户空间(应用+库)→ 内核空间(ALSA核心+驱动)→ 硬件。数据流是双向的,播放时从上往下,录音时从下往上。
1.4 ALSA核心数据结构初步认识
做驱动开发,数据结构就是你的「武器」。ALSA里有几个核心结构体,你必须得认识它们。
1.4.1 struct snd_card——声卡实例
每个物理声卡对应一个snd_card。它是ALSA驱动的顶层结构体。你可以把它理解成「声卡的总管家」。
struct snd_card {
int number; // 声卡编号,从0开始
char id[32]; // 声卡标识符
char driver[32]; // 驱动名称
char shortname[32]; // 短名称
char longname[80]; // 长名称
struct list_head devices; // 设备链表
struct device *dev; // 内核设备指针
// ... 还有很多其他成员
};
我习惯在驱动入口函数里先创建snd_card:
static int my_audio_probe(struct platform_device *pdev)
{
struct snd_card *card;
int ret;
// 第一步:创建声卡实例
ret = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card);
if (ret < 0) {
dev_err(&pdev->dev, "snd_card_new failed\n");
return ret;
}
// 设置声卡名称
strcpy(card->driver, "MyAudio");
strcpy(card->shortname, "My Audio Driver");
strcpy(card->longname, "My Audio Driver for Embedded Board");
// ... 后续注册PCM设备、Control接口等
// 最后注册声卡
ret = snd_card_register(card);
if (ret < 0) {
snd_card_free(card);
return ret;
}
return 0;
}
注意:创建snd_card后,一定要记得调用snd_card_register()。我曾经犯过这个错——创建了卡但忘了注册,结果用户空间死活看不到设备。排查了半天才发现是少了这步。
1.4.2 struct snd_device——通用设备抽象
snd_device是ALSA内部用来管理各种子设备的通用结构体。PCM设备、Control设备、MIDI设备等都会被封装成snd_device挂到snd_card的设备链表上。
struct snd_device {
struct list_head list; // 链表节点
struct snd_card *card; // 所属声卡
snd_device_type_t type; // 设备类型
void *device_data; // 具体设备的数据指针
struct snd_device_ops *ops; // 操作函数集
};
你不需要直接操作snd_device,ALSA核心会帮你管理。你只需要调用snd_pcm_new()这类API,它内部会自动创建snd_device并挂到卡上。
1.4.3 struct snd_pcm——PCM设备核心
这是咱们打交道最多的结构体。PCM(Pulse Code Modulation)设备负责实际的音频数据流传输。
struct snd_pcm {
struct snd_card *card; // 所属声卡
struct list_head list; // 链表节点
unsigned int streams; // 支持的流方向 (播放/录音)
struct snd_pcm_str streams[2]; // 播放和录音两个方向
// ... 还有很多
};
每个snd_pcm可以包含两个snd_pcm_str:一个用于播放(SNDRV_PCM_STREAM_PLAYBACK),一个用于录音(SNDRV_PCM_STREAM_CAPTURE)。
创建PCM设备的典型代码:
struct snd_pcm *pcm;
int ret;
// 创建PCM设备,支持播放和录音
ret = snd_pcm_new(card, "My PCM", 0, 1, 1, &pcm);
if (ret < 0) {
dev_err(&pdev->dev, "snd_pcm_new failed\n");
return ret;
}
// 设置PCM操作函数集
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &my_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &my_capture_ops);
我的习惯:创建PCM设备时,我会先确定硬件支持哪些采样率、位深、通道数。然后在hw_constraints里设置好这些参数。这样上层应用打开设备时,ALSA会自动帮它筛选出支持的配置。省心不少。
1.5 三个核心结构体的关系
这三个结构体的关系,我用一句话总结:
一个snd_card包含多个snd_device,其中最重要的snd_device就是snd_pcm。
画个图更清楚:
嗯,这张图应该能帮你理清关系了。
1.6 小结
这一章咱们聊了:
- 音频子系统在内核中的位置——字符设备驱动层
- ALSA为什么取代了OSS——功能更强、设计更合理
- ALSA的三层架构——用户空间、内核空间、硬件层
- 三个核心数据结构——
snd_card、snd_device、snd_pcm
说实话,这些概念刚接触时可能会觉得抽象。但别急,后面我们会一步步深入到代码细节。等你亲手写过几个驱动,回头再看这些结构体,就会觉得「哦,原来就这么回事」。
一句话总结:ALSA就是Linux音频的「交通警察」——它负责把应用程序的音频数据,安全、高效地送到硬件手里,或者反过来。
公众号:蓝海资料掘金营,微信deep3321