2. 声学基础
说实话,很多做声学特征提取的朋友,一上来就调库、算MFCC,结果数据稍微变个采样率就崩了。为什么?因为底层的数字音频原理没吃透。这一章,我们就来把数字音频的四个核心概念——采样、量化、编码、PCM——彻底讲明白。
我个人习惯把数字音频比作「用尺子量声音」。你想想看,声音是连续的波浪,但计算机只能存离散的数字。怎么把波浪变成数字?这就是本章要解决的问题。
2.1 采样定理:多快才算够?
采样,说白了就是每隔一小段时间,记录一次声音的瞬时值。问题来了:隔多久采一次,才能把原始声音完整还原回来?
这里有个黄金法则——奈奎斯特-香农采样定理:
采样频率必须大于信号最高频率的两倍。
公式:fs > 2 × fmax
举个例子。人耳能听到的最高频率大约是20kHz。所以CD音频的采样率定在44.1kHz,刚好大于40kHz。为什么不是40kHz整?因为实际滤波器做不到一刀切,得留点余量。
我在项目中遇到过一件事:有个同事录鸟叫声,采样率设了22kHz。结果高频鸟鸣全变成了「嘶嘶」的混叠噪声。嗯,这就是典型的欠采样——高频信号伪装成了低频信号,混进来了。
避坑指南:我曾经在嵌入式项目里为了省存储,把采样率降到8kHz。结果语音识别准确率直接腰斩。后来才发现,8kHz只能覆盖电话语音(300Hz-3.4kHz),高频信息全丢了。所以,采样率的选择取决于你的目标频段,不是越低越好。
常见的采样率有这些:
| 采样率 | 可还原最高频率 | 典型应用 |
|---|---|---|
| 8 kHz | 4 kHz | 电话、对讲机 |
| 16 kHz | 8 kHz | 语音识别、语音合成 |
| 44.1 kHz | 22.05 kHz | CD、音乐 |
| 48 kHz | 24 kHz | 电影、专业音频 |
| 96 kHz | 48 kHz | 高解析度音频 |
2.2 量化:把连续值变成离散值
采样解决了「什么时候记」的问题。量化解决的是「记成什么数」的问题。
你想想看,采样得到的电压值是连续的,比如3.14159V。但计算机只能用有限位数的二进制数来表示。量化就是把这个连续值,就近取整到最近的量化电平上。
这里有个关键参数——量化位数(bit depth):
- 8 bit:256个量化等级。动态范围约48dB。听起来像老式游戏机。
- 16 bit:65536个等级。动态范围96dB。CD标准。
- 24 bit:16777216个等级。动态范围144dB。录音棚常用。
- 32 bit float:浮点表示,动态范围接近无限。后期处理首选。
我的经验:做声学特征提取时,如果原始音频是16bit,我一般不会降成8bit。因为量化噪声会抬高底噪,影响信噪比。但反过来,16bit升到24bit不会提升音质——就像把一张480p的图片放大成4K,细节不会凭空出现。
量化会产生一个不可避免的问题——量化误差。每次取整都会引入一点小偏差,这些偏差听起来就是背景「嘶嘶」声,我们叫它量化噪声。
量化噪声的功率计算公式:
量化噪声功率 = Δ² / 12
其中 Δ = 满量程电压 / 2^(bit_depth)
看出来了吗?每增加1 bit,量化噪声功率降低6dB。这就是为什么16bit比8bit安静那么多的原因。
2.3 编码:把数字变成字节流
采样和量化做完,我们得到了一串数字。但计算机怎么存这些数字?这就轮到编码出场了。
最常见的编码方式就是PCM(脉冲编码调制)。说白了,就是把量化后的数值,按顺序一个接一个地写进文件里。
PCM编码有几个关键参数:
- 采样率:每秒采多少个点
- 量化位数:每个点用多少bit
- 声道数:单声道、立体声、5.1环绕等
- 字节序:大端(Big Endian)还是小端(Little Endian)
- 编码格式:有符号整型、无符号整型、浮点型
举个例子,一段CD音质的立体声音频:
采样率:44100 Hz
量化位数:16 bit
声道数:2(立体声)
字节序:小端
编码格式:有符号整型
每秒数据量 = 44100 × 2 × 2 = 176400 字节 ≈ 172 KB/s
我刚开始做音频处理时,经常搞混字节序。有一次在ARM芯片上读WAV文件,出来的声音全是噪声。查了半天才发现,PC是小端,ARM默认大端,读出来的字节顺序反了。嗯,从那以后我写代码都会显式指定字节序。
2.4 PCM格式:最朴素的音频格式
PCM是音频的「原始格式」。WAV、AIFF这些格式,本质上就是在PCM数据外面包了一层文件头。
一个标准的WAV文件结构是这样的:
| 偏移地址 | 长度 | 内容 |
|---|---|---|
| 0x00 | 4 bytes | "RIFF" 标识 |
| 0x04 | 4 bytes | 文件总长度 |
| 0x08 | 4 bytes | "WAVE" 标识 |
| 0x0C | 4 bytes | "fmt " 块标识 |
| 0x10 | 4 bytes | fmt块长度(16) |
| 0x14 | 2 bytes | 音频格式(1=PCM) |
| 0x16 | 2 bytes | 声道数 |
| 0x18 | 4 bytes | 采样率 |
| 0x1C | 4 bytes | 字节率 |
| 0x20 | 2 bytes | 块对齐 |
| 0x22 | 2 bytes | 量化位数 |
| 0x24 | 4 bytes | "data" 块标识 |
| 0x28 | 4 bytes | 数据长度 |
| 0x2C | ... | PCM数据 |
用Python读取PCM数据其实很简单:
import numpy as np
def read_pcm_from_wav(filename):
"""从WAV文件中读取PCM数据"""
import wave
with wave.open(filename, 'rb') as wf:
# 获取参数
n_channels = wf.getnchannels()
sampwidth = wf.getsampwidth() # 字节数
framerate = wf.getframerate()
n_frames = wf.getnframes()
# 读取原始数据
raw_data = wf.readframes(n_frames)
# 根据量化位数选择dtype
if sampwidth == 1:
dtype = np.uint8 # 8-bit 无符号
elif sampwidth == 2:
dtype = np.int16 # 16-bit 有符号
elif sampwidth == 4:
dtype = np.int32 # 32-bit 有符号
else:
raise ValueError(f"不支持的量化位数: {sampwidth}")
# 转为numpy数组
audio = np.frombuffer(raw_data, dtype=dtype)
# 如果是多声道,重塑为 (帧数, 声道数)
if n_channels > 1:
audio = audio.reshape(-1, n_channels)
return audio, framerate
实用技巧:我写音频处理代码时,习惯先把PCM数据归一化到[-1, 1]范围。这样不管原始是16bit还是24bit,后续处理逻辑都一样。归一化公式很简单:audio_float = audio_int / (2^(bit_depth-1))。
2.5 知识体系总览
说了这么多,我们来画一张图,把数字音频的整个流程串起来:
这张图把整个流程串起来了。从模拟信号开始,经过采样、量化、编码,最终得到PCM数字音频。每一步都有对应的参数需要关注,任何一个环节出问题,最终的声音都会「变味」。
2.6 动手实践:用Python查看音频参数
光说不练假把式。我们来写段代码,看看真实音频文件的参数:
import wave
import numpy as np
def inspect_audio(filename):
"""查看音频文件的基本信息"""
with wave.open(filename, 'rb') as wf:
print(f"声道数: {wf.getnchannels()}")
print(f"量化位数: {wf.getsampwidth() * 8} bit")
print(f"采样率: {wf.getframerate()} Hz")
print(f"总帧数: {wf.getnframes()}")
print(f"时长: {wf.getnframes() / wf.getframerate():.2f} 秒")
# 读取前1秒的数据做分析
frames_to_read = wf.getframerate()
raw = wf.readframes(frames_to_read)
audio = np.frombuffer(raw, dtype=np.int16)
print(f"\n前1秒数据统计:")
print(f" 最大值: {audio.max()}")
print(f" 最小值: {audio.min()}")
print(f" 平均值: {audio.mean():.2f}")
print(f" 标准差: {audio.std():.2f}")
# 使用示例
# inspect_audio("example.wav")
这段代码会告诉你一个音频文件的所有底层参数。我每次拿到新的音频数据集,第一件事就是跑这个脚本,看看采样率、量化位数对不对得上。曾经有一次,标注团队说「所有音频都是16kHz」,结果我一跑脚本,发现有一半是8kHz——嗯,数据预处理阶段就把问题揪出来了,省了后面大量返工的时间。
核心要点回顾:
- 采样定理:fs > 2fmax,欠采样会导致混叠
- 量化:bit数越高,动态范围越大,量化噪声越小
- PCM:最原始的音频编码方式,WAV/AIFF都是它的「马甲」
- 字节序:跨平台时一定要显式指定,否则全是噪声