🎬 第三章 FFmpeg三大常用工具及应用选项详解
📄 一句话概括

三个 exe 压在同一个内核上:ffmpeg 转、ffplay 放、ffprobe 看。 一条命令按「段」读,退出码只有三种结局。

📝 本节和前面几节的关系

第一章讲了七个库,第二章摸了一遍命令。这一章把三个工具的选项逐个过一遍。 3.2 讲 ffplay,3.3 讲 ffprobe,3.4 起回到 ffmpeg 自己的选项。

一、三个工具,三句话分工

这三个 exe 装在同一目录下,用的是同一批动态库:

工具 一句话职责 输入 输出


ffmpeg 把输入转成输出 文件/流/设备 文件/流 ffplay 把输入放给人看 文件/流/设备 屏幕+声音 ffprobe 把输入的信息打出来 文件/流 文本/JSON

它们认识的格式完全一样——因为格式识别这件事是 libavformat 干的,不是 exe 干的。 所以 ffprobe 能读出来的东西,ffmpeg 一定也能读,区别只在它顺手把东西转了。

反过来说,一个文件 ffprobe 读不出来,ffmpeg 也一定读不出来。 这就是为什么排障时先跑 ffprobe:它连解码都不做,能到的最远处就是格式识别。

二、命令是按段读的

一条 ffmpeg 命令分成三段,边界是两个「地标」:

段 起止地标 常见参数


全局 程序一启动就生效 -y -hide_banner -loglevel 输入 到下一个 -i 为止 -ss -t -r -f -stream_loop 输出 到下一个输出文件为止 -c:v -b:v -s -pix_fmt

段内顺序不影响含义,跨段才改变含义。 所以 -ss 写两遍可以,写一遍就得挑对位置——这是 2.1 讲过的那条规则。

这里要补一句 2.1 没说的:全局段在 Windows 上有个特殊写法。 -y 这类全局参数如果写在 -i 后面,某些版本会报「Unrecognized option」。 所以养成习惯:全局参数一律写在最前面,不要夹在中间。

三、退出码只有三种结局

脚本里判断成败只能看退出码,不能看日志里有没有 error 字样:

退出码 含义 典型触发


0 全部成功 正常转完 1 出错退出 文件不存在、参数不合法 其他 被信号打断 Ctrl+C、被系统杀掉

为什么不能看日志?因为 ffmpeg 遇到「可恢复的小问题」也会打 error。 比如某个元数据字段解析不了,它会打一行 error,然后照样把片子转完,退出 0。

反过来也有陷阱:-loglevel quiet 时一行日志都不打,但退出码照样是 0 或 1。 所以「静默 + 看退出码」是最稳的组合,也是所有自动化流水线的标准做法。

四、动手 Demo:三个外壳压在同一个内核上

沙箱里没有这三个 exe,但有它们链接的同一批库。 这一段用库把「三个工具各自的能力边界」和「这套构建的规模」量出来。

import av

# 三个工具共用这三个计数器
print(u"格式 %d 个" % len(av.formats_available))
print(u"编解码器 %d 个" % len(av.codecs_available))
print(u"滤镜 %d 个" % len(av.filter.filters_available))

# 七个库的版本,证明是同一份内核
for k, v in sorted(av.library_versions.items()):
    print(u"%-14s %s" % (k, v))

实跑输出:

--------------------------------------------------------------------
ffmpeg 工具简介——三个外壳压在同一个内核上
--------------------------------------------------------------------

**① 三个 exe,分工就三句话**

  工具        一句话职责             输入               输出
  --------------------------------------------------------------------
  ffmpeg      把输入转成输出         文件/流/设备     文件/流
  ffplay      把输入放给人看         文件/流/设备     屏幕+声音
  ffprobe     把输入的信息打出来     文件/流           文本/JSON

**这三个工具都链同一批 libav* 动态库,所以它们认识的格式一个不差。**
**ffprobe 能读出来的东西,ffmpeg 一定也能读,区别只在它顺手把东西转了。**

**② 一条命令是按「段」读的,不是按「参数顺序」读的**

  段       起止地标                 常见参数
  ---------------------------------------------------------------
  全局     程序一启动就生效         -y -hide_banner -loglevel
  输入     到下一个 -i 为止         -ss -t -r -f -stream_loop
  输出     到下一个输出文件为止     -c:v -b:v -s -pix_fmt

**同一段里参数的先后不影响含义,跨段才会改变含义。**
**这就是为什么 `-ss` 写两遍可以,写一遍就得挑对位置。**

**③ 退出码只有三种结局**

  退出码        含义           典型触发
  -------------------------------------------------------
  0             全部成功       正常转完
  1             出错退出       文件不存在、参数不合法
  非 0 其他     被信号打断     Ctrl+C、被系统杀掉

**脚本里判断成败只能看退出码,不能看日志里有没有 error 字样。**
**因为 ffmpeg 遇到可恢复的小问题也会打 error,但最后照样退出 0。**

**④ 本机这套库的规模,用一行代码就能数出来**

  库              暴露出来的条目     对应命令
  -----------------------------------------------
  libavformat     414 个格式         -formats
  libavcodec      557 个编解码器     -codecs
  libavfilter     468 个滤镜         -filters

**这三个数就是 `ffmpeg -formats`、`-codecs`、`-filters` 的行数。**
**它们不是「功能清单」,是「编译进这份二进制的东西」——换一份构建,三个数全变。**

这三个计数器就是 ffmpeg -formats、-codecs、-filters 的行数。 它们不是「功能清单」,是「编译进这份二进制的东西」——换一份构建三个数全变。

七个库的版本号也要看一眼:它们并不齐步走。 codec 和 format 都到了 62,可 filter 只有 11、swscale 只有 9—— 每个库自己记自己的接口变更次数,谁改得多谁号大。

「三个外壳同一个内核」这句话的实际意义在这里: 你在 ffprobe 里看到的时间基、帧率、像素格式,和 ffmpeg 编码时用的是同一套值。 所以 3.3 那几节读出来的数,可以直接拿来算 3.5 的参数。

30 秒实验

  1. 数一次:ffmpeg -version 最后那段 configuration 里全是 --enable-,数一遍就知道这份构建开了哪些开关。
  2. 比一次:ffmpeg -formats 和 ffprobe -show_format 对同一个文件,看两边的说法是否一致。
  3. 试一次错:故意给一个不存在的文件,echo $? 看退出码是不是 1。

常见误区

  1. 以为三个 exe 各有一套格式支持。它们链接同一批库,格式识别能力完全相同。区别只在「拿识别结果做什么」。
  2. 用日志里有没有 error 判断成败。可恢复的小问题也会打 error,但退出码仍是 0。只认退出码。
  3. 把全局参数写在中间。-y、-loglevel 这类必须写在最前面,夹在 -i 之间某些版本会报错。
自测三问(点开对答案) 1. 为什么 ffprobe 读不出来的文件,ffmpeg 也一定读不出来? —— 因为格式识别是 libavformat 干的,两个工具调的是同一份代码。读不出来说明这一层就没过,后面无从谈起。 2. 退出码为 0 是否代表完全没问题? —— 代表「程序认为任务完成了」。它可能带着警告完成,也可能只转出了一半就被容错机制收了尾。要更严就开 verbose 看日志。 3. 三个工具里哪个最安全地用来「检查一个文件」? —— ffprobe。它只读不写,跑一百遍也不会改坏文件;ffmpeg 一旦参数写错就可能覆盖掉东西。

相关笔记