🎬 第五章 FFmpeg命令行实现音视频转码
📄 一句话概括

PCM 就是「每秒 44100 次、每次两个 16 位整数」,没有任何压缩。 一段 2 秒的立体声,裸 PCM 是 356352 字节,AAC 只要 33555。

📝 本节和前面几节的关系

视频那边讲完了 YUV 与 RGB,这一节换到音频,把 AAC 解回最原始的采样点。 下一节会在这个裸数据前面加 44 个字节,让它变成能双击播放的 WAV。

一、PCM 是音频的「原始数据」

把 AAC 解回 PCM,字节数同样是算出来的:

项 公式 值


采样点数 — 89088 应占字节 点数×2 字节×2 声道 356352 实测文件 — 356352 时长 点数÷采样率 2.02 秒

第二行与第三行分毫不差,说明 PCM 就是「点数乘声道乘位深」,一个字节不多。 这也是判断「这个文件到底是不是裸 PCM」最快的办法:算一遍,对得上就是。

第四行是 2.02 而不是 2.00:AAC 是按 1024 个采样点一包编的, 87 包加起来是 89088 点,除下来就多了 0.02 秒。多出来的这点尾巴是编码格式带来的。

二、和压缩音频差多少

同一段声音,两种存法:

文件 字节 码率


原始 PCM(s16 立体声) 356352 1411 kbps 同样内容的 AAC(128k) 33555 133 kbps

两行差 10.7 倍。这个倍数不是巧合:PCM 的码率是「采样率×位深×声道」算出来的, 44100×16×2 就是 1411 kbps,而 AAC 按心理声学模型只留下听得见的部分。

方向反过来看更有意思:想「提升音质」去转成 PCM,什么也提升不了。 PCM 只是把已有的信息摊开,摊开之后还是那点信息。

三、提取 PCM 的两个坑

坑一:planar 与 packed 的区别。 解码出来的音频帧通常是 planar(左右声道分开存),而 s16 文件要求 packed(左右交错)。

这就是为什么代码里要过一个 AudioResampler:它负责把格式、声道布局、采样率一次改到位。 少了这一步,写出来的字节数会翻倍或者错位。

坑二:声道布局要显式给。 只写「2 声道」不够,还得说清是 stereo 还是 downmix,否则重采样器不知道该怎么摆。

这两条在命令行里对应 -f s16le -ac 2 -ar 44100,三个参数一个都不能少。 注意 -f s16le 指的是输出封装格式,-acodec pcm_s16le 才是编码器。

四、动手 Demo:把 AAC 解回 PCM,对一遍字节数

解码音频流,用重采样器统一成 s16、立体声、44100,再写进一个裸的 .pcm 文件。 最后把「点数乘 4」与实测文件大小对一遍,再和压缩版比体积。

import av, os

c1 = av.open(u"o.pcm", u"w", format=u"s16le")
s1 = c1.add_stream(u"pcm_s16le", rate=44100)
s1.layout = u"stereo"
c0 = av.open(u"s5.mp4")
rs = av.AudioResampler(format=u"s16", layout=u"stereo", rate=44100)
n = 0
for f in c0.decode(audio=0):
    for rf in rs.resample(f):
        n += rf.samples
        for o in s1.encode(rf):
            c1.mux(o)
for rf in rs.resample(None):
    n += rf.samples
    for o in s1.encode(rf):
        c1.mux(o)
for o in s1.encode(None):
    c1.mux(o)
c1.close()
c0.close()

print(u"采样点 %d  应占 %d  实测 %d"
      % (n, n * 4, os.path.getsize(u"o.pcm")))
print(u"时长 %.2f 秒" % (n / 44100.0))

实跑输出:

--------------------------------------------------------------------
5.2.5 提取音频的 PCM
--------------------------------------------------------------------

**① 把 AAC 解回 PCM,字节数同样能算**

  项           公式                   值
  --------------------------------------------
  采样点数     —                      89088
  应占字节     点数×2 字节×2 声道     356352
  实测文件     —                      356352
  时长         点数÷采样率            2.02 秒

**第二行与第三行分毫不差:PCM 没有任何压缩,字节数只由「点数×声道×位深」决定。**
**第四行算出来是 2.02 秒而不是 2.00——因为采样点数是按整包凑出来的,末尾多了一小段。**

**② PCM 与压缩音频的体积对照**

  文件                       字节       码率
  ------------------------------------------------
  原始 PCM(s16 立体声)     356352     1411 kbps
  同样内容的 AAC(128k)     33349      132 kbps

**两行差 10.7 倍——这就是「有损压缩」在这段素材上的实际收益。**
**换成无损压缩(FLAC)大概能省一半,换成有损压缩能省一个数量级,差别就在「敢丢多少」。**

rs.resample(None) 是冲洗:重采样器内部也有缓存,不冲就会少最后一小段。 这一句和视频那边的 cc.decode(None) 是同一类东西。

n * 4 里的 4 是「2 字节 × 2 声道」。 如果换成 24 位就是乘 6,换成单声道就是乘 2,这个乘法永远成立。

如果输出文件是应占字节的两倍,八成是把 planar 当 packed 写了。 那种情况下左右声道会被拉成前后两段,听起来像「先左后右」的怪声。

30 秒实验

  1. 命令行对照:ffmpeg -i 片子.mp4 -vn -f s16le -ac 2 -ar 44100 出.pcm。
  2. 改位深:换成 -f s32le,文件大小立刻翻倍,听感没变化。
  3. 改声道:-ac 1,文件减半,能听出「左右糊在一起」。

常见误区

  1. 以为 PCM 音质更好。它只是没压缩,源有多少信息就只有多少。
  2. 漏了重采样。planar 直接写进 s16 文件会错位,声音变怪。
  3. 把 -f s16le 当成编码器。它是封装格式,编码器是 -acodec pcm_s16le。
自测三问(点开对答案) 1. 44100 采样、16 位、立体声的 PCM,每秒多少字节? —— 176400 字节。44100 乘 2 字节乘 2 声道,换算成码率是 1411 kbps。 2. 为什么解出来的时长是 2.02 秒而不是 2.00? —— AAC 按 1024 点一包编码,87 包凑成 89088 点,比 2 秒多出一点尾巴。 3. planar 与 packed 有什么区别? —— planar 是一个声道一块连续存,packed 是左右交错存。裸 PCM 文件一般要 packed。

相关笔记