2. 音频文件读取与播放

任务目标

读取一个WAV音频文件,将其解码为PCM样本数据,通过系统音频设备播放,并验证读取的音频数据与原始文件一致。本案例使用Python标准库wave完成WAV解析,使用sounddevice库实现音频播放。

环境依赖

  • Python 3.8+
  • 安装依赖:pip install sounddevice numpy
  • 操作系统需具备可用的音频输出设备

完整实现

import wave
import numpy as np
import sounddevice as sd
import hashlib
import sys

def read_wav(filepath):
    """读取WAV文件,返回PCM数据、采样率、声道数、采样宽度"""
    with wave.open(filepath, 'rb') as wf:
        channels = wf.getnchannels()
        sampwidth = wf.getsampwidth()
        framerate = wf.getframerate()
        nframes = wf.getnframes()
        pcm_bytes = wf.readframes(nframes)
    return pcm_bytes, framerate, channels, sampwidth

def bytes_to_int16(pcm_bytes, channels):
    """将字节串转为int16数组,按帧交错排列"""
    data = np.frombuffer(pcm_bytes, dtype=np.int16)
    # 确保帧数为整数
    valid_len = (len(data) // channels) * channels
    data = data[:valid_len]
    return data.reshape(-1, channels)

def play_audio(data, framerate):
    """播放音频数据(阻塞直到播放完成)"""
    sd.play(data, samplerate=framerate)
    sd.wait()

def verify_integrity(original_bytes, filepath):
    """重新读取文件,对比哈希验证读取正确性"""
    with open(filepath, 'rb') as f:
        file_bytes = f.read()
    # 跳过WAV文件头(44字节标准头),比较PCM数据部分
    pcm_start = 44
    original_pcm = original_bytes
    file_pcm = file_bytes[pcm_start:pcm_start + len(original_pcm)]
    return hashlib.sha256(original_pcm).hexdigest() == hashlib.sha256(file_pcm).hexdigest()

def main():
    if len(sys.argv) < 2:
        print("用法: python wav_player.py <音频文件.wav>")
        sys.exit(1)
    
    wav_path = sys.argv[1]
    
    # 1. 读取WAV
    pcm_data, framerate, channels, sampwidth = read_wav(wav_path)
    print(f"采样率: {framerate} Hz, 声道数: {channels}, 采样宽度: {sampwidth*8} bit")
    print(f"PCM数据长度: {len(pcm_data)} 字节")
    
    # 2. 转换为numpy数组用于播放
    if sampwidth == 2:  # 16-bit PCM
        audio_array = bytes_to_int16(pcm_data, channels)
    else:
        print(f"仅支持16-bit PCM,当前为{sampwidth*8} bit")
        sys.exit(1)
    
    # 3. 播放
    print("开始播放...")
    play_audio(audio_array, framerate)
    print("播放完成")
    
    # 4. 验证数据完整性
    if verify_integrity(pcm_data, wav_path):
        print("✓ 数据完整性验证通过:读取的PCM数据与文件一致")
    else:
        print("✗ 数据完整性验证失败")

if __name__ == "__main__":
    main()

运行方式

# 生成测试WAV文件(可选,用于验证)
python -c "import wave, struct; wf=wave.open('test.wav','wb'); wf.setnchannels(1); wf.setsampwidth(2); wf.setframerate(16000); [wf.writeframes(struct.pack('<h', int(10000*__import__('math').sin(2*__import__('math').pi*440*i/16000)))) for i in range(16000)]; wf.close()"

# 运行播放器
python wav_player.py test.wav

验证方法

  • 播放验证:程序执行后应能听到440Hz正弦音持续1秒,控制台输出播放完成无报错。
  • 数据完整性验证:程序自动计算读取的PCM数据与源文件PCM段的SHA256哈希并比对,输出“验证通过”即表示读取正确。
  • 参数输出验证:控制台打印的采样率、声道数、采样宽度应与生成文件时设置的参数一致(16000Hz、单声道、16bit)。

常见问题

  • 播放无声音:检查系统音量、默认音频输出设备是否可用;尝试将sd.play改为sd.play(audio_array, framerate, blocking=True)。
  • 非16-bit WAV:本案例仅处理16-bit PCM格式,其他格式需扩展bytes_to_int16函数。
  • 文件头非标准:部分WAV文件头超过44字节(包含扩展信息),此时verify_integrity中的偏移量需调整,可改用wave模块重新读取对比。