从零写OS内核 | 音频子系统——ALSA 是怎么播放声音的

你有没有想过这个过程:播放一首歌,CPU 怎么把数字信号变成扬声器里的振动? 从 WAV/MP3 文件到耳机里的音乐,中间隔着采样率、位深、alsa-lib、ALSA 驱动、HDA 控制器——这条链路比大多数人想象的深。

本文从 PCM 原理出发,讲解 ALSA 架构、HDA Intel 控制器,给你一张完整的”音频从文件到耳机”地图。


一、声音的数字化:采样率和位深

声音是连续的模拟信号,数字化需要两步:采样(Sampling)量化(Quantization)

Bash
  模拟信号(连续)
  ╭──────────────────────────────╮
  │        ╱╲      ╱╲          │
  │   ╱╲╱╲      ╱╲  │    ╱╲    │
  ──╱──────────────────────────╱────→ 时间
  │
  ↓ 采样
  ╭──────────────────────────────╮
  │ ■   ■   ■   ■   ■   ■   ■  │  ← 采样点
  ────────────────────────────────→ 时间
  │ ← 每隔 T 秒采一个幅度值

关键参数:

  • 采样率(Sample Rate):每秒钟采样点数,单位 Hz。CD 标准是 44100Hz(44.1kHz),足够还原 22kHz 的声音(乃奎斯特定理:采样率 ≥ 2×最高频率)
  • 位深(Bit Depth):每个采样点用多少 bit 表示。CD 是 16bit,Hi-Res 是 24bit。位深越大,动态范围越大(SNR = 6.02×bit + 1.76 dB)
  • 声道数(Channels):单声道=1,立体声=2,环绕声=6(5.1)

PCM 帧结构:

Bash
  立体声 44.1kHz 16bit,每帧:
  ┌──────────┬──────────┐
  │ L (16bit)│ R (16bit)│
  └──────────┴──────────┘

  时域:────────────────────────────────→ 时间
  帧N: [L0][R0]  [L1][R1]  [L2][R2] ...

二、ALSA 架构:从 libasound 到 DMA

ALSA(Advanced Linux Sound Architecture)是 Linux 的音频框架,分用户空间和内核空间两层:

Bash
  应用程序(aplay / Firefox / Spotify)
        │
        ▼
  ┌──────────────────────────────────┐
  │      alsa-lib (libasound.so)      │  ← 用户空间
  │  PCM API: snd_pcm_open/write      │
  │  混音器 API: snd_mixer_           │
  └──────────────┬───────────────────┘
                 │ 写入 PCM ring buffer
  ┌─────────────┴──────────────────────┐
  │   ALSA Core (snd-pcm.ko)          │  ← 内核空间
  │  音频流管理、硬件参数协商          │
  └──────────────┬───────────────────┘
                 │ DMA 描述符
  ┌─────────────┴──────────────────────┐
  │   HDA Intel 驱动 (snd-hda-intel)   │  ← 硬件驱动
  │  Intel HD Audio Codec              │
  └──────────────┬───────────────────┘
                 │ I2S / HDA link
  ┌─────────────┴──────────────────────┐
  │   CODEC(Realtek ALC / Cirrus)    │  ← 数模转换
  │  DAC → 功放 → 扬声器/耳机          │
  └────────────────────────────────────┘

HDA(High Definition Audio)Link:
HDA 是 Intel 2004 年推出的板载音频标准,通过 HDA link 连接主板南桥和音频 CODEC,支持多声道、192kHz/24bit、jack detection。


三、实战:Linux 音频调试命令

Bash
# 1. 查看声卡列表
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC1220 Analog [ALC1220 Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: NVidia [HDA NVidia], device 3: HDMI 0 [HDMI 0]
  Subdevices: 1/1

# 2. 查看声卡支持的参数
$ aplay -D hw:0,0 --dump-phrase /dev/urandom
  Access:  RW_INTERLEAVED
  Format:   S16_LE / S32_LE / S24_LE
  Subformat:  STD
  Channels:   2
  Rate:       44100 / 48000 / 88200 / 96000 / 176400 / 192000
  Period size: 512 - 8192
  Buffer size: 4096 - 32768

# 3. 查看alsa插件配置
$ cat /etc/asound.conf
# 或 ~/.asoundrc

# 4. 录制一段音频
$ arecord -D hw:0,0 -f cd -r 44100 -c 2 /tmp/test.wav
Recording WAVE 'stdin' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
^C  # Ctrl+C 停止

# 5. 用 alsamixer 调整音量
$ alsamixer
# 界面显示各声道的音量和静音状态

# 6. 查看 /proc/asound/
$ cat /proc/asound/cards
 0 [PCH            ]: HDA-Intel - HDA Intel PCH
                      Intel Corporation Device 9a13
 1 [NVidia         ]: HDA-Intel - HDA NVidia
                      NVIDIA Corporation Device 50

$ ls /proc/asound/card0/
codec#0  id  pcm0c/  pcm0p/  # pcm0c=录制,pcm0p=播放

四、DMA 和环形缓冲区:声音为什么能连续播放

CPU 不能精确控制每个样本的发送时间——它把一批 PCM 数据一次性写入 环形缓冲区(Ring Buffer),然后由 DMA 控制器自动按固定间隔从缓冲区取数据送到 CODEC:

Bash
  Ring Buffer(2048 样本,8KB @ 44.1kHz ≈ 46ms):
  ┌────────────────────────────────────────────────┐
  │ [样本0] [样本1] [样本2] ... [样本2047]          │
  └────────────────────────────────────────────────┘
  ↑
  DMA 自动从这里取样
  (每帧2个样本×2声道×2字节=4字节)

  ┌────────────────────────────────────────────────┐
  │          DMA 控制器                             │
  │  • 周期性发送 DMA 请求(以 Sample Rate 为周期)   │
  │  • 从 RAM 读样本,送往 HDA Codec                │
  │  • 触发周期性中断,通知 ALSA driver "buffer 消费" │
  └────────────────────────────────────────────────┘

ALSA 的 period/buffer 概念:

  • buffer size:总缓冲区大小(影响延迟)
  • period size:每次 DMA 中断传输的数据量(影响 CPU 频率)

Bash
  buffer_size = period_size × periods
  buffer = 8192 × 4 = 32KB
  period = 2048 × 4 = 8KB
  periods = 4(缓冲 4 次,实际 ~184ms @ 44.1kHz)

五、wandos 的音频子系统

wandos 目前没有完整的音频子系统。wandos 的输出以串口/VGA 文本为主。

如果要在 wandos 上实现音频,需要:

  1. 实现 HDA 控制器驱动(在 arch/x86/core/audio.cpp
  2. 实现 AC97/I2S 接口
  3. 实现 DMA 环形缓冲区管理
  4. 实现基本的 PCM 播放(可参考 demos/audio/

wandos 的 audio roadmap:

  • 阶段 1:串口 beep(PC Speaker)
  • 阶段 2:AC97 驱动
  • 阶段 3:ALSA 兼容层

公众号 footer

相关阅读:

  • 《从零写OS内核 | 总线与地址空间——CPU 是怎么找到每个设备的》(343)
  • 《从零写OS内核 | DMA——内存和设备之间的直连高速公路》

动手环节:
运行 aplay -l 看你电脑有几张声卡,然后用 aplay -D hw:0,0 test.wav 播放一个 WAV 文件(如果没有可以先 arecord 录一个)。

仓库地址:
github.com/golang12306/os-kernel-from-scratch


关注公众号「AI不着急」,回复”资料”获取内核学习路线图。

最后修改: 2024年9月13日

作者

评论

发表评论

您的邮箱地址不会被公开。