4、麦克风音频采集实战:使用PyAudio捕获麦克风音频,设置采样率与声道
音频采集是整个流媒体链路的第一道关卡。麦克风进来的声音好不好,直接决定了后面编码和推流的天花板。这一章我们动手用 PyAudio 把麦克风的声音抓到内存里,顺便把采样率、声道这些基础概念落地。
4.1 为什么是 PyAudio?
Python 里能操作音频的库不少,但 PyAudio 是最接近底层硬件的。它封装了 PortAudio 这个跨平台音频 I/O 库,能直接跟麦克风驱动打交道。我个人习惯用它来做原型验证,因为代码量少,调试方便。
你想想看,如果用 C 语言调 ALSA 或 WASAPI,光初始化设备列表就得写上百行。PyAudio 几行就能搞定。当然,代价是它依赖 PortAudio 的运行时库,Windows 下需要单独安装 pip install pyaudio,Linux 下还得装 portaudio19-dev。
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,人耳基本听不到差别了。声道数嘛,说白了就是麦克风数量。单声道录一个人说话完全够用,立体声更适合环境声采集。
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 文件,中间经历了哪些环节,一目了然。
从图上你能看到,数据从麦克风硬件出发,经过 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 捕获,并做重试或降级处理。
4.6 验证采集结果
代码跑完后,你会得到一个 output.wav 文件。怎么验证它对不对?我一般用三个方法:
- 听: 用播放器打开,听声音是否清晰,有没有爆音或丢帧。
- 看波形: 用 Audacity 或 Python 的
matplotlib画波形图,检查振幅是否正常。 - 算数据量: 5 秒 16 kHz 单声道 16 bit 的原始数据量 = 16000 * 2 * 5 = 160000 字节。WAV 文件头加 44 字节,文件大小应该在 160044 字节左右。如果差太多,说明参数设置有问题。
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()