同一份素材转 AVI:音频成功,视频失败。 失败的原因不是「AVI 不支持 H.264」,而是「写法不对」。
4.3.1 换盒子很顺,这一节会撞墙——撞的正好是 4.2.2 讲过的那个墙。 这一节把「格式不支持」这句话拆开看。
一、一次失败,一次成功,差在哪
同一条命令,只换「转哪条流」,结果完全不同:
转哪条流 结果 字节或原因
只转音频 成功 40156 只转视频 失败 InvalidData
第二行那个 InvalidData 一句话都不解释,但原因很具体:
AVI 要求视频流用起始码(Annex B),而 MP4 里存的是长度前缀(AVCC)。
所以「AVI 不支持 H.264」这句话是错的——AVI 能装 H.264,只是要 Annex B 写法。
加上 h264_mp4toannexb 过一遍,立刻就写进去了。
二、加一个位流滤镜就过了
两行只差一个滤镜:
做法 结果 字节
直接转视频 失败 — 加 annexb 滤镜后转 成功 37136
这就是「格式不支持」这句话要拆开看的原因:不支持的往往是「这种写法」。 分辨方法很简单——看报错是「不认这个名字」还是「不认这份数据」。
命令行里对应 -bsf:v h264_mp4toannexb,位置在输出侧。
这一条在把 mp4 喂给老设备、老软件时几乎是必备的。
三、AVI 的外壳与它的两个包袱
AVI 属于 RIFF 家族,头 12 个字节就能认出来:
位置 内容 含义
0~4 RIFF 这是 RIFF 家族的容器 8~12 AVI 加一个空格 具体是 AVI 这一种
RIFF 是 1992 年的格式,那个年代还没有「流」这个概念,只有「块」。 所以 AVI 里视频块和音频块是交替堆着的,时间戳粒度也比 MP4 粗。
包袱 表现
索引必须写在文件尾 录到一半断电,文件就废了 对 B 帧支持不好 老播放器可能花屏或卡顿
所以「转成 AVI 给老设备用」这件事,能成功不代表用得舒服。 真要给老设备,通常还要顺手把 B 帧关掉、把关键帧间隔调小。
四、动手 Demo:音频过得去,视频过不去,加滤镜就过去了
同一份素材,分别只转音频和只转视频到 AVI,看哪一半失败。 再给视频加一道 h264_mp4toannexb,看它从「拒绝」变成「写进去了」。
import av, os
def one(dst, keep):
c0 = av.open(u"m4.mp4")
c1 = av.open(dst, u"w", format=u"avi")
mp = {s.index: c1.add_stream_from_template(s)
for s in c0.streams if s.type == keep}
for pk in c0.demux():
if not pk.size or pk.stream.index not in mp:
continue
pk.stream = mp[pk.stream.index]
c1.mux(pk)
c1.close()
c0.close()
return os.path.getsize(dst)
for keep in (u"audio", u"video"):
try:
print(u"%s 成功 %d" % (keep, one(u"o_%s.avi" % keep, keep)))
except Exception as e:
print(u"%s 失败 %s" % (keep, type(e).__name__))
# 视频先过一遍 h264_mp4toannexb 再封装
c0 = av.open(u"m4.mp4")
c1 = av.open(u"ok.avi", u"w", format=u"avi")
st = c0.streams.video[0]
tgt = c1.add_stream_from_template(st)
bsf = av.BitStreamFilterContext(u"h264_mp4toannexb", st)
for pk in c0.demux(video=0):
if not pk.size:
continue
for q in bsf.filter(pk):
q.stream = tgt
c1.mux(q)
c1.close()
c0.close()
print(u"加滤镜后 %d 字节" % os.path.getsize(u"ok.avi"))
实跑输出:
--------------------------------------------------------------------
4.3.2 MP4 转 AVI——失败的那一半正好是上一节讲的
--------------------------------------------------------------------
**① 直接转:音频成功,视频失败**
转哪条流 结果 字节或原因
-----------------------------------------------------
只转音频 成功 40156
只转视频 失败 InvalidData(拒绝这种写法)
**视频失败的原因不是「AVI 不支持 H.264」,而是「AVI 要求起始码,MP4 里存的是长度前缀」。**
**这正是 4.2.2 那个 `h264_mp4toannexb` 要解决的问题——先换写法,再封装。**
**② 先过一遍 h264_mp4toannexb,再转就成功**
做法 结果 字节
-----------------------------------------
直接转视频 失败 —
加 annexb 滤镜后转 成功 37136
**两行只差一个位流滤镜,结果从「拒绝」变成「写进去了」。**
**所以「格式不支持」这句话要看清楚:不支持的往往是「这种写法」,不是「这种编码」。**
**③ AVI 的外壳是 RIFF,结构一目了然**
位置 内容 含义
---------------------------------------
0~4 RIFF RIFF 家族的容器
8~12 AVI 具体是 AVI 这一种
**RIFF 是 1992 年的格式,那个年代还没有「流」这个概念,只有「块」。**
**所以 AVI 里视频块和音频块是交替堆着的,时间戳粒度也比 MP4 粗。**
**④ AVI 的两个历史包袱**
包袱 表现
---------------------------------------------------
索引必须写在文件尾 录到一半断电,文件就废了
对 B 帧支持不好 老播放器可能花屏或卡顿
**所以「转成 AVI 给老设备用」这件事,能成功不代表能用得舒服。**
**真要给老设备,通常还要顺手把 B 帧关掉、把关键帧间隔调小。**
音频那一行成功、视频那一行抛 InvalidDataError,这就是本节的全部现象。
很多教程到这里就说「AVI 不支持 H.264」,那是把结论说反了——它支持,只是要 Annex B。
加滤镜那一段有两个细节:bsf.filter(pk) 返回列表要遍历,
每个输出包还要把 stream 改指向目标流,少一步都会报错。
结果 37136 字节,比原来的 mp4 小一点——因为 AVI 的索引比 MP4 的 moov 省。 这也顺带说明:换盒子时字节的差值是「记账方式」的差,不是画质的差。
30 秒实验
- 直接转:
ffmpeg -i 片子.mp4 -c copy 出.avi,看它报什么。 - 加滤镜再转:
ffmpeg -i 片子.mp4 -c:v copy -bsf:v h264_mp4toannexb 出.avi。 - 看头:
xxd -l 16 出.avi,前四个字节应是RIFF,8~12 是AVI。
常见误区
- 以为 AVI 不支持 H.264。支持,只是要求 Annex B 写法,加个位流滤镜就行。
- 以为转成功了就能给老设备用。B 帧与索引位置这两个包袱还在,可能要额外关 B 帧。
- 把「格式不支持」当成一句话。要分清是不认编码、不认写法,还是不认参数组合。
自测三问(点开对答案)
1. MP4 转 AVI 时视频为什么会失败? —— AVI 要求视频用 Annex B 起始码写法,而 MP4 里存的是 AVCC 长度前缀,封装器拒绝。 2. 怎么让它成功? —— 先过一遍 `h264_mp4toannexb` 位流滤镜换成起始码写法,再封装。 3. AVI 的两个历史包袱是什么? —— 索引必须写在文件尾(中断就废),以及对 B 帧支持不好(老播放器可能花屏)。- ☐ (本节完,下一节 4.3.3 其他格式转换)