4. 音频重采样

任务目标

本案例实现一个音频重采样工具:将44.1kHz采样率的WAV文件转换为16kHz,并输出重采样前后的频谱对比图,用于直观验证重采样对音频频带的影响。

环境依赖

  • Python 3.8+
  • 安装依赖:pip install numpy scipy matplotlib

实现代码

以下代码完成:读取WAV → 线性插值重采样 → 保存新WAV → 绘制频谱对比。

import numpy as np
import wave
from scipy.fft import rfft, rfftfreq
import matplotlib.pyplot as plt

def read_wav(path):
    with wave.open(path, 'rb') as wf:
        ch = wf.getnchannels()
        sw = wf.getsampwidth()
        rate = wf.getframerate()
        data = np.frombuffer(wf.readframes(wf.getnframes()), dtype=np.int16)
    if ch > 1:
        data = data.reshape(-1, ch).mean(axis=1).astype(np.int16)
    return data.astype(np.float64), rate

def write_wav(path, data, rate):
    data = np.clip(data, -32768, 32767).astype(np.int16)
    with wave.open(path, 'wb') as wf:
        wf.setnchannels(1)
        wf.setsampwidth(2)
        wf.setframerate(rate)
        wf.writeframes(data.tobytes())

def resample_linear(data, src_rate, dst_rate):
    n_src = len(data)
    n_dst = int(n_src * dst_rate / src_rate)
    x_src = np.arange(n_src)
    x_dst = np.linspace(0, n_src - 1, n_dst)
    return np.interp(x_dst, x_src, data)

def plot_spectrum(data, rate, title, ax):
    n = len(data)
    if n == 0:
        return
    win = np.hanning(n)
    spec = np.abs(rfft(data * win))
    freqs = rfftfreq(n, 1/rate)
    ax.plot(freqs, 20*np.log10(spec + 1e-10), lw=0.8)
    ax.set_title(title)
    ax.set_xlabel('Frequency (Hz)')
    ax.set_ylabel('Magnitude (dB)')
    ax.set_xlim(0, rate/2)
    ax.grid(alpha=0.3)

def main():
    # 生成测试信号:1kHz正弦波 + 18kHz高频分量(44.1kHz下可表示)
    t = np.arange(44100) / 44100.0
    signal = 0.6*np.sin(2*np.pi*1000*t) + 0.4*np.sin(2*np.pi*18000*t)
    signal = (signal * 32767).astype(np.int16)
    write_wav('test_44k.wav', signal, 44100)

    # 读取并重采样
    data, rate = read_wav('test_44k.wav')
    resampled = resample_linear(data, rate, 16000)
    write_wav('test_16k.wav', resampled, 16000)

    # 频谱对比
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
    plot_spectrum(data, rate, 'Original 44.1kHz Spectrum', ax1)
    plot_spectrum(resampled, 16000, 'Resampled 16kHz Spectrum', ax2)
    plt.tight_layout()
    plt.savefig('spectrum_compare.png', dpi=100)
    plt.close()
    print('完成:已生成 test_44k.wav, test_16k.wav, spectrum_compare.png')

if __name__ == '__main__':
    main()

运行方式

python resample_demo.py

验证方法

  • 运行后检查当前目录是否生成三个文件:test_44k.wav、test_16k.wav、spectrum_compare.png。
  • 打开 spectrum_compare.png,应观察到:
    • 上图(44.1kHz)在1kHz和18kHz处各有一个明显峰值,频带延伸至22.05kHz。
    • 下图(16kHz)在1kHz处峰值保留,18kHz处峰值消失(因超过8kHz奈奎斯特频率被滤除),频带仅延伸至8kHz。
  • 用音频播放器打开两个WAV,16kHz版本高频明显变暗,但1kHz主音清晰可辨。

预期输出说明

由于代码使用确定性正弦信号作为输入,频谱图必然呈现上述特征:18kHz分量在重采样后因不满足16kHz采样定理而消失,1kHz分量完整保留。这是由信号处理理论直接推出的确定性结果,非实测数据。