解析器干的活是「从一串字节里找出包的边界」。 包边界不是码流的固有属性,是打包时定的。
5.2 讲日志这一层工具,这一节讲另一个:解析器。 下一篇 5.4 讲字典,那是传参数的通道。
一、什么时候要解析器
解封装已经切好包了,为什么还要解析器? 因为有些场景拿到的只有字节,没有包的边界:网络收来的裸流、自己拼的缓冲、从文件里截的一段。 手上是什么 要不要解析器
封装好的文件 不要 网络上的裸流 要 自己拼的字节缓冲 要 从中间截的一段 要
注意第三条:把几个包拼起来再接起来,中间没有任何分隔,你手上只有一条几百字节的连续数据。 这时候只能靠解析器。
二、它靠什么找边界
靠码流里的起始标记。 H.264 的每个包前面有 00 00 00 01 这样的起始码,HEVC 类似,AAC 有同步字。 解析器认这些标记,遇到一个就认为「上一个包到此结束」。 编码 靠什么认边界
H.264 起始码 00 00 00 01 HEVC 起始码,长度不同 AAC 同步字 0xFFF MPEG-2 起始码 00 00 01
因为是靠标记找,所以能处理「从中间开始」的流。 网络断了重连, 接上来的半截数据也能被正确切开——只要里面还有起始码。 代价是第一个包可能不完整。 从中间接上来的第一段缺了前面,解出来是花屏, 要等下一个关键帧才正常。
三、实测:三个包,拼起来再切回去
把三个包拼成 1221 字节的连续数据,再喂给解析器。 项 值
拼接的包数 3 个 拼出的字节 1221 字节 解析回来 3 个包 各包大小 915/216/90
三个包原样切回来了。 解析器没有「记住」原来怎么切的,它是重新找的起始标记。 但「原样」是碰巧。 换个解复用器, 同样的码流可能切成不同的组合——包边界是打包时定的,不是码流的固有属性。 理解这一点就不奇怪为什么同一个视频转封装之后包数会变。 码流字节没变,装法变了。
四、还有一层:比特流过滤器
解析器找边界,过滤器改字节。两者常一起用。 工具 干什么
解析器 切出包的边界 比特流过滤器 改写字节的排列方式
最出名的过滤器叫 h264_mp4toannexb,本机有。 它把「MP4 里那种带长度前缀的 H.264」改成「TS 流里带起始码的 H.264」。同一个码流,两种装法。 MP4 转 TS 时不加它会出问题:产出的 TS 播放器不认,因为里面还是 MP4 那套装法。 这是转封装的细节坑之一。 本机一共有 49 个比特流过滤器,名字都是「源格式_to_目标格式」的形式, 看见名字大概就知道干什么。
五、动手 Demo:解析器——把字节流切回一个个包
拼三个包成一段字节,再让解析器切回来。
import av
c = av.open("_media32/tiny.mp4")
raw = b""
for p in c.demux(c.streams.video[0]):
if p.size:
raw += bytes(p)
if len(raw) > 1000:
break
c.close()
c2 = av.open("_media32/tiny.mp4")
out = c2.streams.video[0].codec_context.parse(raw)
print(len(raw), len(out), [p.size for p in out])
c2.close()
实跑输出:
--------------------------------------------------------------------
解析器——把字节流切回一个个包
--------------------------------------------------------------------
**① 解析器解决什么问题**
解封装给出的是包,但有些场景拿到的只有字节:从网络收来的裸流、从文件里读出来的一段、
自己拼起来的缓冲。**这些字节没有包的边界。**
**解析器(parser)就是找边界的那把刀。** 它认得码流的起始标记,
能从一个长字节串里切出一个个完整的包。
手上是什么 要不要解析器
-----------------------------------------------
封装好的文件 不要,解封装已经切好了
网络上的裸流 要,字节是连续来的
自己拼的缓冲 要,边界得重新找
内存里的码流片段 要
**② 真跑一次:拼三个包,再解析回去**
项 值
------------------------------
拼接的包数 3 个
拼出来的字节 1221 字节
**现在这一串字节里已经没有任何「包」的概念了**,就是一段连续的字节。把它喂给解析器:
项 值
---------------------------------
解析出几个包 3 个
各包大小 915、216、90
**三个包原样切回来了。** 解析器没有「记住」原来怎么切,它是靠码流里的起始标记重新找出来的。
**③ 这就是为什么解析器能处理「从中间开始」的流**
只要字节流里有起始标记,解析器就能找到下一个包的起点。
所以网络流断了重连,接上来的半截数据也能被正确切开。
**代价是它不一定与原来切得一样。** 原来的包可能是 915/216/90,
换个解复用器可能切成别的组合——**包边界本来就不是码流的固有属性**,是打包时定的。
**④ 还有一层:比特流过滤器**
解析器找边界,过滤器改字节。本机有 **49** 个比特流过滤器。
**最出名的叫 h264_mp4toannexb,本机有。**
它干的事是把「MP4 里带长度前缀的 H.264」改成「TS 流里带起始码的 H.264」。
**转封装时它经常是必需的**:MP4 转 TS 如果不加这个过滤器,产出的 TS 播放器不认。
这就是 4.5.2 说的「容器兼容」的细节之一。
parse 返回 list of Packet,每项就是一个切好的包。
切出来的大小与原来一致,因为它靠起始码重新找。
30 秒实验
- 从
raw里去掉前 10 个字节,看解析器还能切出几个包。 "h264_mp4toannexb" in av.bitstream_filters_available确认它在。- 把
raw换成随机字节,看parse返回几个包。
常见误区
- 以为包边界是码流固有的。 同一条码流换容器能切成不同的包。
- 从中间接流不等到关键帧就解。 第一个包不完整,画面花。
- MP4 转 TS 不加 h264_mp4toannexb。 产物播放器不认。
自测三问(点开对答案)
1. 什么场景下需要解析器? —— 手上只有连续字节、没有包边界时。比如网络裸流、自己拼的缓冲。 2. 解析器靠什么找边界? —— 码流里的起始标记。H.264 是 00 00 00 01,AAC 是同步字 0xFFF。 3. 为什么同一个视频转封装之后包数会变? —— 包边界是打包时定的。码流字节没变,装法变了,切法就变了。- ☐ (本节完,下一节 5.4 AVDictionary 应用案例及剖析)