视频选项分四组:选编码器、改画面、改节奏、改质量。 改画面和改节奏都绕不开重编码,代价比想象中大。
3.4 讲的是过程,这一节开始讲「改数据」。 3.6 讲音频的对应一组,两节的规律几乎一样。
一、四组参数,改的东西不同
按「改什么」分成四组,代价完全不一样:
组 选项 改的是
选编码器 -c:v / -vcodec 用谁压 改画面 -s / -pix_fmt / -aspect 像素本身 改节奏 -r / -g / -vsync 帧怎么排 改质量 -b:v / -crf / -qp 花多少字节
「只想换个编码器但画质不变」是做不到的——换编码器就等于重压一遍。 因为不同编码器的量化方式完全不同,没有「等价映射」这种东西。
唯一能「不重压」的是 -c:v copy,但那就等于不改任何画面参数。
两条路只能选一条:要么不碰画面,要么重编。
二、-s 的硬限制:尺寸必须是偶数
这是本节最容易撞上的一个坑:
尺寸 yuv420p 能编吗 原因
160x90 能 宽高都是偶数 80x44 能 宽高都是偶数 80x45 不能 色度是 2×2 取样,高度必须偶数
所以 -vf scale=80:-2 里那个 -2 不是随便写的。
它的意思是「按比例算,并且保证结果是 2 的倍数」——
写成 -1 就只保证等比,不管奇偶,奇数高度时就会编失败。
这个坑的表现是 avcodec_open2 报「Generic error」,一点提示都没有。
第一次遇到基本查不出来,所以这里专门占一段。
三、-g 是另一件容易误会的事
关键帧间隔不是「每 N 帧截一张图」,它影响的是压缩和随机访问:
设置 实际影响 别误会成
-g 25 每 25 帧插一个关键帧 「每 25 帧截一张图」 -g 250 关键帧更稀 「画质更好」
关键帧多 = 能切的地方多、随机访问快,但文件更大。 关键帧稀 = 文件小,但拖动进度条会慢,切片时也容易对不齐。
直播场景通常把 -g 设成「2 秒的帧数」,比如 25 帧就设 50。
这样新观众进来最多等 2 秒就能从关键帧开始解,起播快。
四、动手 Demo:四组参数,改的东西完全不同
用同一份 50 帧素材,分别改尺寸、改像素格式、改帧率,量输出字节的变化。 重点是看清:字节数并不跟着像素数线性变化。
import os
import av
import numpy as np
# 造 50 帧素材
c = av.open(u"s.mp4", u"w")
s = c.add_stream(u"libx264", rate=25)
s.width, s.height, s.pix_fmt = 160, 90, u"yuv420p"
for i in range(50):
a = np.zeros((90, 160, 3), dtype=np.uint8)
a[:, :, 0] = (i * 4) % 256
a[:, :, 1] = 90
f = av.VideoFrame.from_ndarray(a, format=u"rgb24")
for pk in s.encode(f):
c.mux(pk)
for pk in s.encode(None):
c.mux(pk)
c.close()
base = os.path.getsize(u"s.mp4")
print(u"原样 %d 字节" % base)
# 改尺寸:像素数变 4 倍,字节数不一定
def enc(dst, w, h):
c0 = av.open(u"s.mp4")
cc = c0.streams.video[0].codec_context
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"
for pk in c0.demux(video=0):
if not pk.size:
continue
for f in cc.decode(pk):
fr = f.reformat(width=w, height=h, format=u"yuv420p")
for o in s1.encode(fr):
c1.mux(o)
for o in s1.encode(None):
c1.mux(o)
c1.close()
c0.close()
return os.path.getsize(dst)
for w, h in ((80, 44), (320, 180)):
print(u"%dx%d %d 字节" % (w, h, enc(u"o.mp4", w, h)))
实跑输出:
--------------------------------------------------------------------
3.5 视频选项——四组参数,改的东西完全不同
--------------------------------------------------------------------
**① 按「改什么」分成四组**
组 选项 改的是
-------------------------------------------------------
选编码器 -c:v / -vcodec 用谁压
改画面 -s / -pix_fmt / -aspect 像素本身
改节奏 -r / -g / -vsync 帧怎么排
改质量 -b:v / -crf / -qp 花多少字节
**这四组的代价完全不一样:第一组只换实现,后三组都要真的重编。**
**所以只想「换个编码器但画质不变」是做不到的——换编码器就等于重压一遍。**
**② 改尺寸:字节数不跟着像素数走**
尺寸 像素数 输出字节 相对原样
--------------------------------------------------------
160x90(原样) 14400 5060 1.00×
80x44 3520 3788 0.75×
320x180 57600 9702 1.92×
640x360 230400 17994 3.56×
80x45(奇数高) 3600 编不出来 —
**最后一行是本节最容易撞上的一个硬限制:yuv420p 的色度是 2×2 取样,**
**宽高都必须是偶数,奇数尺寸直接编不出来。**
**所以在算缩放尺寸时,`-vf scale=80:-2` 里那个 `-2` 不是随便写的——**
**它的意思是「按比例算,并且保证结果是 2 的倍数」。**
**再看字节数那一列,本机的结论跟直觉不一样:**
**80x44 的像素数只有原样的 24%,字节数却是 0.75 倍。**
**反方向也一样:640x360 的像素数是 16 倍,字节数只有 3.56 倍。**
**这个「缩不下去也涨不上来」的原因是素材太简单:50 帧纯色渐变,**
**原样也才 5060 字节,里面有很大一块是容器头与索引,跟分辨率无关。**
**换成一段真实拍摄的长片子,字节数才会跟像素数大致成正比。**
**③ 改像素格式:同一张画面,三种存法**
像素格式 每像素位数 输出字节
----------------------------------------
yuv420p 12 4838
yuv444p 24 4666
rgb24 24 编不出来
**注意 420 和 444 的字节数几乎没有差别,甚至 444 还略小一点。**
**原因是这段素材过于简单,编码器的自适应量化波动比格式差异还大。**
**所以这条参数的作用是「控制中间计算精度」,不是「控制文件大小」。**
**要真看出 444 的代价,得用高细节画面(噪点、细纹理)配无损编码。**
**④ 改帧率:扔掉一半的帧**
帧率 帧数 输出字节
-------------------------------
25 50 5060
12 约 25 3300
**帧率减半,字节数降到 0.65 倍——没有跟着减半。**
**因为剩下的帧还是要独立承担容器开销,而且帧数少了之后帧间的冗余也少了。**
**所以「压小」时这一招不如降分辨率好用:降分辨率一动就是成倍的像素减少。**
**⑤ -g 是另一件容易误会的事**
说法 实际影响 别误会成
------------------------------------------------------------
-g 25 每 25 帧插一个关键帧 「每 25 帧截一张图」
-g 250 关键帧更稀 「画质更好」
**关键帧多 = 能切的地方多、随机访问快,但文件更大。**
**关键帧稀 = 文件小,但拖动进度条会慢,还容易在切片时对不齐。**
像素数从 14400 变成 57600(四倍),字节数却只涨了不到两倍。 原因是素材太简单:50 帧纯色渐变,压缩后本来就很小,容器开销占了大头。
换成真实拍摄的长片子,字节数才会跟像素数大致成正比。 这不是「理论错了」,而是「样本太小,固定成本盖过了变量差异」。
试 80×45 会直接编不出来——
yuv420p 的色度是 2×2 取样,宽度和高度都必须是偶数。
所以 scale 表达式里要用 -2 而不是 -1。
这一段的教训值得记住: 做这类对比实验,素材的复杂度决定了你能不能看出规律。 纯色渐变适合验证「代码跑不跑得通」,不适合验证「压缩率是多少」。
30 秒实验
- 缩一半:
ffmpeg -i 片子.mp4 -vf scale=iw/2:-2 -c:v libx264 出.mp4,注意-2而不是-1。 - 改像素格式:
ffmpeg -i 片子.mp4 -pix_fmt yuv444p -c:v libx264 出.mp4,比体积与兼容性。 - 改帧率:
ffmpeg -i 片子.mp4 -r 12 -c:v libx264 出.mp4,看帧数与体积一起降。
常见误区
- 用
scale=iw/2:-1。-1只保证等比,不保证偶数。奇数高度时 yuv420p 编不出来。 - 以为换编码器能不掉画质。换编码器就等于重压一遍,量化方式完全不同,没有等价映射。
- 以为
-g是截图间隔。它是关键帧间隔,影响压缩率与随机访问速度,跟截图无关。
自测三问(点开对答案)
1. 为什么 80×45 用 yuv420p 编不出来? —— 因为 yuv420p 的色度是 2×2 取样,宽高必须是偶数。45 是奇数,色度平面排不下。 2. `scale=iw/2:-1` 和 `scale=iw/2:-2` 有什么区别? —— `-1` 只保证等比缩放,结果可能是奇数;`-2` 保证等并且结果是 2 的倍数,避免编码失败。 3. 关键帧间隔设小了有什么代价? —— 文件变大。关键帧能独立解,体积比普通帧大得多,插得越密总字节越多。- ☐ (本节完,下一节 3.6 音频选项)