🎬 第七章 AVFormat封装层理论及案例实战
📄 一句话概括

同一批包,装进五种容器,体积与刻度全变,画质一点不变。 avi 那条路在 PyAV 走不通——这类边界要老实写出来。

📝 本节和前面几节的关系

7.5 是「不要容器」,这一节是「换一个容器」。 这一章到此收尾,第八章进编解码层。

一、转封装的四步

一次解码都不做,只搬字节。 步 做什么 关键函数


一 打开输入 av.open(M) 二 建输出与映射 add_stream_from_template 三 包换流号再 mux pk.stream = 新流 四 关输出 container.close()

第二步有两个易错点。 一是不能用 add_stream(template=...), 要用 add_stream_from_template();二是必须留一张新旧流号的映射表。 第三步那个 pk.stream 赋值最容易忘:忘了就直接 mux,包还记着旧流号,写出去就乱。

二、五种容器,四种成了一种不成

同一批 138 个包,装进五个容器。 容器 结果 体积


matroska 成 45155 mpegts 成 74824 flv 成 46382 mov 成 46188 avi 失败 —

ts 最大,是别人的 1.6 倍。 因为它在每个包前面都加 4 字节包头,还反复插节目表。 拿它做直播对,存片就浪费。 mkv 最小,省掉了 mp4 里那些对齐与预留。但也就省百分之几,别指望它救空间。 四个成功的体积都在 4.5 万到 7.5 万之间,原文件 46137。 这个量级本身就是个结论: 容器开销很小。

三、刻度被改写了

这是转封装最容易让人以为出问题的地方。 容器 视频 time_base 音频 time_base


原 mp4 1/12800 1/44100 matroska 1/1000 1/1000 mpegts 1/90000 1/90000

mux 的时候 FFmpeg 会把 pts 换算成目标容器的刻度。 所以 pts 数字变了、秒数没变。 mkv 用 1/1000 是毫秒,够细也不太大; ts 用 1/90000 是历史原因,当年按 90 kHz 时钟定下来的。 排查这类问题时唯一正确的做法:两边都换成秒再比。 只看 pts 会得出「时间轴被破坏」的错误结论。

四、avi 为什么不行

它报的是 InvalidDataError,不是不支持这个容器。 原因是码流格式不匹配:avi 这条老路要求 h264 用 Annex-B 码流, 而 mp4 里存的是另一种封装(AVCC),中间得插一个比特流过滤器做转换。 命令行 ffmpeg 会自动插这个过滤器,所以 ffmpeg -i a.mp4 a.avi 一般没问题。 PyAV 没有这个入口——dir() 里查不到任何 bsf 相关属性。 所以结论只能老实写:PyAV 这条路,mp4 转 avi 走不通。 要么改用命令行,要么换目标容器。 这类边界写清楚比绕过去有用。 硬凑一个「我看似成功了」的方案,到用的人手里才炸, 那才是真正的麻烦。

五、这一章的账

把七节串起来看,封装层就三句话。 要记住的 一句话


容器是什么 箱子,换箱子不换货 怎么操作 开、找、读、写,状态机顺序不能乱 最容易错 时基混用、空包没跳、流号没改

这三句话覆盖了本章九成的问题。 剩下的那一成,靠的是每换一个容器实测一次,而不是背规则。 下一章进编解码层,那里管的是「压缩包 ↔ 原始帧」,封装层的东西一个都不碰。 分层最大的好处就在这里:可以分开学。

六、动手 Demo:五种容器转封装,只换箱子不换货

同一批包往五个容器里各装一遍,记体积、包数、读回来的刻度。

import av

for tag in ("matroska", "mpegts", "flv", "mov", "avi"):
    p = "out." + tag
    try:
        ic = av.open("tiny.mp4")
        oc = av.open(p, "w", format=tag)
        cmap = {}
        for st in ic.streams:
            cmap[st.index] = oc.add_stream_from_template(st)
        for pk in ic.demux():
            if pk.size == 0:
                continue
            pk.stream = cmap[pk.stream.index]
            oc.mux(pk)
        oc.close()
        ic.close()
        r = av.open(p)
        print(tag, [str(s.time_base) for s in r.streams])
        r.close()
    except Exception as e:
        print(tag, "失败", type(e).__name__)

实跑输出:

--------------------------------------------------------------------
五种容器转封装,只换箱子不换货
--------------------------------------------------------------------

**① 同一个 mp4,往五个容器里各装一遍**

**这次不碰编码器**:拿模板流建立输出,再把包原样 mux 进去。

  目标容器     体积      包数                 读回来的 time_base     时长
  ----------------------------------------------------------------------------
  matroska     45155     138                  1/1000/1/1000         2.023
  mpegts       74824     138                  1/90000/1/90000       2.043
  flv          46382     138                  1/1000/1/1000         2.023
  mov          46188     138                  1/12800/1/44100       2.023
  avi          失败      InvalidDataError     —                      —

**五个里成了 4 个。** 原文件 **46137 字节**。

**② 体积差在哪**

**ts 最大,因为它在每个包前面都要加 4 字节包头,还要反复插节目表。** 拿它当直播流没错,
拿来存片就浪费。

**mkv 最小,因为它省掉了 mp4 里那些对齐与预留空间。** 但省得有限,通常在百分之几。

**记住一句话**:转封装的体积变化只跟「装箱开销」有关,**跟画质一点关系都没有。**

**③ time_base 被容器改了**

**mp4 里视频是 1/12800、音频是 1/44100;进了 mkv 与 flv 全变成 1/1000;进了 ts 全变成 1/90000。**

**这不是 bug,是容器自己的规定。** 每个容器有自己的时间刻度,
mux 的时候 FFmpeg 会帮你把 pts 换算过去。

**所以转封装前后 pts 的数字会变,秒数不变。** 只比 pts 不看 time_base,
就会得出「时间全乱了」的错误结论。

**④ avi 那个为什么失败**

它报的是 `InvalidDataError`。
**avi 这条老路要求 h264 用 Annex-B 码流**,而 mp4 里存的是另一种封装,
中间得插一个比特流过滤器做转换。

**命令行 ffmpeg 会自动插**,所以 `ffmpeg -i a.mp4 a.avi` 一般没问题。
**PyAV 没有这个入口**——`dir()` 里查不到任何 bsf 相关属性。

**所以结论要老实写:PyAV 这条路,mp4 转 avi 走不通。** 要么改用命令行,要么换目标容器。
**这类「本机做不到」的边界,写清楚比绕过去有用。**

四个成功、avi 失败,失败原因是码流格式不匹配。 刻度全变了:mkv 1/1000、ts 1/90000、mov 保持原样。

30 秒实验

  1. 把 pk.stream 那行删掉再跑,看输出文件的时间戳乱成什么样。
  2. 统计每个输出文件的包数,看是不是都等于输入的 138。
  3. 试试 flv 装 h265,看它报什么(提示:容器不支持这个编码)。

常见误区

  1. 忘了给 pk.stream 换流号。 写出去时间戳全乱。
  2. 拿 pts 直接比前后。 刻度变了,要先换成秒。
  3. 以为 avi 失败是 PyAV 的 bug。 是缺比特流过滤器,而它不提供入口。
自测三问(点开对答案) 1. 转封装为什么快? —— 一次解码都不做,只把包搬进新容器再写一份新索引。 2. 转封装之后 time_base 变了,时间变了吗? —— 没有。换算过去了,秒数不变,只是刻度不一样。 3. avi 失败的根本原因是什么? —— avi 要求 Annex-B 码流,mp4 里是另一种封装,中间要插比特流过滤器。

相关笔记