4. FFmpeg 滤镜调试:让看不见的流处理过程现形
滤镜调试,说白了就是给视频流做「X光检查」。你写了一个复杂的 filtergraph,跑起来效果不对——画面黑了、花屏了、时间戳乱了。这时候怎么办?靠猜?我早年就是这么干的,结果经常在会议室里对着领导说「嗯,这个我再看看」。后来学乖了,直接用工具把滤镜内部扒开来看。
这一章,我就带你走一遍 FFmpeg 滤镜调试的三板斧:filtergraph 可视化、showinfo 滤镜、drawtext 叠加调试信息。这三招用熟了,大部分滤镜问题你都能在五分钟内定位。
4.1 filtergraph 可视化:先看清你的图长什么样
你写了一个 filter_complex 串,十几个滤镜连在一起。跑起来报错,或者画面不对。你盯着那行命令看了十分钟,愣是没看出哪里接错了。嗯,我也有过这种时候。
FFmpeg 提供了一个隐藏功能——filtergraph 可视化。它能把你的滤镜图输出成点图(DOT 格式),然后用 Graphviz 转成图片。说白了,就是把你脑子里的流程图画出来,让你肉眼检查。
核心命令:
ffmpeg -i input.mp4 -filter_complex \
"[0:v]split[base][over];[base]scale=iw/2:ih/2[small];[over][small]overlay=10:10" \
-filter_complex_dump graph.dot -f null -
这个命令不会真的编码输出,它只负责生成 graph.dot 文件。然后用 Graphviz 转成图片:
dot -Tpng graph.dot -o graph.png
我个人习惯在写复杂滤镜链之前,先跑一遍 -filter_complex_dump 看看图。有一次我在项目里写了一个 8 路视频拼接的滤镜,手动检查了三遍没发现问题,结果一可视化——有一条线的输出端口接错了。要是直接跑编码,那得等十几分钟才能看到错误画面。
小技巧:如果你不想装 Graphviz,也可以用 -filter_complex_dump graph.dot 生成点图后,直接打开看文本。点图的结构很清晰,节点和连线一目了然。
4.2 showinfo 滤镜:给每一帧做体检
画面出来了,但颜色不对、尺寸不对、时间戳跳变。这时候你需要知道每一帧的精确信息。showinfo 滤镜就是干这个的。
它的用法很简单:
ffmpeg -i input.mp4 -vf "showinfo" -f null -
输出大概长这样:
[Parsed_showinfo_0 @ 0x7f8b1c00] n: 1 pts: 0 pts_time:0.000 duration: 1 duration_time:0.033
[Parsed_showinfo_0 @ 0x7f8b1c00] n: 2 pts: 1 pts_time:0.033 duration: 1 duration_time:0.033
[Parsed_showinfo_0 @ 0x7f8b1c00] n: 3 pts: 2 pts_time:0.067 duration: 1 duration_time:0.033
...
每一行告诉你:帧序号(n)、PTS、PTS 时间、时长。这些信息在排查时间戳问题时特别有用。
注意:showinfo 输出的是日志信息,不是视频画面。所以一般配合 -f null - 使用,不生成输出文件,只看日志。
我曾经遇到过一个诡异的问题:视频播放到一半突然卡住,但编码没有报错。用 showinfo 一查,发现从第 300 帧开始,PTS 突然回退了 2 秒。原来是源文件的时间戳有损坏,showinfo 帮我精准定位到了问题帧。
4.3 drawtext 滤镜:把调试信息直接写在画面上
showinfo 虽然好,但它是文本输出。有时候你需要把信息叠加在画面上,比如看字幕对齐、看时间戳变化、看帧率波动。这时候 drawtext 滤镜就派上用场了。
基本用法:
ffmpeg -i input.mp4 -vf \
"drawtext=text='Frame %{n} : PTS %{pts}':x=10:y=10:fontsize=24:fontcolor=white" \
output.mp4
效果就是在画面左上角显示当前帧号和 PTS。你还可以用更多变量:
| 变量 | 含义 | 示例值 |
|---|---|---|
%{n} |
帧序号(从 0 开始) | 42 |
%{pts} |
当前帧 PTS | 1260 |
%{pts_time} |
PTS 时间(秒) | 42.000 |
%{duration} |
帧时长(时间基单位) | 30 |
%{duration_time} |
帧时长(秒) | 0.033 |
%{pict_type} |
帧类型(I/P/B) | I |
你想想看,如果你在做字幕叠加,字幕时间戳对不对?直接 drawtext 把 %{pts_time} 打在画面上,一帧一帧对比字幕出现的时间,问题一目了然。
实战案例:调试帧率转换
有一次我把 30fps 的视频转成 25fps,结果画面一卡一卡的。我怀疑是帧率转换算法有问题,于是用 drawtext 把帧号和 PTS 打在画面上:
ffmpeg -i input_30fps.mp4 -vf \
"fps=25,drawtext=text='Frame %{n} : %{pts_time}':x=10:y=10:fontsize=20:fontcolor=yellow" \
output_25fps.mp4
播放一看,发现有些帧的 PTS 时间间隔不均匀——有的 0.04 秒,有的 0.033 秒。原来是 fps 滤镜的丢帧策略导致时间戳抖动。后来我改用 setpts 手动调整,问题解决。
4.4 组合拳:filtergraph + showinfo + drawtext 联合调试
单个工具各有局限。我的习惯是三步走:
- 先可视化:用
-filter_complex_dump确认滤镜图结构正确。 - 再 showinfo:跑一遍空输出,检查时间戳、帧率、尺寸等元数据。
- 最后 drawtext:把关键信息叠加到画面上,做视觉验证。
举个例子,你写了一个画中画效果:
ffmpeg -i main.mp4 -i pip.mp4 -filter_complex \
"[0:v]scale=1280:720[main];[1:v]scale=320:240[pip];[main][pip]overlay=W-w-10:H-h-10" \
output.mp4
跑出来发现画中画位置不对。你怀疑是 scale 后的尺寸有问题。这时候:
- 用
-filter_complex_dump看 scale 节点的输出尺寸。 - 在 overlay 前加 showinfo,看两路视频的 PTS 是否同步。
- 在最终画面上用 drawtext 显示两路视频的当前 PTS,确认叠加时机。
这样一套下来,99% 的问题都能定位。
我的经验:drawtext 的字体文件路径在 Windows 和 Linux 上不一样。Windows 用 fontfile='C\:/Windows/Fonts/arial.ttf',Linux 用 fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf。路径写错了,滤镜直接报错,不会默默跳过。
4.5 本章知识体系
下面这张图总结了 filtergraph 调试的三个层次和它们之间的关系:
4.6 避坑指南
最后分享几个我踩过的坑:
- drawtext 的字体缓存:FFmpeg 会缓存字体信息。如果你改了字体文件,记得重启 FFmpeg 进程,否则还是用的旧字体。我曾经因为这个在演示现场翻车——字体改成了红色,但画面还是白色,重启后才生效。
- showinfo 的输出位置:showinfo 输出到 stderr,不是 stdout。如果你用
2>/dev/null重定向了错误输出,showinfo 的信息就没了。我习惯用2>&1 | grep showinfo单独过滤。 - filtergraph 可视化在复杂图上的性能:如果你的滤镜图有上百个节点,
-filter_complex_dump生成的点图会非常大。Graphviz 渲染时可能卡死。这时候我建议分段调试,一次只可视化一部分。 - drawtext 的变量转义:在 Windows 命令行里,
%{n}的百分号会被解释成变量。需要用%%{n}转义。这个坑我至少踩过三次,每次都是「怎么我的帧号显示不出来?」然后恍然大悟。