🎬 第十章 SWResample音频重采样理论及案例实战
📄 一句话概括

PCM 就是「一秒采多少点、每点多大」,一点不压缩。 裸 PCM 文件里没有头,参数全靠外面给——给错就全错。

📝 本节和前面几节的关系

10.1.1 讲了三个参数,这一节讲不压缩的存法。 所有压缩格式(AAC、MP3)解码出来都是 PCM,它是声音的「原样」。

一、PCM 是什么

脉冲编码调制,说白了就是「按时间顺序记下每个点的振幅」。 特点 说明


不压缩 一个点就是几个字节,没有编码 没有头 文件里全是样本,没有参数 能直接播 硬件认它,不用解码

「没有头」这一条最要命:打开的 PCM 文件像一段没标签的磁带, 你必须先知道它是 8000 还是 44100,才能算它有多长。 WAV 就是在 PCM 前面加了几十字节的头,头里写着这三个参数。 所以 WAV 能自己说明自己,裸 PCM 不能。

二、自己写一个裸 PCM

写盘的时候只有样本,没有别的东西。 项 值


样本数 8000 格式 s16(每样本 2 字节) 文件字节 16000 文件里有没有头 没有,一个字节都没有

8000 × 2 = 16000,一个不多一个不少。裸 PCM 的账能拿计算器核对,这是它最大的好处。 也正因为它这么干净,调试音频时经常先把一段转成裸 PCM, 用字节数与样本数互相验算,比在容器里猜要快。

三、参数给错会怎样

样本数一个不差,时长却差好几倍。 做法 样本数 算出的时长


给对参数 8000 8000 1.0000 秒 不给参数(默认 44100) 8000 0.1814 秒

同样的 16000 个字节,按 8000 读是一秒,按 44100 读只有 0.18 秒。 PCM 的「正确」不在文件里,在你脑子里的参数。 这类错的表现是「声音变快」或「变慢」, 而不是报错——它很像磁带被快放,专业人士管这叫「采样率不匹配」。

四、裸 PCM 的命名约定

没有头,就只能把参数写进文件名。 文件名 读出来是什么


xxx.s16le.8k.mono.pcm 16 位小端、8k、单声道 xxx.f32le.48k.stereo.pcm 32 位浮点、48k、立体声

这种命名不是哪个公司的规定,是社区里自然形成的习惯。 看到陌生的 pcm 文件,先找名字里的这三个数。 le 是「小端」:多字节整数在内存里的排法。 x86 与 ARM 都是小端,所以大部分场合不用管,但跨平台传文件时要留意。

五、为什么还要用它

压缩格式满天飞,裸 PCM 反而没被淘汰,原因有三条。 场合 为什么用它


设备对接 硬件只认裸样本 算法处理 不用解码,直接算 调试与验收 字节数能手算核对

这三条都是「中间态」的场合:PCM 不做最终交付, 它是所有音频处理的通用货币——解码器给你 PCM,滤镜吃 PCM,编码器吃 PCM,设备播放的还是 PCM。 所以「懂 PCM」等于「懂音频的底层」,压缩格式只是外面包的一层壳。

六、动手 Demo:裸 PCM 没有头,参数全靠外面给

写一个 8000 样本的 s16 文件,再用「给对参数」与「不给参数」各读一遍。

import numpy as np
import av

arr = (np.arange(8000, dtype=np.int32) % 1000).astype(np.int16)
open("test.pcm", "wb").write(arr.tobytes())

# 参数说清楚
c = av.open("test.pcm", format="s16le",
             options={"ar": "8000", "ac": "1"})
st = c.streams.audio[0]
n = sum(f.samples for f in c.decode(st))
print(st.codec_context.name, st.codec_context.sample_rate, n)
c.close()

# 参数不说,让它自己猜
c = av.open("test.pcm", format="s16le")
st = c.streams.audio[0]
n2 = sum(f.samples for f in c.decode(st))
print(st.codec_context.sample_rate, n2,
      n2 / float(st.codec_context.sample_rate))
c.close()

实跑输出:

--------------------------------------------------------------------
裸 PCM 没有头,参数全靠外面给
--------------------------------------------------------------------

**① 写一个纯样本的二进制文件**

  项                   值
  ---------------------------------------------
  样本数               8000
  每个样本几个字节     2(s16)
  文件字节             16000
  文件里有没有头       没有,一个字节都没有

**8000 × 2 = 16000 字节,一个不多一个不少。** 文件里除了样本什么都没有——没有采样率、没有声道数、
没有格式标记。

**② 读回来,把参数说清楚**

  项             值
  ----------------------------
  编码           pcm_s16le
  采样率         8000
  布局           mono
  读回样本       8000
  算出的时长     1.0000 秒

**③ 参数说错了会怎样**

  做法                       样本数     算出的时长
  ----------------------------------------------------
  给对参数(8000)           8000       1.0000 秒
  不给参数(默认 44100)     8000       0.1814 秒

**样本数一个不差,时长却差了 5.5 倍。** 同样的字节,按 8000 读是一秒,按 44100 读只有 0.18 秒。
**PCM 的「正确」不在文件里,在你知道参数。**

**这就是为什么容器那么重要**:WAV、MP4 都有头,头里写着这三个参数;
**而裸 PCM 的文件名 `xxx.s16le.8k.mono` 就是它的头**,全靠约定。

不给参数时它按 44100 单声道猜,同样 8000 个样本,时长从 1 秒变 0.18 秒。 样本数不会变,变的只是你对「一个样本多长」的理解。

30 秒实验

  1. 把写入的样本数改成 16000,重跑,看两个时长各是多少。
  2. 用 format="s16be" 打开同一个文件,听/看数值变成什么样(提示:字节序反了)。
  3. 把这个 pcm 用 format="u8" 打开,看样本数变成几(提示:一字节还是两字节)。

常见误区

  1. 以为 PCM 文件里有采样率信息。 一个字节都没有。
  2. 拿默认参数去读。 默认 44100 单声道,读出来时长全错。
  3. 以为 PCM 是「过时的格式」。 它是所有音频处理的中间货币。
自测三问(点开对答案) 1. 裸 PCM 文件里有什么? —— 只有样本,没有头、没有参数。 2. 8000 个 s16 样本写盘有多大? —— 16000 字节,样本数乘每样本字节数。 3. 参数给错的表现是什么? —— 样本数不变、时长不对,听起来像快放或慢放。

相关笔记