音频基础:波形、采样与傅里叶变换 本节摘要:波形(Waveform)是原始信号,频谱图(Spectrogram)是它的表示,梅尔特征(Mel Features)是机器学习友好的形态。每一条现代 ASR(自动语音识别)和 TTS(文本转语音)流水线都在攀爬这架阶梯,而第一级就是搞懂采样(Sampling)与傅里叶变换(Fourier Transform)。麦克风输出的是「声压随时间变化」的连续信号,神经网络吃的是张量,两者之间夹着一整套约定——一旦违反,bug 是沉默的:模型照常训练,词错率(WER)却悄悄翻倍;TTS 上线后全是嘶嘶声;语音克隆系统记住了麦克风特性而不是说话人。
本节摘要:波形(Waveform)是原始信号,频谱图(Spectrogram)是它的表示,梅尔特征(Mel Features)是机器学习友好的形态。每一条现代 ASR(自动语音识别)和 TTS(文本转语音)流水线都在攀爬这架阶梯,而第一级就是搞懂采样(Sampling)与傅里叶变换(Fourier Transform)。麦克风输出的是「声压随时间变化」的连续信号,神经网络吃的是张量,两者之间夹着一整套约定——一旦违反,bug 是沉默的:模型照常训练,词错率(WER)却悄悄翻倍;TTS 上线后全是嘶嘶声;语音克隆系统记住了麦克风特性而不是说话人。本节将从零讲透采样率、奈奎斯特定理、位深、离散傅里叶变换(DFT)、快速傅里叶变换(FFT)、分帧加窗与短时傅里叶变换(STFT),为后续 16 节铺平地基。读完本节,你能手写 DFT、解释混叠(Aliasing)成因,并知道为什么 Whisper 一定要吃 16 kHz 单声道 float32。
阅读完本节,你应当能够:
麦克风把空气的压强变化转成「压强-时间」的连续电信号,模数转换器(ADC)再以固定频率对它采样,得到一串数字。神经网络吃的则是张量。在「连续声波」与「张量」之间,横亘着一整套约定:采样率是多少、是否已混叠、信号是原始样本还是频域表示。
语音系统里几乎每一个 bug,都能追溯到这三个问题之一:
这三件事做对了,Phase 6 剩下的部分就顺水推舟;做错了,即便是 Whisper-Large-v4 也会吐出垃圾结果。
波形是一维浮点数组,取值范围在 [-1.0, 1.0] 之间,按下标索引样本序号。把样本序号换算成秒,除以采样率即可:t = n / sr。一段 10 秒、16 kHz 的音频,就是 160 000 个浮点数。
采样率(Sample Rate, sr)即每秒采样次数。2026 年常见的几档:
| 采样率 | 用途 |
|---|---|
| 8 kHz | 电话、老式 VOIP。奈奎斯特只到 4 kHz,辅音被切掉,ASR 慎用。 |
| 16 kHz | ASR 标准。Whisper、Parakeet、SeamlessM4T v2 都吃 16 kHz。 |
| 22.05 kHz | 老式 TTS 声码器训练。 |
| 24 kHz | 现代 TTS(Kokoro、F5-TTS、xTTS v2)。 |
| 44.1 kHz | CD 音质、音乐。 |
| 48 kHz | 影视、专业音频、高保真 TTS(VALL-E 2、NaturalSpeech 3)。 |
采样率为 sr 时,能无歧义表示的最高频率是 sr/2,这个边界叫奈奎斯特频率(Nyquist Frequency)。高于奈奎斯特的能量会被混叠——折叠回低频,污染信号。所以下采样前一定要先低通滤波。
💡 一个常被忽略的推论:人耳听觉上限约 20 kHz,因此 CD 采样率定在 44.1 kHz(略高于 40 kHz)以保留全部可闻频段。把 8 kHz 电话录音重采样到 16 kHz,绝不会凭空恢复出 4~8 kHz 的信息——上采样无法创造信息。
16 位 PCM(有符号 int16,范围 ±32 767)是通用交换格式;音乐用 24 位;内部数字信号处理(DSP)常用 32 位浮点。像 soundfile 这类库读 int16 文件时,会自动把数据暴露为 [-1, 1] 区间的 float32 数组。
任意有限信号都可以分解成不同频率正弦波的叠加。离散傅里叶变换(DFT)对 N 个样本算出 N 个复数系数,每个系数对应一个频率箱(Frequency Bin)。箱 k 对应的频率是 k · sr / N 赫兹;系数的模是该频率上的振幅,辐角是相位。
快速傅里叶变换(FFT)是 DFT 在 N 为 2 的幂时的 O(N log N) 算法。所有音频库底层都跑 FFT。一个 1024 点的 FFT,在 16 kHz 下给出 512 个可用频率箱,覆盖 0~8 kHz,分辨率约 15.6 Hz。
实际工程中,我们不会对整段音频做一次 FFT,而是把它切成重叠的帧(典型 25 ms 帧、10 ms 步长),每帧乘一个窗函数(汉宁窗 Hann、汉明窗 Hamming)消除边缘不连续,再逐帧 FFT。这就是短时傅里叶变换(STFT),第 02 节从这里接棒。
⚠️ 为什么必须加窗?直接对一段截断的信号做 FFT,等于乘了一个矩形窗,而矩形窗的频谱有大量旁瓣,会把相邻频率的能量「漏」出去(频谱泄漏)。Hann 窗让帧两端平滑衰减到 0,大幅压低旁瓣,代价是频率分辨率略降。这是工程权衡,不是可选步骤。
下面用纯标准库 wave 模块演示零依赖流程。生产环境请改用 soundfile 或 torchaudio.load,两者都返回 (waveform, sr) 元组。
import soundfile as sf waveform, sr = sf.read("clip.wav", dtype="float32") # shape (T,), sr 为 int
import math def sine(freq_hz, sr, seconds, amp=0.5): n = int(sr * seconds) return [amp * math.sin(2 * math.pi * freq_hz * i / sr) for i in range(n)]
一个 440 Hz(音乐会标准音 A)的正弦波,16 kHz 下录 1 秒,就是 16 000 个浮点数。可用 wave.open(..., "wb") 以 16 位 PCM 编码写入文件。
def dft(x): N = len(x) out = [] for k in range(N): re = sum(x[n] * math.cos(-2 * math.pi * k * n / N) for n in range(N)) im = sum(x[n] * math.sin(-2 * math.pi * k * n / N) for n in range(N)) out.append((re, im)) return out
复杂度 O(N²),对 N=256 验证正确性没问题,真实音频根本跑不动。生产代码调用 numpy.fft.rfft 或 torch.fft.rfft。
设计要点:DFT 公式的本质,是把信号
x与频率为k的正弦/余弦基做内积——内积越大,说明信号里这个频率的成分越多。复数表达把正弦和余弦合并成一个旋转矢量,模长即能量,辐角即相位。
幅度谱的峰值下标 k_star 对应频率 k_star * sr / N。把上一步的 440 Hz 正弦波喂给 DFT,峰值应出现在 440 * N / sr 这个箱上。
用 10 kHz 采样一个 7 kHz 的正弦波(奈奎斯特只有 5 kHz)。7 kHz 高于奈奎斯特,折叠成 10 − 7 = 3 kHz。FFT 峰值会出现在 3 kHz 处。这就是经典的混叠演示,也是每个数模/模数转换器都内置「砖墙」低通滤波器的原因。
2026 年实际要上线的工具栈:
| 任务 | 库 | 为什么选它 |
|---|---|---|
| 读写 WAV/FLAC/OGG | soundfile(libsndfile 封装) |
最快、最稳,直接返回 float32。 |
| 重采样 | torchaudio.transforms.Resample 或 librosa.resample |
内置正确的抗混叠滤波。 |
| STFT / 梅尔 | torchaudio 或 librosa |
GPU 友好,PyTorch 生态。 |
| 实时流式采集 | sounddevice 或 pyaudio |
跨平台的 PortAudio 绑定。 |
| 检视音频文件 | ffprobe 或 soxi |
命令行、快,报告采样率/声道/编码。 |
决策铁律:先对齐采样率,再对齐其他一切。Whisper 期望 16 kHz 单声道 float32;喂给它 44.1 kHz 立体声,会得到看起来像模型 bug、实则是数据 bug 的垃圾输出。
# 错误:直接喂 44.1 kHz 立体声 → Whisper 输出乱码 # 正确:先重采样到 16 kHz 单声道 import torchaudio wav, sr = torchaudio.load("clip.wav") # 可能是 44.1k / 立体声 if sr != 16000: wav = torchaudio.transforms.Resample(sr, 16000)(wav) if wav.shape[0] > 1: wav = wav.mean(dim=0, keepdim=True) # 立体声混成单声道 # wav 现在是 16 kHz 单声道,可送入 Whisper
💡
librosa.load(..., sr=16000)会在读取时自动重采样,看似省事,但隐式行为在工程里是大忌——你不知道它何时改了采样率。生产代码请显式调用Resample,把重采样这一步暴露在代码里。
本节产出一个技能文档(原课程 outputs/skill-audio-loader.md),帮你检查音频输入是否匹配下游模型的期望,并在不匹配时正确重采样。核心检查清单:
[-1, 1]?(int16 文件读出来要除以 32768)这份清单可复用于任何语音项目的前处理模块,是「数据进入模型前的最后一道防线」。
基础:合成一段 1 秒、220 Hz + 440 Hz + 880 Hz 混合波(16 kHz 采样)。跑 DFT,确认三个峰值出现在预期频率箱上。
进阶:录一段 3 秒自己说话的 WAV(48 kHz)。用 torchaudio.transforms.Resample(带抗混叠)下采样到 16 kHz;再用「每隔三个样本取一个」的朴素抽样下采样到 16 kHz。分别 FFT,比较两者的频谱——混叠能量出现在哪里?为什么朴素抽样在高频处出现毛刺?
挑战:仅用 math 和第 3 步的 DFT,从零搭一个 STFT。帧长 400、步长 160、Hann 窗,用 matplotlib.pyplot.imshow 画出幅度矩阵。这就是第 02 节的频谱图。
[-1,1] 的一维 float 数组,样本序号除以采样率即得秒数;10 秒 16 kHz 音频 = 160 000 个浮点数。sr 能无歧义表示的最高频率是 sr/2,超过就混叠;下采样前必须先低通滤波。k 对应 k·sr/N Hz,模是振幅、辐角是相位;手写复杂度 O(N²)。O(N log N) 加速版,要求 N 为 2 的幂,所有音频库底层都用它。下一节,我们把 STFT 的复数矩阵转成频谱图,再用梅尔尺度(Mel Scale)把它压缩成人耳感知友好的梅尔特征——这是几乎所有现代语音模型的输入形态。