🎬 第三章 夯实FFmpeg基础:重要数据结构及API
📄 一句话概括

十个结构体不是十个孤立的东西,是一条链上的十个点。 本机能直接摸到七个,三个协议层的全在墙后面。

📝 本节和前面几节的关系

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 秒实验

  1. hasattr(av, "AVRational") 看有理数类型在不在。
  2. 打印 type(c).__name__,看 PyAV 把它叫什么。
  3. c.streams 能否按 index 取:试 c.streams[1]。

常见误区

  1. 把 AVDictionary 当结构体去找。 Python 侧直接就是 dict。
  2. 以为 AVCodecParameters 不存在就等于参数读不到。 参数在上下文里,只是合并了。
  3. 在「流」上找编码参数。 编码参数在 codec_context 上,流只管时间。
自测三问(点开对答案) 1. 要在 Python 里查一个视频的档次(profile),往哪找? —— `stream.codec_context.profile`。档次是编码参数,属于上下文。 2. 为什么三个协议层结构体一个都没导出? —— 它们是「自己实现协议」时才用的底层,PyAV 的定位是不暴露这一层。 3. AVDictionary 换成 dict 之后,少了什么保护? —— 少了返回码。键名拼错会被静默忽略,C 侧至少能看见 av_dict_set 的返回值。

相关笔记