3、Python音频库入门:PyAudio安装与配置、sounddevice库基础、wave模块读写、音频设备枚举

好,咱们进入实战环节。前面聊了那么多音频基础概念,现在该让Python真正“听到”声音了。这一章我会带你搞定三个最常用的音频库——PyAudio、sounddevice和wave。说白了,它们就是Python和音频硬件之间的翻译官。

我个人习惯把这几个库分个工:PyAudio是瑞士军刀,功能全但配置稍麻烦;sounddevice是轻骑兵,API简洁,适合快速原型;wave是后勤兵,专门读写WAV文件。咱们一个一个来。

3.1 PyAudio:安装与配置

PyAudio是对PortAudio的Python封装。PortAudio是个跨平台音频I/O库,几乎所有专业音频软件底层都在用它。嗯,这里要注意——PyAudio的安装曾经是很多初学者的噩梦。

⚠ 常见坑:Windows上直接 pip install pyaudio 大概率会报错,因为缺少PortAudio的二进制依赖。别慌,有解决方案。

3.1.1 安装方法

不同平台,安装方式不一样。我列个表,你对着自己的系统来:

操作系统 推荐安装方式 备注
Windows pip install pipwin → pipwin install pyaudio 或者直接下载预编译whl文件
macOS brew install portaudio → pip install pyaudio Homebrew先装依赖
Linux (Ubuntu/Debian) sudo apt install portaudio19-dev → pip install pyaudio 开发头文件必须装
Linux (其他发行版) 安装对应portaudio-dev包 → pip install pyaudio 包名可能略有不同

我曾经在Windows上被这个坑折腾了整整一下午。后来发现,直接用 pipwin 是最省心的——它会自动从Gohlke的仓库拉取编译好的whl文件。

3.1.2 验证安装

装完之后,跑个简单的验证脚本:

import pyaudio

# 创建PyAudio对象
p = pyaudio.PyAudio()

# 获取默认输入设备信息
info = p.get_default_input_device_info()
print(f"默认输入设备: {info['name']}")
print(f"采样率: {info['defaultSampleRate']}")

# 别忘了关闭
p.terminate()

如果没报错,并且打印出了设备信息,恭喜你,PyAudio装好了。

3.2 sounddevice库:轻量级替代方案

说实话,我现在做原型验证时更常用sounddevice。为什么?因为它API设计得更Pythonic,不用手动管理流对象的生命周期。你想想看,PyAudio里你得自己open、start、stop、close,而sounddevice一个函数调用就搞定了。

3.2.1 安装与基础用法

安装简单到离谱:

pip install sounddevice

没有依赖陷阱,没有编译问题。这才是现代Python库该有的样子。

来个最简单的录音示例:

import sounddevice as sd
import numpy as np

# 录制3秒音频,采样率44100Hz
fs = 44100
duration = 3
print("开始录音...")
recording = sd.rec(int(duration * fs), samplerate=fs, channels=1)
sd.wait()  # 等待录制完成
print("录音结束")

# recording是一个numpy数组
print(f"数据形状: {recording.shape}")
print(f"数据类型: {recording.dtype}")

看到了吗?三行核心代码就完成了录音。sd.rec()是阻塞式调用,sd.wait()确保数据写满。我在项目中经常用这个组合做快速音频采集验证。

3.2.2 播放音频

播放同样简单:

import sounddevice as sd
import numpy as np

# 生成一个440Hz的正弦波,播放2秒
fs = 44100
t = np.linspace(0, 2, int(fs * 2), endpoint=False)
wave = 0.5 * np.sin(2 * np.pi * 440 * t)

sd.play(wave, samplerate=fs)
sd.wait()  # 等待播放完成
💡 小技巧:sounddevice默认使用float32数据类型,范围在[-1.0, 1.0]。如果你从文件读到的数据是int16,记得先归一化再播放,否则声音会炸。

3.3 wave模块:读写WAV文件

wave是Python标准库的一部分,不需要额外安装。它只支持WAV格式,但WAV恰恰是音频采集中最常用的无损格式。说白了,你采集到的原始数据,最终大概率要存成WAV。

3.3.1 读取WAV文件

import wave
import numpy as np

# 打开WAV文件
with wave.open('test.wav', 'rb') as wf:
    # 获取文件参数
    n_channels = wf.getnchannels()
    sampwidth = wf.getsampwidth()
    framerate = wf.getframerate()
    n_frames = wf.getnframes()
    
    print(f"声道数: {n_channels}")
    print(f"采样宽度: {sampwidth} 字节")  # 1=8bit, 2=16bit, 4=32bit
    print(f"采样率: {framerate} Hz")
    print(f"总帧数: {n_frames}")
    
    # 读取所有音频数据
    raw_data = wf.readframes(n_frames)
    # 转为numpy数组(假设是16bit)
    audio_data = np.frombuffer(raw_data, dtype=np.int16)
    # 如果是多声道,reshape成二维数组
    if n_channels > 1:
        audio_data = audio_data.reshape(-1, n_channels)

