把包原样写出去,就得到一个没有容器的裸流。 体积小了,但不是因为压得更好,是因为丢了索引与音轨。
这是最「原始」的一种存法,也是理解容器价值的最好对照。 下一篇 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 秒实验
- 把音频流也 dump 出来(存成 .aac),看两个裸流加起来与原文件差多少。
- 用十六进制看裸流开头,找
00 00 00 01起始码。 - 把裸流拿去 av.open,看它能不能认出格式、能不能读到时长。
常见误区
- 以为裸流更省所以更好。 丢的是索引与音轨,不是压缩率。
- 以为裸流能拖进度条。 没有索引,拖不动。
- 忘记跳过空包。 空包写进去就是几个垃圾字节。
自测三问(点开对答案)
1. 裸流为什么比原文件小? —— 丢了索引、元数据与其它流。压缩数据一个字节都没变。 2. 裸流能拖进度条吗? —— 不能。没有索引,也不知道时长。 3. 裸流有什么正当用途? —— 喂硬件解码器、做码流分析、作为网络分包发送的原料。- ☐ (本节完,下一节 7.6 FFmpeg转封装案例实战)