音频文件的读写与基础操作:WAV/MP3/FLAC格式解析,采样率、位深、通道数的获取与转换
音频文件读写,是声学特征提取的第一步。这一步要是搞砸了,后面所有算法都是白搭。我见过不少同学,拿着 librosa 一加载就开始提特征,结果采样率对不上、位深不对、甚至左右声道混在一起——嗯,出来的特征全是错的。
今天咱们就把这件事彻底讲清楚。从文件格式到核心参数,从读取到转换,一步到位。
3.1 三大音频格式:WAV / MP3 / FLAC
你想想看,音频文件本质上就是一段数字化的声波。但不同的格式,存储方式完全不同。我个人的习惯是:能用 WAV 就用 WAV,因为它最纯粹,没有压缩带来的信息损失。
| 格式 | 压缩方式 | 是否无损 | 典型场景 |
|---|---|---|---|
| WAV | 无压缩 / PCM | ✅ 无损 | 科研、录音、中间处理 |
| FLAC | 无损压缩 | ✅ 无损 | 存档、高保真传输 |
| MP3 | 有损压缩 | ❌ 有损 | 流媒体、存储受限 |
3.2 三个核心参数:采样率、位深、通道数
这三个参数,决定了音频文件的「质量」和「大小」。说白了,它们就是数字音频的 DNA。
采样率(Sample Rate)
采样率表示每秒采集多少个样本点。单位是 Hz。常见的采样率有:
- 8000 Hz:电话语音,窄带
- 16000 Hz:语音识别标准,宽带
- 44100 Hz:CD 音质,音乐常用
- 48000 Hz:影视、专业录音
为什么语音识别常用 16kHz?因为人声的主要能量集中在 8kHz 以下,16kHz 采样率刚好能覆盖 8kHz 带宽,再高就是浪费存储和计算。我在做远场语音项目时,试过用 48kHz 采样,结果模型训练时间翻了一倍,效果几乎没变——后来就老老实实降采样到 16kHz 了。
位深(Bit Depth)
位深决定了每个样本点的动态范围。常见的有:
- 8-bit:256 级量化,噪声大,基本不用
- 16-bit:65536 级,CD 标准,语音够用
- 24-bit:16777216 级,录音棚标准
- 32-bit float:浮点表示,处理时常用
通道数(Channels)
- Mono(单声道):1 个通道,语音常用
- Stereo(立体声):2 个通道,音乐常用
- 多声道:5.1、7.1 等,影视环绕声
做声学特征提取时,我建议先把多声道混成单声道。除非你专门做空间音频或波束形成,否则多声道只会增加计算量,不会提升特征质量。
3.3 用 Python 读写音频文件
Python 里读写音频的库很多,我常用的有三个:soundfile、librosa、scipy.io.wavfile。它们各有优劣,我列个表给你看:
| 库 | 支持格式 | 返回数据类型 | 特点 |
|---|---|---|---|
| soundfile | WAV, FLAC, OGG | numpy array (float) | 速度快,支持 24-bit |
| librosa | WAV, MP3, FLAC | numpy array (float) | 自带重采样,功能全 |
| scipy.io.wavfile | WAV 仅 | numpy array (int) | 轻量,但只支持 WAV |
下面我给出一个完整的读取示例,包含参数获取和格式转换:
import soundfile as sf
import librosa
import numpy as np
# 方法一:用 soundfile 读取 WAV/FLAC
data, sr = sf.read('audio.wav')
print(f"采样率: {sr} Hz")
print(f"数据形状: {data.shape}") # (样本数,) 或 (样本数, 通道数)
print(f"数据类型: {data.dtype}") # float64 或 float32
# 方法二:用 librosa 读取(支持 MP3)
data_lib, sr_lib = librosa.load('audio.mp3', sr=None)
# sr=None 表示保持原始采样率
# 默认 sr=22050 会重采样
# 获取位深信息(soundfile 可以直接获取)
info = sf.info('audio.wav')
print(f"位深: {info.subtype}") # 比如 'PCM_16', 'PCM_24', 'FLOAT'
print(f"通道数: {info.channels}")
print(f"时长: {info.duration:.2f} 秒")
sf.info() 获取元数据,不需要把整个文件加载到内存。这在处理大批量文件时非常有用,可以先筛选出符合参数要求的文件,再逐个读取。
3.4 采样率转换与通道合并
实际项目中,你拿到的音频文件往往参数不统一。有的 16kHz,有的 44.1kHz;有的单声道,有的立体声。这时候就需要做统一转换。
采样率转换
import librosa
# 重采样到 16000 Hz
data_16k = librosa.resample(data, orig_sr=sr, target_sr=16000)
# 或者用 soundfile 配合 scipy
from scipy import signal
data_resampled = signal.resample(data, int(len(data) * 16000 / sr))
我个人更推荐 librosa.resample,因为它内部用了高质量的 sinc 插值,比 scipy.signal.resample 的 FFT 方法更保真。不过要注意,librosa 的 resample 在边界处会有少量延迟,做对齐时要小心。
多声道转单声道
# 如果 data 是二维数组 (样本数, 通道数)
if data.ndim > 1:
# 方法一:直接取平均
mono = np.mean(data, axis=1)
# 方法二:只取左声道
mono_left = data[:, 0]
# 方法三:加权混合(适合立体声转单声道)
mono_weighted = 0.5 * data[:, 0] + 0.5 * data[:, 1]
3.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以把它当作一个「音频读取检查清单」:
3.6 实战避坑指南
最后,我把自己这些年踩过的坑总结一下,希望能帮你少走弯路:
- 采样率不一致:不同来源的音频,采样率可能不同。一定要在特征提取前统一,否则 MFCC 的频带划分会错位。
- 位深转换丢失信息:从 24-bit 转 16-bit 时,直接截断会引入量化噪声。建议先做抖动处理(dithering),或者直接用 32-bit float 做中间格式。
- MP3 的隐式重采样:librosa 加载 MP3 时,默认会重采样到 22050 Hz。如果你没指定
sr=None,拿到的数据可能不是你想要的。 - 大文件内存溢出:一个 48kHz、24-bit、立体声、10 分钟的 WAV 文件,大约 1.7 GB。一次性读入内存可能会崩。建议用
soundfile.blocks()分块读取。
好了,音频文件的读写与基础操作就讲到这里。记住一句话:数据质量决定特征质量,特征质量决定模型上限。把这一步做扎实了,后面的工作才能事半功倍。