这里有个细节——readframes()返回的是原始字节,你得自己根据采样宽度转成合适的numpy类型。我曾经见过有人直接用int16去读8bit的WAV,结果数据全乱了。

3.3.2 写入WAV文件

import wave
import numpy as np

# 准备数据:2秒的440Hz正弦波,16bit
fs = 44100
t = np.linspace(0, 2, int(fs * 2), endpoint=False)
wave_data = (0.5 * np.sin(2 * np.pi * 440 * t) * 32767).astype(np.int16)

# 写入WAV
with wave.open('output.wav', 'wb') as wf:
    wf.setnchannels(1)        # 单声道
    wf.setsampwidth(2)        # 16bit = 2字节
    wf.setframerate(fs)       # 采样率
    wf.writeframes(wave_data.tobytes())

print("WAV文件已保存")
⚠ 注意:写入前一定要把数据缩放到对应位深的动态范围。16bit的范围是[-32768, 32767],32bit float的范围是[-1.0, 1.0]。搞错了,播放出来的就是噪音。

3.4 音频设备枚举

做音频采集的第一步是什么?不是写代码,是搞清楚你的机器上有哪些音频设备。我刚开始做项目时,直接用了默认设备,结果采集到的全是麦克风的底噪——因为我压根没意识到还有别的输入源。

3.4.1 用PyAudio枚举设备

import pyaudio

p = pyaudio.PyAudio()
device_count = p.get_device_count()

print(f"检测到 {device_count} 个音频设备\n")
print(f"{'索引':<6} {'名称':<40} {'输入通道':<10} {'输出通道':<10} {'采样率':<10}")
print("-" * 80)

for i in range(device_count):
    info = p.get_device_info_by_index(i)
    print(f"{i:<6} {info['name']:<40} {info['maxInputChannels']:<10} "
          f"{info['maxOutputChannels']:<10} {info['defaultSampleRate']:<10.0f}")

p.terminate()

3.4.2 用sounddevice枚举设备

sounddevice更简洁:

import sounddevice as sd

# 获取所有设备
devices = sd.query_devices()
print(devices)

# 获取默认设备
default_input = sd.default.device[0]
default_output = sd.default.device[1]
print(f"默认输入设备索引: {default_input}")
print(f"默认输出设备索引: {default_output}")

输出类似这样:

  0 MacBook Pro Microphone, Core Audio (1 in, 0 out)
  1 MacBook Pro Speakers, Core Audio (0 in, 2 out)
  2 USB Audio Device, Core Audio (2 in, 2 out)
💡 经验之谈:设备索引在不同机器上不一样,甚至插拔USB声卡后也会变。我建议你在程序启动时动态枚举,不要硬编码设备索引。用设备名称匹配更靠谱。

3.5 本章知识体系

下面这张图帮你理清三个库的关系和适用场景:

Python音频库知识体系 Python音频采集与处理应用 PyAudio sounddevice wave 特性 • 功能最全面 • 需手动管理流 • 安装略复杂 特性 • API简洁直观 • 安装零依赖 • 适合快速原型 特性 • 标准库无需安装 • 仅支持WAV格式 • 文件读写专用 核心功能 设备枚举 · 流控制 · 回调 核心功能 录音 · 播放 · 设备查询 核心功能 读取 · 写入 · 参数解析 组合使用:sounddevice采集 → wave存储 → PyAudio精细控制

这张图把三个库的定位说得很清楚了。我个人建议的学习路径是:先用sounddevice快速上手录音和播放,再用wave搞定文件读写,最后深入PyAudio做精细的流控制。别一上来就啃PyAudio的复杂API,容易劝退。

3.6 避坑指南

最后,分享几个我踩过的坑:

  • 设备被占用:PyAudio打开设备后,如果不调用close()或terminate(),其他程序就访问不了这个设备。我曾经调试时忘了关流,结果Audacity一直报错说设备不可用。
  • 缓冲区大小不匹配:sounddevice的blocksize参数默认是自动选择的,但在某些USB声卡上会导致爆音。手动设成1024或512帧通常能解决问题。
  • 采样率不匹配:不是所有设备都支持所有采样率。枚举设备时记得检查defaultSampleRate,别想当然地用96000去录一个只支持48000的麦克风。
  • 数据类型转换:wave模块读出来的是bytes,sounddevice要的是numpy数组,PyAudio要的是bytes或numpy。这三者之间的转换最容易出bug。我的习惯是统一用numpy做中间格式。