六个 API 对应六个动作,一一对得上。 两个实测各说一件事:参数能运行时改,格式会被滤镜偷偷换掉。
9.3.1 讲了六步动作,这一节讲每一步背后调的是哪个函数。 9.4 会把这套 API 用到完整的一条转码链里。
一、C 的 API 对照
左边是 C 的函数名,右边是 PyAV 里怎么调。 C 的 API PyAV 里
avfilter_graph_alloc av.filter.Graph() avfilter_graph_create_filter graph.add(name, args) avfilter_link a.link_to(b) avfilter_graph_config graph.configure() av_buffersrc_add_frame buffer.push(frame) av_buffersink_get_frame sink.pull() avfilter_graph_queue_command node.process_command()
七行里前六行是搭图与跑图,最后一行是「改」。 这一行最容易被忽略, 但它决定了这套 API 能不能用在直播里。 PyAV 这一列其实是薄薄一层包装,参数与顺序基本照抄 C。 所以看得懂 C 的文档,就等于看得懂 PyAV。
二、实测一:运行时改尺寸真的生效
图配好、帧推过一帧之后,再发命令。 时刻 输出宽度 图重建了吗
改之前 80 — 发两条命令之后 40 没有
process_command("width", "40") 与 process_command("height", "30")两条发下去,
下一帧出来宽度就是 40。图没拆、没重配。
这条能力在直播里很值钱:观众带宽不够时现场降分辨率,不用断流重连。
断流重连对观众的观感就是「卡了一下」。
代价是那一瞬间画面会有跳变,
而且编码器得跟着改参数——所以真实产品里通常只在小范围内动,不会从 1080p 一刀切到 360p。
三、实测二:音频滤镜会偷偷改格式
同一条音频,加不加 volume,输出格式不一样。 链 输出格式 样本数 首样本值
不加滤镜 s16 1024 1000 volume=0.5 flt 1024 0.01526 volume=2.0 flt 1024 0.06104
注意第二列:加滤镜之后格式从 s16 变成了 flt。因为 volume 内部按浮点算,
它顺手把格式也转了。
样本数没变、只有格式变了,所以这件事不会引起任何报错——你按整数去读,读出来是垃圾。
这个坑在真实项目里非常常见:音频处理链一加滤镜,下游那段代码的读法就全错了。
而错的表现是「声音变噪音」,不是异常。
四、格式变了,值为什么也变
同一个 1000,在两种格式里根本不是同一个数。 格式 范围 1000 的含义
s16 −32768 到 32767 整数,按 32768 归一 flt −1.0 到 1.0 浮点,直接就是比例
1000 除以 32768 约等于 0.0305,再乘 0.5 就是 0.01526。
所以 0.01526 不是滤镜算错,是基准换了。
这也解释了为什么乘 2 之后是 0.06104:0.0305 乘 2,正好翻倍。
两行一对照,就验证了「它只是换了刻度」。
记住一个判据:拿到音频帧先看 format.name,别默认它还是你送进去的那个格式。
五、什么时候该怀疑格式被改
三个征兆,出现任何一个都先查格式。 征兆 多半是什么
数值小得离谱(0.0x) 整数变成了浮点 数值大得离谱(几万) 浮点被当整数读 声音变噪音但没报错 读法跟格式对不上
第三种最难查,因为它不报错。 有效办法是在链的入口与出口各打一行格式,两头一对比就知道是哪一环换的。 这一招在视频那头也管用:滤镜把 yuv420p 换成 rgb24 也是同样的道理, 只是视频的错更显眼——颜色不对,肉眼就看出来了。
六、动手 Demo:AVFilter 的 API 与两处实测
先实测运行时改宽度,再拿音频跑一遍,看格式与数值怎么一起变。
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)
g.configure()
s.push(make_frame(0))
print("改之前", k.pull().width)
sc.process_command("width", "40")
sc.process_command("height", "30")
s.push(make_frame(1))
print("改之后", k.pull().width)
# 音频侧:加不加 volume,输出格式不一样
def audio(args):
ga = av.filter.Graph()
buf = ga.add_abuffer(format="s16", sample_rate=8000,
layout="mono", time_base=Fraction(1, 8000))
node = ga.add("volume", args)
snk = ga.add("abuffersink")
buf.link_to(node); node.link_to(snk)
ga.configure()
fa = av.AudioFrame(format="s16", layout="mono", samples=1024)
fa.sample_rate = 8000
fa.planes[0].update(bytes([0xE8, 0x03]) * 1024)
buf.push(fa)
out = snk.pull()
return (out.format.name, out.samples,
out.to_ndarray().flat[0])
print(audio("1.0"))
print(audio("0.5"))
实跑输出:
--------------------------------------------------------------------
AVFilter 的 API 与两处实测
--------------------------------------------------------------------
**① C 的 API 对照**
C 的 API PyAV 里
-----------------------------------------------------------
avfilter_graph_alloc av.filter.Graph()
avfilter_graph_create_filter graph.add(name, args)
avfilter_link a.link_to(b)
avfilter_graph_config graph.configure()
av_buffersrc_add_frame buffer.push(frame)
av_buffersink_get_frame sink.pull()
avfilter_graph_queue_command node.process_command()
**② 实测一:运行时改尺寸真的生效**
改之前 **80**,发两条命令之后 **40**。**参数真的变了,图没重建。**
**这条能力在直播里很值钱**:观众带宽不够时现场降分辨率,不用断流重连。
**代价是那一瞬间的画面会有跳变。**
**③ 实测二:音频滤镜会偷偷改格式**
链 输出格式 样本数 首样本值
---------------------------------------------------
不加滤镜 s16 1024 1000
volume=0.5 flt 1024 0.01526
volume=2.0 flt 1024 0.06104
**注意第二列:加滤镜之后格式从 `s16` 变成了 `flt`。** 因为 volume 内部按浮点算,
它顺手把格式也转了。
**于是同一个 1000,前后两个值的写法完全不同**:整数 1000 与浮点 0.01526。
**这不是滤镜算错,是基准变了**——整数按 32768 归一到 1。
**这个坑很实际**:处理音频时拿到帧先看一眼 `format.name`,别默认还是自己送进去的那个格式。
process_command 之后宽度从 80 变 40,图没重建。
加了 volume 之后格式从 s16 变 flt,同一个 1000 变成 0.0x。
30 秒实验
- 只发
width不发height,看输出会不会变形。 - 把 volume 的参数换成
"3.0",验算 0.0305×3 是不是它给的值。 - 在音频链尾部再加一个
aformat=sample_fmts=s16,看能不能把格式拉回来。
常见误区
- 以为渲染链的输出格式等于输入格式。 volume 就会把它改成浮点。
- 以为数值变了就是滤镜算错。 多半只是刻度从整数换成了浮点。
- 为了改一个参数去重建整张图。 用 process_command 就够。
自测三问(点开对答案)
1. `av_buffersink_get_frame` 对应 PyAV 里的什么? —— sink.pull(),也就是在汇节点上取一帧。 2. 加 volume 之后格式为什么会变? —— 它内部按浮点运算,顺手把输出格式转成了 flt。 3. 拿到音频帧第一件该确认的事是什么? —— 看 format.name,确认格式还是不是自己送进去的那个。- ☐ (本节完,下一节 9.4.1 FFmpeg命令行方式体验过滤器)