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 联合调试

单个工具各有局限。我的习惯是三步走:

  1. 先可视化:用 -filter_complex_dump 确认滤镜图结构正确。
  2. 再 showinfo:跑一遍空输出,检查时间戳、帧率、尺寸等元数据。
  3. 最后 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 后的尺寸有问题。这时候:

  1. 用 -filter_complex_dump 看 scale 节点的输出尺寸。
  2. 在 overlay 前加 showinfo,看两路视频的 PTS 是否同步。
  3. 在最终画面上用 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 调试的三个层次和它们之间的关系:

FFmpeg 滤镜调试三板斧 FFmpeg 滤镜调试三板斧 filtergraph 可视化 -filter_complex_dump showinfo 滤镜 帧级元数据输出 drawtext 叠加 画面内嵌调试信息 输出:结构图 / 帧日志 / 带调试信息的视频 结构问题 滤镜连接错误 端口不匹配 时序问题 PTS 跳变 帧率不稳定 视觉问题 位置偏移 颜色/尺寸异常 调试流程建议 先可视化确认结构 → 再 showinfo 检查时序 → 最后 drawtext 做视觉验证 三步走完,90% 的滤镜问题都能定位

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} 转义。这个坑我至少踩过三次,每次都是「怎么我的帧号显示不出来?」然后恍然大悟。