3、ALSA核心概念:声卡(Card)、设备(Device)、子设备(Subdevice)、PCM流的概念
好,咱们今天来聊聊ALSA里最基础、也最绕不开的几个概念。说实话,我刚入行那会儿,对着ALSA的文档看了三天,脑子里还是一团浆糊。什么Card、Device、Subdevice,还有PCM流,感觉像俄罗斯套娃一样一层套一层。后来在项目里调了两年驱动,才慢慢摸清楚它们到底是怎么回事。
嗯,咱们今天就把它彻底讲明白。你想想看,搞音频驱动,本质上就是在跟硬件打交道。而ALSA这套框架,就是帮我们把复杂的硬件抽象成几个简单的层次。说白了,就是给硬件“建档立卡”。
3.1 声卡(Card)—— 物理存在的“大管家”
声卡,就是Card,代表一个物理的或者逻辑的音频硬件实体。在车载Linux系统里,一个Card通常对应一颗Codec芯片,或者一个SoC内部的音频控制器。
我个人习惯把Card想象成一个“大管家”。它管理着所有跟这块硬件相关的资源。比如,你的车机上有一颗TI的TAS6424功放芯片,那它就是一个Card。如果还有一颗NXP的音频DSP,那可能就是另一个Card。
在系统里,每个Card都有一个编号,从0开始。你可以通过 /proc/asound/cards 看到它们:
cat /proc/asound/cards
0 [tegrahda ]: HDA-Intel - HDA Tegra
HDA Tegra at 0x3518000 irq 67
1 [tegraxbar ]: XBAR - Tegra XBAR
Tegra XBAR
你看,这里有两个Card。0号是HDA接口的,1号是Tegra内部的XBAR。每个Card都是独立的,它们之间互不干扰。
3.2 设备(Device)—— 功能单元的“部门经理”
一个Card下面,可以有多个Device。Device代表的是这个声卡上的不同功能单元。比如,一个Codec芯片,它可能同时支持播放(Playback)和录音(Capture),那它至少就有两个Device。
你想想看,一个Card是“大管家”,那Device就是“部门经理”。有的部门管输出,有的部门管输入,有的部门管混音。
在ALSA里,Device也是从0开始编号的。比如:
- Device 0:通常用于主音频播放(比如音乐、导航)
- Device 1:可能用于语音通话(比如蓝牙电话)
- Device 2:可能用于录音(比如麦克风阵列)
你可以用 aplay -l 来查看:
aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: tegrahda [HDA Tegra], device 0: HDA Tegra HDMI/DP 0 [HDMI/DP 0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: tegrahda [HDA Tegra], device 3: HDA Tegra HDMI/DP 1 [HDMI/DP 1]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: tegraxbar [Tegra XBAR], device 0: Tegra XBAR PCM [Tegra XBAR PCM]
Subdevices: 1/1
Subdevice #0: subdevice #0
看到了吗?Card 0下面有device 0和device 3,分别对应不同的HDMI输出口。Card 1下面只有device 0。
3.3 子设备(Subdevice)—— 通道的“工位”
Subdevice,子设备,这个概念最容易被忽略。它代表的是同一个Device下的多个独立通道。说白了,就是同一个“部门经理”手下,有几个“工位”,每个工位可以独立干活。
举个例子,一个支持8通道输出的Codec,它可能只有一个Device(device 0),但下面有8个Subdevice。每个Subdevice对应一个物理通道(比如左前、右前、左后、右后...)。
在ALSA里,Subdevice的表示方式是 hw:X,Y,Z,其中X是Card号,Y是Device号,Z是Subdevice号。比如 hw:0,0,0 表示Card 0、Device 0、Subdevice 0。
我记得有一次,我在调试一个车载功放,发现左右声道声音不一样大。查了半天,原来是应用层打开的是 hw:1,0,0,但硬件上左声道是Subdevice 0,右声道是Subdevice 1。应用层只打开了Subdevice 0,所以只有左声道有声音。嗯,这种坑,你不看Subdevice配置根本发现不了。
snd_pcm_substream 来管理每个Subdevice。
3.4 PCM流 —— 数据的“高速公路”
最后,咱们聊聊PCM流。PCM流,说白了就是音频数据流动的通道。它分为两种方向:
- Playback(播放):数据从内存流向硬件(比如播放音乐)
- Capture(捕获):数据从硬件流向内存(比如录音)
每个Device下,可以有多个PCM流。比如一个Device同时支持播放和录音,那它就有两个PCM流。在ALSA的术语里,每个PCM流对应一个 snd_pcm_substream 对象。
你想想看,PCM流就像一条高速公路。数据在高速公路上跑,需要遵守交通规则:采样率、位深、通道数。这些参数就是高速公路的“限速”和“车道数”。
在驱动开发中,我们最常打交道的就是PCM流的配置。比如:
// 设置PCM流的硬件参数
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_any(handle, params);
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(handle, params, 2);
snd_pcm_hw_params_set_rate(handle, params, 48000);
这段代码,就是告诉PCM流:“嘿,你要用16位小端格式、双通道、48kHz采样率来跑数据。”
3.5 它们之间的关系 —— 一张图说清楚
好了,咱们把四个概念串起来。你可以这样理解:
| 层级 | 类比 | 说明 |
|---|---|---|
| Card | 公司 | 一个物理音频硬件,比如一颗Codec芯片 |
| Device | 部门 | 一个功能单元,比如播放、录音、混音 |
| Subdevice | 工位 | 一个独立的通道,比如左声道、右声道 |
| PCM流 | 高速公路 | 数据流动的通道,有方向(播放/捕获) |
在驱动代码里,它们的创建顺序通常是:先注册Card,然后在Card上添加Device,每个Device下再创建Subdevice,最后通过PCM流来传输数据。
嗯,这里要注意:并不是所有Device都有Subdevice。有些简单的Device只有一个Subdevice(即Subdevice #0),那它其实就退化成了一对一的映射关系。但理解了这个层次结构,你以后看任何ALSA驱动的代码,都不会再晕了。
好了,这一章就到这里。下一章咱们会深入PCM流的内部机制,讲讲DMA、环形缓冲区、以及Xrun的处理。到时候我会分享一个我在车载项目中遇到的“声音延迟”的案例,挺有意思的。