编码比解码多一件麻烦事:要设一堆参数,而且设错不报错。
六个 API 的顺序有硬要求:私有选项必须在 open 之前设。
上一节讲流程,这一节讲 API 顺序和参数。 参数的效果在 04.02 已经量过一轮,这里补「怎么设进去」。 选项名与单位的坑在 04.02.02,本节只提最关键的那几条。
一、五个必设参数,两个「能用但不合适」
| 参数 | 在哪设 | 不设会怎样 |
|---|---|---|
| 宽高 | VideoCodecContext |
报错(必需) |
| 像素格式 | pix_fmt |
报错或转错 |
| 帧率 | framerate |
时间戳算错 |
| 码率/质量 | bit_rate 或私有选项 |
用默认值,可能糊 |
| GOP | gop_size |
用默认 250,seek 很慢 |
前三个是必需的,不设直接报错; 后两个是「能用但不合适」——这才是最麻烦的,因为程序照跑,问题要到回放时才发现。 帧率那一行尤其隐蔽:不设帧率,编码器按默认 25 算时间戳,你按 30 fps 给的 pts 就会被打乱, 播放出来快慢不对。
二、六个 API 与硬性顺序
| API | 作用 | 顺序 |
|---|---|---|
avcodec_find_encoder |
按名字找编码器 | 1 |
avcodec_alloc_context3 |
建上下文 | 2 |
av_opt_set 系列 |
设私有选项 | 3 |
avcodec_open2 |
打开 | 4 |
avcodec_send_frame |
送帧 | 5,循环 |
avcodec_receive_packet |
取包 | 6,循环 |
第 3 步必须在第 4 步之前:编码器打开之后再设私有选项,有的是无效的,有的直接报错。
这个顺序错在代码里很难看出来——因为 av_opt_set 本身返回成功。
这就是 04.02.02 讲的「改完看结果」那套办法的用处。
三、实测四种参数组合
| 参数 | 大小 | 每帧字节 |
|---|---|---|
| crf=18 高质 | 8.1 KB | 1030 |
| crf=23 默认 | 6.3 KB | 811 |
| crf=35 低质 | 3.9 KB | 493 |
| bit_rate=500k | 5.5 KB | 699 |
bit_rate 那一行是「固定码率」模式,和 CRF 是两条路:
CRF 是「按质量给码率」,画面复杂就自动多给;bit_rate 是「不许超」,画面复杂就降质量。
监控常用固定码率,因为要算带宽和硬盘(16 路 × 每路多少 Mbps,硬盘要多大, 都是拿码率乘出来的);离线压制用 CRF,因为要保画质。
四、三条容易踩的坑
| 坑 | 现象 | 怎么办 |
|---|---|---|
| 选项写在 open 之后 | 不报错但无效 | 挪到 open 之前 |
maxrate 忘了乘 1000 |
画面糊成马赛克 | bps,不是 kbps |
| 宽高是奇数 | 报 width not divisible by 2 | 改成偶数 |
第二行是本节最实用的:x264.exe 用 kbps,FFmpeg 用 bps,差 1000 倍。 第三行源于 yuv420p 的结构:两个色度平面各是宽高的一半,奇数除不尽, 所以 641 宽这种尺寸直接编不了。
五、动手 Demo:编码的主要 API 与参数设置
Demo 先列六个 API 的顺序,再用四种参数组合各编一遍,量体积和每帧字节。
重点是看 bit_rate 和 crf 两条路的差别。
def enc(opts, bitrate=None):
c = av.open("t.h264", "w", format="h264")
s = c.add_stream("libx264", rate=30) # 1 找 + 2 建
s.width, s.height, s.pix_fmt = 320, 240, "yuv420p"
if bitrate:
s.bit_rate = bitrate # 固定码率那条路
else:
s.options = {"crf": str(opts)} # 3 设私有选项
for i in range(30): # 4 打开由 encode 首次触发
...
实跑输出:
--------------------------------------------------------------------
编码的主要 API 与参数设置
--------------------------------------------------------------------
**编码比解码多一件麻烦事:要设一堆参数。**
**设错不报错,只是效果不对。**
参数 在哪设 不设会怎样
----------------------------------------------------------------------
宽高 `VideoCodecContext` 报错(必需)
像素格式 `pix_fmt` 报错或转错
帧率 `framerate` 时间戳算错
码率/质量 `bit_rate` 或私有选项 用默认值,可能糊
GOP `gop_size` 用默认 250,seek 很慢
**前三个是必需的**,不设直接报错;**后两个是「能用但不合适」**——这才是最麻烦的,因为程序照跑。
API 作用 顺序
------------------------------------------------------------------
`avcodec_find_encoder` 按名字找编码器 1
`avcodec_alloc_context3` 建上下文 2
`av_opt_set` 系列 设私有选项 3
`avcodec_open2` 打开 4
`avcodec_send_frame` 送帧 5,循环
`avcodec_receive_packet` 取包 6,循环
**第 3 步必须在第 4 步之前**:编码器打开之后再设私有选项,**有的是无效的,有的直接报错。**
**这个顺序错在代码里很难看出来**——因为 `av_opt_set` 本身返回成功。
**实测几种参数组合,看输出差多少:**
参数 大小 每帧字节
--------------------------------------------------------
crf=18 高质 8.1 KB 1030
crf=23 默认 6.3 KB 811
crf=35 低质 3.9 KB 493
bit_rate=500k 5.5 KB 699
**`bit_rate` 那一行是「固定码率」模式**,和 CRF 是两条路:
CRF 是「按质量给码率」,**画面复杂就自动多给**;bit_rate 是「不许超」,**画面复杂就降质量**。
**监控常用固定码率**,因为要算带宽和硬盘;**离线压制用 CRF**,因为要保画质。
PyAV 在你第一次 encode() 时才真正调 avcodec_open2,
所以「选项要在 open 之前设」在 Python 里表现为「要在 encode() 之前设」。
bit_rate 那行 699 字节/帧 ≈ 5.6 KB 总量,和 crf=23 的 811 差不太多,
因为这段画面本来就好压,500k 的额度用不完。想看出固定码率的威力,得用复杂画面。
30 秒实验
- 把
bit_rate设成 50000(50 kbps),看画面糊到什么程度、每帧字节掉到多少。 - 故意把选项设在
encode()之后(用一个已经编过的流),看是不是静默无效。 - 把宽改成 641,看报错原文。
常见误区
- 以为参数设了就行。 顺序错、名字错、单位错都静默失败,只能靠「改完看结果」验证。
- 以为 CRF 和固定码率可以混着用。 两条路,
-crf优先,bit_rate退化成参考。 - 以为奇数宽高只是「不整齐」。 yuv420p 除不尽,直接编不了。
自测三问(点开对答案)
1. 为什么私有选项要在 `avcodec_open2` 之前设? —— 编码器打开时会把选项读进自己的内部结构,**之后改的上层副本不再生效**,有的还会报错。 2. 监控为什么用固定码率而不用 CRF? —— 要算带宽和硬盘容量。**16 路 × 每路 4 Mbps × 天数,是直接乘出来的**,CRF 给不了这个确定性。 3. 641 宽为什么编不了 yuv420p? —— 色度平面是宽高各一半,**641 除以 2 除不尽**。所以宽高必须是偶数。下一节开始拼完整案例:从 YUV 文件一路做到能播的文件。
- ☐ (本节完,下一节 04.05.01 案例:使用FFmpeg将YUV编码为H.264)