📹 第四章 H.264/H.265视频编码并存储
📄 一句话概括

转封装换容器时,时间基往往跟着变,包上的所有刻度数都要换。 不换算的文件照样能打开、画面照样动,就是节奏全乱——这种错最阴。

📝 本节和前面几节的关系

上一节讲了公式,这一节把它用起来。 04.05.04 的转封装之所以不用换算,是因为输出照抄了输入的时间基。 这一节故意把输出轨改成 1/90000,然后三条路并排走。

一、三条路,差多少

文件 时间基 末包 pts 读回时长
原片 1/15360 14848 1.00 秒
全程不换 1/90000 14848 0.17 秒
只换 pts/dts 1/90000 87000 0.97 秒
连 duration 一起换 1/90000 87000 1.00 秒

「全程不换」是灾难现场:pts 还是 0、512、1024 那几个数,但输出轨的时间基已经变成 1/90000, 同一个 512 从「1/30 秒」变成了「5.7 毫秒」,时长只剩原来的 17%。

「只换 pts/dts」看着快对了,其实还差 0.03 秒。pts 全对上了,但每包还带着自己的 duration, 末帧那个 512 没换,512 刻度在 1/90000 里只有 5.7 毫秒,本该是 3000 刻度(33 毫秒)。 这 0.03 秒的缺口,就是「pts 换对了、duration 忘了换」的铁证。

二、要换的一共四处

序号 要换的东西 漏了会怎样
① packet.pts 播放位置全错
② packet.dts dts 可能大于 pts,解码器直接报错
③ packet.duration 总时长差一帧,越往后越明显
④ packet.time_base 数值对、基不对,等于全错

前两条是所有人都记得的,后两条是所有人都会忘的。 ③ 只影响总时长、不影响画面,所以最难发现——你看着画面正常,一查时长少了半秒。

记住一个判断标准:只要时间基变了,包上所有「以刻度为单位的数」都要换。pts、dts、 duration 一个不漏。

三、为什么内容一个字节没动

这 30 个包的大小、内容一个字节没改,换的只是那几个数字。 所以这种错误最阴:文件能打开、画面能播、拖动也能拖,就是「快进一小时只走了十分钟」。

排查办法:读回来算「末包 pts ÷ 时间基分母」,和预期的秒数比。 差一个固定倍数就是没换算,差一点点就是 duration 的问题。

四、什么时候必须换

场景 要不要换 说明
MP4 → MP4,同参数 不用 照抄模板,基不变
MP4 → TS 要 TS 用 1/90000
MP4 → MKV 要 MKV 用 1/1000
TS → MP4 要 1/90000 → 1/15360

判据很简单:in_stream.time_base == out_stream.time_base 就跳过换算,不等就必须换。 别「反正都换一遍」——换两次等于没换,而且更难查。先判断,再决定换不换。

五、动手 Demo:转封装换时间基,不换算会错成什么样

Demo 生成三个文件:全程不换、只换 pts/dts、连 duration 一起换。 三个文件的内容完全一样,只有时间戳不同,读回来的时长差得很明显。

def build(path, mode):
    c = av.open("in.mp4")
    s = c.streams.video[0]
    o = av.open(path, "w")
    st = o.add_stream_from_template(s)
    st.time_base = Fraction(1, 90000)        # 故意换基
    for pk in c.demux(s):
        if mode in ("pts", "all"):
            pk.pts = rescale(pk.pts, tb_in, tb_out)
            pk.dts = rescale(pk.dts, tb_in, tb_out)
        if mode == "all":
            pk.duration = rescale(pk.duration, tb_in, tb_out)
        pk.time_base = tb_out
        o.mux(pk)

实跑输出:

--------------------------------------------------------------------
转封装换时间基,不换算会错成什么样
--------------------------------------------------------------------
**上一节那个 remux(Demo 57)之所以不用换算,是因为输出照抄了输入的时间基。**
  **这一节故意把输出轨的时间基改成 1/90000(MPEG-TS 的常用值)。**
  **然后三条路并排走:全程不换、只换 pts/dts、连 duration 一起换。**

**四个文件摆一起:**

  文件                时间基        首包 pts    末包 pts    读回时长
  ----------------------------------------------------------------------
  原片                1/15360       0           14848       1.00 秒
  全程不换            1/90000       0           14848       0.17 秒
  只换 pts/dts       1/90000       0           87000       0.97 秒
  连 duration 一起换  1/90000       0           87000       1.00 秒

  **「全程不换」那一行是灾难现场**:pts 还是 0、512、1024 那几个数,
  但输出轨的时间基已经变成 1/90000。
  **同一个 512 从「1/30 秒」变成了「5.7 毫秒」**,时长只剩原来的 17%。

  **「只换 pts/dts」那一行看着快对了,其实还差 0.03 秒**:30 帧的 pts 全对上了,
  但**每包还带着自己的 duration**,末帧那个 512 没换,512 刻度在 1/90000 里只有 5.7 毫秒,
  本该是 3000 刻度(33 毫秒)。
  **这 0.03 秒的缺口,就是「pts 换对了、duration 忘了换」的铁证。**

  **「连 duration 一起换」才和原片的 1.00 秒严丝合缝。**

  注意这 30 个包的大小、内容一个字节没动,**换的只是那几个数字**。
  所以这种错误最阴——文件能打开,画面也能动,就是节奏全乱。

  **转封装要换的一共四处,漏一处错一处:**

  序号    要换的东西              漏了会怎样
  ------------------------------------------------------------------
  ①       `packet.pts`            播放位置全错
  ②       `packet.dts`            dts 可能大于 pts,解码器直接报错
  ③       `packet.duration`       总时长差一帧,越往后越明显
  ④       `packet.time_base`      数值对、基不对,等于全错

  **前两条是所有人都记得的,后两条是所有人都会忘的。**
  **记住一个判断标准:只要时间基变了,包上所有「以刻度为单位的数」都要换**,pts、dts、
  duration 一个不漏。

三行读回时长(0.17/0.97/1.00 秒)就是本节的全部证据:少换一处,就少一点时间。 注意 0.17 秒这个数不是「压成了 1/5」,是 17%——15360 ÷ 90000 ≈ 0.17。 报比例前先算清方向,别把 6 倍说成 5 倍。

30 秒实验

  1. 只换 pts 不换 dts,看重跑时 mux 会不会报错。
  2. 把输出轨改成 1/1000(MKV 常用),重跑,看三个时长各是多少。
  3. 把 pkt.time_base 那一行删掉,看会怎样——数值对、基不对的结果。

常见误区

  1. 以为 pts 数值对了就行。 时间基不对,等于全错,而且文件照样能打开。
  2. 以为 duration 不用换。 它也是刻度数,漏了总时长就差一帧,越往后越明显。
  3. 以为「反正都换一遍」最保险。 换两次等于没换,要先判断两个基是否相同。
自测三问(点开对答案) 1. 为什么「只换 pts」还会差 0.03 秒? —— **每包还带着自己的 duration**,末帧那个没换,512 刻度在 1/90000 里只有 5.7 毫秒, 本该是 33 毫秒。 2. 转封装要换哪四处? —— `pts`、`dts`、`duration`、`time_base`。 **前两个影响播放位置,第三个影响总时长,第四个决定前三个怎么解释。** 3. 怎么快速判断「是不是忘了换算」? —— 读回来算「末包 pts ÷ 时间基分母」和预期秒数比。**差固定倍数就是没换算。**

下一节换到转码:帧上的 pts 怎么改,为什么改错了会「全塌成一个点」。

相关笔记