2. 声学基础
说实话,很多初学者一上来就急着写代码,结果卡在环境配置上半天出不来。我见过太多人因为Python版本冲突、库装不上、音频设备打不开,直接劝退了。这一章,咱们就把这些坑一个个填平。
环境搭建这事儿,说白了就是给你的电脑装上合适的工具。就像做木工活之前得先备好刨子、凿子一样。咱们做麦克风阵列信号处理,需要一套专门的Python工具箱。
本章核心目标:搭建一个稳定、可复用的Python音频处理环境,掌握音频文件读写、播放、录制的基础操作。
2.1 Python虚拟环境:为什么必须用?
我刚开始做项目时,所有库都装在全局环境里。结果有一次,项目A需要NumPy 1.19,项目B需要NumPy 1.24,两个库互相打架,搞得我重装了三次系统。从那以后,我养成了一个习惯——每个项目单独建虚拟环境。
虚拟环境说白了就是一个隔离的Python运行空间。每个环境有自己的库,互不干扰。你想想看,这就像每个项目有自己的工具箱,不会把螺丝刀和扳手混在一起。
创建虚拟环境的两种方式
| 方式 | 命令 | 特点 |
|---|---|---|
| venv(内置) | python -m venv myenv |
Python 3.3+自带,轻量,够用 |
| conda | conda create -n myenv python=3.9 |
适合科学计算,管理非Python依赖更方便 |
我个人习惯用venv,因为它轻量、不需要额外装东西。但如果你做大型项目,conda的包管理确实更省心。
小技巧:虚拟环境的名字最好跟项目名相关,比如 mic_array_env。这样时间长了也不会搞混。
激活与退出
# Windows
myenv\Scripts\activate
# macOS / Linux
source myenv/bin/activate
# 退出
deactivate
激活后,终端前面会出现 (myenv) 的提示。嗯,看到这个就说明你已经在虚拟环境里了,可以放心装库了。
2.2 NumPy与SciPy:音频计算的基石
做音频信号处理,NumPy和SciPy是绕不开的两个库。NumPy提供高效的数组操作,SciPy则封装了大量信号处理算法。说白了,NumPy是咱们的"手",SciPy是咱们的"工具箱"。
安装命令
pip install numpy scipy
这里有个坑——如果你用conda,建议用 conda install numpy scipy,因为conda会自动处理底层依赖(比如BLAS库),性能会好一些。我在项目中遇到过,同样的FFT计算,conda装的NumPy比pip装的快了将近30%。
快速验证
import numpy as np
import scipy.signal as signal
# 生成一个1kHz的正弦波
fs = 44100 # 采样率
t = np.arange(0, 1, 1/fs)
x = np.sin(2 * np.pi * 1000 * t)
print(f"数组长度: {len(x)}")
print(f"数据类型: {x.dtype}")
# 用SciPy做低通滤波
b, a = signal.butter(4, 500, fs=fs, btype='low')
y = signal.filtfilt(b, a, x)
print(f"滤波完成,输出长度: {len(y)}")
这段代码虽然简单,但已经包含了音频处理最核心的两个操作:生成信号和滤波。后面的章节,我们会反复用到这些基础操作。
2.3 SoundFile与PyAudio:音频I/O的左右手
音频处理离不开文件的读写和设备的交互。SoundFile负责读写音频文件,PyAudio负责跟麦克风、扬声器打交道。这两个库,一个管文件,一个管设备,分工明确。
安装
pip install soundfile pyaudio
注意:PyAudio在Windows上可能需要预装PortAudio。如果pip安装失败,可以去 Christoph Gohlke的页面 下载对应的whl文件。我曾经在这上面折腾了两个小时,后来发现直接下载whl是最省事的。
SoundFile基础用法
import soundfile as sf
import numpy as np
# 写入音频文件
fs = 44100
t = np.arange(0, 2, 1/fs)
data = np.sin(2 * np.pi * 440 * t) # 440Hz A音
sf.write('test.wav', data, fs)
# 读取音频文件
data_read, fs_read = sf.read('test.wav')
print(f"采样率: {fs_read}, 数据形状: {data_read.shape}")
SoundFile支持多种格式:WAV、FLAC、OGG等。我个人建议在项目里统一用WAV,兼容性最好,处理起来也最直接。
PyAudio基础用法
import pyaudio
import numpy as np
# 列出所有音频设备
p = pyaudio.PyAudio()
for i in range(p.get_device_count()):
info = p.get_device_info_by_index(i)
print(f"设备 {i}: {info['name']}, 输入通道: {info['maxInputChannels']}")
p.terminate()
这段代码会列出你电脑上所有的音频设备。做麦克风阵列的时候,你需要从这里找到你的阵列设备编号。我一般会先跑一遍这个代码,确认设备编号后再写采集程序。
2.4 音频I/O基础操作实战
理论说完了,咱们来点实际的。下面这个例子,演示了从麦克风采集音频、保存到文件、再读取播放的完整流程。
完整流程:采集 → 保存 → 读取 → 播放
import pyaudio
import soundfile as sf
import numpy as np
import time
# 参数设置
FORMAT = pyaudio.paInt16 # 16位整数
CHANNELS = 1 # 单声道
RATE = 44100 # 采样率
CHUNK = 1024 # 每次读取的帧数
RECORD_SECONDS = 3 # 录制时长
# 初始化PyAudio
p = pyaudio.PyAudio()
# 打开输入流
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK)
print("开始录制...")
frames = []
# 采集数据
for _ in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
frames.append(data)
print("录制结束")
# 关闭流
stream.stop_stream()
stream.close()
p.terminate()
# 将字节数据转为NumPy数组
audio_data = np.frombuffer(b''.join(frames), dtype=np.int16)
# 保存为WAV文件
sf.write('recorded_audio.wav', audio_data, RATE)
print(f"文件已保存,长度: {len(audio_data)/RATE:.2f}秒")
# 读取并验证
data_check, fs_check = sf.read('recorded_audio.wav')
print(f"读取验证: 采样率={fs_check}, 数据长度={len(data_check)}")
这段代码虽然不长,但涵盖了音频I/O的四个核心操作:打开设备、采集数据、保存文件、读取验证。后面的项目里,你会反复用到这个模式。
避坑指南:我曾经在采集时忘记把字节数据转成NumPy数组,结果后面做FFT分析时一直报错。记住,stream.read()返回的是字节串,必须用np.frombuffer()转成数值数组才能做信号处理。
2.5 本章知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作一张地图,后面遇到问题就知道该查哪个部分。
这张图展示了从虚拟环境到底层库、再到音频I/O操作、最后到实战应用的完整链路。你可以看到,虚拟环境是地基,NumPy/SciPy是计算核心,SoundFile和PyAudio是跟音频打交道的接口。后面的所有章节,都会建立在这个基础之上。
2.6 常见问题与避坑指南
- PyAudio安装失败:Windows用户优先下载whl文件,别在pip上死磕。我曾经在conda环境里装PyAudio,结果发现conda-forge版本更稳定。
- 音频设备找不到:先跑设备枚举代码,确认设备编号。麦克风阵列通常有多个输入通道,选设备时注意看
maxInputChannels。 - 采样率不匹配:采集和播放的采样率必须一致。我遇到过采集用44100,播放用48000,结果声音变调了。
- 数据类型搞混:SoundFile默认返回float64,PyAudio采集返回int16。做FFT分析前,记得统一数据类型。
重要提醒:虚拟环境一定要在项目一开始就建好。中途换环境,所有库都得重装,依赖关系还可能出问题。我有个同事,项目做了一半才想起来建虚拟环境,结果迁移时花了整整一天。