🎬 第九章 AVFilter过滤器层理论及案例实战
📄 一句话概括

四个结构体:图、节点、连线、进出口。 C 里它们是四个独立的结构,PyAV 里前两个合成一个对象。

📝 本节和前面几节的关系

前五节讲形状,这一节讲 C 里的名字——看到 FFmpeg 源码与旧文档时要能对上。 下一节 9.2 换到 DirectShow,那套框架也是同样的四个角色。

一、四个结构体一一对照

左边是 C 的名字,右边是 PyAV 里的东西。 C 的结构体 管什么 PyAV 里


AVFilterGraph 整张图 av.filter.Graph AVFilterContext 图里的一个节点 Graph.add 的返回值 AVFilterLink 两个节点之间的连线 link_to 建出来的 AVFilterInOut 图与外界的进出口 buffer/buffersink

第二行那个「add 的返回值」就是节点本身,它同时是「滤镜实例」与「图里的位置」。 C 里这两件事是同一个结构体。 第三行在 PyAV 里没有对象给你:连线一建好就归图管,你拿不到、也不用管。 这是 PyAV 故意藏起来的一层。

二、图上有哪些方法

图上一共 12 个公开方法,实际常用的不到一半。 方法 作用


add 加一个滤镜节点 add_buffer 加视频源 add_abuffer 加音频源 link_nodes 一次连一串节点 configure 把图配好,之后才能跑 push/pull 喂一帧/取一帧 set_audio_frame_size 定音频帧长 threads 设线程数

能用的就这十来个,剩下的都是细节。 真正要记住的只有七个:add、add_buffer、add buffersink、link_to、configure、push、pull。 这七个凑齐,图就能搭起来。

三、节点上有哪些方法

节点上的方法分两类:问信息的与干活。 方法 作用


filter/graph/name 它是什么、在哪张图、叫什么 inputs/outputs 它有几个进、几个出 link_to 连到下一个节点 init 初始化参数 push/pull 从这一头喂进/取出 process_command 运行时改参数

inputs 与 outputs 是判类型的好工具:想确认一个滤镜是简单还是复杂,直接数它的口数, 比记名字可靠。 process_command 是这批方法里最特殊的一个:它改的是「已经配好的图」,不用重建。 下一节就会实测它。

四、process_command 值钱在哪

两条路都能改参数,代价差很远。 做法 要做什么 代价


重建图 拆掉、重搭、重配 中间几帧丢 process_command 发一条命令 下一帧就生效

实测里把 scale 节点的宽度改成 40,下一帧出来宽度就是 40,图没动。 这在直播里很值钱:观众带宽不够时现场降分辨率, 不用断流重连——断流重连对观众的观感是「卡了一下」。 代价是那一瞬间画面会有跳变,因为尺寸变了、编码器要跟着改。 所以真实产品里通常只在小范围内动,不会从 1080p 一刀切到 360p。

五、看 C 代码时怎么对上号

一份 C 版的搭图代码,五行就能认出来。 C 里的写法 干什么


avfilter_graph_alloc() 建图 avfilter_graph_create_filter() 加节点(含头尾) avfilter_link(a, 0, b, 0) 连线 avfilter_graph_config() 配好 av_buffersrc_add_frame() 喂帧进去

avfilter_link(a, 0, b, 0) 那四个参数:a 的第 0 个输出接 b 的第 0 个输入。 这就是 link_to 那两个下标的 C 版。 认下这五行,C 代码与 PyAV 代码就是同一件事的两种写法,换语言不用重新学一遍概念。

六、动手 Demo:四个结构体在 PyAV 里叫什么

把图与节点上的方法原样列一遍,不猜,直接问对象。

import av
import av.filter
from fractions import Fraction

g = av.filter.Graph()
s = g.add_buffer(width=160, height=120, format="yuv420p",
                  time_base=Fraction(1, 25))
sc = g.add("scale", "80:60")
k = g.add("buffersink")
s.link_to(sc)
sc.link_to(k)

print("图上的方法", [x for x in dir(g) if not x.startswith("_")])
print("节点上的方法", [x for x in dir(sc) if not x.startswith("_")])

# 节点的口数:简单滤镜一进一出
print("scale 进", len(sc.inputs), "出", len(sc.outputs))
print("节点名", sc.name, "它属于这张图", sc.graph is g)

实跑输出:

--------------------------------------------------------------------
四个结构体在 PyAV 里叫什么
--------------------------------------------------------------------

**① 一一对照**

  C 的结构体          管什么                 PyAV 里
  -----------------------------------------------------------------
  AVFilterGraph       整张图                 av.filter.Graph
  AVFilterContext     图里的一个节点         Graph.add 的返回值
  AVFilterLink        两个节点之间的连线     link_to 建出来的
  AVFilterInOut       图与外界的进出口       buffer/buffersink

**第二行那个「add 的返回值」就是节点本身**,它同时是「滤镜实例」与「图里的位置」。
**C 里这两件事是同一个结构体。**

**② 图上有哪些方法**

Graph 上一共 **12 个公开方法**:

  方法                     作用
  -------------------------------------------------
  add                      加一个滤镜节点
  add_abuffer              加音频源
  add_buffer               加视频源
  configure                把图配好,之后才能跑
  configured               图配好了没
  link_nodes               一次连一串节点
  pull                     取一帧出来
  push                     喂一帧进去
  set_audio_frame_size     定音频帧长
  threads                  设线程数
  vpull                    取一帧(另一套接口)
  vpush                    喂一帧(另一套接口)

**能用的就这十来个,剩下的都是细节。**
**抓住 add/buffer/sink/link_to/configure/push/pull 七个,图就搭起来了。**

**③ 节点上有哪些方法**

节点上有 **10 个**:

  方法                作用
  ----------------------------------------
  filter              它对应的滤镜定义
  graph               它在哪张图上
  init                初始化参数
  inputs              它的输入口
  link_to             连到下一个节点
  name                节点名
  outputs             它的输出口
  process_command     **运行时改参数**
  pull                从这一头取帧
  push                从这一头喂帧

**最值钱的是 `process_command`**:不用重建图就能改参数。**9.3 会实测它把输出宽度从 80 改成 40。**

len(node.inputs) 与 len(node.outputs) 是判简单/复杂最硬的办法。 节点上有 process_command,这是运行时改参数的入口。

30 秒实验

  1. 打印 g.add("overlay") 那个节点的 inputs/outputs,看是几进几出。
  2. 打印 k.inputs 与 k.outputs,看 sink 是不是只有进没有出。
  3. 把 sc.name 改成自定义名字再打一遍(提示:add 时给 name 参数)。

常见误区

  1. 以为 PyAV 里也能拿到「连线」对象。 连线一建就归图管,拿不到。
  2. 把 AVFilterContext 当成「滤镜本身」。 它是「滤镜在图里的一个实例」。
  3. 以为 process_command 是重设图。 它只改参数,图不动。
自测三问(点开对答案) 1. C 里管「整张图」的结构体叫什么? —— AVFilterGraph,PyAV 里对应 av.filter.Graph。 2. 怎么用对象本身判断一个滤镜是简单还是复杂? —— 数 inputs 与 outputs 的口数,一进一出就是简单。 3. `avfilter_link(a, 0, b, 0)` 的四个参数各是什么? —— 源的输出口下标、目标的输入口下标——也就是 link_to 那两下标的 C 版。

相关笔记