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

把包原样写出去,就得到一个没有容器的裸流。 体积小了,但不是因为压得更好,是因为丢了索引与音轨。

📝 本节和前面几节的关系

这是最「原始」的一种存法,也是理解容器价值的最好对照。 下一篇 7.6 换成另一种存法:换容器但保留一切。

一、直接把包写出去

三行就够:demux 出一个包,转成字节,写文件。 步 代码 注意


一 for pk in demux(vs) 只取一条流 二 跳过 size 为 0 那是结束标记 三 f.write(bytes(pk)) 写的是包体本身

bytes(pk) 拿到的是包里的压缩数据,不含任何容器头。所以一个个写下去,就是一个纯码流文件。 写出来的东西大小正好等于所有包的 size 之和,一个字节不多不少。这一点 demo 里核过。

二、少了什么

少的不是数据,是说明。 信息 容器里有 裸流里有


时长 有 没有 帧率 有 要自己数 分辨率 有 解一帧才知道 时间戳 有 全丢 索引 有 没有,拖不动

分辨率要解一帧才知道,因为参数在码流的 SPS 里,解一次就拿到了。 这也是为什么裸流播放器打开时要闪一下。 时间戳全丢是最要命的:光有帧,不知道每帧该在什么时候显示。 播放器只能按固定帧率猜,遇到可变帧率就错。 索引没了就拖不动:想跳到第 30 秒,得从头一个个包数过去,还得找关键帧对齐。

三、那它有什么用

三个正当用途,都不是「省空间」。 用途 为什么必须裸流


喂硬件解码器 很多只吃裸流 做码流分析 要看起始码、SPS、PPS 网络分包发送 RTP 就是一包一包发

码流分析这一条最实用:出问题时把裸流拿出来,看第一个包前面那几个字节是不是 00 00 00 01, 就知道是不是 Annex-B 格式。 这也是 7.6 里 avi 失败的原因:avi 要求 Annex-B,而 mp4 里存的是另一种封装,中间得转。

四、一个必须说清的误区

「存成裸流体积小,所以更省」——这个推论是错的。 体积小的原因是丢了索引、丢了音轨、丢了元数据。 压缩数据一个字节都没变。 要验证很简单:把裸流的大小与所有包的 size 之和比一比,两者完全相等。 相等就说明「没有压得更狠」。 真要省空间,只有两条路:降码率(有损)或者换更高效的编码器(如 h265)。 换容器、去容器,都不在此列。

五、动手 Demo:把压缩包直接存下来,得到一个没有容器的裸流

一个个包往外写,写完核一遍:是不是正好等于包大小之和。

import av

c = av.open("tiny.mp4")
vs = c.streams.video[0]
tot = 0
with open("raw.h264", "wb") as f:
    for pk in c.demux(vs):
        if pk.size == 0:
            continue
        f.write(bytes(pk))
        tot += pk.size
c.close()
print("包合计", tot)

实跑输出:

--------------------------------------------------------------------
把压缩包直接存下来,得到一个没有容器的裸流
--------------------------------------------------------------------

**① 一个包一个包往外写,中间不碰编码器**

  东西           大小           说明
  ------------------------------------------------
  原 mp4         46137 字节     含容器与索引
  裸流 h264      11095 字节     只有压缩数据
  包大小合计     11095 字节     与裸流应该相等

**裸流 11095 字节,正好等于 50 个包的字节合计 11095。** 一个字节都没多、
也没少——**写出去的就是包里的东西本身。**

**② 省下来的那部分是什么**

原文件 **46137**,裸流 **11095**,差 **35042** 字节。

**差的是索引、元数据,还有音频那一整条流。** (这个文件里音频包一点没写,所以差得比索引多得多。)

**③ 裸流能播,但什么都不告诉你**

  信息         容器里有     裸流里有
  --------------------------------------------
  时长         有           没有
  帧率         有           要自己数
  分辨率       有           要解一帧才知道
  能不能拖     能           不能

**因为裸流里只剩「压缩数据」这一样东西了。** 时间戳、编码参数、索引全在容器里,容器没了就全没了。

**④ 那这东西有什么用**

**三个正当用途**:喂给硬件解码器(很多只吃裸流)、做码流分析(看起始码、看 SPS/PPS)、
往网络上一包一包发(RTP 就是这样)。

**一个常见误区**:以为存成裸流能「更小更快」。
**体积是小了,但那是因为少了索引与音轨,不是因为压缩得更好。**

裸流大小与包合计完全相等,一个字节都没多。 小的那部分是索引、元数据、以及整条音轨。

30 秒实验

  1. 把音频流也 dump 出来(存成 .aac),看两个裸流加起来与原文件差多少。
  2. 用十六进制看裸流开头,找 00 00 00 01 起始码。
  3. 把裸流拿去 av.open,看它能不能认出格式、能不能读到时长。

常见误区

  1. 以为裸流更省所以更好。 丢的是索引与音轨,不是压缩率。
  2. 以为裸流能拖进度条。 没有索引,拖不动。
  3. 忘记跳过空包。 空包写进去就是几个垃圾字节。
自测三问(点开对答案) 1. 裸流为什么比原文件小? —— 丢了索引、元数据与其它流。压缩数据一个字节都没变。 2. 裸流能拖进度条吗? —— 不能。没有索引,也不知道时长。 3. 裸流有什么正当用途? —— 喂硬件解码器、做码流分析、作为网络分包发送的原料。

相关笔记