日志获取:命令行重定向、-report 与回调函数
日志是调试的「眼睛」。没有日志,你就像在黑箱子里修电路——全靠猜。
这一章,我们专门聊聊怎么把 FFmpeg 的日志「抓」到手。三种方式:命令行重定向、-report 参数、C 语言回调函数。每种都有它的适用场景,我一个个说。
方式一:命令行日志重定向(2>&1)
这是最基础、最常用的方法。FFmpeg 默认把日志输出到标准错误(stderr),而不是标准输出(stdout)。很多新手会犯一个错:只重定向 stdout,结果啥也没抓到。
核心命令:
ffmpeg -i input.mp4 output.mp4 2> log.txt
这会把 stderr 单独重定向到文件。如果你想同时看屏幕和存文件,用 tee:
ffmpeg -i input.mp4 output.mp4 2>&1 | tee log.txt
这里有个细节:2>&1 的意思是把 stderr(文件描述符 2)合并到 stdout(文件描述符 1)。然后管道 | 把合并后的流传给 tee,tee 一边写文件一边打印到终端。
我个人习惯在调试复杂转码时,用 2>&1 | tee log.txt。为什么?因为我可以实时看到进度,同时保留一份完整的日志用于事后分析。有一次排查一个 HDR 转 SDR 的色彩问题,就是靠这份日志里的色彩空间转换信息定位的。
小技巧:如果你只想看错误,不想看 INFO 级别的啰嗦信息,可以加 -loglevel error:
ffmpeg -loglevel error -i input.mp4 output.mp4 2> error.log
方式二:-report 参数
这个参数是个「懒人神器」。加上 -report,FFmpeg 会自动生成一个日志文件,命名格式是 ffmpeg-YYYYMMDD-HHMMSS.log,放在当前目录。
ffmpeg -report -i input.mp4 output.mp4
它和手动重定向有什么区别?我列个表:
| 特性 | 手动重定向 | -report 参数 |
|---|---|---|
| 日志级别 | 默认(通常 INFO) | 强制 VERBOSE(更详细) |
| 文件名控制 | 你指定 | 自动生成(带时间戳) |
| 额外信息 | 无 | 包含命令行参数、版本信息 |
| 适用场景 | 日常使用 | 调试、提交 bug 报告 |
我曾经在帮同事排查一个编码器崩溃问题时,就是靠 -report 生成的日志。它把每个线程的调度信息都打印出来了,最终发现是某个自定义 filter 的线程安全问题。
注意:-report 会生成非常详细的日志,文件可能很大。生产环境慎用。我见过有人忘记关 -report,跑了一晚上转码任务,第二天发现磁盘被日志撑爆了。
方式三:C 语言回调函数(av_log_set_callback)
如果你是做二次开发,或者需要把日志集成到自己的 GUI 或服务里,那就得用回调函数了。
FFmpeg 的日志系统核心是 av_log() 函数。它默认把日志输出到 stderr。但你可以通过 av_log_set_callback() 替换这个行为。
看代码:
#include <stdio.h>
#include <libavutil/log.h>
void my_log_callback(void *ptr, int level, const char *fmt, va_list vl)
{
// 只处理 ERROR 及以上级别
if (level > AV_LOG_ERROR)
return;
// 获取当前时间
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_buf[32];
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
// 输出到文件
FILE *log_file = fopen("my_ffmpeg.log", "a");
if (log_file) {
fprintf(log_file, "[%s] [%s] ", time_buf, av_default_item_name(ptr));
vfprintf(log_file, fmt, vl);
fprintf(log_file, "\n");
fclose(log_file);
}
// 同时输出到终端(带颜色)
fprintf(stderr, "\033[31m[%s]\033[0m ", av_default_item_name(ptr));
vfprintf(stderr, fmt, vl);
fprintf(stderr, "\n");
}
int main(int argc, char *argv[])
{
// 注册回调
av_log_set_callback(my_log_callback);
// 设置日志级别
av_log_set_level(AV_LOG_DEBUG);
// 测试
av_log(NULL, AV_LOG_INFO, "FFmpeg 日志系统初始化完成\n");
av_log(NULL, AV_LOG_ERROR, "这是一个错误示例\n");
// 这里放你的转码逻辑...
return 0;
}
这段代码做了几件事:
- 过滤日志级别,只处理 ERROR 及以上(你可以改成其他级别)
- 给每条日志加上时间戳
- 同时写入文件和终端
- 终端输出带颜色(红色)
你想想看,这个回调函数能做什么?
- 把日志发送到远程日志服务器(比如 syslog、ELK)
- 在 GUI 里实时显示进度条和错误
- 根据日志内容触发告警(比如检测到 "Out of memory" 就发邮件)
经验之谈:我在做一个视频监控平台时,需要同时处理 32 路转码流。每个流的日志如果都写 stderr,根本没法看。我用回调函数把每个流的日志按流 ID 分文件存储,排查问题时直接看对应流的日志文件,效率高了很多。
三种方式的对比与选择
说白了,怎么选取决于你的场景:
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 日常命令行转码 | 2>&1 | tee log.txt | 简单、灵活、实时可见 |
| 调试复杂问题 | -report | 信息最全,自动命名 |
| 二次开发/集成 | av_log_set_callback | 完全可控,可定制 |
| 生产环境自动化 | 回调 + 日志轮转 | 避免磁盘写满,可集中管理 |
知识体系总览
下面这张图帮你理清三种日志获取方式的脉络:
避坑指南
最后,分享几个我踩过的坑:
- 日志文件权限问题:我曾经在 Docker 容器里跑 FFmpeg,忘了挂载日志目录,结果 -report 写不进去,排查了半天才发现是权限问题。建议先测试一下文件是否能写入。
- 回调函数线程安全:FFmpeg 的日志回调可能被多个线程同时调用。如果你在回调里写文件,记得加锁。我见过有人没加锁,日志文件内容乱成一团。
- 日志级别设置:av_log_set_level 要在 av_log_set_callback 之后调用,否则回调可能收不到日志。嗯,这个顺序问题我 debug 了半小时。
- 不要在生产环境用 -report:除非你确定磁盘空间足够。我有个同事在 24 小时转码任务里开了 -report,第二天发现 50GB 的日志文件——转码任务也因为这个磁盘写满而失败了。