🎬 第五章 FFmpeg命令行实现音视频转码
📄 一句话概括

名字后面的数字是每像素位数:16 位省一半空间,24 位是真彩色,32 位多一个字节。 位数少的代价不是「模糊」,是「色带」。

📝 本节和前面几节的关系

上一节只用了 rgb24 一种。这一节把 555、565、32 位都试一遍,看字节与颜色数怎么变。 这几个格式在采集卡、老显卡、嵌入式屏上很常见。

一、名字里的数字指的是位深

四种格式各裸存一份,字节与位深如下:

像素格式 总字节 每像素位数


rgb555 1440000 16 rgb565 1440000 16 rgb24 2160000 24 rgb32 2880000 32

第三列不是照名字抄的,是把总字节除以「像素数×帧数」再乘 8 算出来的。 所以能看出一个反直觉的地方:555 的名字里三个 5 加起来只有 15,实际占 16 位。

多出来的那一位不用来存颜色,只用来凑齐两个字节。 同理 rgb32 的第 4 个字节多数时候是占位的,少数格式才拿它存透明度。

二、位数少了,丢的是颜色档数

每通道几位,就决定了每一档能分多细:

格式 每通道档数 能表示的颜色数


rgb555 5/5/5 位 32768 rgb565 5/6/5 位 65536 rgb24 8/8/8 位 16777216

第二行是 565 的特殊设计:绿色多给一位。 因为人眼对绿最敏感,同样少一位,蓝红掉了不太看得出来,绿掉了就很明显。

第三行的 16777216 就是常说的「1677 万色」,也就是 2 的 24 次方。 这个数字到今天还是「真彩色」的标准说法,虽然它已经是三十年前的定义了。

三、什么时候该用哪个

按场景选,不按「越大越好」选:

场景 建议 理由


存档、后期处理 rgb24 不差这一倍空间 嵌入式屏、老设备 rgb565 显存按位算,省一半 要透明度 rgba 第 4 个字节存 alpha 给 GPU 上传纹理 rgb32 四字节对齐最快

第三行提醒一件事:真正的透明度要用 rgba,不是 rgb32。 名字像,但 rgb32 的第 4 字节在多数实现里是不参与显示的。

低色深最典型的毛病是「色带」:平滑渐变会变成一级一级的台阶。 在天空、人脸高光这些大面积渐变上最容易看出来。

四、动手 Demo:四种 RGB 各存一份,反算每像素位数

把同一批帧分别按 rgb555、rgb565、rgb24、rgb32 裸存,比较字节。 然后用「总字节 ÷ 像素总数 × 8」反算位深,看与名字里的数字对不对得上。

import av, os

for pf in (u"rgb555", u"rgb565", u"rgb24", u"rgb32"):
    fn = pf + u".rgb"
    c1 = av.open(fn, u"w", format=u"rawvideo")
    s1 = c1.add_stream(u"rawvideo", rate=25)
    s1.width, s1.height, s1.pix_fmt = 160, 90, pf
    c0 = av.open(u"s5.mp4")
    n = 0
    for f in c0.decode(video=0):
        n += 1
        for o in s1.encode(f.reformat(width=160, height=90, format=pf)):
            c1.mux(o)
    for o in s1.encode(None):
        c1.mux(o)
    c1.close()
    c0.close()
    sz = os.path.getsize(fn)
    print(u"%s %8d 字节  %.0f 位/像素" % (pf, sz, sz * 8.0 / (160 * 90 * n)))

实跑输出:

--------------------------------------------------------------------
5.2.4 RGB16/RGB24/RGB32——位深与排列
--------------------------------------------------------------------

**① 四种 RGB 各存一份**

  像素格式     总字节      每像素位数
  ------------------------------------
  rgb555       1440000     16
  rgb565       1440000     16
  rgb24        2160000     24
  rgb32        2880000     32

**第三列是把总字节除以「像素数×帧数」再乘 8 算出来的,不是照名字抄的。**
**555 与 565 都是 16 位(2 字节)——名字里的三位数字加起来只有 15,**
**多出来的那一位是留给透明的,四个格式的位深其实是 16/16/24/32。**

**② 位数少了,丢的是「颜色的档数」**

  格式       每通道档数     能表示的颜色数
  -----------------------------------------
  rgb555     5/5/5 位     32768
  rgb565     5/6/5 位     65536
  rgb24      8/8/8 位     16777216

**第二行是 565 的特殊之处:绿色多给一位,因为人眼对绿最敏感。**
**第三行 16777216 色就是「真彩色」的由来——2 的 24 次方。**

**所以低色深格式的代价不是「模糊」而是「色带」:渐变会变成一条条台阶。**

rgb555 在 FFmpeg 里是 16 位存的,高 1 位不用。 所以它的位深与 rgb565 一样,都是 16,字节数也就完全一样。

rgb32 是 4 字节但只有 24 位有效,第 4 字节多半是 0。 它换来的好处是对齐:四字节一个像素,GPU 与 SIMD 处理起来更顺。

最后那个除法一定要除以帧数。 忘了除,算出来的位深会变成几百,一眼就知道算错了。

30 秒实验

  1. 看色带:把一张渐变图转成 rgb565 再转回来,放大看台阶。
  2. 看对齐:-pix_fmt rgb0 与 rgb32 各转一遍,字节一样,第 4 字节含义不同。
  3. 命令行指定:-pix_fmt rgb565le 注意字节序,小端与大端的区别在这时就显出来了。

常见误区

  1. 以为 555 是 15 位。它占 16 位,多的一位不用,字节数按 2 字节算。
  2. 把 rgb32 当 rgba 用。第 4 字节多数实现不参与显示,要透明度得用 rgba。
  3. 以为降位深只会「稍微差一点」。大面积渐变上的色带非常显眼。
自测三问(点开对答案) 1. rgb555 与 rgb565 的字节数为什么一样? —— 因为两者都按 16 位存储,差别只在 16 位怎么分配给三个通道。 2. 为什么 565 给绿色多一位? —— 人眼对绿色最敏感。同样少一位,绿通道掉档比红蓝更容易被看出来。 3. rgb32 的第 4 个字节通常存什么? —— 多数情况什么都不存,只为了四字节对齐;要透明度应该用 rgba。

相关笔记