2. 语音数据集的音频格式统一与重采样

任务目标

将一段采样率为44.1kHz的立体声音频文件重采样至16kHz,并转换为单声道,输出为新文件。本案例使用Python标准库wave与audioop完成,不依赖第三方音频处理库。

环境依赖

仅需Python 3.6+,无需安装额外包。wave与audioop均为标准库模块。

核心实现

以下代码读取输入WAV文件,完成线性插值重采样(44.1kHz→16kHz),将双声道混合为单声道(取左右声道平均),并写入新WAV文件。

import wave
import audioop
import struct

def resample_to_16k_mono(input_path, output_path):
    # 1. 读取输入文件
    with wave.open(input_path, 'rb') as wav_in:
        params = wav_in.getparams()
        n_channels = params.nchannels
        samp_width = params.sampwidth
        frame_rate = params.framerate
        n_frames = params.nframes
        
        if samp_width != 2:
            raise ValueError("仅支持16-bit PCM WAV文件")
        if frame_rate != 44100:
            raise ValueError(f"输入采样率应为44100Hz,实际为{frame_rate}Hz")
        
        raw_data = wav_in.readframes(n_frames)
    
    # 2. 转换为单声道(立体声取平均)
    if n_channels == 2:
        mono_data = audioop.tomono(raw_data, samp_width, 0.5, 0.5)
    else:
        mono_data = raw_data
    
    # 3. 重采样:44100Hz → 16000Hz(线性插值)
    # 将字节数据转为16-bit整数列表
    samples = list(struct.unpack('<{}h'.format(len(mono_data)//2), mono_data))
    
    old_rate = 44100
    new_rate = 16000
    old_len = len(samples)
    new_len = int(old_len * new_rate / old_rate)
    
    # 计算每个新采样点对应的旧位置(浮点索引)
    resampled = []
    for i in range(new_len):
        src_pos = i * old_len / new_len
        idx = int(src_pos)
        frac = src_pos - idx
        
        if idx + 1 < old_len:
            # 线性插值
            val = samples[idx] * (1 - frac) + samples[idx + 1] * frac
        else:
            val = samples[idx]
        resampled.append(int(val))
    
    # 4. 写为16-bit PCM单声道WAV
    out_data = struct.pack('<{}h'.format(len(resampled)), *resampled)
    
    with wave.open(output_path, 'wb') as wav_out:
        wav_out.setnchannels(1)
        wav_out.setsampwidth(2)
        wav_out.setframerate(new_rate)
        wav_out.writeframes(out_data)
    
    print(f"完成:{input_path} → {output_path}")
    print(f"输出格式:{new_rate}Hz / 单声道 / 16-bit PCM")

if __name__ == "__main__":
    resample_to_16k_mono("input_44k_stereo.wav", "output_16k_mono.wav")

运行方式

  1. 准备一个44.1kHz、16-bit、立体声的WAV文件,命名为input_44k_stereo.wav,放在与脚本相同目录。
  2. 运行脚本:python resample.py
  3. 输出文件为output_16k_mono.wav。

验证方法

运行以下Python命令检查输出文件属性:

import wave
with wave.open("output_16k_mono.wav", 'rb') as w:
    print(f"采样率: {w.getframerate()} Hz")
    print(f"声道数: {w.getnchannels()}")
    print(f"采样宽度: {w.getsampwidth()*8} bit")

通过条件:输出显示采样率为16000Hz、声道数为1、采样宽度为16bit,且文件可正常播放(时长约为原文件的36.3%)。

常见问题

  • 输入不是44.1kHz:脚本会报错并终止,请先确认输入文件采样率。
  • 输入为8-bit或24-bit:脚本仅支持16-bit PCM,需先转换格式。
  • 重采样质量:线性插值适用于语音等对频率精度要求不高的场景;如需更高保真度,可改用scipy.signal.resample_poly或librosa。