目标码率是预算上限,不是承诺:三档目标实测只花掉 47%、35%、20%。 定码率下比字节没有意义,比容量要换定质量。
帧率管「每秒几张」,码率管「每秒多少字节」,分辨率管「一张多大」。 这三个参数互相牵连,动一个另外两个的感受就变。
一、定码率:三档都花不完
同一段素材,三个目标码率各压一遍:
目标码率 字节 实际码率 占目标
100 kbps 11793 47 kbps 47% 200 kbps 17408 69 kbps 35% 400 kbps 19722 78 kbps 20%
三行都远低于目标:目标 100 kbps 只花掉 47 kbps,目标 400 kbps 也只花掉 78 kbps。 这段素材太简单,编码器在预算之内就够用了。
这和 5.1.2 里 AAC 目标 192 只做到 145 是同一件事。 所以「码率」这个词有两层意思:说目标的时候它是预算上限,说实际的时候它是结果。
要注意第三列的边际收益:目标翻四倍,实际码率只从 47 涨到 78。 说明这段素材的信息量就在那儿,多给的预算它花不出去。
二、定码率下比分辨率会得到反直觉的结果
同一个 200 kbps 目标,两个分辨率:
分辨率 目标码率 字节
160×90 200 kbps 17408 96×54 200 kbps 26194
小画面反而更大,差了 50%——这跟直觉相反。 两者都在「花不完预算」的状态里,谁花得多只取决于编码器在各自尺寸上的决策。
所以定码率模式下比字节没有意义。 要比就得比画质,或者比「谁能用更少的字节达到同样画质」。
三、换定质量,关系就正常了
同一档 CRF,两个分辨率:
分辨率 CRF 字节
160×90 26 4696 96×54 26 3653
小画面掉到 78%——质量固定时,像素少了自然花得少。 这才是可以拿来估容量的一列:CRF 与分辨率定了,字节大致就有了。
所以两种模式的用途完全不同:定质量用来交付,定码率用来控带宽。 拿定码率去估算存储,会算得偏高,因为实际用量可能只有一半。
四、什么时候用哪个
按场景选,不按「哪个更专业」选:
场景 选哪个
要估算流量与存储 定码率,并且设 maxrate 要保证画质一致 定质量 直播、带宽有限 定码率加缓冲区上限 本地存档 定质量
第一行那句「并且设 maxrate」是关键:单给目标码率只是「想花这么多」,
加上 maxrate 与 bufsize 才是「不许超过这么多」——真要卡带宽,两个都得给。
这也是本次实跑三档都「花不完」的根本原因:bit_rate 只传了目标,没给上限,
编码器完全可以少花,而且它确实少花了。
五、动手 Demo:三个目标码率各压一遍,再看两个分辨率
先按 100、200、400 kbps 三个目标各压一遍,比实际码率与目标的比例。 再看同一目标下两个分辨率的字节,最后换成 CRF 26 对照。
import av, os
def enc(dst, w, h, br=None, crf=None):
c1 = av.open(dst, u"w")
s1 = c1.add_stream(u"libx264", rate=25)
s1.width, s1.height, s1.pix_fmt = w, h, u"yuv420p"
if crf is None:
s1.bit_rate = br
else:
s1.options = {u"crf": u"%d" % crf}
c0 = av.open(u"s5.mp4")
for f in c0.decode(video=0):
for o in s1.encode(f.reformat(width=w, height=h, format=u"yuv420p")):
c1.mux(o)
for o in s1.encode(None):
c1.mux(o)
c1.close()
c0.close()
return os.path.getsize(dst)
for br in (100000, 200000, 400000):
n = enc(u"br%d.mp4" % (br // 1000), 160, 90, br=br)
real = n * 8 / 2.0
print(u"%d kbps -> %d 字节 实际 %d kbps 占 %.0f%%"
% (br // 1000, n, real / 1000, real * 100 / br))
print(u"160x90 200kbps %d" % enc(u"br_big.mp4", 160, 90, br=200000))
print(u"96x54 200kbps %d" % enc(u"br_small.mp4", 96, 54, br=200000))
c = enc(u"crf_big.mp4", 160, 90, crf=26)
d = enc(u"crf_small.mp4", 96, 54, crf=26)
print(u"CRF 26 160x90 %d" % c)
print(u"CRF 26 96x54 %d 占 %.0f%%" % (d, d * 100.0 / c))
实跑输出:
--------------------------------------------------------------------
5.6.2 控制视频的码率及分辨率
--------------------------------------------------------------------
**① 定码率:三个目标码率各压一遍**
目标码率 字节 实际码率 占目标
-------------------------------------------
100 kbps 11793 47 kbps 47%
200 kbps 17408 69 kbps 35%
400 kbps 19722 78 kbps 20%
**三行都远低于目标:目标 100 kbps 只花掉 47 kbps,目标 400 kbps 也只花掉 78 kbps。**
**这段素材太简单,编码器在预算之内就够用了——和 5.1.2 里 AAC 目标 192 只做到 145 是同一件事。**
**所以「码率」这个词有两层意思:说目标的时候它是预算上限,说实际的时候它是结果。**
**② 同一个目标码率,两个分辨率**
分辨率 目标码率 字节
------------------------------
160×90 200 kbps 17408
96×54 200 kbps 26194
**小画面反而更大,差了 50%——这跟直觉相反,但原因很简单:**
**两者都在「花不完预算」的状态里,谁省得多只取决于编码器在各自尺寸上的决策。**
**所以定码率模式下比字节没有意义——要比画质,或者比「谁能用更少的字节达到同样画质」。**
**③ 换定质量,关系就正常了**
分辨率 CRF 字节
------------------------
160×90 26 4696
96×54 26 3653
**小画面掉到 78%——质量固定时,像素少了自然花得少,这才是可以拿来估容量的一列。**
**④ 什么时候用哪个**
场景 选哪个
--------------------------------------------
要估算流量与存储 定码率,并且设 maxrate
要保证画质一致 定质量
直播、带宽有限 定码率加缓冲区上限
本地存档 定质量
**第一行那句「并且设 maxrate」是关键:单给目标码率只是「想花这么多」,**
**加上 `maxrate` 与 `bufsize` 才是「不许超过这么多」——真要卡带宽,两个都得给。**
s1.bit_rate 与 s1.options = {"maxrate": ..., "bufsize": ...} 是两回事:
前者是目标,后者才是硬上限。要真卡带宽两个都要给。
f.reformat(width=w, height=h, ...) 一步完成缩放与色彩空间转换,
比先解码成 ndarray 再用别的库缩放快得多,也更不容易出错。
实际码率是用「文件字节乘 8 除时长」算的,这里的时长取 2 秒。 严格来说该用容器里的真实时长,差一点点不影响结论。
30 秒实验
- 命令行对照:
ffmpeg -i 源.mp4 -b:v 200k -maxrate 200k -bufsize 400k 出.mp4。 - 改质量:把
-b:v换成-crf 26,看字节怎么变。 - 同码率比画质:两档编码器给同样
-b:v,人眼对比谁的块效应少。
常见误区
- 以为给了目标码率就一定用满。它只是上限,简单画面上编码器会少花。
- 在定码率模式下比字节。两者都没花完预算,比出来的是编码器的决策差异。
- 只给
-b:v就当卡住了带宽。不加maxrate与bufsize就压不住瞬时峰值。
自测三问(点开对答案)
1. 目标 400 kbps 为什么只花掉 78 kbps? —— 目标只是预算上限。这段素材简单,编码器在预算内就够用了。 2. 为什么小画面在定码率下反而更大? —— 两者都没花完预算,字节取决于各自尺寸上的编码决策,与像素数没有单调关系。 3. 要真卡住带宽还得加什么? —— `maxrate` 与 `bufsize`。只给目标码率压不住瞬时峰值。- ☐ (本节完,下一节 5.6.3 控制视频的 GOP)