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")
运行方式
- 准备一个44.1kHz、16-bit、立体声的WAV文件,命名为
input_44k_stereo.wav,放在与脚本相同目录。 - 运行脚本:
python resample.py - 输出文件为
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。