文章目录
- [1 多通道PCM字节流单通道PCM字节流【多通道转单通道】](#1 多通道PCM字节流单通道PCM字节流【多通道转单通道】)
- [2 其他采样率PCM转换为16KHZ PCM](#2 其他采样率PCM转换为16KHZ PCM)
- [3 采样精度(每个采样点用多少个字节存储)](#3 采样精度(每个采样点用多少个字节存储))
-
- [3.1 精度对应解释](#3.1 精度对应解释)
- [3.2 转换24-bit PCM -> 16-bit PCM](#3.2 转换24-bit PCM -> 16-bit PCM)
- [3.3 8-bit PCM -> 16-bit PCM](#3.3 8-bit PCM -> 16-bit PCM)
- [3.4 32-bit PCM -> 16-bit PCM](#3.4 32-bit PCM -> 16-bit PCM)
- [4 `main`函数](#4
main函数) - [5 总结](#5 总结)
1 多通道PCM字节流单通道PCM字节流【多通道转单通道】
python
def pcm_multichannel_to_mono(pcm_bytes, channels):
"""
pcm_bytes: 多通道 PCM byte 流(int16, interleaved)
channels: 声道数(如 2 / 4)
"""
# 输入的是PCM二进制字节流 int16格式 输出->int16数组
pcm = np.frombuffer(pcm_bytes, dtype=np.int16)
# 确保长度合法
assert pcm.size % channels == 0, "PCM length not divisible by channels"
# (frames, channels)
"""
输出示例:
[[100 200]
[101 201]
[102 202]]
"""
pcm = pcm.reshape(-1, channels)
# 混音:简单平均(防止溢出)
mono = pcm.mean(axis=1)
# clip + int16
# 将数据类型从float 转回 int6
mono = np.clip(mono, -32768, 32767).astype(np.int16) # numpy格式
return mono.tobytes()
numpy转PCM流:
mono.tobytes()
PCM流转numpy数组:
pcm = np.frombuffer(pcm_bytes, dtype=np.int16)
2 其他采样率PCM转换为16KHZ PCM
python
def pcm8To16(data, input_rate, output_rate):
pcm = np.frombuffer(data, dtype=np.int16) # PCM->numpy (int16)
pcm_16k = soxr.resample( # 8K numpy -> 16K numpy
pcm,
in_rate=input_rate,
out_rate=output_rate,
quality='HQ'
)
pcm_16k = np.clip(pcm_16k, -32768, 32767).astype(np.int16) #
pcm_bytes_16k = pcm_16k.tobytes()
return pcm_bytes_16k
3 采样精度(每个采样点用多少个字节存储)
3.1 精度对应解释
| 数据类型 | bit 数 | dtype | 范围 | 说明 |
|---|---|---|---|---|
| int16 | 16 | np.int16 | -32768 ~ 32767 | 常见的 PCM 音频,支持正负波形,左右对称,WAV 文件最常用 |
| uint8 | 8 | np.uint8 | 0 ~ 255 | 8-bit PCM,没有负数,中值 128 表示静音,0/255 表示最大幅度 |
| float32 | 32 | np.float32 | -1.0 ~ 1.0 | 归一化音频,AI/ASR/深度学习常用 |
各个采样精度之间的转换
查看音频的原生精度:
python
audio_path = r"asr_en.wav"
wf = wave.open(audio_path)
logger.warning(f"原始音频格式:"
f"{wf.getnchannels(),wf.getframerate(),wf.getsampwidth()}")
print(wf.getsampwidth())
这里的wf.getsampwidth()表示精度。
| sampwidth | PCM 位深 | 常见 dtype | 是否有坑 |
|---|---|---|---|
| 1 | 8-bit PCM | uint8 |
⚠️ 有偏置 |
| 2 | 16-bit PCM | int16 |
✅ 标准 |
| 3 | 24-bit PCM | ❌ | 🚨 大坑 |
| 4 | 32-bit PCM | int32 / float |
⚠️ |
3.2 转换24-bit PCM -> 16-bit PCM
python
# 24-bit PCM -> 16-bit PCM
def pcm24_to_int16(pcm_bytes, channels):
# 每个 sample 3 字节
pcm = np.frombuffer(pcm_bytes, dtype=np.uint8)
# 对齐
pcm = pcm[: (pcm.size // 3) * 3]
pcm = pcm.reshape(-1, 3)
# 小端拼接(WAV 默认 little-endian)
samples = (
pcm[:, 0].astype(np.int32)
| (pcm[:, 1].astype(np.int32) << 8)
| (pcm[:, 2].astype(np.int32) << 16)
)
# 符号扩展
samples = np.where(
samples & 0x800000,
samples | ~0xFFFFFF,
samples
)
# 24bit → 16bit(右移 8 位)
pcm_i16 = (samples >> 8).astype(np.int16)
return pcm_i16
3.3 8-bit PCM -> 16-bit PCM
python
def pcmu8_to_int16(pcm_bytes):
pcm_u8 = np.frombuffer(pcm_bytes, dtype=np.uint8)
# 去偏置 → 放大
pcm_i16 = (pcm_u8.astype(np.int16) - 128) << 8
return pcm_i16
3.4 32-bit PCM -> 16-bit PCM
python
def pcmint32_to_int16(pcm_bytes):
pcm_i32 = np.frombuffer(pcm_bytes, dtype=np.int32)
pcm_i16 = (pcm_i32 >> 16).astype(np.int16)
return pcm_i16
4 main函数
python
# -------------------------------------
# 保存文件
# -------------------------------------
out_wav = wave.open("output_16k_mono.wav", "wb")
out_wav.setnchannels(1) # 单声道
out_wav.setsampwidth(2) # int16 = 2 bytes
out_wav.setframerate(16000) # 16 kHz
# -------------------------------------
# 读取文件,通道,采样率,PCM位深
# -------------------------------------
audio_path = r"E:\1_ALL_Dataste\音频数据集\本地中文语音训练\印度英文报告-1.wav"
wf = wave.open(audio_path)
logger.warning(f"原始音频格式:"
f"{wf.getnchannels(),wf.getframerate(),wf.getsampwidth()}")
rawRate = wf.getframerate()
chunk = 960
while True:
data = wf.readframes(chunk) # (2, 44100, 2)
data_1channel = pcm_multichannel_to_mono(data, 2) # 转通道
data_16K = pcm8To16(data_1channel, rawRate, 16000) # 采样率
if not data_16K:
break
out_wav.writeframes(data_16K)
print(data_16K)
5 总结
从读取文件开始,音频通常以PCM二进制流,numpy或者base64的格式存在,音频的主要参数有三个:通道数(channel)、采样率(rate)、位深。
写于2026.2.4