4. 音频基础:声音的数字化、采样率与位深度、音频文件格式(WAV、MP3、FLAC)、Python音频读写操作
好,咱们进入第四讲。这一章是硬核基础,说白了就是搞清楚「声音在计算机里到底长什么样」。我当年刚接触语音交互时,以为音频就是播放个MP3那么简单,结果一上手就踩了无数坑。嗯,今天咱们把这些坑一个个填平。
4.1 声音的数字化:从模拟到数字
声音本质上是空气的振动。麦克风把这种振动转成电信号,也就是连续的模拟波形。但计算机只认0和1,所以必须把连续的波形「切」成离散的点。这个过程就叫数字化。
数字化包含两步:采样和量化。采样是每隔一段时间取一个值,量化是把取到的值用整数表示。你想想看,就像用尺子量一段曲线,尺子越密、刻度越细,量出来的结果就越接近原样。
核心公式:
数字音频 = 采样(时间离散化) + 量化(幅度离散化)
我在项目中遇到过一个问题:用低端麦克风录制的语音,背景噪声特别大。后来发现是量化精度不够,导致微弱信号被「吃掉」了。所以,数字化这一步的质量,直接决定了后续所有处理的上限。
4.2 采样率与位深度
这两个参数是音频数字化的灵魂。咱们一个一个说。
4.2.1 采样率(Sample Rate)
采样率就是每秒采样的次数,单位是Hz。常见的采样率有:
| 采样率 | 常见用途 | 我的评价 |
|---|---|---|
| 8 kHz | 电话语音 | 够用,但音质像「罐头」 |
| 16 kHz | 语音识别、语音交互 | 我个人最常用,性价比之王 |
| 44.1 kHz | CD音质音乐 | 音乐制作标配 |
| 48 kHz | 影视、专业音频 | 视频项目里常见 |
这里有个关键定理——奈奎斯特采样定理:采样率必须大于信号最高频率的两倍,才能无失真地还原。说白了,人耳能听到20 kHz,所以CD用44.1 kHz(略大于40 kHz)。语音交互用16 kHz就够了,因为人声主要能量在4 kHz以下。
我的经验:做语音识别项目时,我建议直接用16 kHz采样率。8 kHz虽然省带宽,但识别率会明显下降。别问我怎么知道的——我曾经为了省存储空间用8 kHz,结果模型准确率掉了5个点,得不偿失。
4.2.2 位深度(Bit Depth)
位深度决定了每个采样点的精度。常见的有:
- 8 bit:256个等级,音质粗糙,有量化噪声
- 16 bit:65536个等级,CD标准,动态范围96 dB
- 24 bit:1677万个等级,专业录音室标准,动态范围144 dB
- 32 bit float:浮点表示,处理时不会溢出,常用于后期
为什么会这样?位深度越大,能记录的声音细节就越丰富。16 bit已经能覆盖人耳从最轻到最响的听觉范围。但如果你做音频处理(比如降噪、增益),建议用32 bit float,因为中间计算不会丢失精度。
避坑指南:我曾经在批量处理音频时,直接把32 bit float保存成16 bit,结果所有音频都出现了「削波」失真。后来才意识到,浮点转定点时必须做归一化处理。切记:保存前检查动态范围。
4.3 音频文件格式:WAV、MP3、FLAC
格式这东西,说白了就是「怎么把数字音频存成文件」。不同格式有不同的压缩方式和适用场景。
4.3.1 WAV(无损、未压缩)
WAV是微软和IBM联合开发的格式。它直接把采样数据存下来,不压缩。优点是质量无损,读写快;缺点是文件大。一分钟16 bit/44.1 kHz的立体声WAV,大约10 MB。
我个人的习惯是:原始录音和中间处理结果都用WAV。因为每次压缩都会损失信息,等最终定稿再转成其他格式。
4.3.2 MP3(有损压缩)
MP3利用心理声学模型,去掉人耳不敏感的频率成分。压缩比能达到10:1以上。128 kbps的MP3听起来和CD差别不大,但文件小得多。
但注意:MP3不适合做语音识别。我在项目中测试过,同一段语音,WAV格式的识别准确率比MP3高3-5%。因为压缩过程会丢失高频细节,而这些细节对某些声学模型很重要。
4.3.3 FLAC(无损压缩)
FLAC是自由无损音频编解码器。它像zip一样压缩WAV,但解压后和原数据完全一致。压缩率大约2:1到3:1。既节省空间,又保留全部信息。
如果你做音频存档或分发,FLAC是很好的选择。我自己的音乐库全部是FLAC格式,既省硬盘又不怕质量损失。
| 格式 | 压缩方式 | 质量 | 文件大小 | 推荐场景 |
|---|---|---|---|---|
| WAV | 无压缩 | 无损 | 大 | 原始录音、中间处理 |
| MP3 | 有损压缩 | 有损 | 小 | 音乐播放、网络传输 |
| FLAC | 无损压缩 | 无损 | 中等 | 音频存档、高质量分发 |
4.4 Python音频读写操作
理论说完了,咱们上手实操。Python里读写音频,我推荐两个库:wave(标准库,轻量)和 soundfile(第三方,功能强)。
4.4.1 使用 wave 库读写 WAV
wave 是Python自带的,不用安装。适合处理简单的WAV文件。
import wave
import numpy as np
# 读取WAV文件
with wave.open('input.wav', 'rb') as wf:
# 获取参数
n_channels = wf.getnchannels() # 声道数
sampwidth = wf.getsampwidth() # 采样字节数(1=8bit, 2=16bit)
framerate = wf.getframerate() # 采样率
n_frames = wf.getnframes() # 总帧数
# 读取音频数据(bytes)
data_bytes = wf.readframes(n_frames)
# 转成numpy数组(16bit时用int16)
data = np.frombuffer(data_bytes, dtype=np.int16)
data = data.reshape(-1, n_channels) # 多声道时reshape
print(f'采样率: {framerate} Hz')
print(f'声道数: {n_channels}')
print(f'时长: {n_frames / framerate:.2f} 秒')
# 写入WAV文件
with wave.open('output.wav', 'wb') as wf:
wf.setnchannels(1) # 单声道
wf.setsampwidth(2) # 16 bit = 2字节
wf.setframerate(16000) # 16 kHz
# 生成一个1秒的正弦波(440 Hz)
t = np.linspace(0, 1, 16000, endpoint=False)
sine_wave = np.sin(2 * np.pi * 440 * t) * 32767 # 16bit最大值
sine_wave = sine_wave.astype(np.int16)
wf.writeframes(sine_wave.tobytes())
小技巧:用 numpy.frombuffer 转数据时,dtype一定要和采样字节数匹配。16 bit用 np.int16,24 bit用 np.int32 然后右移8位。我曾经因为类型不匹配,读出来的数据全是噪声,排查了半天。
4.4.2 使用 soundfile 库读写多种格式
soundfile 支持WAV、FLAC、OGG等多种格式,而且直接返回numpy数组,非常方便。先安装:
pip install soundfile
然后看代码:
import soundfile as sf
# 读取音频(自动识别格式)
data, samplerate = sf.read('input.flac')
# data 是 numpy 数组,shape = (帧数, 声道数)
# 值范围:-1.0 到 1.0(浮点)
print(f'采样率: {samplerate}')
print(f'数据形状: {data.shape}')
print(f'数据类型: {data.dtype}')
# 写入音频
sf.write('output.wav', data, samplerate, subtype='PCM_16')
# subtype 可选:'PCM_16', 'PCM_24', 'FLOAT' 等
# 写入FLAC
sf.write('output.flac', data, samplerate, subtype='PCM_16')
注意 soundfile 读出来的数据是浮点型(-1.0 ~ 1.0),而 wave 读出来是整型。这个区别很重要,做信号处理时建议用浮点,避免溢出。
4.4.3 实战:批量转换音频格式
最后分享一个我常用的脚本,批量把MP3转成16 kHz/16 bit的WAV:
import os
import soundfile as sf
import librosa # 需要安装:pip install librosa
input_dir = 'mp3_files/'
output_dir = 'wav_files/'
os.makedirs(output_dir, exist_ok=True)
for filename in os.listdir(input_dir):
if not filename.endswith('.mp3'):
continue
filepath = os.path.join(input_dir, filename)
# 用librosa读取MP3(自动解码)
data, sr = librosa.load(filepath, sr=16000, mono=True)
# 保存为16 kHz/16 bit WAV
out_path = os.path.join(output_dir, filename.replace('.mp3', '.wav'))
sf.write(out_path, data, 16000, subtype='PCM_16')
print(f'已转换: {filename} -> {out_path}')
注意:librosa.load 默认会做两件事:重采样到22.05 kHz,并转成单声道。如果你需要原始采样率,记得传 sr=None。我刚开始用的时候没注意,所有音频都被重采样了,导致后续分析全错。
知识体系总览
下面这张图总结了本章的核心逻辑,从模拟声音到数字文件的全流程:
这张图把整个流程串起来了:模拟声音经过采样和量化变成数字音频,然后以不同格式存成文件,最后用Python工具进行读写和处理。每个环节的参数选择都会影响最终效果。