日志获取:命令行重定向、-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 完全可控,可定制
生产环境自动化 回调 + 日志轮转 避免磁盘写满,可集中管理

知识体系总览

下面这张图帮你理清三种日志获取方式的脉络:

FFmpeg 日志获取方式总览 FFmpeg 日志输出 方式一:2>&1 重定向 stderr → stdout 管道 + tee 实时查看 适合日常命令行 方式二:-report 参数 自动生成日志文件 VERBOSE 级别 适合调试/bug 报告 方式三:回调函数 av_log_set_callback 完全自定义处理 适合二次开发 选择原则:场景决定方式,没有银弹

避坑指南

最后,分享几个我踩过的坑:

  • 日志文件权限问题:我曾经在 Docker 容器里跑 FFmpeg,忘了挂载日志目录,结果 -report 写不进去,排查了半天才发现是权限问题。建议先测试一下文件是否能写入。
  • 回调函数线程安全:FFmpeg 的日志回调可能被多个线程同时调用。如果你在回调里写文件,记得加锁。我见过有人没加锁,日志文件内容乱成一团。
  • 日志级别设置:av_log_set_level 要在 av_log_set_callback 之后调用,否则回调可能收不到日志。嗯,这个顺序问题我 debug 了半小时。
  • 不要在生产环境用 -report:除非你确定磁盘空间足够。我有个同事在 24 小时转码任务里开了 -report,第二天发现 50GB 的日志文件——转码任务也因为这个磁盘写满而失败了。