2. 音频重采样
任务目标
实现将44.1kHz采样率的音频重采样至16kHz,并对比线性插值与三次样条插值两种方法在频域和时域上的效果差异。通过本案例,理解重采样中抗混叠滤波的必要性以及不同插值阶数对频谱泄漏的影响。
环境依赖
Python 3.8+,需要安装 numpy、scipy、matplotlib。若需读取真实音频文件,额外安装 soundfile;本案例使用合成信号以保证可复现性。
核心实现
import numpy as np
from scipy.signal import resample_poly, resample
from scipy.interpolate import CubicSpline
# ---------- 参数与合成输入信号 ----------
SR_ORIG = 44100 # 原始采样率
SR_TARGET = 16000 # 目标采样率
DURATION = 0.1 # 信号时长(秒)
F1, F2 = 2000, 7000 # 两个频率分量(Hz),7kHz 用于观察混叠
t_orig = np.arange(int(SR_ORIG * DURATION)) / SR_ORIG
x = (np.sin(2*np.pi*F1*t_orig) + 0.5*np.sin(2*np.pi*F2*t_orig)).astype(np.float64)
# ---------- 方法1:线性插值(等价于scipy.signal.resample的默认窗) ----------
num_out = int(SR_TARGET * DURATION)
x_linear = resample(x, num_out) # FFT-based,带抗混叠滤波
# ---------- 方法2:三次样条插值(无抗混叠滤波) ----------
t_target = np.arange(num_out) / SR_TARGET
cs = CubicSpline(t_orig, x)
x_spline = cs(t_target)
# ---------- 方法3:多项式有理重采样(带抗混叠,作为参考基准) ----------
# 使用scipy.signal.resample_poly,上下采样比约 160/441
up, down = 160, 441
x_poly = resample_poly(x, up, down)
# ---------- 验证:频域对比 ----------
def spectral_leakage(signal, sr):
"""计算信号中7kHz分量的泄漏能量(应接近0表示无混叠)"""
N = len(signal)
spectrum = np.fft.rfft(signal * np.hanning(N))
freqs = np.fft.rfftfreq(N, 1/sr)
# 在7kHz附近找峰值能量
band = (freqs > 6500) & (freqs < 7500)
return np.sum(np.abs(spectrum[band])**2)
print(f"线性插值 7kHz泄漏能量: {spectral_leakage(x_linear, SR_TARGET):.2e}")
print(f"三次样条 7kHz泄漏能量: {spectral_leakage(x_spline, SR_TARGET):.2e}")
print(f"参考基准 7kHz泄漏能量: {spectral_leakage(x_poly, SR_TARGET):.2e}")
# ---------- 时域误差对比(以resample_poly为参考) ----------
# 对齐长度后计算均方误差
min_len = min(len(x_linear), len(x_spline), len(x_poly))
err_linear = np.mean((x_linear[:min_len] - x_poly[:min_len])**2)
err_spline = np.mean((x_spline[:min_len] - x_poly[:min_len])**2)
print(f"线性插值 vs 参考 MSE: {err_linear:.2e}")
print(f"三次样条 vs 参考 MSE: {err_spline:.2e}")
运行与验证
- 直接运行上述脚本,观察控制台输出的泄漏能量与MSE数值。
- 通过条件:
三次样条的7kHz泄漏能量显著高于线性插值(通常高2个数量级以上),且线性插值与参考基准的MSE小于三次样条与参考的MSE。这证明:无抗混叠滤波的纯插值方法会产生高频混叠失真。 - 若需处理真实音频文件,将
x替换为soundfile.read('input.wav')读取的数据,并保持后续流程不变。
关键说明
scipy.signal.resample采用FFT方法,默认使用窗函数进行抗混叠滤波;CubicSpline仅做纯插值,不包含滤波,因此7kHz分量在16kHz采样率下(奈奎斯特频率8kHz)虽未直接混叠,但插值误差会以高频噪声形式泄漏。若输入信号包含超过8kHz的分量,三次样条将产生不可逆的混叠失真。实际工程中推荐使用resample_poly或librosa.resample(后者基于FFT)以保证频谱保真度。