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

十二个样本格式:六种精度乘两种存法。 带 p 的是 planar,不带的是 packed——这个后缀到处通用。

📝 本节和前面几节的关系

10.1 讲的是「PCM 是什么」,这一节讲 FFmpeg 给它起了哪些名字。 10.2.2 专门讲两种存法的差别。

一、十二个名字

六种精度,两种存法,乘起来十二个。 名字 位数 每样本字节 存法


u8 8 1 packed s16 16 2 packed s32/flt 32 4 packed s64/dbl 64 8 packed 上面五个的 p 版 同左 同左 planar

十二个名字全都能构造出来,一个不缺。 u 是无符号整数,s 是有符号整数,flt/dbl 是浮点。 u8 是唯一常见的无符号格式,多用于老式声卡与某些 WAV。 它有 128 的偏置:静音不是 0,是 128。这一条经常被人忽略。

二、属性直接问对象

别背表,问对象。 属性 含义


bits 每个样本多少位 bytes 每个样本多少字节 is_packed 是不是交错存 is_planar 是不是分平面存

s16 与 s16p 的 bits 与 bytes 完全一样,只差存法。 所以「换格式」有两种完全不同的意思:换精度,或换存法。 这两种换法在代码里长得一样(都是传一个字符串给重采样器),但代价完全不同:换精度要舍入, 换存法只是搬家。

三、解码器默认给哪一种

不是 s16,这一点很多人猜错。 来源 解码后的格式


AAC fltp(32 位浮点、planar) MP3 fltp 或 s16p WAV(s16) s16(packed)

AAC 解出来是 fltp。 因为压缩算法内部就是浮点算的, 解码器直接把中间结果给你,省一次转换。 所以「拿到帧先看格式」不是小心眼,是必需动作。 默认按 s16 去读 fltp 的数据,读出来全是垃圾。

四、名字写错会怎样

报错信息还算清楚,但容易看漏。 写法 结果


s16 能构造 s16le 构造不出来,那是「编码名」不是「格式名」 S16 大小写敏感,失败

s16 是「样本格式」,pcm_s16le 是「编解码器名」, 两套命名体系不能混用。 装裸 PCM 时用后者,处理帧时用前者。 这条区分在第六章也出现过:格式名(容器/设备)与编解码器名是两个命名空间。 FFmpeg 里到处都有这种「两层名字」,混了就报「找不到」。

五、怎么在前两者之间选

一句话:中间处理用浮点,存盘交付用整数。 场合 选什么 为什么


内部处理 fltp 不丢精度,算得快 存盘交付 s16 体积小一半,兼容性好 送硬件 看设备要求 多数要 s16 packed

浮点的好处是「怎么算都不溢出」:整数相乘很容易超出范围, 要时时想着限幅;浮点只用管 −1.0 到 1.0。 代价是体积翻倍、转回整数时要舍入。 所以「进来就转浮点、出去再转整数」是很常见的写法。

六、动手 Demo:FFmpeg 认得哪些样本格式

把十二个名字逐个构造出来,看位数、字节数与存法;再查真实音轨用的是哪个。

import av

names = ["u8", "s16", "s32", "flt", "dbl", "s64",
         "u8p", "s16p", "s32p", "fltp", "dblp", "s64p"]
for n in names:
    f = av.audio.format.AudioFormat(n)
    print(n, f.bits, f.bytes, f.is_packed, f.is_planar)

c = av.open("tiny.mp4")
print(c.streams.audio[0].codec_context.format.name)
c.close()

# 名字混用会怎样:s16 是样本格式,pcm_s16le 是编解码器名
try:
    av.audio.format.AudioFormat("s16le")
except Exception as e:
    print("s16le 构造失败", type(e).__name__)

实跑输出:

--------------------------------------------------------------------
FFmpeg 认得哪些样本格式
--------------------------------------------------------------------

**① 六种「精度」乘两种「存法」,一共十二个名字**

  名字     位数     每样本字节     存法
  -------------------------------------------
  u8       8        1              packed
  s16      16       2              packed
  s32      32       4              packed
  flt      32       4              packed
  dbl      64       8              packed
  s64      64       8              packed
  u8p      8        1              planar
  s16p     16       2              planar
  s32p     32       4              planar
  fltp     32       4              planar
  dblp     64       8              planar
  s64p     64       8              planar

**十二个名字全都能构造出来,一个不缺。** 带 `p` 后缀的是 planar,不带的是 packed。
**这个后缀写法在 FFmpeg 里到处通用。**

**② 属性直接问对象,不用背**

  属性          s16       s16p
  ---------------------------------
  bits          16        16
  bytes         2         2
  is_packed     True      False
  is_planar     False     True

**`s16` 与 `s16p` 的位数、字节数完全一样,只差存法。**
**所以「换格式」有两种完全不同的意思**:换精度(s16→flt),或换存法(s16→s16p)。

**③ 真实音轨用的是哪一种**

  项                        值
  ---------------------------------------
  编码                      aac
  解码后的样本格式          fltp
  是多少位                  32 位浮点
  是 packed 还是 planar     planar

**AAC 解出来是 `fltp`(32 位浮点、planar)。** 记住这个事实:**解码器给你的默认不是 s16**,
想用整数得自己转。

十二个都能构造,带 p 的是 planar,is_packed 与 is_planar 直接读。 AAC 解出来是 fltp,不是 s16——默认按整数读会读错。

30 秒实验

  1. 把 s16le 换成 pcm_s16le 再试一次,看能不能构造(提示:还是不行,它是编解码器名)。
  2. 打印 av.audio.format.AudioFormat("u8").bits,想一想静音应该是多少。
  3. 用 dbl 建一帧再 to_ndarray(),看数组的 dtype 是什么。

常见误区

  1. 把 s16 与 pcm_s16le 当成同一个名字。 一个是样本格式,一个是编解码器。
  2. 以为解码出来一定是 s16。 AAC 给的是 fltp。
  3. 忽略 u8 的 128 偏置。 静音不是 0,按有符号读会读到噪声。
自测三问(点开对答案) 1. 样本格式名字里带 `p` 是什么意思? —— planar,按声道分平面存,对应的是 packed。 2. `s16` 与 `pcm_s16le` 有什么区别? —— 前者是样本格式名,后者是编解码器名,两套命名空间。 3. AAC 解码出来的样本格式是什么? —— fltp,32 位浮点、planar。

相关笔记