3. 音频信号特性
好,咱们进入正题。这一章,我带你亲手摸一摸音频数据长什么样。说白了,就是让你学会用 Python 把音频文件“拆开”,看看里面到底是什么东西。
我个人习惯把音频处理比作“听诊”。你总得先学会怎么拿听诊器,怎么听心跳,才能去诊断问题。Librosa 就是咱们的听诊器。
3.1 Librosa 库的安装与基础环境
安装这事,其实没啥好说的。但我还是想提醒一句:千万别用系统自带的 Python 环境。我刚开始学的时候,直接在系统 Python 里 pip install,结果把系统依赖搞崩了,重装了两次系统……血的教训。
建议你先建一个虚拟环境:
# 创建虚拟环境
python -m venv audio_dsp_env
# 激活(Windows)
audio_dsp_env\Scripts\activate
# 激活(Mac/Linux)
source audio_dsp_env/bin/activate
# 安装核心库
pip install librosa
pip install numpy matplotlib soundfile
装好之后,验证一下:
import librosa
print(librosa.__version__)
# 输出类似:0.10.1
能打印出版本号,说明环境通了。嗯,这一步很简单,但别跳过。
3.2 读取与写入音频文件
音频文件读取,是后续所有操作的基础。Librosa 的 load 函数,干的就是这事。
3.2.1 读取音频文件
import librosa
# 读取音频文件
# 返回两个东西:音频时间序列 y,采样率 sr
y, sr = librosa.load('example.wav', sr=22050)
print(f"音频时长: {len(y)/sr:.2f} 秒")
print(f"采样率: {sr} Hz")
print(f"样本点数: {len(y)}")
这里有个关键参数 sr。默认是 22050 Hz,也就是 Librosa 会把所有音频重采样到这个采样率。为什么是 22050?因为人耳能听到的频率范围大约是 20 Hz ~ 20 kHz,根据奈奎斯特定理,采样率至少要是最高频率的两倍。22050 Hz 刚好能覆盖 20 kHz,而且文件大小适中。
sr=None。我曾经在处理 48 kHz 录音时忘了这个参数,结果分析出来的频谱全偏了,排查了半天才发现是重采样搞的鬼。
3.2.2 写入音频文件
处理完音频,有时候需要保存结果。Librosa 本身不直接提供写入功能,但可以用 soundfile 库:
import soundfile as sf
# 写入 WAV 文件
sf.write('output.wav', y, sr)
# 写入 FLAC 文件(无损压缩)
sf.write('output.flac', y, sr)
写入时要注意数据类型。默认是 float64,但很多播放器只认 int16。你可以这样转:
import numpy as np
# 转成 int16
y_int16 = (y * 32767).astype(np.int16)
sf.write('output_int16.wav', y_int16, sr)
3.3 音频可视化:波形图与频谱图
光看数字没感觉。你得把音频画出来,一眼就能看出门道。
3.3.1 波形图
波形图就是音频信号的“心电图”。横轴是时间,纵轴是振幅。
import matplotlib.pyplot as plt
import librosa.display
# 读取音频
y, sr = librosa.load('example.wav')
# 绘制波形图
plt.figure(figsize=(12, 4))
librosa.display.waveshow(y, sr=sr)
plt.title('波形图')
plt.xlabel('时间 (秒)')
plt.ylabel('振幅')
plt.tight_layout()
plt.show()
波形图能告诉你什么?
- 静音段:振幅接近 0 的区域
- 瞬态:振幅突然变大的尖峰,比如鼓点、拍手声
- 包络:整体振幅的起伏,能看出声音的“响度轮廓”
我在做语音端点检测时,就是靠波形图先肉眼观察静音段的位置,再写算法去自动检测。说白了,可视化是调试的“眼睛”。
3.3.2 频谱图
波形图只告诉你“响不响”,但没告诉你“是什么频率在响”。频谱图(Spectrogram)补上了这个缺口。
# 计算频谱图
D = librosa.amplitude_to_db(
np.abs(librosa.stft(y)),
ref=np.max
)
# 绘制频谱图
plt.figure(figsize=(12, 6))
librosa.display.specshow(D, sr=sr, x_axis='time', y_axis='hz')
plt.colorbar(format='%+2.0f dB')
plt.title('频谱图')
plt.xlabel('时间 (秒)')
plt.ylabel('频率 (Hz)')
plt.tight_layout()
plt.show()
频谱图里,横轴还是时间,纵轴变成了频率。颜色越亮,表示该频率的能量越强。
你想想看,一段钢琴声和一段吉他声,波形图可能看起来差不多,但频谱图绝对不一样。钢琴的泛音更丰富,频谱上能看到很多等间距的亮线。
stft 函数默认帧长 2048 点,帧移 512 点。这些参数会影响频谱图的时间分辨率和频率分辨率——帧越长,频率分辨率越高,但时间分辨率越低。这是个 trade-off。
3.4 基本特征提取
可视化是给人看的。但机器看不懂图,它需要数字特征。这一节,我带你提取两个最基础的特征:过零率和短时能量。
3.4.1 过零率(Zero Crossing Rate, ZCR)
过零率,说白了就是单位时间内,音频信号穿过零轴的次数。计算公式很简单:
ZCR = (1 / (N-1)) * Σ |sign(x[n]) - sign(x[n-1])|
其中 sign 是符号函数,x[n] 是第 n 个样本点。
Librosa 一行搞定:
# 计算过零率
zcr = librosa.feature.zero_crossing_rate(y)
print(f"过零率形状: {zcr.shape}")
print(f"平均过零率: {zcr.mean():.4f}")
过零率有什么用?
- 区分有声/无声:无声段的过零率通常很低(接近 0),有声段会高一些
- 区分浊音/清音:浊音(元音)过零率低,清音(摩擦音如 /s/)过零率高
- 音乐类型识别:金属乐过零率通常比古典乐高
我记得有一次做语音活动检测(VAD),单纯用能量阈值效果很差,背景噪声一高就误判。后来加上过零率做辅助判断,准确率直接提升了 15%。
3.4.2 短时能量(Short-Time Energy, STE)
短时能量衡量的是某一帧内信号的能量大小。公式:
E = Σ (x[n])²
Librosa 没有直接的短时能量函数,但我们可以自己算:
import numpy as np
# 分帧
frame_length = 2048
hop_length = 512
# 计算每帧能量
energy = np.array([
np.sum(np.square(y[i:i+frame_length]))
for i in range(0, len(y) - frame_length, hop_length)
])
# 或者用 Librosa 的 rms(均方根)来算
rms = librosa.feature.rms(y=y, frame_length=frame_length, hop_length=hop_length)
# rms 是均方根,能量 = rms² * frame_length
energy_from_rms = (rms[0] ** 2) * frame_length
print(f"短时能量形状: {energy.shape}")
print(f"最大能量: {energy.max():.4f}")
短时能量的应用场景:
- 端点检测:能量突然上升的地方,往往是语音的开始
- 节拍检测:音乐中鼓点的能量峰值很明显
- 动态范围压缩:根据能量调整增益
3.5 本章知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个“地图”,随时回来对照。
3.6 本章小结
这一章,咱们干了四件事:
- 装了 Librosa——记住用虚拟环境,别像我一样踩坑
- 读了写了音频文件——
load和write是基本功,采样率参数要留心 - 画了波形图和频谱图——波形图看“响不响”,频谱图看“是什么频率在响”
- 提取了过零率和短时能量——两个最基础的特征,但非常实用
这些东西,说白了就是音频 DSP 的“加减乘除”。后面所有的算法——滤波、降噪、特征工程、深度学习——都建立在这些基础操作之上。你把这章的东西练熟了,后面的路就好走了。
嗯,去打开你的 Python 环境,找一段音频文件试试吧。动手才是最好的学习方式。