四个结构体:图、节点、连线、进出口。 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 秒实验
- 打印
g.add("overlay")那个节点的 inputs/outputs,看是几进几出。 - 打印
k.inputs与k.outputs,看 sink 是不是只有进没有出。 - 把
sc.name改成自定义名字再打一遍(提示:add 时给 name 参数)。
常见误区
- 以为 PyAV 里也能拿到「连线」对象。 连线一建就归图管,拿不到。
- 把 AVFilterContext 当成「滤镜本身」。 它是「滤镜在图里的一个实例」。
- 以为 process_command 是重设图。 它只改参数,图不动。
自测三问(点开对答案)
1. C 里管「整张图」的结构体叫什么? —— AVFilterGraph,PyAV 里对应 av.filter.Graph。 2. 怎么用对象本身判断一个滤镜是简单还是复杂? —— 数 inputs 与 outputs 的口数,一进一出就是简单。 3. `avfilter_link(a, 0, b, 0)` 的四个参数各是什么? —— 源的输出口下标、目标的输入口下标——也就是 link_to 那两下标的 C 版。- ☐ (本节完,下一节 9.2 DirectShow框架原理与流程解析)