协议层管「字节从哪来」,三个结构体分工:缓冲、协议、连接。 本机一个都摸不到,只能通过选项与回调间接用。
3.2 讲的是解码侧的三个结构体,这一节往回退一层,到了最底下的输入。 下一篇 3.4 进封装层,正好夹在协议与编解码之间。
一、AVIOContext:字节从哪来
它是 libavformat 与真正数据源之间的那层缓冲。 读文件时,底下是文件系统;读网络流时,底下是 socket; 读内存里的数据时,底下是一块 malloc 出来的缓冲。 上层看不见区别,因为 AVIOContext 把它们抹平了。 底层是 谁来填这个缓冲
本地文件 file 协议 HTTP 流 http 协议,带重连与分段 内存数据 调用者自己实现读回调 自定义协议 自己写 URLProtocol
这一层的存在解释了一件事:为什么用同一个 avformat_open_input,既能开 mp4 也能开 http 地址。 输入源的类型被这层吃掉了。
二、URLProtocol:一种协议的实现
一个协议就是一个函数表:打开、读、写、定位、关闭。 协议 用途 判断依据
file 本地文件 能开本地文件 http/https 网页与点播流 能拿到服务器回复 tcp/udp 裸传输 要真连一次才知道 rtmp 直播推拉流 没有同名容器格式 rtsp 摄像头 有同名容器格式 rtp 音视频实时传输 有同名容器格式
「判断依据」这一栏要看清:它说的不是「协议在不在」。 formats_available 那张表里躺的是容器格式的名字,不是协议名。 所以 http、file、tcp 这些纯协议一个都查不到,但它们明明能用。 rtsp 与 rtp 能查到,是因为它们同时是容器格式(有同名解复用器与复用器),跟「它是协议」无关。 rtmp 查不到,也不代表它一定没编进来。 正确的判断只有一个办法:真开一次看报什么错。 报 protocol not found 才是真的没有; 报连不上、404、超时,说明协议在,问题在别处。
三、URLContext:一次具体的连接
URLProtocol 是模板,URLContext 是一次实例。 同一个 http 协议开两个地址,就有两个 URLContext,各自有自己的连接状态与缓冲位置。 模板共享,状态不共享。 这个「模板与实例」的分离,在封装层与编解码层又出现两次。 AVInputFormat 与 AVFormatContext、 AVCodec 与 AVCodecContext,都是同一个套路。看清一次,后面两次就通了。
四、Python 侧一个都摸不到,靠选项用
三个结构体在 Python 里全都不存在,连枚举协议名的函数都没有。 想做的事 Python 侧的做法
选协议 看地址前缀自动定 设协议参数 av.open 的 options 字典 列有哪些协议 做不到 自己实现协议 做不到,回 C
选项法能设参数,但没有任何校验。 传一个拼错的键、或者对本地文件传一个只对网络有意义的键, 都不会报错。 唯一露出来的一只手是容器的 io_open 回调,能让调用者接管「子资源怎么打开」,HLS 分片就靠它。 但它只能换打开方式,换不了读写实现。 结论:协议层在 Python 侧是「能用但看不见」。 知道它在哪一层,报错时能对上号,就够了。
五、动手 Demo:协议层——三个结构体,一个都没露出来
先确认摸不到,再看唯一的两个入口。
import av
print(hasattr(av, "protocols_available"))
opts = {"protocol_whitelist": "file,http,https,tcp,tls",
"rtsp_transport": "tcp"}
c = av.open("_media32/tiny.mp4", options=opts)
print(c.format.name, c.size, hasattr(c, "io_open"))
c.close()
实跑输出:
--------------------------------------------------------------------
协议层——三个结构体,一个都没露出来
--------------------------------------------------------------------
**① 教材说的协议层三大件**
结构体 管什么 本机能摸到吗
---------------------------------------------------------
AVIOContext 读写缓冲,字节从哪来 摸不到
URLProtocol 一种协议的实现 摸不到
URLContext 一次具体的连接 摸不到
**三行都是「摸不到」,这不是查找方式不对。** av 顶层的导出清单里没有它们,
连 av.protocols_available 这个函数都没有——协议层在 Python 侧是隐形的。
实测 hasattr(av, 'protocols_available') = False。
**② 隐形不等于用不了,协议靠选项选**
C 侧要设协议参数,是往 AVDictionary 里塞键值。Python 侧一样,只是包在一个 kwargs 里。
下面是真开一次:
av.open(path, options={'protocol_whitelist': 'file,http,https,tcp,tls',
'rtsp_transport': 'tcp'})
打开结果:格式 mov,mp4,m4a,3gp,3g2,mj2,文件大小 46137 字节。**两个选项都被吃了,没报错。**
**不报错不代表生效。** protocol_whitelist 只对网络协议有意义,
读本地文件时它形同虚设——传错了它也认。这是选项法的通病:**没有校验**。
**③ 唯一露出来的一只手:io_open 回调**
容器对象上有个 io_open,能让调用者自己接管「打开子资源」这件事——HLS 的 m3u8 指向一堆 ts 分片时,
就靠它决定每个分片怎么打开。
实测 hasattr(container, 'io_open') = True。
**这是协议层唯一一处给你留的门**,而且只留了半扇:能换「怎么打开」,换不了「怎么读写」。
真正的自定义协议(私有加密传输、内存里的数据流)在 Python 侧做不了,只能回到 C。
**④ 这一节的实际价值**
教材花一节讲协议层,是给做播放器的人打底:要支持 http、rtmp、rtsp,得知道协议在哪注册、在哪选。
**用 Python 的人可以跳过实现,但要知道一件事**——报错说「protocol not found」,
意思是这一版构建里没编进那个协议,跟你代码写没写对无关。
**⑤ 想查某个协议在不在,formats_available 帮不上忙**
这是本节最容易搞错的一点。
**formats_available 列的是「容器格式」,不是「协议」。** 拿它去查 http、file、tcp 一律查不到,
但那几个协议其实都在。
名字 格式表里有 是容器格式吗
----------------------------------------
http 没有 不是
file 没有 不是
tcp 没有 不是
udp 没有 不是
rtmp 没有 不是
rtsp 有 是
rtp 有 是
**http、file、tcp、udp 三个都「查不到也不是格式」**,
但它们是真能用的协议——本书第六章会真开一次 http 地址来证明。
**rtsp 与 rtp 能查到,是因为它们同时是容器格式**(有同名解复用器与复用器),不是因为它俩是协议。
同理 rtmp 查不到,不代表它一定没编进来——**这个表压根不是用来看协议的。**
**正确的判断办法只有一个:真开一次,看报什么错。** 报「protocol not found」才是真的没有;
报别的(连不上、404、超时)说明协议在,问题在别处。
**这个方法第四章 4.5.3 会用到**:推流能不能做,取决于有没有对端,不取决于格式表里有没有那个名字。
两个选项都被吃了,也没报错,但读本地文件时它们都不起作用。
io_open 在,是协议层唯一留给调用者的接口。
30 秒实验
- 把一个键名故意拼错,看 av.open 报不报错。
"http" in av.formats_available看纯协议查不查得到(查不到)。- 传
options={"timeout": "1"}打开一个 http 地址,看超时生效不生效。
常见误区
- 以为传了选项就一定生效。 选项没有校验,错键静默忽略。
- 把「protocol not found」当代码 bug。 那是构建里没编进那个协议。
- 想在 Python 里实现自定义协议。 做不到,得回 C 写 URLProtocol。
自测三问(点开对答案)
1. AVIOContext 解决的是什么问题? —— 把「输入源」的差异抹平。文件、HTTP、内存,上层看到的都是同一个读接口。 2. URLProtocol 与 URLContext 是什么关系? —— 模板与实例。协议实现共享,每次连接的缓冲与状态各自独立。 3. formats_available 能用来判断协议在不在吗? —— 不能。它只列容器格式名。纯协议(http、file、tcp)一个都查不到,但那几个协议照样能用。判断只有一招:真开一次看报什么错。- ☐ (本节完,下一节 3.4 封装层的四大重要数据结构)