第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 portaudiopip install pyaudio |
先装portaudio,再装pyaudio |
| Linux | sudo apt install portaudio19-devpip 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库 — 实时播放和录音
- 实战 — 写了个简单的音频播放器
说实话,这些只是音频处理的冰山一角。但万丈高楼平地起,把基础打牢了,后面做音效合成、音频特效、实时处理才能得心应手。
下节课,咱们聊聊音频数据的数学表示,看看声音在计算机里到底长什么样。到时候我会分享一个我在游戏项目中用到的音频分析技巧,保证让你大开眼界。
课后练习:
- 用wave模块读取一个WAV文件,打印出它的所有参数
- 生成一个1kHz的正弦波,保存为WAV文件
- 用pyaudio播放你生成的WAV文件
- 尝试录制一段自己的声音,然后播放出来
有问题随时在课程群里问我。记住,音频处理没有捷径,多动手写代码才是王道。