十个结构体不是十个孤立的东西,是一条链上的十个点。 本机能直接摸到七个,三个协议层的全在墙后面。
3.1.3 读过流信息,那些字段就是从这些结构体里取的。 下一篇 3.2.2 专讲其中最重要的两个:包与帧。
一、教材的清单,逐个对一遍
教材点名的有十四个,主体是这十个。 教材结构体 Python 侧 状态
AVFormatContext Container 有,按输入输出分 AVCodecContext CodecContext 有,同名去前缀 AVStream Video/AudioStream 有,按类型分 AVPacket Packet 有 AVFrame Video/AudioFrame 有,按类型分
教材结构体 Python 侧 状态
AVCodec Codec 有 AVInput/OutputFormat ContainerFormat 合成一个 AVCodecParameters 揉进上下文 半个 AVDictionary dict 换成内置字典 AVRational AVRational 有
十个里七个完整、一个半个、两个变形。 后面三节(协议、封装、编解码)就是把这张表按层拆开讲。
二、三个变形里最要紧的是字典
AVDictionary 换成 Python 字典,省掉一整类错误。 C 侧用它的方式是:av_dict_set 塞进去,用完 av_dict_free 释放,中间还要接返回码判断有没有重复键。 三步,每一步都能写错。 Python 侧就是一个 dict 字面量,出了作用域自动回收。 这类「内存管理带来的复杂度」在 Python 侧结构性地消失了。 代价是传错键名没人拦。 C 侧也是一样,但至少 av_dict_set 返回码能看见。 Python 侧传个拼错的键,静默忽略。
三、三个不暴露的,都在协议层
AVIOContext、URLProtocol、URLContext,一个都摸不到。 这不是没找到入口,是压根没导出:av 顶层的名字列表里没有它们, 连 av.protocols_available 这个枚举函数都不存在。 结构体 管什么 Python 侧
AVIOContext 读写缓冲 无 URLProtocol 一种协议实现 无 URLContext 一次连接 无
这三层是给「自己实现协议」的人准备的。 用 Python 的人通常不需要, 需要的时候也就到了该回 C 的时候。3.3 会细说。
四、结构体之间的那条链
C 侧靠指针串起来,Python 侧靠属性访问。形状没变。 关系 本机的写法
容器 → 流 c.streams 流 → 编解码上下文 s.codec_context 上下文 → 编解码器 cc.codec 包 → 所属流 pkt.stream 包 → 解码 pkt.decode() 帧 → 换格式 frame.to_ndarray()
记住这条链,后面查任何字段都知道往哪找。 要问「这个文件的码率」去容器, 问「这一路的帧率」去流,问「这个编码器的档次」去上下文。 链上最容易搞混的是「流」与「上下文」:流管时间与容器里的位置,上下文管编码参数。 两者都有 bit_rate,但含义不同——流上的是这一路在文件里占的,上下文上的是编码器用的。
五、动手 Demo:十个结构体,在本机还剩几个
三个协议层的、七个能摸到的,逐个查。
import av
for name in ("AVIOContext", "URLProtocol", "URLContext"):
print(name, hasattr(av, name))
c = av.open("_media32/tiny.mp4")
cc = c.streams.video[0].codec_context
print(cc.codec.name, cc.codec.long_name[:20])
c.close()
实跑输出:
--------------------------------------------------------------------
十个经典结构体,在本机还剩几个
--------------------------------------------------------------------
**① 教材点名的十四个,逐个查**
教材结构体 Python 侧 状态
---------------------------------------------------------------------
AVFormatContext InputContainer 有了,改名成容器对象
AVCodecContext CodecContext 有了,同名去前缀
AVStream VideoStream 有了,按类型分开
AVPacket Packet 有了,直接叫包
AVFrame VideoFrame 有了,按音视频分开
AVCodec Codec 有了
AVCodecParameters codec_context 揉进上下文,不单独给
AVInputFormat ContainerFormat 有了,输入输出合一
AVOutputFormat ContainerFormat 同上,靠 is_output 区分
AVDictionary dict 直接用 Python 字典
AVIOContext 无 不暴露
URLProtocol 无 不暴露
URLContext 无 不暴露
AVRational AVRational 有了
十个结构体里 **11 个能直接摸到**,三个协议层的一个都没有。
**② 三个「有了但变形」的,变形最要紧**
AVDictionary 变成了 Python 字典——原来要 av_dict_set、av_dict_free 配对的活儿,
现在一个 dict 字面量就完了,内存也不用管。
AVCodecParameters 被揉进了 CodecContext。
C 侧把「从文件里读出来的参数」和「解码器正在用的参数」分成两个结构体;
PyAV 合成一个,代价是**分不清哪些值是文件里写的、哪些是解码器自己填的**。
**③ 结构体之间怎么串起来**
关系 在本机怎么表达
--------------------------------------------------
容器 → 流 c.streams 是可迭代的集合
流 → 编解码上下文 st.codec_context
上下文 → 编解码器 st.codec_context.codec
包 → 所属流 pkt.stream
包 → 解码 pkt.decode()
**C 侧靠指针穿起来的那条链,Python 侧靠属性访问。** 链的形状没变,只是写法变了。
**记住这条链**:容器 → 流 → 上下文 → 编解码器,一路往下。
第三章后面三节(协议、封装、编解码)就是这条链上的三截。
三个 False 是实打实的,不是名字写错了。
链的末端 cc.codec 拿到的是编解码器模板,不是实例。
30 秒实验
hasattr(av, "AVRational")看有理数类型在不在。- 打印
type(c).__name__,看 PyAV 把它叫什么。 c.streams能否按 index 取:试c.streams[1]。
常见误区
- 把 AVDictionary 当结构体去找。 Python 侧直接就是 dict。
- 以为 AVCodecParameters 不存在就等于参数读不到。 参数在上下文里,只是合并了。
- 在「流」上找编码参数。 编码参数在 codec_context 上,流只管时间。
自测三问(点开对答案)
1. 要在 Python 里查一个视频的档次(profile),往哪找? —— `stream.codec_context.profile`。档次是编码参数,属于上下文。 2. 为什么三个协议层结构体一个都没导出? —— 它们是「自己实现协议」时才用的底层,PyAV 的定位是不暴露这一层。 3. AVDictionary 换成 dict 之后,少了什么保护? —— 少了返回码。键名拼错会被静默忽略,C 侧至少能看见 av_dict_set 的返回值。- ☐ (本节完,下一节 3.2.2 AVPacket 与 AVFrame)