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 秒实验
- 试着
getattr(cc, "gop_size", "默认值"),看默认值能不能兜住异常。 av.Codec("aac", "w").audio_rates看编码方向有没有采样率清单。- 数一数
av.Codec("libx264", "w").video_formats里有哪些格式。
常见误区
- 在解码上下文上读 gop_size。 抛 RuntimeError,读不到。
- 建 Codec 不写方向。 不写方向默认按解码,属性全空。
- 以为参数合并了就等于信息更全。 合并丢掉了「头写的」与「实算的」的区分。
自测三问(点开对答案)
1. 为什么 AVCodec 与 AVCodecContext 要分开? —— 模板是「能干什么」,实例是「这一次怎么干」。同一个解码器可以同时建多个实例。 2. `av.Codec('libx264', 'w')` 与 `av.Codec('libx264', 'r')` 有什么区别? —— 方向不同。写方向是编码器,有像素格式清单;读方向是解码器,清单为空。 3. 想读原视频的 GOP 大小,怎么办? —— 解码上下文不给,只能从码流里数关键帧之间隔了多少帧。- ☐ (本节完,下一节 3.6 FFmpeg 的重要 API 函数)