容器是箱子,编码是货物。换箱子不换货,画质一点不变。 「能不能读」与「能不能写」是两列独立的能力,别混着记。
前三章讲的是「层」与「流程」,这一章钻进封装这一层。 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 秒实验
- 把
is_input与is_output分别统计一遍,看有没有只占一边的。 - 试着
av.ContainerFormat("foo"),看报的是哪种异常。 - 把
mpegts与flv的long_name打出来,对照它们的用途。
常见误区
- 把容器当编码。 mp4 转 mkv 画质不变,转 h264 才变。
- 以为凡是格式都能写。 能读能写是两列。
- 拿容器表判协议在不在。 表里躺的是容器名。
自测三问(点开对答案)
1. 同一个视频把 mp4 换成 mkv,画质会变吗? —— 不会。只换装箱方式,压缩数据一个字节都不重压。 2. 为什么直播容器不需要索引? —— 边生成边发,播放器跟着最新的一包走,不需要随机定位。 3. `rtmp` 为什么查不到? —— 它是纯协议,不是容器。没有「rtmp 文件」这种东西。- ☐ (本节完,下一节 7.1.2 使用FFmpeg处理音视频的封装与解封装)