ffprobe 是「不问不知道」的工具:四层信息,四个开关。 它的价值不只是给人看,更在于 -print_format json 让它变成程序接口。
2.1 到 2.5 都在「产出」,这一节换个方向——先看清手上是什么。 排障时它通常是第一条该敲的命令。
一、四种问法,四层信息
ffprobe 的参数不多,常用的就四个,各看一层:
参数 看哪一层 信息量
-show_format 整个文件 时长、码率、容器名 -show_streams 每条流 编码、分辨率、时间基 -show_packets 每一个包 几百上千行 -show_frames 每一帧 要全解一遍,最慢
从上到下越来越细,也越来越慢。
-show_format 几乎瞬间返回;-show_frames 要把整片解一遍。
所以排障的顺序也应该从上往下:先看 format 有没有时长, 再看 streams 的编码对不对,最后才去看 packet 和 frame。
二、这四层各自能回答什么问题
举个具体的例子,同样一句「这个文件放不了」,四层给出四种答案:
层 能看出什么 典型问题
format 容器认不认、时长有没有 时长是 N/A,说明索引坏了 stream 编码是不是本机支持的 编码名很冷门,播放器不认 packet 时间戳从几开始、有没有跳变 pts 从 10000 起,播放就卡 frame 解码出来的画面正不正常 帧数是 0,说明解码失败
第三行是很经典的一类问题:pts 不从 0 开始。 很多播放器假设第一帧的 pts 是 0,结果拿到 10000 就开始算错,表现是开头卡住几秒。
第四行的「帧数 0」则说明问题在解码器上,不在容器上—— 这两层的区别很重要,决定了你去换播放器还是换文件。
三、加一个开关,工具变接口
-print_format json 是 ffprobe 最被低估的一个参数。
加了它,输出就从「给人看的表格」变成「给程序解析的结构」。
输出格式 适合谁 为什么
默认文本 人眼扫一眼 字段对齐,看着舒服 json 程序 结构明确,不怕字段顺序变 flat shell 脚本 一行一个键值,grep 好切
做自动化流水线时,解析 JSON 比用正则切字符串可靠一个数量级。 因为 JSON 的字段顺序变了不影响解析,而切字符串的方案一改版就崩。
这个区别在长期维护里特别明显: 用 JSON 的脚本,ffmpeg 升级几版都不用动;用文本切的脚本,每次升级都可能要改。
四、动手 Demo:四个视图
拿同一份素材,用四个层次分别探一遍,看每一层能拿到什么。 这也是排障时的固定动作:从粗到细,一层一层往下确认。
import av
c = av.open(u"src.mp4")
# 第一层:整个文件(相当于 -show_format)
print(u"容器 %s" % c.format.name)
print(u"时长 %.0f 微秒" % float(c.duration))
print(u"流数 %d" % len(c.streams))
# 第二层:每条流(相当于 -show_streams)
st = c.streams.video[0]
print(u"编码 %s %dx%d" % (st.codec_context.name, st.width, st.height))
print(u"时间基 1/%d" % st.time_base.denominator)
# 第三层:每个包(相当于 -show_packets)
npk = 0
total = 0
biggest = 0
for pk in c.demux(video=0):
if not pk.size:
continue
npk += 1
total += pk.size
biggest = max(biggest, pk.size)
print(u"包 %d 个,合计 %d 字节,最大 %d" % (npk, total, biggest))
c.close()
# 第四层:每一帧(相当于 -show_frames,最慢)
c = av.open(u"src.mp4")
nf = sum(1 for _ in c.decode(video=0))
c.close()
print(u"解码出 %d 帧" % nf)
实跑输出:
--------------------------------------------------------------------
ffprobe 获取视频信息——四个视图
--------------------------------------------------------------------
**① ffprobe 的四种问法,对应四层信息**
参数 看哪一层 信息量
-------------------------------------------------------
-show_format 整个文件 时长、码率、容器名
-show_streams 每条流 编码、分辨率、时间基
-show_packets 每一个包 几百上千行
-show_frames 每一帧 几百上千行
**② 本机这份素材的四层信息**
层 看到了什么
----------------------------------------------------------------------
format 容器 mov,mp4,m4a,3gp,3g2,mj2,时长 5000000 微秒,1 条流
stream h264 160×90,时间基 1/12800
packet 125 个包,合计 7696 字节,最大一个 731 字节
frame 解码后才看得到,125 帧
**③ 拿这一步做排障的固定动作**
**不确定参数怎么写的时候,先跑 -show_streams 看时间基和像素格式,**
**再跑 -show_packets 看时间戳是不是从 0 开始。**
**这两条能把「输出文件时间戳错乱」这类问题定位掉大半。**
**④ 加上 -print_format json,输出就能直接给程序用**
**这是 ffprobe 最被低估的一项:它让 ffprobe 从「给人看的工具」变成「给程序用的接口」。**
**做自动化流水线时,解析 JSON 比切字符串可靠一个数量级。**
四段代码对应四个开关,一层比一层细,也一层比一层慢。
最后那一行 sum(1 for _ in decode()) 要把整片解一遍,所以它最贵。
这一次包数和帧数正好都是 125,看着一样大。但两者不是同一个概念。
demux 切出来的是容器的段,里面还带零长度的结束标记;decode 出的是真实画面。
含 B 帧的片子上,这两个数就会对不上,包数通常会多一点。
第三层那个「最大一个多少字节」很实用: 如果最大包大得离谱(比如几十 MB),基本可以确定有一帧是关键帧且塞满了细节, 这种片子在弱设备上播放容易在那一帧卡一下。
还有一点:如果第四层解出的帧数是 0,说明解码器根本没出帧—— 这跟「容器坏了」是两个完全不同的问题,处理方向也不一样。
30 秒实验
- 看一遍:
ffprobe -show_streams 输入.mp4,重点看time_base、pix_fmt、bit_rate三项。 - 给程序用:
ffprobe -v error -print_format json -show_format -show_streams 输入.mp4,把结果丢给 Python 解析。 - 查时间戳:
ffprobe -show_packets -select_streams v 输入.mp4 | head -20,看第一个包的 pts 是不是 0。
常见误区
- 排障先看 frame 层。它最慢,而且大部分问题在 format 和 stream 两层就看清了。从粗往细查。
- 用正则切 ffprobe 的文本输出。字段顺序和缩进都可能变,一升级脚本就崩。要程序读就用 JSON。
- 把包数当成帧数。包数是容器里切出来的段数,含零长度包;帧数要解码后才知道,通常更少。
自测三问(点开对答案)
1. 为什么排障要从 `-show_format` 开始,而不是直接 `-show_frames`? —— 因为它最快,而且很多问题在这一层就露出来了(比如时长是 N/A 说明索引坏了)。从粗到细查,省时间。 2. pts 不从 0 开始会有什么表现? —— 不少播放器假设第一帧 pts 是 0,拿到大数值就会算错进度,表现为开头卡住几秒或者进度条乱跳。 3. `-print_format json` 解决了什么实际问题? —— 让脚本能稳定解析。文本输出的字段顺序和缩进会随版本变,JSON 的结构不随顺序变,升级不用改脚本。- ☐ (本节完,下一节 2.7.1 Y4M 格式简介)