像素格式就是「一个像素怎么存」,三个属性合起来决定它占多少字节。 yuv420p 平均 12 位、rgb24 是 24 位,正好差一倍。
3.11.11 讲颜色名,这一节讲像素格式——3.11 这一串查询的最后一节。 这一节收尾时会把整章的十个查询串成一套排查动作。
一、三个属性:位深、平面、色度尺寸
名字不同,本质是这三个属性不同:
格式 每像素位 平面吗 色度尺寸
yuv420p 12 是 80×45 yuv422p 16 是 80×90 yuv444p 24 是 160×90 rgb24 24 否 160×90 gray 8 否 160×90
第四列是按 160×90 算出来的色度平面尺寸:420 是 80×45、422 是 80×90、444 不减。 所以「420 和 444 差多少」不用背,看这一列就有答案。
rgb24 和 gray 那一列也是整幅尺寸,因为它们根本没有独立的色度平面。 这个「有没有色度平面」就是 YUV 系与 RGB 系最本质的区别。
二、一帧占多少字节,两个数相乘
原始帧的字节数只跟尺寸和格式有关,跟内容一点关系都没有:
尺寸 yuv420p 一帧 rgb24 一帧
160x90 21600 字节 43200 字节 1280x720 1382400 字节 2764800 字节
两个尺寸下 rgb24 都是 yuv420p 的两倍,因为 12 位对 24 位。 这个比例跟压缩后的文件大小没关系——压缩率取决于画面内容。
所以「一帧 1.4 MB」这种换算只能用来估内存带宽,不能用来估文件大小。 这两件事经常被混为一谈,实际差着一两个数量级。
三、三个高频坑,都在表里能看出来
本节这三个坑,都是实际调参数时最常撞上的:
坑 从表里怎么看
yuv420p 宽高必须偶数 色度平面只有一半,奇数除不尽 rgb24 编不进 libx264 它是 packed,编码器只要 yuv 带 a 的格式大一圈 多一个 8 位平面,20 位对 12 位
第一行就是 3.5 那个 scale=-2 的来历,第二行是第一次调 libx264 常撞的墙。
第三行值得算一下:yuva420p 是 20 位对 yuv420p 的 12 位,多了三分之二,不是一点点。
所以不需要透明度时,别顺手选带 a 的格式——那 8 位白花在内存和带宽上。 要透明度时也别指望它「稍微大一点」,它大得很实在。
四、十个查询,一套排查动作
3.11 这一串小节到这里收尾,回头看它们其实是一张排查表:
想知道 查哪个
这个文件能不能读 -formats / -demuxers 我要的编码在不在 -encoders / -decoders 这个滤镜在不在 -filters 网络推流行不行 -protocols 硬件加速行不行 -hwaccels 声道模式怎么填 -layouts 采样格式怎么填 -sample_fmts
这七行就是这一章存在的意义:把「猜」换成「查」。 遇到任何「参数不认」「找不到编码器」「滤镜不存在」,先回这张表过一遍。
多数情况下答案是「你这份构建里就没有」,而不是「你写错了」。 分清这两者,排障时间能省掉一大半——因为前者要换构建,后者才要改命令。
五、动手 Demo:像素格式的三个属性与字节数
沙箱里打不出完整的 -pix_fmts,但每个编码器都声明了「我吃哪些像素格式」。
把这份声明的并集收起来,就能看到构建里真实在用的一批名字。
再把这几个常见格式的位深、平面、色度尺寸读出来对照。
import av
names = set()
for n in av.codecs_available:
try:
c = av.codec.Codec(n, u"w")
except Exception:
continue
if c.is_encoder:
for f in (c.video_formats or []):
names.add(f.name)
print(u"编码器声明的像素格式 %d 种" % len(names))
for name in (u"yuv420p", u"yuv422p", u"yuv444p", u"rgb24", u"gray"):
f = av.VideoFormat(name)
print(u"%-9s %2d 位 %s 色度 %dx%d"
% (name, f.bits_per_pixel,
u"平面" if f.is_planar else u"打包",
f.chroma_width(160), f.chroma_height(90)))
实跑输出:
--------------------------------------------------------------------
3.11.12 -pix_fmts——像素格式的四个属性
--------------------------------------------------------------------
**① `-pix_fmts` 打的是「这份构建认识的全部像素格式」**
**沙箱里没有 exe,打不出那张完整表;但每个编码器都声明了「我吃哪些像素格式」,**
**把这些声明的并集收起来,就能看到这份构建里真实在用的一批名字。**
项 值
-----------------------------------
编码器声明的像素格式 126 种
其中带硬件前缀的 4 种
软件像素格式 122 种
**第一行是并集,不是 `-pix_fmts` 的行数——`-pix_fmts` 还包含没有编码器接受的格式。**
**两者的关系是:这里的是「有人用」,那边的是「全都列」。**
**② 每个像素格式有三个属性,合起来决定它占多少字节**
格式 每像素位 平面吗 色度平面尺寸
-----------------------------------------------------
yuv420p 12 是 80×45
yuv422p 16 是 80×90
yuv444p 24 是 160×90
rgb24 24 否 160×90
gray 8 否 160×90
yuva420p 20 是 80×45
**第四列是按 160×90 算出来的色度平面尺寸(色度要传尺寸才给结果,空着调用返回 0)。**
**420 是 80×45(横竖都减半)、422 是 80×90(只横减半)、444 是 160×90(不减)。**
**rgb24 和 gray 那一列也是 160×90,因为它们没有独立的色度平面,这一列等于整幅尺寸。**
**③ 一帧占多少字节,是两个数相乘**
尺寸 yuv420p 一帧 rgb24 一帧
----------------------------------------------
160x90 21600 字节 43200 字节
1280x720 1382400 字节 2764800 字节
**两个尺寸下 rgb24 都是 yuv420p 的 2.0 倍,原因在第二列那个 12 位与 24 位:**
**yuv420p 平均每像素 12 位(1.5 字节),rgb24 是 24 位(3 字节),正好差一倍。**
**这个比例跟压缩后的文件大小没关系——压缩率取决于画面内容,不取决于原始帧字节数。**
**④ 三个最常见的坑,都在这一列里能看出来**
坑 看哪一列能发现
-----------------------------------------------------------
yuv420p 宽高必须偶数 色度平面只有一半,奇数除不尽
rgb24 编不进 libx264 它是 packed,编码器只要 yuv
带 a 的格式大一圈 多一个 8 位平面,20 位对 12 位
**第一行就是 3.5 那个 `scale=-2` 的来历,第二行是很多人第一次调 libx264 撞的墙。**
**第三行也值得算一下:yuva420p 是 20 位、yuv420p 是 12 位,多了 8 位正好一个 alpha,**
**按字节算就是 2.5 字节对 1.5 字节,多出三分之二,不是「多一点点」。**
**所以不需要透明度时,别顺手选带 a 的格式。**
第一行的 126 种是「有人用」的那一批,不是 -pix_fmts 的行数。
后者是「全都列」,还包括没有编码器接受的格式——两个口径要分清。
下面几行的色度尺寸都是按 160×90 算的:420 给 80×45、422 给 80×90、444 给 160×90。
注意 chroma_width() 不传尺寸时返回 0,必须把宽高传进去才拿得到真实结果。
这一段和 3.5 那个 scale=-2 是同一个坑的两头:
那边讲「奇数高度编不出来」,这边给出原因——色度平面只有一半,奇数除不尽。
30 秒实验
- 列格式:
ffmpeg -pix_fmts,每行前面也有能力标记。 - 查编码器要什么:
ffmpeg -h encoder=libx264,看它支持的像素格式那一列。 - 强制转换:
ffmpeg -i 片子.mp4 -pix_fmt yuv420p -c:v libx264 出.mp4,解决兼容性。
常见误区
- 用 126 种当成
-pix_fmts的行数。那只是「被编码器接受」的并集,全集更大。 chroma_width()不传参数。它会返回 0,看着像「这个格式没有色度」,其实是漏了参数。- 拿原始帧字节数估文件大小。一帧 1.4 MB 的文件可能只有几百 KB,压缩率差着一两个数量级。
自测三问(点开对答案)
1. `yuv420p` 和 `rgb24` 每像素分别占多少位?为什么是两倍关系? —— 12 位与 24 位。420 的色度只有四分之一,合起来平均 1.5 字节,正好是 rgb24 的一半。 2. 为什么 `yuv420p` 的宽高必须是偶数? —— 因为它的色度平面对宽高都减半,奇数除不尽,色度平面排不下。 3. 「参数不认」和「写错了」怎么快速区分? —— 先用 3.11 这批查询命令看这份构建里有没有那个东西;没有就是构建问题,有才去查写法。- ☐ 第三章完,第四章从「视频容器及封装与解封装」开始