1. 音频信号基础:声音的物理特性、时域与频域、采样定理与量化、音频文件格式解析
各位同学,欢迎来到《音频算法实战能力提升》的第一章。我是你们这门课的主讲,一个在音频信号处理领域摸爬滚打了十几年的老工程师。今天咱们不聊虚的,直接切入正题——音频信号到底是个什么东西?
很多人觉得音频算法就是调调参数、跑跑模型。其实不然。你想想看,如果连声音的本质都搞不清楚,后面做降噪、做编解码,那不就成了空中楼阁吗?我见过太多新手,上来就调FFT参数,结果频谱图都看反了。嗯,咱们先把地基打牢。
本章核心目标:理解声音从物理世界到数字世界的完整映射过程,掌握时域与频域的基本概念,搞懂采样定理和量化这两个“数字音频的基石”。
1.1 声音的物理特性:从振动到波形
声音的本质是什么?说白了,就是机械振动在介质(通常是空气)中的传播。你说话,声带振动;你敲桌子,桌面振动。这些振动挤压空气,形成疏密波,传到我们耳朵里,我们就听到了声音。
描述一个声音,我们通常用三个物理量:
- 频率(Frequency):单位是赫兹(Hz),表示每秒振动的次数。频率决定了音调的高低。人耳能听到的频率范围大约是20Hz到20kHz。低于20Hz的叫次声波,高于20kHz的叫超声波。我在项目中遇到过有人想用麦克风采集蝙蝠的叫声做定位,结果发现普通麦克风的高频响应根本不够,最后换了超声波传感器才搞定。
- 幅度(Amplitude):表示振动的强弱,决定了音量的大小。幅度越大,声音越响。
- 相位(Phase):描述振动在时间轴上的起始位置。相位差是很多空间音频算法(比如波束成形)的核心。
一个最简单的纯音(比如音叉发出的声音),可以用正弦波来表示:
y(t) = A * sin(2π * f * t + φ)
其中A是幅度,f是频率,φ是相位。但现实世界的声音,比如音乐、人声、环境噪声,都是无数个不同频率、不同幅度、不同相位的正弦波的叠加。这就是傅里叶变换存在的意义——把复杂的声音拆解成简单的正弦波组合。
我的小习惯:每次拿到一个新的音频信号,我第一件事就是听,第二件事就是看波形。听能感知到有没有异常(比如爆音、削波),看波形能快速判断信号的动态范围和能量分布。别小看这一步,它能帮你省掉很多调试时间。
1.2 时域与频域:看声音的两个视角
同一个声音,我们可以从两个完全不同的角度去观察它。
时域(Time Domain),就是看声音的幅度随时间怎么变化。横轴是时间,纵轴是幅度。你打开Audacity或者Cool Edit,看到的那条波浪线,就是时域波形。时域直观,但信息量有限。比如一段音乐里同时有钢琴和吉他,你在时域波形上根本分不清谁是谁。
频域(Frequency Domain),就是看声音里包含了哪些频率成分,以及每个频率的强度是多少。横轴是频率,纵轴是幅度(或者能量)。频域图(频谱图)能让你一眼看出声音的“成分”。
时域和频域之间怎么转换?靠傅里叶变换。对于计算机来说,我们用的是离散傅里叶变换(DFT),而最常用的实现是快速傅里叶变换(FFT)。
我建议你记住一个核心公式:
X(k) = Σ_{n=0}^{N-1} x(n) * e^{-j(2π/N)kn}
这个公式看着复杂,但它的物理意义很简单:把时域信号x(n)和一系列不同频率的复指数信号做内积,看看每个频率成分的贡献有多大。
下面这张图,是我自己画的一个时域到频域的映射关系,帮你建立直观印象:
1.3 采样定理与量化:把连续世界装进离散计算机
计算机是数字的,只能处理离散的0和1。而声音是连续的模拟信号。怎么把连续的声音变成离散的数字?两个步骤:采样和量化。
1.3.1 采样定理(Nyquist-Shannon定理)
采样,就是在时间轴上每隔一段时间取一个点。这个间隔的倒数就是采样率(Sample Rate),单位是Hz。
采样定理告诉我们:要无失真地重建一个信号,采样率必须至少是信号最高频率的两倍。
fs ≥ 2 * f_max
其中fs是采样率,f_max是信号中的最高频率分量。
举个例子:人耳能听到的最高频率是20kHz,所以CD音质的采样率是44.1kHz(略大于2*20kHz)。为什么不是刚好40kHz?因为实际滤波器做不到理想的陡降,需要留一点过渡带。
我曾经踩过的坑:有一次做语音增强项目,麦克风采集的语音信号里混入了22kHz的超声波噪声。我用的采样率是44.1kHz,按说没问题。但我忘了前置抗混叠滤波器没做好,结果22kHz的噪声被折叠(Aliasing)到了22.05-22=0.05kHz,也就是50Hz的工频噪声区域。我花了整整两天排查,最后发现是采样前的模拟滤波没加。从那以后,我设计任何音频系统,第一件事就是检查抗混叠滤波器。
下表总结了常见音频场景的采样率选择:
| 应用场景 | 采样率 (kHz) | 最高频率 (kHz) | 说明 |
|---|---|---|---|
| 电话语音 | 8 | 3.4 | 窄带语音,带宽有限 |
| 宽带语音 | 16 | 7 | VoIP、视频会议常用 |
| CD 音质 | 44.1 | 20 | 音乐、消费音频标准 |
| 专业音频 | 48 | 22 | 电影、广播、后期制作 |
| 高采样率 | 96 / 192 | 40 / 80 | 高保真、超声研究 |
1.3.2 量化:把幅度变成数字
采样解决了时间轴离散化的问题。量化解决的是幅度轴离散化的问题。我们把连续的幅度值映射到有限个离散的等级上。
量化精度用位深(Bit Depth)表示。常见的位深有:
- 8-bit:256个等级,动态范围约48dB。语音通信够用,但音乐会有明显的量化噪声。
- 16-bit:65536个等级,动态范围约96dB。CD音质标准,人耳基本听不出量化噪声。
- 24-bit:16777216个等级,动态范围约144dB。专业录音棚标准,给后期处理留足余量。
量化误差(量化噪声)是不可避免的。它的功率可以用一个简单公式估算:
量化噪声功率 ≈ (Δ²) / 12
其中Δ是量化步长。位深每增加1bit,信噪比提升约6dB。这就是为什么16-bit比8-bit好那么多。
我的建议:做算法开发时,内部处理尽量用32-bit浮点。浮点数的动态范围极大,几乎不用担心溢出和量化噪声。只在最终输出到硬件(比如保存为16-bit WAV文件)时才做定点化。这样可以避免很多精度问题。
1.4 音频文件格式解析:WAV、MP3、AAC、FLAC
搞音频算法,天天跟文件格式打交道。我简单说说最常见的几种。
1.4.1 WAV(波形音频文件格式)
WAV是微软和IBM联合开发的,是Windows平台上最基础的未压缩音频格式。它的结构很简单:一个44字节的文件头,后面跟着原始的PCM数据。
WAV文件头关键字段:
| 偏移量 | 长度(字节) | 内容 | 示例值 |
|---|---|---|---|
| 0 | 4 | RIFF标识 | "RIFF" |
| 8 | 4 | 文件格式类型 | "WAVE" |
| 12 | 4 | "fmt "子块标识 | "fmt " |
| 16 | 4 | 子块1大小 | 16 (PCM) |
| 20 | 2 | 音频格式 | 1 (PCM) |
| 22 | 2 | 声道数 | 1 (单声道) / 2 (立体声) |
| 24 | 4 | 采样率 | 44100 |
| 28 | 4 | 字节率 | 采样率 * 声道数 * 位深/8 |
| 32 | 2 | 块对齐 | 声道数 * 位深/8 |
| 34 | 2 | 位深 | 16 |
| 36 | 4 | "data"子块标识 | "data" |
| 40 | 4 | 数据大小 | 音频数据总字节数 |
读取WAV文件的代码示例(Python):
import struct
def read_wav(filepath):
with open(filepath, 'rb') as f:
# 读取文件头
riff = f.read(4)
file_size = struct.unpack('<I', f.read(4))[0]
wave = f.read(4)
# 读取 fmt 子块
fmt_id = f.read(4)
fmt_size = struct.unpack('<I', f.read(4))[0]
audio_format = struct.unpack('<H', f.read(2))[0]
num_channels = struct.unpack('<H', f.read(2))[0]
sample_rate = struct.unpack('<I', f.read(4))[0]
byte_rate = struct.unpack('<I', f.read(4))[0]
block_align = struct.unpack('<H', f.read(2))[0]
bits_per_sample = struct.unpack('<H', f.read(2))[0]
# 跳过可能的扩展字段
if fmt_size > 16:
f.read(fmt_size - 16)
# 读取 data 子块
data_id = f.read(4)
data_size = struct.unpack('<I', f.read(4))[0]
raw_data = f.read(data_size)
return sample_rate, bits_per_sample, num_channels, raw_data
1.4.2 压缩格式:MP3、AAC、FLAC
WAV文件太大了。一首3分钟的CD音质歌曲,大约30MB。所以我们需要压缩。
- MP3 (MPEG-1 Audio Layer 3):有损压缩,利用心理声学模型,去掉人耳不敏感的频率成分。128kbps是常见的“可接受”质量,320kbps接近无损。我早期做嵌入式开发时,MP3解码器是必啃的硬骨头。
- AAC (Advanced Audio Coding):MP3的继任者,同等码率下音质更好。苹果生态的标配,也是YouTube、Netflix等流媒体平台的首选。
- FLAC (Free Lossless Audio Codec):无损压缩,压缩比约50%-60%。音质和WAV完全一样,但文件小一半。适合音乐存档和发烧友。
下表对比了这几种格式:
| 格式 | 压缩方式 | 典型码率 | 音质 | 适用场景 |
|---|---|---|---|---|
| WAV | 无压缩 | 1411 kbps (CD) | 完美 | 算法开发、专业制作 |
| FLAC | 无损压缩 | ~700 kbps | 完美 | 音乐存档、发烧友 |
| MP3 | 有损压缩 | 128-320 kbps | 好到可接受 | 音乐分发、便携设备 |
| AAC | 有损压缩 | 64-256 kbps | 优秀 | 流媒体、视频平台 |
注意:做音频算法开发时,我强烈建议用WAV或FLAC作为中间格式。有损压缩会引入不可逆的失真,这些失真可能会干扰你的算法效果评估。比如你在MP3上调试好的降噪算法,换成WAV源文件后效果可能完全不同。嗯,我吃过这个亏。
好了,第一章的内容就到这里。音频信号的基础概念,是后面所有算法章节的基石。采样定理、量化、时频域转换,这些概念会贯穿整个课程。你最好能亲手写代码读一个WAV文件,画一下时域波形和频谱图,感受一下理论和实际的差距。