🎬 第四章 精通FFmpeg框架流程:击鼓传花之责任链设计模式
📄 一句话概括

编码是解码的镜像:上面倒帧,下面接包,出包总比送帧晚一步。 gop_size 决定关键帧多久来一次,这是码率与拖动的兑换。

📝 本节和前面几节的关系

4.3 讲解码方向,这一节是它的镜像。 下一篇 4.5.1 把两个方向串成转码。

一、两个方向正好对称

把 4.3 那张表倒过来看,就是这一节。 解码 编码


上面倒 包 帧 下面接 帧 包 缓冲攒什么 参考帧 一整帧的量化结果 冲空包 取尾帧 取尾包

两边都要冲空包,这一点完全对称。 忘了冲,解码丢最后几帧,编码丢最后几个包。 但编码方向多了一个东西:参数要自己定。 解码时格式与尺寸由码流决定,编码时全靠调用者给。

二、编码器要你先说清四件事

少说一件,编码器就打不开或产出怪东西。 要给的 不给会怎样


尺寸 直接报错 像素格式 报错或颜色错乱 帧率 时间戳全错,播出来忽快忽慢 码率或质量参数 能出文件,但大小不可控

前三个是硬性要求,第四个是软性的。 不给码率参数,libx264 会用一个默认值编出来, 文件大得离谱但画质也就那样。 码率与质量二选一:给 crf 是按质量编(大小不定),给 b 是按大小编(质量不定)。 两个都给,crf 会失效。

三、gop_size:多久塞一个能独立解的帧

这是编码参数里最影响体验的一个。 gop 小 gop 大


拖进度条立刻出画面 要等下一个关键帧 码率高 码率低 文件大 文件小 适合点播、剪辑 适合长时间归档

实测把 gop_size 设成 10,关键帧正好落在第 0、10、20、30、40 个包上。 设定与实际完全一致, 说明它就是这个意思。 直播场景通常把 gop 定成帧率的整数倍。 25 帧配 50,两秒一个关键帧, 新观众最多等两秒就能出画面。 gop 设成 1 就是全 I 帧,每一帧都能独立解,代价是码率暴涨几倍。只在需要逐帧精确编辑时这么干。

四、编码比解码更贵

同一段内容,编码的耗时通常是解码的几倍到几十倍。 解码是「按码流说的做」,路径确定;编码是「找最优的压法」,要试各种分块与运动估计。 搜索空间大,所以贵。 preset 速度 压缩率


ultrafast 最快 最差 medium 中等 中等 veryslow 最慢 最好

preset 不动画质,只动「花多少时间去找更好的压法」。 想要画质好又不加时间, 该调 crf 而不是 preset。 这个区分很实用:压片子赶时间就调 preset,要画质就调 crf,两者互不替代。

五、动手 Demo:编码流程——出包总比送帧晚一步

送 50 帧进去,看包什么时候出来;再改 gop 看关键帧位置。

import av

with av.open("_media32/tiny.mp4") as ic:
    with av.open("_media32/enc_probe.mp4", "w") as oc:
        vs = oc.add_stream("libx264", rate=25)
        vs.width, vs.height, vs.pix_fmt = 160, 120, "yuv420p"
        vs.options = {"crf": "30", "preset": "ultrafast"}
        seq = []
        for frame in ic.decode(ic.streams.video[0]):
            seq.append(len(vs.encode(frame)))
        tail = len(vs.encode())
print(sum(seq), tail)

实跑输出:

--------------------------------------------------------------------
编码流程——出包总比送帧晚一步
--------------------------------------------------------------------

**① 编码方向与解码方向正好相反**

             解码             编码
  --------------------------------------------------
  上面倒     包               帧
  下面接     帧               包
  缓冲       攒参考帧         攒一整帧的量化结果
  收尾       冲空包取尾帧     冲空包取尾包

**两边都要冲空包,这一点对称。** 忘了冲,就会丢掉最后几帧或最后几个包。

**② 真跑一次:送 50 帧,看包什么时候出来**

  项                 数量
  ----------------------------
  送进去的帧         50 帧
  编码期间出的包     49 个
  冲空包后补出的     1 个
  合计               50 个

**第一帧送进去,出 0 个包**(表里第一个数是 0)。编码器要先看完整一帧才能决定怎么压。

**每帧出包数,前十六个:**

0、1、1、1、1、1、1、1、1、1、1、1、1、1、1、1

**③ gop_size 决定关键帧多久来一次**

把 gop_size 设成 10,一共出 **50** 个包,关键帧落在:**0、10、20、30、40**。

**每隔 10 个包一个关键帧,跟设定完全对上。** gop_size 就是「多久塞一个能独立解出来的帧」。

**④ 这个数不是白设的**

gop 小,随机访问快(拖进度条立刻出画面),但码率高;gop 大,码率低,拖进度条要等下一个关键帧。
**这是码率与响应速度的兑换。**

**直播场景通常把 gop 定成帧率的整数倍**(比如 25 帧率配 gop 50,两秒一个),
这样新观众最多等两秒就能出画面。

第一帧送进去出 0 个包,编码器要先看完整一帧才能决定怎么压。 tail 是冲空包补出来的,不冲就会少最后一个包。

30 秒实验

  1. 把 preset 换成 veryslow,看耗时怎么变、文件小多少。
  2. 把 crf 从 30 改到 18,看文件大多少。
  3. 把 gop_size 设成 1,数关键帧占比。

常见误区

  1. 忘了冲空包。 最后一个包丢掉,播出来结尾少一小段。
  2. 同时给 crf 与 b。 crf 会失效,实际按 b 走(或反之),结果不可预期。
  3. 以为 preset 能提画质。 preset 只影响搜索时间,画质由 crf 定。
自测三问(点开对答案) 1. 送第一帧进去为什么出 0 个包? —— 编码器要先看完整一帧,才能决定怎么量化、怎么预测。 2. gop_size 设成 10 意味着什么? —— 每 10 个包有一个关键帧,能独立解出来。 3. 画质不够,该调 preset 还是 crf? —— crf。preset 只改编码花的时间,不改目标质量。

相关笔记