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

责任链就是每一环只做一件事,做完把东西交给下一环。 一环断了,后面的环一次都不执行。

📝 本节和前面几节的关系

第三章把结构体与 API 拆开讲过,这一章把它们串成流程。 这一节先讲形状,4.2 起再套到 FFmpeg 真实的链上。

一、什么叫击鼓传花

教材用「击鼓传花」打比方,说的是数据在几道工序之间往前传。 每一道工序只关心两件事:手上这份东西合不合格,合格就往下交。至于下一道怎么干,它不管。 特征 含义 好处


单职责 一环只做一件事 改一环不影响别环 串行 必须按顺序走 因果关系清楚 可中断 不合格就停 错误不外溢 可拆装 中间几环能摘 同一套代码出多种产品

最后一条是关键。 FFmpeg 能同时做转封装、转码、滤镜处理、推流,靠的不是四套代码, 而是同一条链上摘掉或插进不同的环。

二、对比一把梭的写法

初学者常写成一个几百行的大函数,从头到尾一把梭。 一把梭 责任链


读起来 从上看到下 一环一环看 改一处 牵动全局 只动那一环 出错 只知道失败 知道走到哪一环 复用 整段搬走 按环挑着用

「知道走到哪一环」这一条最实用。 一把梭只知道最后失败了,前面几步成没成看不出来。 责任链把轨迹留下来了:哪一环过的、哪一环断的,一眼看清。下面 Demo 会真跑两份数据对比。

三、FFmpeg 这条链有五环

整条流程拆开就是五个环,一环对应一个库或几个库。 环 干什么 归谁


解封装 拆容器,分给各条流 libavformat 解码 压缩包变原始帧 libavcodec 处理 改尺寸、加滤镜、美颜 swscale/avfilter 编码 原始帧变压缩包 libavcodec 封装 打包成文件或流 libavformat

解封装与封装都归 libavformat,解码与编码都归 libavcodec。 同一个库在两个方向上各出现一次,这是理解整条链的关键。 转封装只走两头两环,转码要走全部五环——这就是为什么两者速度差一个量级。

四、这个模式在别处也见过

责任链不是 FFmpeg 发明的,是通用设计模式。 Web 框架的中间件、编译器的词法到代码生成、网络协议栈的分层,形状都一样:每一层只跟相邻层打交道。 认出了形状,学新框架就快。 看见「中间件」三个字就知道数据会一层层过,每层可以短路过。 但也要注意它的代价:环多了调用层次深,调试时栈很长,性能上每层都有转发开销。 FFmpeg 的链只有五环,这个代价可以接受。

五、动手 Demo:五环串一条链,一环断了后面全停

手搓一条五环链,用两份数据各跑一次。

RINGS = [
    ("解封装", lambda s: s["bytes"] > 0),
    ("识别格式", lambda s: s["fmt"] is not None),
    ("解码", lambda s: s["frame"] is not None),
    ("换格式", lambda s: s["rgb"] is not None),
    ("输出", lambda s: True),
]


def run(state):
    trace = []
    for name, ok in RINGS:
        good = ok(state)
        trace.append((name, good))
        if not good:
            break
    return trace

实跑输出:

--------------------------------------------------------------------
五环串一条链,一环断了后面全停
--------------------------------------------------------------------

**① 责任链就一句话:每一环只做一件事,做完把东西交给下一环。**

FFmpeg 的整条流程就是五个这样的环。先用手搓的小例子看形状,再对照真正的解码流程。

  环           正常那份     解码失败那份
  ------------------------------------------
  解封装       通过         通过
  识别格式     通过         通过
  解码         通过         断在这里
  换格式       通过         没走到
  输出         通过         没走到

**第二份数据在第 3 环断了,后面 2 环一次都没执行。**

**这就是责任链的两个要点**:一环只判断一件事;断了就不再往下传。

**② 换成一把梭的写法,结果一样但形状不同**

  数据       责任链的结论     一把梭的结论
  --------------------------------------------
  正常       五环走完         成功
  帧为空     第 3 环断        解码失败

**结论一样,但责任链多了一样东西:完整的轨迹。** 一把梭只知道「哪一步失败」,
不知道「前面几步成功了吗」。

**③ 链的形状还能改**

把「换格式」这一环摘掉,链照跑——这就是转封装为什么不解码:它把中间两环直接跳过了。
4.5.2 会真跑一次对比。

摘掉解码与换格式之后,还剩 **3** 环:解封装、识别格式、输出。

**链能拆能拼,这是 FFmpeg 架构最要紧的一条性质。** 转封装、转码、滤镜处理,
区别只是摘掉或插进哪几环。

两份数据只差一个字段,链的走向就完全不同。 break 那一句就是「击鼓传花」停下的时候。

30 秒实验

  1. 把 break 改成 continue,看轨迹会变成什么样。
  2. 在链里加一环「校验时间戳」,看它插在哪一步合适。
  3. 用 time.perf_counter() 给每一环计时,找出最慢的一环。

常见误区

  1. 一环干好几件事。 出了问题分不清是哪件事坏的。
  2. 断了还继续往下传。 后面几环拿到半成品,报的错会指向错误的地方。
  3. 把环写得互相依赖。 第 3 环直接改第 1 环的变量,链就退化成一把梭了。
自测三问(点开对答案) 1. 责任链模式最要紧的性质是哪一条? —— 可拆装。中间几环能摘掉或插进,同一套代码能出转封装、转码、推流多种产品。 2. 一环失败就 `break`,好处是什么? —— 错误不外溢。后面几环不会拿半成品继续算,报错位置准确。 3. 为什么解封装与封装都归 libavformat? —— 它们都在处理容器的读写,一个拆一个装,共用同一套格式定义。

相关笔记