编码前四件事必须先说清:尺寸、格式、刻度、帧率。 码率是上限不是定额——画面简单时根本用不满。
8.2 是拆,这一节是装。 下一篇 8.4 专门讲编码里那几把尺子。
一、四件事必须先说清
前两件不说,open 直接报错;后两件不说,open 通过但结果错。 参数 是什么 不说的后果
width/height 画面尺寸 直接报错 pix_fmt 像素格式 直接报错 time_base 时间刻度 pts 没意义 framerate 帧率 码率控制失灵
「直接报错」反而是好事,至少当场知道。后两件才是麻烦的:能跑,但时间轴与码率都不对。 所以编码代码的第一步不是 open,是把这四个值想清楚。 想不清就别急着往下写。
二、gop_size 与关键帧
这个数决定「每多少个包插一个关键帧」。 gop_size 关键帧位置 适合
10 0、10、20… 随机访问多、直播 250 约 0、250… 电影、体积优先 1 全都是关键帧 要逐帧编辑,体积爆炸
demo 里 25 帧、gop 10,关键帧正好落在 0、10、20。 这个对应关系是死的, 可以拿去验自己的代码对不对。 关键帧大得离谱是正常的:它要独立描述一整幅画面,不能靠别的帧;后续帧只要描述「哪里变了」。 所以 gop 小会明显涨体积,但换来「随时能起播、随时能拖」。这是个体积换灵活性的兑换。
三、码率是上限不是定额
这一条实测最有说服力。 画面 设 50k 设 800k
纯色渐变 16k 左右 17k 左右 满屏噪点 64k 左右 1000k 左右
纯色那行:要 80 万,只给了 1.7 万。 因为画面本来就没内容, 编码器没东西可花——它不会为了凑数去浪费字节。 噪点那行反过来:要 5 万,给了 6.4 万。 短片段上码率控制来不及匀,超一点是常态。 所以「设了 2 Mbps 输出就一定是 2 Mbps」是错的。 码率更像「预算上限」,花不完很正常, 偶尔超一点也正常。
四、编码为什么会慢
因为它要「找出怎么描述最省」。 步骤 干什么 贵不贵
分块 把画面切成小块 便宜 搜索相似块 在参考帧里找最像的 很贵 变换量化 把残差压小 中等 熵编码 按概率分配码字 中等
第二行是全部开销的大头。 同一块画面要在参考帧的一大片区域里逐个位置比,比得越细越慢、
压得越好。
这就是「预设」参数的意义:preset 从极快到极慢,本质就是在调这一行的搜索强度。
解码没有这一行,所以解码永远比编码快——它只是照单执行,不做任何搜索。
这个不对称是编解码的基本事实。
五、编码侧的三条纪律
三条都是「不做就出错,而且不报错」。 纪律 漏了会怎样
pts 必须递增 播放顺序乱 冲空包 最后几帧丢 帧格式与设定一致 编码器直接拒收
第一条最隐蔽:pts 不递增不会报错,但播放器拿到之后会乱序,表现是「画面来回跳」。 第三条倒是会报错,报的是格式不匹配。这也是为什么「先 reformat 再送进去」成了标准动作。 三条合起来是一句话:编码器很挑食,喂之前先按它的口味处理好。
六、动手 Demo:编码之前必须先说清的四件事
先按四件事设好,再编 25 帧看关键帧落在哪,最后比三组码率。
import av
from fractions import Fraction as F
enc = av.CodecContext.create("libx264", "w")
enc.width, enc.height = 160, 120
enc.pix_fmt = "yuv420p"
enc.time_base = F(1, 25)
enc.framerate = F(25, 1)
enc.bit_rate = 200000
enc.gop_size = 10
enc.max_b_frames = 0
enc.open()
for i in range(25):
fr = av.VideoFrame(160, 120, "yuv420p")
for p in fr.planes:
p.update(bytes([(i * 7) % 256]) * p.buffer_size)
fr.pts = i
for pk in enc.encode(fr):
print(pk.pts, pk.size, pk.is_keyframe)
for pk in enc.encode(None):
print(pk.pts, pk.size, pk.is_keyframe)
实跑输出:
--------------------------------------------------------------------
编码之前必须先说清的四件事
--------------------------------------------------------------------
**① 四件事不说清,open 就报错**
参数 是什么 不说会怎样
-----------------------------------------------
width/height 画面尺寸 open 报错
pix_fmt 像素格式 open 报错
time_base 时间刻度 pts 没有意义
framerate 帧率 码率控制失灵
**后两个不报错但会出错。** time_base 不定,pts 就是一堆无意义的整数;
framerate 不定,码率控制器不知道一秒有多少帧。
**② 25 帧进,几个包出**
25 帧送进去,出来 **25 个包**,关键帧落在 **0、10、20**。
**gop_size=10 的意思就是「每 10 个包插一个关键帧」**,所以关键帧落在 0、10、20 三个位置,
正好对上。
**总字节 2460,平均每包 98 字节。** 第一个包 773 字节,其余几十字节——**关键帧大是正常的**,
它要独立描述一整幅画面。
**③ 码率这个参数,是「目标」不是「定额」**
画面 设 50k 实际 设 200k 实际 设 800k 实际
--------------------------------------------------------------
纯色渐变 16208 17108 17168
满屏噪点 64080 251024 1005932
**纯色那行最值得看**:要 80 万,只给了 **17168**。
因为画面本来就没内容,编码器没东西可花——**码率是上限,不是任务**。
**噪点那行反过来**:要 5 万,给了 **64080**,超了。**短片段上码率控制来不及匀,超一点是常态。**
**这条实测纠正了一个常见误解**:以为设了 bit_rate 就一定是那个数。
**实际它只约束「别超过太多」,内容简单时根本用不满。**
25 帧出 25 个包,关键帧落在 0、10、20,与 gop_size 对得上。 改码率设 80 万,实际只出 1.7 万——画面简单,花不完。
30 秒实验
- 把
gop_size改成 5,看关键帧位置怎么变。 - 把
max_b_frames改成 2,看包数会不会少于帧数。 - 把噪点帧喂进去,重跑三组码率,看实际值怎么变。
常见误区
- 只设尺寸不设 pix_fmt。 有的编码器会默认一个你不想用的格式。
- 以为设了码率就一定是那个数。 它是上限,不是定额。
- pts 从 1 开始或者乱给。 不报错,但播放顺序会乱。
自测三问(点开对答案)
1. 编码前必须设哪四件事? —— 宽高、像素格式、time_base、framerate。前两个不设直接报错。 2. gop_size=10 意味着什么? —— 每 10 个包插一个关键帧,关键帧落在 0、10、20 这样的位置。 3. 为什么设了 800k 码率只出了 17k? —— 画面本身没有内容可编码,码率是上限不是任务。- ☐ (本节完,下一节 8.4 FFmpeg编解码与时间基详解)