开发日志
ALSA 音频子系统:从内核驱动到 PCM 编程
Linux 音频这块经常被”PulseAudio 还是 PipeWire”的讨论盖过,但两者其实都是建立在 ALSA 之上的——搞清楚 ALSA 这一层,上面的分歧才看得明白。
两层架构
ALSA(Advanced Linux Sound Architecture)分两层:
- 内核驱动层:具体硬件的驱动,比如
snd-hda-intel(大部分主板集成声卡)、snd-usb-audio(USB 声卡/耳机)。这层负责跟硬件寄存器打交道。 - 用户态库
libasound:应用程序实际链接的库,提供snd_pcm_*这套 API, 把 ioctl 细节封装掉。
/proc/asound/cards 能看到当前系统识别到的声卡,aplay -l 能看到可用的播放
设备——这些命令行工具本身就是 libasound 的薄封装。
PCM 设备命名:hw / plughw / default 的区别
初学者最容易踩坑的地方:
aplay -Lhw:CARD=PCH,DEV=0 # 直接访问硬件,格式必须跟硬件完全匹配,不做任何转换plughw:CARD=PCH,DEV=0 # hw 的封装,会自动做采样率/格式转换default # ALSA 配置文件(.asoundrc / asound.conf)里定义的默认设备, # 通常经过 dmix 插件支持多进程混音hw: 设备是独占的、不做任何软件转换——如果你的程序申请 48kHz 但硬件配置成了
44.1kHz,hw: 直接返回错误;plughw: 会在中间插一层重采样帮你转换。这也是为什么
两个程序同时用 hw:0,0 会有一个失败:真硬件设备不支持多路复用,default/dmix
才是允许多进程共享的那一层。
hw_params:协商真正生效的参数
打开一个 PCM 设备之后,实际参数需要协商,不是你指定什么就一定拿到什么:
snd_pcm_t *handle;snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
snd_pcm_hw_params_t *params;snd_pcm_hw_params_alloca(¶ms);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);
unsigned int rate = 44100;snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0); // 注意是 "near"snd_pcm_hw_params(handle, params); // 真正提交协商结果
snd_pcm_writei(handle, audio_buffer, frame_count); // 写入交织的 PCM 数据snd_pcm_drain(handle); // 等缓冲区播放完再关闭,避免截断尾音set_rate_near 这个函数名是个提示:ALSA 允许硬件返回”最接近”的采样率而不是精确
匹配,协商完之后一定要读回 rate 变量确认实际值,不能假设申请的就是拿到的——这个
模式跟 V4L2 的 S_FMT 几乎一模一样,都是”申请 → 驱动可能打折 → 读回实际值”。
跟 PulseAudio / PipeWire 的关系
一个常见误解是”PipeWire 取代了 ALSA”——不对,PipeWire(现在大多数发行版的默认 音频服务器)和更早的 PulseAudio 一样,底层仍然通过 ALSA 跟硬件通信,它们解决 的是 ALSA 本身不管的问题:
- 多个应用同时访问同一个硬件设备(软件混音)
- 音频路由(把某个应用的输出转到蓝牙耳机)
- 网络透明(跨机器播放)
- 对 Vulkan/Video 场景很重要的低延迟/图数据流处理(这是 PipeWire 相对 PulseAudio 的主要设计目标之一,最初就是奔着统一音频 + 视频流处理去的)
应用程序通常不直接调用 snd_pcm_*,而是链接 PulseAudio/PipeWire 提供的客户端库
(或者走 ALSA 的 pulse/pipewire plugin,让老程序”以为”自己还在直接跟 ALSA
对话)。理解这层关系之后,排查”为什么这个程序听不到声音”的问题会顺畅很多——
先确认 ALSA 层硬件本身工作正常(speaker-test -D hw:0,0),再往上排查路由层。