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

AVCodec 是模板,AVCodecContext 是实例,两者必须分清。 解码器不告诉你编码参数,这是设计如此,不是 bug。

📝 本节和前面几节的关系

3.4 拆出来的包,这一节把它变成帧。 下一篇 3.6 把这些结构体上的方法整理成一张函数表。

一、模板与实例

这个分离是 FFmpeg 里最一致的一个模式,出现了三次。 层 模板 实例


协议 URLProtocol URLContext 封装 AVInputFormat AVFormatContext 编解码 AVCodec AVCodecContext

模板是「这类东西能干什么」,实例是「这一次具体怎么干」。 avcodec_find_decoder 找的是模板, avcodec_alloc_context3 建的是实例。 同一个模板能建多个实例,比如同时解两路视频,两个上下文共用一个 h264 解码器。 这就是模板要独立存在的理由。

二、AVCodecParameters 是文件里读出来的那份

C 侧把参数分成两个地方放:AVCodecParameters 装容器里读出来的, AVCodecContext 装解码器实际在用的。 为什么要分:容器里的参数可能不完整、也可能是错的。 解码器要拿它去初始化,初始化之后自己算出一套真参数,放在上下文里。 两者不一致时,能发现「容器头写错了」。 PyAV 把它们合成一个,代价是分不清哪个值是文件写的、哪个是解码器填的。 这是用 Python 时丢掉的一个诊断能力。 想知道的事 能不能分清


容器头写的宽高 分不清 解码器实际用的宽高 分不清 容器里有没有写错 发现不了

真遇到头写错的文件,只能回 C 逐步对比。 用 avcodec_parameters_to_context 把容器里的参数导进上下文,再与解码器自己算出来的那一套对照。 一般素材碰不到,做播放器的人必须处理。

三、编码参数在解码侧读不到

这是实测出来的一个反直觉结论。 在解码上下文上访问 gop_size 或 time_base,抛的不是 AttributeError,而是 RuntimeError, 消息是「Cannot access as a decoder」。 抛 RuntimeError 说明属性存在、只是拒绝访问。 gop_size 是编码器的概念(多久塞一个 I 帧), 解码器不需要它。 这条实测有实际用处:想「读出原视频的 GOP 大小」,在解码上下文里永远拿不到, 只能从码流里数 I 帧间隔。

四、能力清单只有编码器才有

av.Codec('h264', 'r') 的 video_formats 是 None。 而 av.Codec('libx264', 'w') 的 video_formats 有十五种。同一个库,方向不同,属性完全不一样。 属性 解码方向 编码方向


video_formats None 15 种 frame_rates None 有值 audio_rates None 有值

为什么解码器不列:编码器要给调用者挑(用哪种像素格式编), 解码器没什么可选——码流里是什么就得解什么。 所以建 Codec 必须说清方向,'r' 还是 'w'。不写清楚拿到的是解码模板,属性全是空的。

五、动手 Demo:编解码层——解码器为什么什么都不告诉你

两个方向各建一个模板,对比属性。

import av

c = av.open("_media32/tiny.mp4")
cc = c.streams.video[0].codec_context
for attr in ("gop_size", "time_base"):
    try:
        getattr(cc, attr)
    except RuntimeError:
        print(attr, "拒绝访问")
print(av.Codec("h264", "r").video_formats)
print(len(av.Codec("libx264", "w").video_formats))
c.close()

实跑输出:

--------------------------------------------------------------------
编解码层——解码器为什么什么都不告诉你
--------------------------------------------------------------------

**① C 侧三个,Python 侧两个半**

  教材结构体            Python 侧        状态
  -----------------------------------------------
  AVCodec               Codec            完整
  AVCodecContext        CodecContext     完整
  AVCodecParameters     揉进上下文       半个

**② 上下文里的参数,一半能读一半不能**

  字段               值                       备注
  ------------------------------------------------------------
  name               h264                     编码名
  width/height      160×120                  真实尺寸
  pix_fmt            yuv420p                  存储格式
  framerate          25                       帧率
  profile            Constrained Baseline     档次
  level              11                       等级
  codec_tag          avc1                     四字符标记
  extradata_size     38                       全局头字节数

**③ 编码参数在解码侧读不到,这是设计如此**

cc.gop_size → 抛 RuntimeError
cc.time_base → 抛 RuntimeError

**报的是 RuntimeError,不是 AttributeError**,说明属性存在、只是拒绝访问。
gop_size 是编码器才有的概念(多久塞一个 I 帧),解码器不需要它。

**这条实测很有用**:写代码时想「读出原视频的 GOP 大小」,在解码上下文里永远拿不到,
只能从码流里数 I 帧间隔。

**④ Codec 对象的能力清单基本是空的**

av.Codec('h264', 'r') 拿到的是 Codec,mode = r。

  属性              值        为什么
  --------------------------------------------------
  video_formats     None      解码器不限输出格式
  frame_rates       None      帧率来自码流
  is_decoder        True      这个方向是解码
  capabilities      12322     位标志,数不出名堂

**编码器才需要「我能输出哪些格式」这张清单**,因为调用者要从里面挑一个。
解码器没什么可挑的:码流里是什么就得解什么。

**⑤ 换个方向看,编码器就有清单了**

av.Codec('libx264', 'w'):is_encoder = True,能输出 **15** 种像素格式。

**同一个名字,加个 w 就换了方向**,属性立刻不一样。这就是为什么建 Codec 必须说清是读还是写。

两个 RuntimeError 是关键证据:属性在、不给用。 末尾两个数对比出「解码器不列格式、编码器才列」。

30 秒实验

  1. 试着 getattr(cc, "gop_size", "默认值"),看默认值能不能兜住异常。
  2. av.Codec("aac", "w").audio_rates 看编码方向有没有采样率清单。
  3. 数一数 av.Codec("libx264", "w").video_formats 里有哪些格式。

常见误区

  1. 在解码上下文上读 gop_size。 抛 RuntimeError,读不到。
  2. 建 Codec 不写方向。 不写方向默认按解码,属性全空。
  3. 以为参数合并了就等于信息更全。 合并丢掉了「头写的」与「实算的」的区分。
自测三问(点开对答案) 1. 为什么 AVCodec 与 AVCodecContext 要分开? —— 模板是「能干什么」,实例是「这一次怎么干」。同一个解码器可以同时建多个实例。 2. `av.Codec('libx264', 'w')` 与 `av.Codec('libx264', 'r')` 有什么区别? —— 方向不同。写方向是编码器,有像素格式清单;读方向是解码器,清单为空。 3. 想读原视频的 GOP 大小,怎么办? —— 解码上下文不给,只能从码流里数关键帧之间隔了多少帧。

相关笔记