format 段回答「这个文件整体是什么」。 duration 为 N/A、码率离谱地小,这两个异常最值得盯。
3.3.1 讲的是容器怎么切包,这一节跳到最外层看整个文件。 3.3.3 再往下钻到最细的帧。
一、五个关键字段
format 段的字段不多,但每一个异常都有明确指向:
字段 含义 异常值意味着
format_name 容器名,可能一串别名 空或 unknown duration 时长(秒) N/A,索引坏了 size 文件字节数 比实际文件小 bit_rate 平均码率 离谱地小 nb_streams 流条数 比你预期少
第二行是最常用的诊断点:duration 是 N/A,几乎总是索引坏了。 那种文件能播开头,但进度条拖不动,ffmpeg 转它会在结尾出错。
第五行的「比预期少」也很典型:你以为转出来带音频,结果 nb_streams 是 1。
原因通常是忘了 -map——3.9 会专门讲这个。
二、format_name 那一串逗号是什么
第一次看到 mov,mp4,m4a,3gp,3g2,mj2 会以为有六个格式,其实是一个:
字段 值 怎么读
format_name 一串逗号分隔的名字 同一个组件的所有别名 long_name QuickTime / MOV 人看的正式名 extensions mp4/m4a/mov 等 它认哪些后缀
所以判断「这是什么容器」要看 long_name,别看 format_name 那一串。 那一串是给程序匹配用的,不是给人读的。
这也解释了为什么 .m4a 和 .mp4 由同一个组件处理:它们本来就是同一个容器, 只是约定用不同后缀区分「里面装的是音频还是视频」。
三、三个码率不是一回事
「码率」这个词在不同位置含义不同,混用会算错带宽:
说法 算的是什么 什么时候看
文件码率 总字节 ×8 ÷ 总时长 估传输要多久 视频码率 视频字节 ×8 ÷ 总时长 估画质 音频码率 音频字节 ×8 ÷ 总时长 估音质
注意单位:ffprobe 的 bit_rate 是「比特每秒」,而文件大小按字节算,差 8 倍。
这个 8 倍是算带宽时最常见的错误来源——报了个字节数当带宽,差了整整八倍。
第一行也不等于第二三行之和:容器本身还有索引、头、元数据的开销。 文件越小这个差值占比越大,几百 KB 的片子容器开销能占好几个百分点。
四、动手 Demo:一个文件最有用的几个数
把一份素材的 format 段原样打出来,并亲手算一遍文件码率。 重点是看 format_name 那一串别名,以及码率的单位换算。
import os
import av
c = av.open(u"src.mp4")
f = c.format
dur = float(c.duration) / 1000000
nb = len(c.streams)
c.close()
sz = os.path.getsize(u"src.mp4")
print(u"format_name %s" % f.name)
print(u"long_name %s" % f.long_name)
print(u"duration %.2f 秒" % dur)
print(u"size %d 字节" % sz)
print(u"bit_rate %.0f bit/秒" % (sz * 8 / dur))
print(u"nb_streams %d" % nb)
# 别名与扩展名
ext = getattr(f, u"extensions", None)
print(u"扩展名 %d 个:%s"
% (len(ext) if ext else 0,
u"、".join(sorted(ext)[:6]) if ext else u"无"))
实跑输出:
--------------------------------------------------------------------
ffprobe -show_format——一个文件最有用的几个数
--------------------------------------------------------------------
**① format 段里的关键字段**
字段 含义 常见异常值
----------------------------------------------------------
format_name 容器名,可能一串别名 空或 unknown
duration 时长(秒) N/A,索引坏了
size 文件字节数 比实际小
bit_rate 平均码率 离谱地小
nb_streams 流条数 比你预期少
**第二列的异常值不是随便举的:duration 为 N/A 是 mp4 索引损坏最典型的表现。**
**那种文件能播开头,但进度条拖不动,ffmpeg 转它也会在结尾出错。**
**② 本机这份素材的 format 段**
字段 值
-------------------------------------------
format_name mov,mp4,m4a,3gp,3g2,mj2
long_name QuickTime / MOV
duration 5.00 秒
size 9606 字节
bit_rate 15370 bit/秒
nb_streams 1
**`format_name` 那一串逗号是别名,不是多个格式——mp4、m4a、mov 都由同一个组件处理。**
**所以判断「这是什么容器」要看 long_name,别看 format_name 那一串。**
**③ 码率这个数最容易误会**
说法 算的是什么 什么时候看
---------------------------------------------------
文件码率 总字节 ÷ 总时长 估传输要多久
视频码率 视频字节 ÷ 总时长 估画质
音频码率 音频字节 ÷ 总时长 估音质
**三个数加起来不等于第一个:容器本身还有索引、头、元数据的开销。**
**文件越小,这个差值占比越大——几百 KB 的片子,容器开销能占好几个百分点。**
**④ duration 是从哪来的**
**mp4 的时长记在文件尾的索引里,所以能直接读出来,不用解码。**
**而有些格式(比如裸 H.264 流)根本没有时长这个字段,ffprobe 只能报 N/A。**
**那种情况下想算时长,只能把整片解一遍数帧数——这就是它慢的原因。**
format_name 那一串逗号是别名,六个名字对应一个组件。
所以判断容器类型要看 long_name,那一串是给程序做匹配用的。
bit_rate 那一行是「字节数 × 8 ÷ 秒数」,单位是比特每秒。
忘了乘 8 就会把带宽算小八倍——这是报「传输要多久」时最常见的错。
扩展名那一行说明了「猜格式」的依据:demuxer 自己声明认得哪些后缀。 所以改扩展名不影响识别,因为真正的判断靠文件头,不靠后缀。
30 秒实验
- 看一次:
ffprobe -show_format 片子.mp4,重点确认 duration 不是 N/A。 - 算一次:拿文件大小乘 8 除时长,和 ffprobe 报的 bit_rate 对一下,看差多少。
- 造一个坏文件:
truncate -s 一半大小 片子.mp4,再看 duration 是不是变成 N/A。
常见误区
- 把 bit_rate 当字节每秒。它是比特每秒,差 8 倍。报带宽时算错这一项最要命。
- 以为 format_name 那串逗号是多个格式。那是同一个组件的多个别名。
- duration 是 N/A 就以为文件废了。能用
-c copy重封装救回来,索引会重建。
自测三问(点开对答案)
1. `duration` 显示 N/A 通常说明什么? —— 容器的索引坏了或缺失。这种文件能播开头但拖不动进度条,用 `-c copy` 重封装通常能修好。 2. 为什么 format_name 是一串逗号分隔的名字? —— 那是同一个组件的多个别名。mp4、m4a、mov 都由这个组件处理,所以列在一起。 3. 文件码率怎么算?单位是什么? —— 文件字节数 × 8 ÷ 时长,单位是比特每秒。乘 8 是因为字节和比特差八倍。- ☐ (本节完,下一节 3.3.3 show_frames)