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

packed 按时间排,planar 按声道排——字节数完全一样。 一个像按行读表格,一个像按列读表格。

📝 本节和前面几节的关系

10.2.1 讲了十二个名字,这一节讲那个 p 到底改变了什么。 分不清这两者,读音频数组时形状一定搞错。

一、同一个立体声,两种摆法

六个样本、立体声、s16,两种存法。 存法 名字 shape 平面数 字节


packed s16 (1, 12) 1 24 planar s16p (2, 6) 2 24

字节数一模一样,摆法完全不同。 6 个样本 × 2 声道 × 2 字节 = 24,两种存法都是 24。 所以「转 planar」一个字节都不会少,它只是把同样的数字换了个位置。

二、内存里怎么排

这一条想通了,后面全通。 存法 内存里怎么排


packed 左0 右0 左1 右1 左2 右2 …(左右交替) planar 左0 左1 左2 … | 右0 右1 右2 …(左右分块)

packed 是「按时间排」:先记这一刻的左与右,再记下一刻。 planar 是「按声道排」:先把左声道记完,再记右声道。 拿表格打比方:packed 是逐行读,planar 是逐列读。 同一张表,两种读法,数据一个不少。

三、用真实的数组看一遍

packed 展开是连续的 1 到 12,planar 是两个平面各一套。 存法 展开后的值


packed 1 2 3 4 5 6 7 8 9 10 11 12 planar 平面0 100 101 102 103 104 105 planar 平面1 200 201 202 203 204 205

packed 那一行里,奇数位是左、偶数位是右,所以分离要隔一个取一个。 planar 是两个独立数组,直接取下标就是声道。 这就是为什么 planar 上「分离声道」是零成本的。

四、两种存法互相转换

numpy 里两次 reshape 加一次转置。 方向 怎么做 结果 shape


packed→planar reshape 成 (n,2) 再转置 (2, n) planar→packed 转置后拉平 (1, 2n)

转置就是「行列互换」,数据本身不动,只是看法变了。所以这个转换几乎不花时间。 但真做项目别用 numpy 手工转: 重采样器转更稳,它连精度一起处理(比如 s16 packed 直接转 s16p planar,10.3.3 会做)。

五、为什么要发明 planar

因为按声道处理时,planar 对缓存友好得多。 处理 packed 的麻烦 planar 的好处


音量调节 要跨步访问 整块连续 单声道滤波 取一半丢一半 直接拿一块 声道混音 两路交错算 两路各自算完合并

跨步访问的意思是「隔一个读一个」,那样预取就白做了, 内存带宽浪费一半。 planar 里每个声道都是连续内存,一条流水线走到底。 这就是为什么内部处理都用 planar:不是为了省内存,是为了省带宽。

六、动手 Demo:Packed 与 Planar,同一份声音的两种摆法

造两帧一模一样的立体声,一帧 packed 一帧 planar,把数组打出来对比。

import av

def mk(fmt):
    f = av.AudioFrame(format=fmt, layout="stereo", samples=6)
    f.sample_rate = 8000
    return f

a = mk("s16")   # packed
b = mk("s16p")  # planar
print(a.to_ndarray().shape, len(a.planes), a.to_ndarray().nbytes)
print(b.to_ndarray().shape, len(b.planes), b.to_ndarray().nbytes)

# 互相转换:reshape + 转置
packed = a.to_ndarray()
planar = packed[0].reshape(-1, 2).T
print(planar.shape)
print(planar.T.reshape(1, -1).shape)

实跑输出:

--------------------------------------------------------------------
Packed 与 Planar,同一份声音的两种摆法
--------------------------------------------------------------------

**① 立体声六个样本,两种存法**

  存法       名字     shape       平面数     字节
  ---------------------------------------------------
  packed     s16      (1, 12)     1          24
  planar     s16p     (2, 6)      2          24

**字节数一模一样,摆法完全不同。**

**② 摆法长什么样**

  存法       内存里怎么排
  -----------------------------------------------------------
  packed     左0 右0 左1 右1 左2 右2 ……(左右交替)
  planar     左0 左1 左2 …… | 右0 右1 右2 ……(左右分块)

**packed 是「按时间排」,planar 是「按声道排」。**
**一个像按行读表格,一个像按列读表格。**

**③ 用真实的数组看一遍**

  存法             展开后的值
  ------------------------------------------------------------
  packed           [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  planar 平面0     [100, 101, 102, 103, 104, 105]
  planar 平面1     [200, 201, 202, 203, 204, 205]

**packed 那一行是 1 2 3 4 5 6……**,前六个数刚好是「左右左右」交替;**planar 是两个平面各六个数。**

**④ 两种存法互相转换**

  方向              怎么做                      结果 shape
  ------------------------------------------------------------
  packed→planar     reshape 成 (n,2) 再转置     (2, 6)
  planar→packed     转置后拉平                  (1, 12)

**numpy 里就是两次 reshape 加一次转置。** 但真做项目别这么写——用重采样器转更稳,它连精度一起处理。

字节数一样(都是 24),只是 shape 从 (1,12) 变 (2,6)。 转换只是 reshape 加转置,不涉及任何数值运算。

30 秒实验

  1. 把 samples=6 改成 samples=1024,看两个 shape 各是多少。
  2. 用 fltp 建一帧,看 to_ndarray() 的 dtype(提示:float32)。
  3. 把 planar 两平面相加,看它是不是「混音成单声道」的意思。

常见误区

  1. 以为 planar 更省内存。 字节数完全一样。
  2. 拿 packed 的形状去读 planar。 (1,12) 与 (2,6) 差一个维度。
  3. 手工转存法还顺手改精度。 两件事分开做,别混在一起。
自测三问(点开对答案) 1. packed 与 planar 的字节数一样吗? —— 一样。只是把同样的数字换了位置。 2. planar 为什么对处理更友好? —— 每个声道内存连续,不用跨步访问,缓存与预取效率高。 3. 把 packed 立体声转 planar,第一步做什么? —— reshape 成 (n, 2),再转置成 (2, n)。

相关笔记