4、麦克风音频采集实战:使用PyAudio捕获麦克风音频,设置采样率与声道

音频采集是整个流媒体链路的第一道关卡。麦克风进来的声音好不好,直接决定了后面编码和推流的天花板。这一章我们动手用 PyAudio 把麦克风的声音抓到内存里,顺便把采样率、声道这些基础概念落地。

本章核心目标: 用 Python 代码打开麦克风,设置正确的采样参数,把原始 PCM 数据读出来,并理解每个参数背后的物理含义。

4.1 为什么是 PyAudio?

Python 里能操作音频的库不少,但 PyAudio 是最接近底层硬件的。它封装了 PortAudio 这个跨平台音频 I/O 库,能直接跟麦克风驱动打交道。我个人习惯用它来做原型验证,因为代码量少,调试方便。

你想想看,如果用 C 语言调 ALSA 或 WASAPI,光初始化设备列表就得写上百行。PyAudio 几行就能搞定。当然,代价是它依赖 PortAudio 的运行时库,Windows 下需要单独安装 pip install pyaudio,Linux 下还得装 portaudio19-dev。

⚠ 安装踩坑: 我曾经在 Ubuntu 20.04 上直接 pip install pyaudio 报错,原因是缺少 portaudio 头文件。先执行 sudo apt install portaudio19-dev 再 pip 就过了。macOS 用户可以用 Homebrew 装 portaudio。

4.2 采样率与声道:两个必须搞清楚的参数

在写代码之前,我们先理清两个概念。很多新手在这上面翻车,录出来的声音要么变调,要么只有一边响。

参数 含义 常见值 我的建议
采样率 每秒采集多少个声音样本 8000, 16000, 44100, 48000 语音用 16000,音乐用 44100
声道数 同时采集几路声音 1(单声道), 2(立体声) 直播推流用单声道,省带宽
采样位深 每个样本用多少 bit 表示 16, 24, 32 16 bit 足够,兼容性最好

采样率决定了频率上限。根据奈奎斯特定理,能采集到的最高频率是采样率的一半。所以 44100 Hz 能覆盖 22 kHz,人耳基本听不到差别了。声道数嘛,说白了就是麦克风数量。单声道录一个人说话完全够用,立体声更适合环境声采集。

💡 避坑指南: 我曾经帮一个客户排查音频问题,他录出来的声音像「慢放」。后来发现他把采样率设成了 48000,但播放时当成 44100 去解码。采样率必须发送端和接收端一致,否则音调会变。

4.3 实战:用 PyAudio 捕获麦克风音频

下面这段代码是我常用的模板。它打开默认麦克风,以 16000 Hz、单声道、16 bit 的格式采集 5 秒音频,然后保存到文件。

import pyaudio
import wave

# 参数设置
FORMAT = pyaudio.paInt16      # 16 bit 采样深度
CHANNELS = 1                  # 单声道
RATE = 16000                  # 采样率 16 kHz
CHUNK = 1024                  # 每次读取的帧数
RECORD_SECONDS = 5            # 录制时长

# 初始化 PyAudio
p = pyaudio.PyAudio()

# 打开音频流
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)

print("开始录音...")

frames = []

# 循环读取数据
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
    data = stream.read(CHUNK)
    frames.append(data)

print("录音结束")

# 关闭流
stream.stop_stream()
stream.close()
p.terminate()

# 保存为 WAV 文件
wf = wave.open("output.wav", 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

这段代码的逻辑很直白:打开设备 → 循环读块 → 存列表 → 写文件。注意 CHUNK 这个参数,它控制每次从底层缓冲区拿多少帧。1024 是个比较平衡的值,太小会增加 CPU 开销,太大会导致延迟变高。

4.4 理解音频数据流

为了让你更直观地理解整个采集流程,我画了一张图。从麦克风到 WAV 文件,中间经历了哪些环节,一目了然。

PyAudio 麦克风采集数据流 麦克风硬件 PortAudio 驱动 PyAudio 接口 Python 应用代码 关键参数:采样率 16000 Hz | 单声道 | 16 bit | CHUNK=1024 循环读取过程(每秒约 15.6 次) stream.read(CHUNK) → frames.append(data) → b''.join(frames) → WAV 每次读取 1024 帧,5 秒共读取约 78 次(16000/1024*5 ≈ 78.1)

从图上你能看到,数据从麦克风硬件出发,经过 PortAudio 驱动层,再到 PyAudio 接口,最后进入我们的 Python 代码。每次 stream.read(CHUNK) 返回的是一段二进制数据,长度是 CHUNK * 采样位深 / 8 * 声道数。对于 16 bit 单声道,就是 1024 * 2 = 2048 字节。

4.5 参数调优与常见问题

在实际项目中,参数不是随便设的。我总结了几条经验,供你参考。

  • CHUNK 大小: 语音场景用 1024 或 2048,延迟约 60-120 ms。如果需要低延迟(比如实时对讲),可以降到 512 甚至 256,但 CPU 占用会上升。
  • 采样率选择: 语音通信用 16000 Hz 就够了,人声主要能量在 300-3400 Hz。音乐直播建议 44100 Hz,否则高频会丢失。
  • 声道数: 除非你采集立体声环境音,否则一律用单声道。双声道数据量翻倍,对网络和编码都是负担。
  • 异常处理: 麦克风被占用或拔掉时,stream.read() 会抛出异常。建议用 try/except 捕获,并做重试或降级处理。
⚠ 我曾经踩过的坑: 有一次在树莓派上跑采集程序,CHUNK 设成 4096,结果录音断断续续。后来发现是 USB 麦克风的缓冲区太小,大块读取超时了。换成 1024 就正常了。不同硬件对 CHUNK 的容忍度不一样,建议从 1024 开始试。

4.6 验证采集结果

代码跑完后,你会得到一个 output.wav 文件。怎么验证它对不对?我一般用三个方法:

  1. 听: 用播放器打开,听声音是否清晰,有没有爆音或丢帧。
  2. 看波形: 用 Audacity 或 Python 的 matplotlib 画波形图,检查振幅是否正常。
  3. 算数据量: 5 秒 16 kHz 单声道 16 bit 的原始数据量 = 16000 * 2 * 5 = 160000 字节。WAV 文件头加 44 字节,文件大小应该在 160044 字节左右。如果差太多,说明参数设置有问题。
💡 快速验证代码片段: 你可以用下面这段代码检查 WAV 文件的参数是否和采集时一致。
import wave
wf = wave.open("output.wav", "rb")
print(f"声道数: {wf.getnchannels()}")
print(f"采样率: {wf.getframerate()}")
print(f"采样深度: {wf.getsampwidth()*8} bit")
print(f"总帧数: {wf.getnframes()}")
wf.close()