🎬 第三章 FFmpeg三大常用工具及应用选项详解
📄 一句话概括

像素格式就是「一个像素怎么存」,三个属性合起来决定它占多少字节。 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 秒实验

  1. 列格式:ffmpeg -pix_fmts,每行前面也有能力标记。
  2. 查编码器要什么:ffmpeg -h encoder=libx264,看它支持的像素格式那一列。
  3. 强制转换:ffmpeg -i 片子.mp4 -pix_fmt yuv420p -c:v libx264 出.mp4,解决兼容性。

常见误区

  1. 用 126 种当成 -pix_fmts 的行数。那只是「被编码器接受」的并集,全集更大。
  2. chroma_width() 不传参数。它会返回 0,看着像「这个格式没有色度」,其实是漏了参数。
  3. 拿原始帧字节数估文件大小。一帧 1.4 MB 的文件可能只有几百 KB,压缩率差着一两个数量级。
自测三问(点开对答案) 1. `yuv420p` 和 `rgb24` 每像素分别占多少位?为什么是两倍关系? —— 12 位与 24 位。420 的色度只有四分之一,合起来平均 1.5 字节,正好是 rgb24 的一半。 2. 为什么 `yuv420p` 的宽高必须是偶数? —— 因为它的色度平面对宽高都减半,奇数除不尽,色度平面排不下。 3. 「参数不认」和「写错了」怎么快速区分? —— 先用 3.11 这批查询命令看这份构建里有没有那个东西;没有就是构建问题,有才去查写法。

相关笔记