同一个编码器、同一批帧,写进 AVI 比写进 MP4 大了一倍。 差的是封装层,不是编码器——比较效率必须在同一个容器里比。
第四章转封装到 AVI 时撞过 AVCC 的墙,这一节改用重编码,墙没了。 但新的问题来了:换个盒子,同样的编码体积会变。
一、三行对比:换编码与换容器
同一批帧,三种组合:
容器 编码 字节
AVI mpeg4 16708 AVI libx264 11038 MP4 libx264 5150
第一二行是同一个容器换编码:MPEG-4 比 H.264 大了一半,这就是换代的价值。 但老设备只认 MPEG-4 的时候,你只能接受这个代价。
第二三行更值得注意:同一个编码器、同一档参数、同一批帧,只换个容器,字节差一倍。 差的是封装层——AVI 没有放参数集的规范位置,编码器只能把参数集一路重发。
所以比较编码效率时,一定要在同一个容器里比,否则结论全被封装带偏。
二、重编码能进,搬包进不去
同一个 AVI、同一种编码,两种做法的结果完全相反:
做法 结果
重新编码成 H.264 成功 从 MP4 直接搬包 InvalidDataError
差别在「写法」:编码器吐出来的是起始码(Annex B), 而 MP4 里存的是长度前缀(AVCC)。AVI 只认前者。
所以搬包要过 h264_mp4toannexb 才成,重新编码反而不用管——
因为编码器本来就是从 Annex B 开始写的。
这就是为什么「同一种编码」不等于「同一种数据」。
三、AVI 的两个老包袱
它当年很好用,现在只剩两个明显短板:
包袱 后果
索引放在文件末尾 没下完就不能拖进度 全局头不标准 扩展参数经常丢
第一条在本地播放无所谓,放到网络上就成了硬伤: 播放器必须先拿到整个文件尾部,才能算出每一帧在哪里。
第二条更坑:AVI 里的 H.264 常常缺 SPS/PPS 的规范位置,换个播放器就黑屏。 所以今天除非对方明确要求 AVI,否则不该主动选它。
四、动手 Demo:同一批帧写进 AVI 与 MP4,比字节
先用 MPEG-4 与 H.264 各写一个 AVI,再用同一个 H.264 写一个 MP4。 最后试一次「从 MP4 搬包进 AVI」,看它怎么拒绝。
import av, os
def to(dst, fmt, codec):
c1 = av.open(dst, u"w", format=fmt)
s1 = c1.add_stream(codec, rate=25)
s1.width, s1.height, s1.pix_fmt = 160, 90, u"yuv420p"
c0 = av.open(u"s5.mp4")
for f in c0.decode(video=0):
fr = f.reformat(width=160, height=90, format=u"yuv420p")
for o in s1.encode(fr):
c1.mux(o)
for o in s1.encode(None):
c1.mux(o)
c1.close()
c0.close()
return os.path.getsize(dst)
print(u"avi mpeg4 %d" % to(u"m.avi", u"avi", u"mpeg4"))
print(u"avi h264 %d" % to(u"h.avi", u"avi", u"libx264"))
print(u"mp4 h264 %d" % to(u"h.mp4", u"mp4", u"libx264"))
try:
c0 = av.open(u"s5.mp4")
c1 = av.open(u"copy.avi", u"w", format=u"avi")
mp = {s.index: c1.add_stream_from_template(s) for s in c0.streams}
for pk in c0.demux(video=0):
if pk.size:
pk.stream = mp[pk.stream.index]
c1.mux(pk)
c1.close()
except Exception as e:
print(u"搬包失败:%s" % type(e).__name__)
实跑输出:
--------------------------------------------------------------------
5.4.3 MP4 格式转码为 AVI
--------------------------------------------------------------------
**① MPEG-4 与 H.264 两条路各转一遍**
容器 编码 字节
---------------------------
AVI mpeg4 16708
AVI libx264 11038
MP4 libx264 5150
**第一二行是同一个容器换编码:MPEG-4 比 H.264 大了 1.5 倍,**
**这就是换代的价值——同样的画质,位数少一半。**
**但老设备只认 MPEG-4 的时候,你只能接受这个代价。**
**第二三行更值得注意:同一个编码器、同一档参数、同一批帧,**
**只换个容器,字节就从 5150 变成 11038。**
**差的是封装层:AVI 没有放参数集的规范位置,编码器只能把参数集一路重发。**
**所以比较编码效率时,一定要在同一个容器里比,否则结论全被封装带偏。**
**② 同一个 AVI,搬包就进不去**
做法 结果
--------------------------------------
重新编码成 H.264 成功,11038 字节
从 MP4 直接搬包 InvalidDataError
**同一个容器、同一种编码,一个能进一个不能进——差别在「写法」:**
**编码器吐出来的是起始码(Annex B),而 MP4 里存的是长度前缀(AVCC)。**
**AVI 只认前者,所以搬包要过 `h264_mp4toannexb` 才成,重新编码反而不用管。**
**③ AVI 的两个老包袱**
包袱 后果
----------------------------------------
索引放在文件末尾 没下完就不能拖进度
全局头不标准 扩展参数经常丢
**第二条尤其坑:AVI 里的 H.264 常常缺 SPS/PPS 的规范位置,换个播放器就黑屏。**
**所以今天除非对方明确要求 AVI,否则不该主动选它。**
三个文件用的是同一批解码帧,所以三行字节的差别只能来自两个变量:编码与容器。
add_stream_from_template 会原样抄走编码参数与附加数据。
抄不走的是「写法」:MP4 里的长度前缀到了 AVI 里没人认。
如果真要把 MP4 的 H.264 搬进 AVI,得先过 h264_mp4toannexb 位流滤镜。
这一步在 4.2.2 里讲过,代码里对应 BitStreamFilterContext。
30 秒实验
- 命令行对照:
ffmpeg -i 片子.mp4 -c:v mpeg4 出.avi。 - 只换盒子:
-c copy,看 InvalidData。 - 加滤镜:
-c copy -bsf:v h264_mp4toannexb,这次能成。
常见误区
- 跨容器比编码效率。AVI 里同样的 H.264 比 MP4 大一倍,比出来的结论是假的。
- 以为「H.264 不能放 AVI」。能放,只是要 Annex B 写法。
- 拿 AVI 做网络分发。索引在末尾,必须下完才能拖。
自测三问(点开对答案)
1. 为什么同样的 H.264 写进 AVI 比 MP4 大? —— AVI 没有放参数集的规范位置,编码器只能把 SPS/PPS 一路重发。 2. 为什么重编码能进 AVI,搬包反而不能? —— 编码器吐的是起始码写法,MP4 里存的是长度前缀写法,AVI 只认前者。 3. AVI 最不适合什么场景? —— 边下边播、在线拖动。它的索引在文件末尾,必须整份拿到才能定位。- ☐ (本节完,下一节 5.4.4 MP4 格式转码为 TS 格式)