第1章:Python音频库入门 — wave模块与pyaudio库

各位同学好,我是你们的音频工程师老张。

做游戏音效这些年,我最大的体会就是:不懂音频底层,你做的音效就是空中楼阁。今天咱们就从Python最基础的音频库开始,把音频文件的读写和播放彻底搞明白。

1.1 为什么选Python做音频处理?

说实话,我刚开始做嵌入式音频时,用的都是C语言。后来转到游戏音效设计,发现Python真香。原因很简单:

  • 开发效率高 — 写个音频播放器,Python可能20行代码搞定,C得写200行
  • 生态丰富 — wave、pyaudio、soundfile、librosa...要啥有啥
  • 适合原型验证 — 我经常先用Python调好算法,再移植到嵌入式平台

但注意,Python不是万能的。实时音频处理、低延迟场景,还是得C/C++上。咱们这门课主要用Python做音效生成和原型验证

1.2 wave模块 — 读写WAV文件

wave是Python标准库,不用额外安装。它只支持WAV格式,但够用了。

核心能力:读取WAV文件的采样率、位深、声道数,以及音频数据本身。

1.2.1 读取WAV文件

先看个最简单的例子:

import wave

# 打开一个WAV文件
with wave.open('test.wav', 'rb') as wav_file:
    # 获取音频参数
    channels = wav_file.getnchannels()      # 声道数
    sampwidth = wav_file.getsampwidth()     # 采样字节数(1=8bit, 2=16bit)
    framerate = wav_file.getframerate()     # 采样率
    nframes = wav_file.getnframes()         # 总帧数
    
    print(f'声道数: {channels}')
    print(f'采样位深: {sampwidth * 8} bit')
    print(f'采样率: {framerate} Hz')
    print(f'总帧数: {nframes}')
    print(f'时长: {nframes / framerate:.2f} 秒')
    
    # 读取所有音频数据
    audio_data = wav_file.readframes(nframes)

这里有个坑,我当年第一次用wave模块时踩过:readframes返回的是原始字节数据,不是数值数组。要处理音频数据,得用struct或numpy转换。

我的习惯:读取音频数据后,立刻用numpy转成float数组,方便后续处理。

1.2.2 写入WAV文件

写入比读取稍微复杂点,需要先设置参数:

import wave
import numpy as np

# 生成一个440Hz的正弦波(A4音)
sample_rate = 44100
duration = 2.0  # 2秒
frequency = 440.0

t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
audio_data = 0.5 * np.sin(2 * np.pi * frequency * t)

# 转成16位整数
audio_int16 = (audio_data * 32767).astype(np.int16)

# 写入WAV文件
with wave.open('output.wav', 'wb') as wav_file:
    wav_file.setnchannels(1)          # 单声道
    wav_file.setsampwidth(2)          # 16位 = 2字节
    wav_file.setframerate(sample_rate)
    wav_file.writeframes(audio_int16.tobytes())

print('WAV文件已生成!')

嗯,这里要注意:writeframes接收的是字节数据,不是numpy数组。我见过不少新手直接传numpy数组进去,结果文件打不开。

1.3 pyaudio库 — 实时音频播放

wave模块只能读写文件,不能播放。要播放音频,得用pyaudio。

1.3.1 安装pyaudio

安装pyaudio有点小麻烦,不同平台不一样:

平台 安装命令 注意事项
Windows pip install pyaudio 可能需要安装Visual C++构建工具
macOS brew install portaudio
pip install pyaudio
先装portaudio,再装pyaudio
Linux sudo apt install portaudio19-dev
pip install pyaudio
Ubuntu/Debian系

我曾经踩过的坑:在树莓派上装pyaudio,忘了装portaudio-dev,结果编译报错。折腾了半小时才发现是依赖没装全。

1.3.2 播放WAV文件

来,写个完整的播放器:

import pyaudio
import wave

