转封装(remux):解码器和编码器都不参与,只换盒子。 实测比重新编码快二十多倍,画质一字不改。
上一节是「编码 + 封装」,这一节只有「封装」。流水线从四步变成两步。 时间基的换算问题在这一节只是露个头,正题是 04.06.03。
一、只搬包,不动像素
| 环节 | 编码+封装 | 转封装 |
|---|---|---|
| 读包 | 要 | 要 |
| 解码 | 不要 | 不要 |
| 编码 | 要 | 不要 |
| 写包 | 要 | 要 |
中间两步整段拿掉,所以快得离谱——它只搬字节,不碰像素。 画质也一字不改:数据本身没变,连重新量化的机会都没有。
二、裸流的包没有时间戳
上一节读出来的首包 pts 是 None,因为 h264 裸流不存时间戳。
所以转封装的第一步不是搬包,是「补时间戳」:按帧率自己编号。
step = 时间基分母 ÷ 帧率 # 一帧走多少刻度
pk.pts = k * step # 第 k 帧
pk.dts = pk.pts # 裸流没有 B 帧重排信息,直接相等
这次的样本是 1/1200000 的时间基、30 fps,所以一帧走 40000 个刻度。 40000 ÷ 1200000 = 1/30 秒,正好对上一帧的时长。
三、实测:两条路的差距
| 指标 | 转封装 | 重编码 |
|---|---|---|
| 包数 | 30 | 30 |
| 时长 | 1.00 秒 | 1.00 秒 |
| 文件大小 | 11.4 KB | 11.6 KB |
| 耗时 | 几毫秒 | 几十毫秒 |
| 画质 | 一字不改 | 重新压一遍 |
耗时差二十多倍,而且这个差距会随片长拉大——转封装的耗时基本只和文件大小成正比, 转码还要加上每帧的计算。 所以「换个格式」这种需求,一律先想能不能转封装,别急着转码。
文件大小几乎一样(11.4 对 11.6 KB):因为里头装的是同一份编码数据,差的只是索引写法。
四、转封装不是万能的
它要求目标容器认得原来的编码格式。
| 编码 | 能进的容器 | 不能进 |
|---|---|---|
| H.264 | MP4、MKV、TS、FLV | WebM |
| H.265 | MP4、MKV、TS | WebM、FLV |
| VP9 | WebM、MKV | MP4(老播放器) |
H.264 进 WebM 是常见误区:WebM 只收 VP8/VP9/AV1,必须转码,没有捷径。 同理,音频也一样(AAC 进 Ogg 就不行)。判断标准就一句:容器规格里有没有这个编码的登记。
五、动手 Demo:把现成的 H.264 裸流封成 MP4,一帧都不重编码
Demo 分两条路:一条只搬包(转封装),一条老老实实重编码,两边计时对比。 裸流要自己补时间戳,否则 MP4 里时长会算错。
c = av.open("in.h264")
s = c.streams.video[0]
o = av.open("out.mp4", "w")
st = o.add_stream_from_template(s) # 照抄编码参数,不重编
step = st.time_base.denominator // 30
k = 0
for pk in c.demux(s):
if pk.size == 0:
continue
pk.pts = pk.dts = k * step # 补时间戳
pk.time_base = st.time_base
o.mux(pk)
k += 1
o.close()
实跑输出:
--------------------------------------------------------------------
把现成的 H.264 裸流封成 MP4,一帧都不重编码
--------------------------------------------------------------------
**这件事叫「转封装」(remux):解码器和编码器都不参与,只换盒子。**
流水线从原来的四步变成两步:**读包 → 写包**。中间的「解码」「编码」整段拿掉。
**裸流的包没有时间戳**(上一节读出来是 `None`),所以转封装的第一步不是搬包,
是 **「补时间戳」**:按帧率自己编号。
**实测两条路:**
指标 转封装 重编码
------------------------------------------------------------
输入包数 30 30
输出包数 30 30
解出帧数 30 30
时长 1.00 秒 1.00 秒
文件大小 11.4 KB 11.6 KB
耗时 2.6 毫秒 62.9 毫秒
画质 一字不改 重新压一遍
**耗时那一行是本节的结论**:转封装二十倍于重编码,因为它只搬字节。
**片子里只有 30 帧,转码那点活儿还不够垫,真要上千帧差距会拉到几十倍以上。**
**所以「换个格式」这种需求,一律先想能不能转封装,别急着转码。**
**但要注意:转封装不是万能的**,它要求目标容器认得原来的编码。
**H.264 进 MP4、进 MKV、进 TS 都可以;H.264 进 WebM 就不行**,因为 WebM 只收 VP8/VP9/AV1。
**时间基这一栏要单独看,这是下一节(04.06.03)的正题:**
环节 时间基 说明
------------------------------------------------------------
裸流轨 1/1200000 FFmpeg 给的默认值
输出轨 1/1200000 照抄模板,不变
补时间戳步长 40000 一帧走 40000 个刻度
读回来的轨 1/1200000 MP4 实际存的
**这一栏四个时间基全是 1/1200000**,说明「照抄模板」确实一个刻度都没换。
**真正要看的是「步长 40000」**:40000 ÷ 1200000 = 1/30 秒,一帧走这么多刻度,正好对上 30 fps。
**换个时间基这个数就废了**:如果输出轨的时间基是 1/15360,一帧只要走 512 个刻度,
**把 40000 搬过去,时间轴会慢 78 倍**。
**这次能直接用模板的时间基,是因为输出也走同一套参数。**
**一旦目标容器强制自己的时间基(MP4 常用 1/15360,TS 用 1/90000),就必须换算**,
04.06.03 会给一个「不换算会错成什么样」的实测。
add_stream_from_template 是关键:它把编码参数原样抄进输出轨,
所以容器知道这是 H.264、多大、什么 profile,但不需要编码器参与。
四条路里最省事的是它,最危险的是「补时间戳」这一步——补错就是时长错,而且文件照样能打开。
30 秒实验
- 把
step改成 4000(少一个零),看读回来的时长变成多少。 - 把
pk.dts删掉只给pk.pts,看 MP4 能不能写成功。 - 把输出格式改成
webm,看报什么错——这就是「容器不认这个编码」的样子。
常见误区
- 以为转封装也要解码。 它只搬包,这正是它快二十多倍的原因。
- 以为裸流搬过去时间戳会自动补。 不会,必须自己按帧率编号。
- 以为任何编码都能进任何容器。 WebM 不收 H.264,没有捷径,只能转码。
自测三问(点开对答案)
1. 转封装为什么快? —— 解码和编码两步整段拿掉,**只搬字节**,耗时基本只和文件大小成正比。 2. 裸流转封装前必须做什么? —— **补时间戳**。裸流不存 pts,不补的话容器里时长会算错。 3. H.264 能进 WebM 吗? —— 不能。WebM 只收 VP8/VP9/AV1,**必须转码**。下一节开始讲时间基:为什么同一个「第 5 帧」在不同地方是不同的数。
- ☐ (本节完,下一节 04.06.01 GOP与PTS/DTS)