2. 声学基础
做音频算法,第一个绕不开的问题就是:声音是怎么变成数字的?
我刚开始接触这个领域时,总觉得把模拟信号转成数字是件很“自然”的事——不就是拿个ADC采一下嘛。直到有一次我在做语音增强项目,发现降噪后的音频听起来总有奇怪的“锯齿感”,排查了半天才发现是采样率设置出了问题。嗯,从那以后我再也不敢小看这些基础概念了。
这一章,我们就来把数字音频的三个核心基石讲透:采样定理、量化与位深、常见文件格式。这些知识会贯穿你整个音频算法生涯。
2.1 采样定理:为什么是44.1kHz?
先问一个问题:人耳能听到的频率范围是多少?
大概是20Hz到20kHz。那如果我们想完整记录一段声音,需要多快的采样速度?
答案就是奈奎斯特采样定理:采样频率必须大于信号最高频率的两倍。
核心公式:
fs > 2 × fmax
其中 fs 是采样频率,fmax 是信号中的最高频率成分。
换句话说,如果你想记录20kHz的声音,采样率至少得40kHz以上。这就是为什么CD标准选择了44.1kHz——留了一点余量。
我个人习惯把采样定理理解成“拍照”:你拍一个快速运动的物体,快门速度不够快,拍出来的就是模糊的。音频也一样,采样率不够,高频信息就会“混叠”成低频噪声。
避坑指南:我曾经在做一个语音识别前端时,为了节省计算量把采样率从16kHz降到了8kHz。结果识别率暴跌——因为8kHz只能覆盖到4kHz的语音信息,而很多辅音(比如/s/、/ʃ/)的能量集中在4-8kHz。降采样前一定要先做低通滤波,否则混叠会让你怀疑人生。
2.2 量化与位深:精度与噪声的博弈
采样解决了“时间轴”的问题,那“幅度轴”呢?
量化就是把连续的幅度值映射到离散的等级上。每个等级用一个二进制数表示,这个二进制数的位数就是位深。
| 位深 | 量化等级数 | 动态范围(理论) | 常见用途 |
|---|---|---|---|
| 8-bit | 256 | ~48 dB | 早期电话、低质量语音 |
| 16-bit | 65,536 | ~96 dB | CD、WAV标准 |
| 24-bit | 16,777,216 | ~144 dB | 录音棚、专业音频 |
| 32-bit float | 理论上限极高 | ~1528 dB | 算法内部处理、DAW |
你想想看,16-bit有65536个等级,听起来很多对吧?但在实际录音中,如果信号太弱,量化噪声就会变得很明显。这就是为什么录音时要尽量让信号“满”一点,但又不能削波。
我的经验:在特征提取的预处理阶段,我通常会把音频转成32-bit float来处理。原因很简单——16-bit在做加减乘除时容易产生截断误差,而float格式可以避免很多“莫名其妙”的噪声。等特征提取完了,再根据需求转回16-bit输出。
2.3 音频文件格式:WAV vs MP3
聊完采样和量化,我们来看看实际中怎么存音频。
2.3.1 WAV格式
WAV是微软和IBM联合开发的波形文件格式。它的核心特点就是无损——存的就是原始的PCM数据,或者最多加个简单的压缩。
一个标准的WAV文件结构是这样的:
RIFF头(4字节 "RIFF" + 4字节文件大小 + 4字节 "WAVE")
├── fmt块(采样率、位深、声道数等)
└── data块(实际的音频样本数据)
用Python读取WAV非常简单:
import wave
import numpy as np
with wave.open('audio.wav', 'rb') as wf:
# 获取参数
n_channels = wf.getnchannels() # 声道数
sampwidth = wf.getsampwidth() # 字节宽度(位深/8)
framerate = wf.getframerate() # 采样率
n_frames = wf.getnframes() # 总帧数
# 读取原始数据
raw_data = wf.readframes(n_frames)
# 转成numpy数组(这里假设是16-bit单声道)
audio = np.frombuffer(raw_data, dtype=np.int16)
audio = audio.astype(np.float32) / 32768.0 # 归一化到[-1, 1]
注意:WAV文件可以存多种编码格式,不一定是PCM。我遇到过有人拿了个“WAV”文件,结果里面是MP3编码的——读取时直接报错。所以读取前最好检查一下 wf.getcompname(),确保是 'NONE'(未压缩)。
2.3.2 MP3格式
MP3是有损压缩格式,利用心理声学模型去掉人耳不敏感的频率成分。它的核心思想是:
- 频率掩蔽:一个强音会掩盖附近频率的弱音
- 时间掩蔽:强音前后短暂时间内,人耳对弱音不敏感
- 临界频带:人耳对频率的感知不是线性的,低频分辨率高,高频分辨率低
MP3的编码流程大致如下:
PCM音频 → 时频变换(MDCT) → 心理声学模型 → 量化与编码 → MP3比特流
用Python读取MP3需要额外库,比如 pydub 或 librosa:
from pydub import AudioSegment
import numpy as np
# 读取MP3
audio_seg = AudioSegment.from_mp3('audio.mp3')
# 转成numpy数组(16-bit)
samples = np.array(audio_seg.get_array_of_samples())
samples = samples.astype(np.float32) / 32768.0
# 如果是立体声,reshape成两列
if audio_seg.channels == 2:
samples = samples.reshape((-1, 2))
我曾经踩过的坑:做特征提取时,千万别直接用MP3文件!MP3的压缩过程会引入量化噪声和频带截断,这些“假信息”会被特征提取算法放大。我见过有人用MP3做语音识别训练,结果模型在WAV测试集上表现很好,一到真实场景就崩——因为训练数据里的噪声模式跟MP3压缩噪声“过拟合”了。所以,训练和测试尽量用WAV,如果只有MP3,至少用320kbps的高码率版本。
2.4 知识体系总览
下面这张图把本章的核心概念串起来了:
2.5 本章小结
这一章我们聊了三个核心概念:
- 采样定理:采样率必须大于最高频率的两倍,否则会产生混叠。44.1kHz和16kHz是两种最常见的采样率。
- 量化与位深:位深决定了动态范围和信噪比。16-bit是消费级标准,24-bit是专业级,32-bit float适合算法内部。
- 文件格式:WAV是无损的PCM容器,适合做特征提取;MP3是有损压缩,适合存储和传输,但别用它做训练数据。
这些基础概念会反复出现在后面的每一章里。你想想看,如果连采样率和位深都搞不清楚,后面做MFCC、做滤波器组,那误差会越积越大。所以,花点时间把这些基础打牢,绝对值得。