从零写OS内核|350:音频子系统——ALSA 是怎么播放声音的
摘要:音频是计算机最常用的外设功能之一,从播放音乐到视频通话都离不开它。本篇文章从 PCM 原理、HDA 音频架构、到 Linux ALSA 驱动和 aplay 实战,带你理解数字音频的编解码链路。
1. PCM:数字音频的本质
1.1 采样、量化、编码
模拟声音是连续变化的波形。要变成数字信号,需要三步:
模拟信号(连续)→ 采样(离散时间)→ 量化(离散幅度)→ 编码(二进制)采样率(Sample Rate):每秒采多少个点。常见:
- 44100 Hz(CD 音质)
- 48000 Hz(DVD/专业音频)
- 96000 Hz(Hi-Res)
位深(Bit Depth):每个采样点用多少 bit 描述幅度。常见:
- 16 bit(CD 音质)
- 24 bit(专业录音)
- 32 bit float(DSP 内部处理)
声道(Channels):1=单声道,2=立体声,5.1=环绕声,7.1=…
1.2 PCM 数据流
时间 → 0 1 2 3 4 5 6 7
┌────┬────┬────┬────┬────┬────┬────┬────┐
声道L │ S0 │ S1 │ S2 │ S3 │ S4 │ S5 │ S6 │ S7 │ ← 16-bit LE
声道R │ S0'│ S1'│ S2'│ S3'│ S4'│ S5'│ S6'│ S7'│ ← 16-bit LE
└────┴────┴────┴────┴────┴────┴────┴────┘
对于 44100Hz/16bit/立体声:
Bytes per second = 44100 × 2 × 2 = 176,400 bytes/sec
Bitrate = 1.411 Mbps1.3 有趣的 ASCII 波形
原始模拟波形:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
╱╲ ╱╲╱╲ ╱╲
~~~~~~~~ ╱ ╲____╱ ╲__╱ ╲________________
PCM 采样 (4-bit 量化,仅作示意):
采样点: * * * * * * * *
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
┌──┬──┬──┬──┬──┬──┬──┬──┐
│03│07│0F│0B│03│07│0F│0B│ (4-bit sample values)
└──┴──┴──┴──┴──┴──┴──┴──┘1.4 交叉存取(Interleaved)vs 非交叉(Non-interleaved)
Interleaved(交叉存取)——最常见格式:
[L0][R0][L1][R1][L2][R2]...
↑ ↑
立体声帧(一对采样点)Non-interleaved——给多通道或复杂布局用:
[L0][L1][L2][L3]...
[R0][R1][R2][R3]...2. ALSA:Linux 音频架构
2.1 ALSA 分层
用户空间
├── aplay / arecord (alsa-utils)
├── PulseAudio / PipeWire (声音服务器)
└── libasound (alsa-lib, 应用编程接口)
↓
内核 ALSA Core (sound/core/pcm_native.c)
├── PCM Native API (playback/capture open/read/write)
├── Control API (音量、开关)
└── Raw MIDI API
↓
平台驱动层
├── HDA Intel (sound/pci/hda)
├── USB Audio (sound/usb)
├── ASoC (sound/soc/) - 嵌入式
└── 虚拟声卡 (snd-virmidi, snd-dummy)
↓
硬件 (HDA Codec / I2S Codec / USB Audio Class)2.2 ALSA 设备节点
$ ls -la /proc/asound/cards
0 [PCH ]: HDA-Intel - Intel Haswell Audio
Intel Haswell Audio at 0xf7d34000 irq 50
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC269VC Analog [ALC269VC Analog]
Subdevices: 1/1
Subdevice #0: subdevice #0
$ ls -la /dev/snd/
crw-rw---- 1 root audio 116, 2 May 29 09:00 pcmC0D0p ← card 0, device 0, playback
crw-rw---- 1 root audio 116, 3 May 29 09:00 pcmC0D0c ← card 0, device 0, capture
crw-rw---- 1 root audio 116, 4 May 29 09:00 controlC0 ← card 0, control device设备名格式:pcmC{B}D{D}{p/c}
C{B}= card numberD{D}= device numberp= playback,c= capture
2.3 /proc/asound 信息
# 查看 PCM 硬件参数
$ cat /proc/asound/pcm
00-00: ALC269VC Analog : ALC269VC Analog : playback 1 : capture 1
# 查看声卡编解码器
$ cat /proc/asound/card0/codec#0
Codec: Realtek ALC269VC
Address: 0
Function Id: 0x1
Vendor Id: 0x10ec0269
...3. HDA:高清音频架构
3.1 HDA 总线结构
HDA(High Definition Audio,最初叫 Azalia)是 Intel 在 2004 年提出的取代 AC’97 的音频总线标准:
CPU HDA Controller Codec
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ Memory │ │ │ │ │
│ DMA desc │────────────►│ DMA Engine │─────────►│ DAC │──►Speaker
│ buffers │ │ │ │ ADC │◄──Mic
└──────────┘ │ Command/ │ │ │
│ Response Q │ │ Pin │──►HP Jack
└──────┬───────┘ └──────────┘
│
64-byte frame
(HDA link)HDA 是点对点的:每个 HDA Codec 有一个独立的输出引脚(通常叫 pin),连接 Speaker、Headphone、麦克风。HDA link 是固定 64 字节/帧的高速串行链路。
3.2 HDA 编解码器信息
$ cat /proc/asound/card0/codec#0
Codec: Realtek ALC269VC
Vendor Id: 0x10ec0269
Subsystem Id: 0x10431019
Revision Id: 0x100100
Pin Default Config: 0x411111f0 (Speaker)
Port: Line outs (Jack)
Color: Black
Association: 0
Node 0x04 (Pin Complex):
Amplitude caps: 0x00
Pin cap: 0x400107f7 (HP Out, PresIn, EAPD)3.3 HDA 命令流
// 向 Codec 发送 HDA 命令(简化版)
void hda_codec_write(struct hda_codec *codec, hda_nid_t nid,
unsigned int verb, unsigned int payload) {
u32 cmd = (nid << 20) | (verb << 8) | (payload & 0xFF);
// 写 HDA_REG_GCTL? 不,这里是通过 CORB/RIRB 环
// 实际是:写 CORB (Command Outbound Ring Buffer)
}
// HDA Verb 常见命令:
// 0x705 GET_POWER_STATE - 读功耗状态
// 0x706 SET_POWER_STATE - 置功耗状态
// 0x70C GET_SDI_SELECT - 读 SDI 选择
// 0x71C GET_PROC_COEF - 读处理系数(用于音量等)
// 0x720 SET_PROC_COEF - 设置处理系数
// 0xF0C GET_GPIO_DATA - 读 GPIO4. 实战:aplay 和 WAV 文件
4.1 播放一个 WAV 文件
# 首先准备一个测试音调(可以用 sox 生成)
$ sudo apt install sox -y
$ sox -n -r 44100 -c 2 test.wav synth 2 sine 440
# 用 aplay 播放
$ aplay -D plughw:0,0 test.wav
Playing WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
# 详细参数查看
$ aplay -D plughw:0,0 test.wav -v
Playing WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
Plug PCM: Hardware PCM card 0 'HDA Intel PCH' device 0 subdevice 0
Its setup is:
stream : PLAYBACK
access : RW_INTERLEAVED
format : S16_LE
subformat : STD
channels : 2
rate : 44100
exact rate : 44100 (44100/1)
buffer_size : 44100
period_size : 11025
period_pos : 0x04.2 参数解释
- format: S16_LE = 有符号 16 bit 小端序
- buffer_size: 44100 bytes = 1 秒的播放缓冲(2 channels × 44100 samples/sec × 2 bytes/sample)
- period_size: 11025 = 每 11025 bytes(约 0.25 秒)触发一次 DMA 中断
4.3 arecord:录音
# 录制 5 秒音频
$ arecord -D plughw:0,0 -f cd -d 5 recording.wav
Recording WAVE 'recording.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
# 查看可用录音设备
$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC269VC Analog [ALC269VC Analog]
Subdevices: 1/1
Subdevice #0: subdevice #04.4 用 amixer 调节音量
# 查看所有可调控件
$ amixer -c 0 contents
numid=3,iface=MIXER,name='Master Playback Volume'
; type=INTEGER,access=rw---R--,values=2,min=0,max=87,step=1
numid=2,iface=MIXER,name='Master Playback Switch'
; type=BOOLEAN,access=rw------,values=1
# 查看简化输出
$ amixer -c 0
Simple mixer control 'Master',0
Capabilities: pvolume pvolume-joined pswitch pswitch-joined
Playback channels: Mono
Limits: Playback 0 - 87
Mono: Playback 46 [53%] [-16.50dB] [on]
# 调音量到 50%
$ amixer -c 0 set Master 50%
# 静音
$ amixer -c 0 set Master mute
# 取消静音
$ amixer -c 0 set Master unmute5. ALSA PCM 中间件:PulseAudio 和 PipeWire
5.1 为什么需要声音服务器?
ALSA 是内核驱动层,应用程序直接用 ALSA 比较底层。桌面环境需要混音(多个应用同时出声)、音量控制、重采样、蓝牙音频等高级功能,于是有了 PulseAudio 和 PipeWire:
应用 → libasound → ALSA → PulseAudio/PipeWire → ALSA → HDA/Codec → Speaker5.2 PipeWire(现代方案)
PipeWire 2020 年代逐步取代 PulseAudio,它的优势:
- 支持专业音频(低延迟)
- 统一处理音频 + 视频(摄像头等)
- 支持 Flatpak 沙盒应用访问音频
- 兼容 PulseAudio 和 JACK 客户端
$ pactl info | grep "Server Name"
Server Name: PulseAudio (on PipeWire 0.3.65)
$ pw-cli list-objects Node6. wandos 对比:无音频子系统
wandos 目前没有音频子系统实现。音频的复杂度在于:它需要 DMA(直接内存访问)、中断处理、编解码器配置、以及应用层混音。这些在 wandos 当前阶段优先级较低。wandos 的目标是先实现基本的 PIT(定时器)和简单的 beep 驱动,用于系统启动提示音。
公众号底部:
📢 关注公众号【内核栈】,获取更多 OS/内核干货
💡 本文属于「从零写OS内核」系列,欢迎回顾:
- 篇 349:《SPI 总线——高速串行通信》
- 篇 348:《I2C 总线——嵌入式系统的血管》
- 篇 347:《显示器——从 Framebuffer 到 GPU》
🔗 回复「ALSA」获取 ALSA 编程示例代码和 PCM 参数说明
作者:内核栈 | 原创出品
本文永久链接:https://kernelstack.dev/350-alsa
评论