像素格式位字节序

Pixel format bit endianness

我目前正在实现一个从 BMP 文件读取和解码图像的程序。但我真的对像素格式感到困惑,尤其是位字节序。

我目前的理解是指像素格式,例如RGBA32,我们用人的方式说,就是大端,所以第一个字节是R,第二个G,第三个B,第四个A:

RGBA32
11111111 22222222 33333333 44444444
R        G        B        A

并且当向这种格式附加big/little字节序时,例如RGBA32BE/RGBA32LE,这将改变字节顺序因此,如:

RGBA32BE (unchanged)
11111111 22222222 33333333 44444444
R        G        B        A

RGBA32LE (reversed)
11111111 22222222 33333333 44444444
A        B        G        R

这对我来说看起来很自然,只要我们假设每个组件的单独值正是我们读取的字节值即可。

然而,当组件大小小于 1 字节或 8 位时,事情开始让我感到困惑。说 RGB555BE,我想下面是它如何表示为字节数组:

RGB555BE
1'22222'33 333'44444
A   R      G     B
0'00001'00 000'00000
  1. 我们读 R 分量是 10000=16 还是 00001=1

  2. 我更困惑的是我们读取组件的方式(位字节序)是否与字节字节序相关?

  3. RGB555LE格式用字节数组表示的方式是什么?

    RGB555LE_v1
    333'44444 1'22222'33
    G"    B   A   R   G'
    000'00000 0'00001'00
    
    or RGB555LE_v2 ?
    44444'333 33'22222'1
      B     G      R   A
    00000'000 00'10000'0
    

我认为字节顺序对 rgb555 并不重要,这就是为什么您的 link 将 rgb555rgb555lergb555be 捆绑在一起的原因。就此而言,您使用 rgba 的示例也不对字节序敏感,因为它的组件都是 <= 8 位。

至于 rgb555 是如何用 2 个字节(好吧,15 位)表示的,您可以在 FFmpeg 存储库中搜索 rgb555 并查看 encoders/decoders 如何处理这种像素格式。这是我找到的 rpzaenc.c Line 138:

static uint16_t rgb24_to_rgb555(uint8_t *rgb24)
{
    uint16_t rgb555 = 0;
    uint32_t r, g, b;

    r = rgb24[0] >> 3;
    g = rgb24[1] >> 3;
    b = rgb24[2] >> 3;

    rgb555 |= (r << 10);
    rgb555 |= (g << 5);
    rgb555 |= (b << 0);

    return rgb555;
}

编码器采用 rgb555,一个 16 位无符号整数,并使用 put_bits() 效用函数将这 15 位推入其比特流,如 Line 683[=25= 所示]

put_bits(&s->pb, 16, rgb24_to_rgb555(avg_color));

这里是link到put_bits.h

我们可能会使用 FFmpeg 来测试不同的像素格式。

结论是:

  • 没有位字节序,只有字节字节序。
  • 当基本元素为 2 个字节 (uint16) 时,字节顺序是相关的。
    Little endian:uint16 元素的字节顺序是:[LSB, MSB].
    位字节序:uint16 元素的字节顺序是:[MSB, LSB].

正在执行 FFmpeg CLI 命令进行测试:

rgb24开始,例如:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgb24 -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgb24 -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgb24 -f rawvideo blue.raw

使用像 HxD 这样的十六进制查看器来检查原始文件内容。

red.raw FF 00 00 FF 00 00...
blue.raw 00 FF 00 00 FF 00...
green.raw 00 00 FF 00 00 FF...


rgba像素格式:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo blue.raw

r: FF 00 00 FF
g: 00 FF 00 FF
b: 00 00 FF FF

FFmpeg 约定字节顺序。
我们不能说命名适用小端还是大端。
使用 uint16 组件(而不是 uint8 组件)时,字节序是相关的。


rgba像素格式:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgba -f rawvideo blue.raw

r: FF 00 00 FF
g: 00 FF 00 FF
b: 00 00 FF FF

A 然后 R 然后 G 然后 B 然后 A...

我们可以将 rgba 称为 RGBA32BE,但这太令人困惑了...


rgb555le像素格式:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo blue.raw

r: 00 7C 00 7C
g: E0 03 E0 03
b: 1F 00 1F 00

我们还可以检查低位(用于位排序):
将颜色设置为 8,以便在移动值后为 1。

ffmpeg -y -lavfi color=0x000008:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo red.raw
ffmpeg -y -lavfi color=0x000800:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo green.raw
ffmpeg -y -lavfi color=0x080000:size=8x8:rate=1:duration=1 -pix_fmt rgb555le -f rawvideo blue.raw

r: 01 00 01 00 2 位字节: 00000001 00000000
g: 20 00 20 00 2 位字节:00100000 00000000
b: 00 04 00 04 2 位字节: 00000000 00000100


rgb555be 像素格式:

ffmpeg -y -lavfi color=red:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo red.raw
ffmpeg -y -lavfi color=0x00FF00:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo green.raw
ffmpeg -y -lavfi color=blue:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo blue.raw

r: 7C 00 7C 00
g: 03 E0 03 E0
b: 00 1F 00 1F

检查低位(用于位排序):

ffmpeg -y -lavfi color=0x000008:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo red.raw
ffmpeg -y -lavfi color=0x000800:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo green.raw
ffmpeg -y -lavfi color=0x080000:size=8x8:rate=1:duration=1 -pix_fmt rgb555be -f rawvideo blue.raw

r: 00 01 00 01
g: 00 20 00 20
b: 04 00 04 00

在大端格式中,只交换字节。
(对于每个 uint16 元素(两个字节),交换第一个和第二个字节的顺序)。


问题答案:

  1. 我们读R分量是10000=16还是00001=1 ?

答案:
对于 rgb555le:00000001 00000000
对于 rgb555be:00000000 00000001


  1. 我更困惑的是我们读取组件的方式(位字节序)是否与字节字节序有关?

答案:
在像素格式的上下文中,没有“位字节序”,只有“字节字节序”。

位字节顺序与串行通信更相关,从软件角度来看,我们看不到每个字节中的位顺序。


  1. RGB555LE格式用字节数组表示的方式有哪些?

答案:

RGB555LE(如RGB555LE_v1):

byte0 byte1
gggbbbbb 0rrrrrgg

(最左边的位是每个字节中的高位)。

我们最好将数据视为一个 uint16 元素:

b: 0000000000011111(纯蓝色)
g: 0000001111100000(纯绿色)
r: 0111110000000000(纯红色)

我们可以把它看成uint160rrrrrgggggbbbbb