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

容器是箱子,编码是货物。换箱子不换货,画质一点不变。 「能不能读」与「能不能写」是两列独立的能力,别混着记。

📝 本节和前面几节的关系

前三章讲的是「层」与「流程」,这一章钻进封装这一层。 7.1.2 讲封装与解封装的动作,7.2 起是 API 与实战。

一、容器到底装了什么

一个 mp4 里其实只有三样东西。 内容 是什么 占多少


压缩后的音视频数据 一帧一帧的 h264/aac 包 九成以上 索引 每帧在哪、多长、什么时刻 几十分之一 元数据 时长、编码参数、谁写的 几百字节

index 那个九成以上不是夸张:demo 里 mdat 一个盒子占了 94%,剩下的索引加元数据才 6%。 所以容器本身的效率差异,撑死也就几个百分点。 谁要是说「换个容器能省一半空间」, 那一定是中间偷偷转了码。

二、四类容器,各管一段

按用途分比按名字背有用。 类别 代表 为什么长这样


点播 mp4、mkv、mov、avi 有索引,随便拖 直播 flv、mpegts 边生成边发,没索引 纯音频 mp3、wav、ogg 只有一条流 特殊 gif、image2 逐帧图片拼起来

直播那一行值得多说一句:flv 与 ts 之所以能边发边播, 是因为它们不要求先知道全局——每来一个包就发一个包。 代价是不能拖进度条:没有索引,播放器不知道第 30 秒在哪。 要拖,就得靠服务器另做一套切片(HLS)。

三、能读与能写是两回事

这一条最容易想当然。 组合 存在吗 碰上怎么办


能读又能写 最常见 随便用 只能读 有 想输出就换容器 只能写 极少 通常不值得用

怎么查?av.ContainerFormat(name) 有两个布尔字段:is_input 与 is_output。 demo 里十六个名字逐个问了一遍。 这个检查只要一行,却能省掉半小时的调试。 写出一个格式不支持的文件,报错往往出现在最后一步, 很难往这上面想。

四、名字不在表里,不等于不能用

rtmp 就是那个经典例子。 它不是容器,是纯协议,所以 ContainerFormat("rtmp") 直接报错。 可它照样能推流——拿它当容器名去查,问错了问题。 反过来 rtsp 查得到,因为 FFmpeg 把它同时注册成了协议与格式。 这种「一个名字两种身份」的情况,只有实测才说得清。 判断一个名字到底行不行,唯一可靠的办法是真开一次,看它报的是「没有这个协议」还是「连不上」。 第三章 Demo 38 已经验过这套判法。

五、动手 Demo:容器这东西,本机能认多少种

十六个名字逐个问「能不能读、能不能写」,再按用途归四类。

import av

names = ["mp4", "matroska", "avi", "flv", "mpegts",
         "mov", "webm", "gif", "mp3", "wav", "ogg",
         "hls", "rtsp", "rtmp", "image2", "rawvideo"]

for n in names:
    try:
        f = av.ContainerFormat(n)
        print(n, f.is_input, f.is_output)
    except ValueError:
        print(n, "不在格式表")

实跑输出:

--------------------------------------------------------------------
容器这东西,本机能认多少种
--------------------------------------------------------------------

**① 十六个名字,逐个问它「能不能读、能不能写」**

十六个里 **15 个能读、15 个能写**。

  名字         能读           能写           它是什么
  -------------------------------------------------------------
  mp4          有             有             点播,最常见
  matroska     有             有             点播,开源容器
  avi          有             有             老式点播
  flv          有             有             直播,网页老牌
  mpegts       有             有             直播,数字电视
  mov          有             有             苹果那套
  webm         有             有             网页开源
  gif          有             有             动图
  mp3          有             有             纯音频,最普及
  wav          有             有             纯音频,不压缩
  ogg          有             有             纯音频,开源
  hls          有             有             切片加播放列表
  rtsp         有             有             摄像头那套
  rtmp         不在格式表     不在格式表     —
  image2       有             有             图片序列
  rawvideo     有             有             裸像素,极大

**这两列不是摆设。** 能读不能写(或者反过来)的格式真有,碰上了就得换一条路,别死磕。

**② 按用途分四类,比背名字有用**

  类别         代表                   特点
  ------------------------------------------------------------
  点播容器     mp4、mkv、avi、mov     有索引,能拖进度条
  直播容器     flv、mpegts            边生成边发,不靠索引
  纯音频       mp3、wav、ogg          只有一条流
  特殊用途     gif、image2            逐帧图片拼成动画

**同一个视频换个容器,画质一点不变。** 容器只是「装箱方式」,里面的压缩数据一个字节都没重压。

**这点最容易被误会**:看见 mp4 转 mkv 之后画面糊了,那一定是中间走了转码,不是容器的错。

**③ 有个名字不在表里,值得单独说一句**

**`rtmp` 不在格式表,但它能用。** 因为它是纯协议,不是容器——没有「rtmp 文件」这种东西。

**表里 `hls` 与 `rtsp` 倒是有。** 前者是「一串 ts 分片加一个播放列表」,勉强算一种封装;
后者在 FFmpeg 里被同时注册成了协议与格式。

**所以判断一个名字能不能用,别查这张表,真开一次。**
这一条在第三章 Demo 38 已经用 `foo://` 那个阴性对照证明过。

rtmp 那一行报「不在格式表」,但它能推流。 它是协议不是容器,问题问错了。

30 秒实验

  1. 把 is_input 与 is_output 分别统计一遍,看有没有只占一边的。
  2. 试着 av.ContainerFormat("foo"),看报的是哪种异常。
  3. 把 mpegts 与 flv 的 long_name 打出来,对照它们的用途。

常见误区

  1. 把容器当编码。 mp4 转 mkv 画质不变,转 h264 才变。
  2. 以为凡是格式都能写。 能读能写是两列。
  3. 拿容器表判协议在不在。 表里躺的是容器名。
自测三问(点开对答案) 1. 同一个视频把 mp4 换成 mkv,画质会变吗? —— 不会。只换装箱方式,压缩数据一个字节都不重压。 2. 为什么直播容器不需要索引? —— 边生成边发,播放器跟着最新的一包走,不需要随机定位。 3. `rtmp` 为什么查不到? —— 它是纯协议,不是容器。没有「rtmp 文件」这种东西。

相关笔记