3. GStreamer 调试工具:GST_DEBUG_DUMP_DOT 与管道图生成、GST_DEBUG_FILE 日志重定向
调试 GStreamer 管道,说白了就是两件事:看管道长什么样,看日志说了什么。前者靠 GST_DEBUG_DUMP_DOT_DIR,后者靠 GST_DEBUG 和 GST_DEBUG_FILE。这两个工具用熟了,大部分运行时问题都能快速定位。
我个人习惯是:先 dump 管道图确认拓扑,再开日志抓关键事件。顺序别搞反——图都画不对,看日志也是瞎猜。
3.1 管道图生成:GST_DEBUG_DUMP_DOT_DIR
这个环境变量是 GStreamer 的「X 光机」。设置之后,每次管道状态变化(比如从 NULL 到 READY,从 READY 到 PAUSED),框架都会自动生成一个 .dot 文件。再用 Graphviz 转成 PNG/SVG,管道里每个 element、每个 pad、每条连接,一目了然。
3.1.1 基本用法
export GST_DEBUG_DUMP_DOT_DIR=/tmp/gst_dumps
gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink
运行后去 /tmp/gst_dumps 目录看看,你会看到类似这样的文件:
0.00.00.022222000-gst-launch.NULL_READY.dot
0.00.00.044444000-gst-launch.READY_PAUSED.dot
0.00.00.066666000-gst-launch.PAUSED_PLAYING.dot
文件名格式是:时间戳-进程名.旧状态_新状态.dot。每个状态转换都会生成一张快照。
把 dot 转成图片:
dot -Tpng /tmp/gst_dumps/*.dot -o /tmp/gst_dumps/pipeline.png
# 或者用 svg,更清晰
dot -Tsvg /tmp/gst_dumps/*.dot -o /tmp/gst_dumps/pipeline.svg
3.1.2 在代码中手动触发
如果你用 C 代码写 pipeline,可以手动调用 GST_DEBUG_BIN_TO_DOT_FILE() 或 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(),在关键节点 dump 管道状态。比如:
#include <gst/gst.h>
// 在某个回调或状态变化后手动 dump
GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "my_pipeline");
// 带时间戳的版本
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline),
GST_DEBUG_GRAPH_SHOW_ALL,
"my_pipeline");
第二个参数 GST_DEBUG_GRAPH_SHOW_ALL 表示显示所有细节,包括 ghost pad、内部 element。还有几个可选值:
| 标志 | 含义 |
|---|---|
GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE |
显示 pad 上的媒体类型(caps) |
GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS |
显示 caps 的详细参数(分辨率、帧率等) |
GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS |
只显示非默认参数,减少干扰 |
GST_DEBUG_GRAPH_SHOW_STATES |
显示每个 element 的当前状态 |
GST_DEBUG_GRAPH_SHOW_ALL |
显示全部信息 |
GST_DEBUG_DUMP_DOT_DIR,结果跑了一天后 /tmp 被 dot 文件撑爆了。这个变量只应该在调试时开,上线前务必关掉。
3.1.3 管道图怎么看?
生成的图里,每个 element 是一个矩形框,pad 是框上的小标签。src pad 在右边,sink pad 在左边。箭头从 src 指向 sink,表示数据流向。
我遇到过最典型的场景:管道状态卡在 PAUSED。这时候 dump 一张图,发现某个 element 的 sink pad 是红色(未连接),或者某个 element 状态是 PAUSED 而不是 PLAYING。问题在哪,一眼就能看出来。
3.2 日志重定向:GST_DEBUG_FILE
默认情况下,GStreamer 的调试日志直接往 stderr 上怼。终端一关,日志就没了。而且日志和程序正常输出混在一起,根本没法看。
GST_DEBUG_FILE 就是解决这个问题的。设置之后,所有日志都写到指定文件里,干净利落。
3.2.1 基本用法
export GST_DEBUG=3
export GST_DEBUG_FILE=/var/log/gstreamer.log
gst-launch-1.0 videotestsrc ! fakesink
运行结束后,打开 /var/log/gstreamer.log,所有日志都在里面。你可以慢慢翻,也可以用 grep 搜关键词。
3.2.2 日志级别与分类过滤
只设 GST_DEBUG=3 太粗了。实际调试时,我通常这样组合:
export GST_DEBUG=3
export GST_DEBUG_FILE=/tmp/gst_debug.log
export GST_DEBUG_NO_COLOR=1 # 去掉颜色转义字符,文件里更干净
如果只想看某个 element 的日志:
export GST_DEBUG=3,*videotestsrc:5,*videoconvert:5
export GST_DEBUG_FILE=/tmp/gst_debug.log
这样只有 videotestsrc 和 videoconvert 的日志级别是 5(DEBUG),其他都是 3(WARNING)。日志量大幅减少,定位问题更快。
3.2.3 日志文件轮转
长时间运行的程序,日志文件会越来越大。GStreamer 本身不提供轮转功能,但你可以用 Linux 的 logrotate:
/var/log/gstreamer.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
或者,在代码里自己控制日志文件切换。我见过有人用 SIGUSR1 信号触发日志重开,也是个办法。
3.3 实战:用管道图 + 日志定位 buffer 问题
讲个真实案例。有一次我在做 RTP 推流,画面总是卡顿。我做了两件事:
- dump 管道图:发现
rtpjitterbuffer后面跟了一个queue,但 queue 的max-size-buffers只有 1。这等于 jitter buffer 刚排好序,queue 就丢掉了。 - 看日志:
grep "queue" /tmp/gst_debug.log,发现大量dropped buffer的 WARNING。
解决方案很简单:把 queue 的 max-size-buffers 改成 200,或者干脆去掉 queue。问题解决。
你看,没有管道图,我根本不知道 queue 的存在;没有日志,我不知道 queue 在丢数据。两个工具配合,十分钟定位。
3.4 本章知识体系
下面这张图总结了本章的核心逻辑:调试时先看管道拓扑,再看日志细节,两者互补。
3.5 避坑总结
- 别在生产环境开 DUMP_DOT_DIR:我曾经吃过这个亏,/tmp 被写满,服务挂了。调试完立刻关。
- 日志级别别开太高:
GST_DEBUG=7会输出海量日志,性能下降明显。一般 3-5 就够用。 - 日志文件记得轮转:长时间运行的程序,日志文件能涨到几个 GB。用 logrotate 或者自己写脚本。
- 管道图 + 日志配合用:只看图不看日志,可能漏掉运行时错误;只看日志不看图,可能搞不清拓扑。两个一起上,效率最高。
嗯,调试工具就这些。记住一个原则:先画图,再看日志,最后动手改。顺序对了,问题就简单了。