def play_wav(file_path):
    # 打开WAV文件
    wf = wave.open(file_path, 'rb')
    
    # 初始化pyaudio
    p = pyaudio.PyAudio()
    
    # 打开音频流
    stream = p.open(
        format=p.get_format_from_width(wf.getsampwidth()),
        channels=wf.getnchannels(),
        rate=wf.getframerate(),
        output=True
    )
    
    # 读取数据并播放
    chunk_size = 1024
    data = wf.readframes(chunk_size)
    
    while data:
        stream.write(data)
        data = wf.readframes(chunk_size)
    
    # 清理
    stream.stop_stream()
    stream.close()
    p.terminate()
    wf.close()

# 播放刚才生成的WAV文件
play_wav('output.wav')

这段代码我用了好多年,基本没改过。核心思路就是:打开文件 → 创建流 → 循环写入 → 清理资源

1.3.3 实时录音

pyaudio不仅能播放,还能录音。这个在游戏音效设计中很有用,比如录制玩家语音:

import pyaudio
import wave

def record_audio(filename, duration=5, sample_rate=44100):
    chunk = 1024
    format = pyaudio.paInt16
    channels = 1
    
    p = pyaudio.PyAudio()
    
    # 打开输入流
    stream = p.open(
        format=format,
        channels=channels,
        rate=sample_rate,
        input=True,
        frames_per_buffer=chunk
    )
    
    print(f'开始录音 {duration} 秒...')
    frames = []
    
    for _ in range(0, int(sample_rate / chunk * duration)):
        data = stream.read(chunk)
        frames.append(data)
    
    print('录音结束')
    
    # 停止并清理
    stream.stop_stream()
    stream.close()
    p.terminate()
    
    # 保存为WAV文件
    wf = wave.open(filename, 'wb')
    wf.setnchannels(channels)
    wf.setsampwidth(p.get_sample_size(format))
    wf.setframerate(sample_rate)
    wf.writeframes(b''.join(frames))
    wf.close()

# 录制5秒音频
record_audio('my_voice.wav', duration=5)

小技巧:录音时chunk大小选1024比较稳妥。太小了CPU占用高,太大了延迟明显。我在做游戏语音聊天功能时,试过各种chunk大小,最后发现1024是平衡点。

1.4 实战:做一个简单的音频播放器

把上面的知识串起来,写个带控制台的播放器:

import pyaudio
import wave
import os

class SimpleAudioPlayer:
    def __init__(self):
        self.p = pyaudio.PyAudio()
        self.stream = None
        self.wf = None
    
    def play(self, file_path):
        if not os.path.exists(file_path):
            print(f'文件不存在: {file_path}')
            return
        
        self.wf = wave.open(file_path, 'rb')
        self.stream = self.p.open(
            format=self.p.get_format_from_width(self.wf.getsampwidth()),
            channels=self.wf.getnchannels(),
            rate=self.wf.getframerate(),
            output=True
        )
        
        chunk = 1024
        data = self.wf.readframes(chunk)
        while data:
            self.stream.write(data)
            data = self.wf.readframes(chunk)
        
        self.stop()
    
    def stop(self):
        if self.stream:
            self.stream.stop_stream()
            self.stream.close()
        if self.wf:
            self.wf.close()
    
    def __del__(self):
        self.p.terminate()

# 使用示例
player = SimpleAudioPlayer()
player.play('output.wav')

这个类封装了播放逻辑,你可以在游戏里直接调用。我当年做《节奏大师》类游戏时,就是用类似的类管理音效播放的。

1.5 本章小结

今天咱们学了:

  • wave模块 — 读写WAV文件,获取音频参数
  • pyaudio库 — 实时播放和录音
  • 实战 — 写了个简单的音频播放器

说实话,这些只是音频处理的冰山一角。但万丈高楼平地起,把基础打牢了,后面做音效合成、音频特效、实时处理才能得心应手。

下节课,咱们聊聊音频数据的数学表示,看看声音在计算机里到底长什么样。到时候我会分享一个我在游戏项目中用到的音频分析技巧,保证让你大开眼界。

课后练习:

  1. 用wave模块读取一个WAV文件,打印出它的所有参数
  2. 生成一个1kHz的正弦波,保存为WAV文件
  3. 用pyaudio播放你生成的WAV文件
  4. 尝试录制一段自己的声音,然后播放出来

有问题随时在课程群里问我。记住,音频处理没有捷径,多动手写代码才是王道。