faststart 只做一件事:把 moov 挪到 mdat 前面。 字节数几乎不变,因为它只是重排,不是重压。
4.4.1 看清了默认顺序的毛病,这一节就是那台手术。 也是第四章的收尾:所有盒子的知识,最后都落到「这个文件能不能边下边看」。
一、两次转封装,只差一个参数
同一份素材,一次默认、一次加 faststart:
文件 字节 盒子顺序
默认转封装 37982 ftyp → free → mdat → moov 加 faststart 37982 ftyp → moov → free → mdat
字节完全相同,只有顺序变了——这是「重排不重压」最直接的证据。 moov 从第 4 位挪到第 2 位,mdat 被推到后面去了。
命令行里就是 -movflags +faststart,注意前面有个加号。
这个参数是 MP4 封装器私有的,别的盒子没有对应的东西。
二、它到底做了什么
三步,全在文件层面,不碰码流:
步骤 做什么 代价
1 先按常规写完整个文件 同普通转封装 2 把 moov 单独留在内存或临时区 索引有多大就占多少 3 最后整体重写一遍,把 moov 放最前 多一次全文件写盘
第三行是唯一真实的代价:文件要重写一遍。 所以大文件加 faststart 会明显变慢,尽管它没有重编码。
这也解释了为什么「字节不变但耗时变长」——它在磁盘上多做了一遍搬运。 很多人以为 faststart 很轻,其实它对 I/O 是有实际要求的。
三、什么时候该加,什么时候别加
判断只有一句话:这个文件是「边下边看」还是「下完再看」。
场景 要不要 理由
网页播放 要 浏览器边下边播 手机端拖动 要 要能跳到任意位置 本地存档 不必 整份在硬盘上,索引在哪都一样 还要再转码 不必 下一趟会被整个重写
第三四行是最常见的浪费:本地素材加了 faststart,白多一次全文件重写。 尤其是「先转 mp4 再转别的」这种流水线,中间那次加了完全没用。
所以这条参数应该放在「流水线的最后一步」,而不是每一次转封装都加。 这也是第四章的收尾结论:所有盒子知识最后都落到「给谁看、怎么看」。
四、动手 Demo:加一个 movflags,看 moov 从第 4 位挪到第 2 位
同一份素材转两次:一次默认,一次在输出容器上带 movflags=faststart。
然后各自再数一遍顶层盒子,对比顺序与字节。
import av, os, struct
def remux(dst, opts):
c0 = av.open(u"m4.mp4")
c1 = av.open(dst, u"w", format=u"mp4", options=opts)
mp = {s.index: c1.add_stream_from_template(s) for s in c0.streams}
for pk in c0.demux():
if not pk.size:
continue
pk.stream = mp[pk.stream.index]
c1.mux(pk)
c1.close()
c0.close()
remux(u"plain.mp4", {})
remux(u"fast.mp4", {u"movflags": u"faststart"})
def order(p):
out, off = [], 0
total = os.path.getsize(p)
with open(p, u"rb") as f:
while off + 8 <= total and len(out) < 8:
f.seek(off)
h = f.read(8)
size = struct.unpack(u">I", h[:4])[0]
out.append(h[4:8].decode(u"latin-1"))
off += size
return out
for p in (u"plain.mp4", u"fast.mp4"):
print(u"%-10s %d 字节 %s"
% (p, os.path.getsize(p), u" → ".join(order(p))))
实跑输出:
--------------------------------------------------------------------
4.4.2 faststart——把索引挪到最前面
--------------------------------------------------------------------
**① 同一份素材,带不带 faststart 转两遍**
文件 字节 盒子顺序
--------------------------------------------------------
默认转封装 37982 ftyp → free → mdat → moov
加 faststart 37982 ftyp → moov → free → mdat
**② 两个文件的字节数几乎一样,顺序却不一样**
项 默认 faststart
---------------------------------------
moov 排第几 4 2
mdat 排第几 3 4
字节 37982 37982
**第三行的两个数几乎相等,说明 faststart 只是「重排」:**
**把索引写到一个临时位置,最后再整体搬到文件头,数据区一个字节没重压。**
**③ 什么时候必须加,什么时候加了没意义**
场景 要不要 faststart
-----------------------------------------------
网页播放、边下边播 要
手机端快进拖动 要
本地存档、剪辑素材 不必
准备再转一次码 不必,下一趟会被重写
**判断标准只有一句:这个文件是「边下边看」还是「下完再看」。**
**前者必须加,后者加了只是白白多一次文件重写。**
options={"movflags": "faststart"} 是容器级选项,
对应命令行的 -movflags +faststart;这里是字典,所以不用写那个加号。
两个文件字节完全相同(37982),顺序却是 mdat → moov 对 moov → mdat。
字节相同这件事本身就排除了「重编码」的可能。
order() 里那个 limit=8 是防死循环的小保险:
一旦遇到长度字段为 0 的坏盒子,没有上限就会一直读下去。
30 秒实验
- 转一次:
ffmpeg -i 片子.mp4 -c copy -movflags +faststart 出.mp4。 - 验顺序:用十六进制工具看前 64 字节,
moov应出现在mdat之前。 - 对比大小:两个文件大小几乎相同,说明没有重编码。
常见误区
- 以为 faststart 能减小体积。它只重排,字节几乎不变,甚至会略大几个字节。
- 在流水线中间就加 faststart。后面还要转码的话,这次重写完全是白费。
- 以为对所有格式都有效。它是 MP4 封装器私有选项,别的盒子没有对应参数。
自测三问(点开对答案)
1. faststart 改了文件的什么? —— 只改顶层盒子的顺序,把 moov 从 mdat 后面挪到前面。 2. 为什么字节数几乎不变? —— 因为只是重排,没有任何数据被重新编码,大小自然不变。 3. 它的真实代价是什么? —— 文件要整体重写一遍,大文件时 I/O 开销明显,尽管不耗 CPU 编码。- ☐ 第四章完,第五章从「音视频转码」开始