ZigZag Sin
登 陆
上一篇:3-6 *代码-开始读取 SPS 下一篇:解码重排序的问题

从 SPS 中提取信息计算图像的宽高

乔红
2021-1-26 13:52 阅读 5786

引言

上一小结,我们对照着标准文档中的语法表格,从原始码流中将语法元素一个一个得解码了出来。解码出来的语法元素众多,大部分我们在后面使用的时候再详细介绍。我们在这一小结,介绍一些比较重要和马上就要用到的信息,然后用这些信息来解析出这个视频的宽高。

宏块

开始之前,我们先来介绍一个概念:宏块。以后的课程中,我们将会频繁得用到它,我们先来对他进行一个简单的介绍。

在 H.264 中,我们通常不会逐个像素去处理图像,而是把图像按照固定的长宽划分成一个一个小块,然后按照小块为单位去处理,而这些小块,我们就称之为宏块。

假如我们有一幅图像,宽度为 176 个像素,高度为 144 个像素。按照 H.264 的标准的规定,一个宏块宽为 16 个像素,高为 16 个像素,那么这个图像就会按照下图划分:

MB

pic_width_in_mbs_minus1 和 pic_height_in_map_units_minus1

H.264 中有这样两个语法元素,pic_width_in_mbs_minus1 表示的值是横向宏块的个数减 1,pic_height_in_map_units_minus1 表示的值是纵向宏块的个数减 1。也就是说,把他们两个加上 1,就可以获得横向的宏块个数和纵向的宏块个数。那么我们已经知道一个宏块长 16 个像素,高 16 个像素,那么就可以得出视频宽高的计算公式了:

width = (pic_width_in_mbs_minus1 + 1) * 16;
height = (pic_height_in_map_units_minus1 + 1) * 16;

但是依照这个公式计算得到的宽高,都是 16 的倍数,我们现实中接触到的视频,显然有很多都不是 16 的倍数的。那么 H.264 是怎么处理非 16 倍数的视频的呢?

frame_crop_left_offset, frame_crop_right_offset, frame_crop_top_offset, frame_crop_bottom_offset

在 SPS 中,还有四个量,他们在 frame_cropping_flag 为 1 的时候显式存放,其他时候为 0,这四个值分别代表图像的上下左右的偏移。因为宏块只能表示 16 的倍数,当图像的宽高不是 16 的倍数的时候,H.264 会在图像的边沿添加一些像素,来补齐成 16 的倍数,然后通过 frame_crop_left_offset, frame_crop_right_offset, frame_crop_top_offset, frame_crop_bottom_offset 这 4 个量来记录上下左右补齐了多少数据。我们把这个过程称之为 Crop。

Crop

那么是不是用宏块数乘上 16 然后再减去 4 个 crop 的量就得到了图像的原始宽高呢?

也不是,我们还需要考虑一些情况。

场编码

首先要考虑的特殊情况就是要考虑场编码了。现今我们的视频大部分都是帧编码的,但是是存在场编码的情况的。在场编码中,一帧等于两场,场的竖直宏块数量是帧的一半,而在场中,crop 1 一像素,相当于对帧 crop 2 个像素,所以 frame_crop_xxx_offset 也要做特殊处理。

在 SPS 中,frame_mbs_only_flag 用来表示场编码相关信息,frame_mbs_only_flag 等于 1 的时候,表示都是帧编码,frame_mbs_only_flag 等于 0 的时候,表示有可能存在场编码。

那么是不是把场编码考虑进去就可以了呢?

也不是,我们还需要考虑更多情况。

YUV 420, YUV 422, YUV 444 的 Crop

我们知道,H.264 压缩前的数据是 YUV 格式的原始数据,一般情况下,我们使用 4 个 Y 分量公共 1 组 UV 分量的 YUV 420 模式。但是 H.264 同样是支持 YUV 422,YUV 444 还有只有 Y 分量的单色模式的。我们先来看一下三种 YUV 的像素排列情况。

  • YUV 420 的像素排列如下:

    YUV420

  • YUV 422 的像素排列如下:

    YUV422

  • YUV 444 的像素排列如下:

    YUV444

那么问题来了,对于 YUV 420 的图像,你可以减去一排像素吗?

YUV444

例如这张图,你可以把红框中的 Y 分量去掉吗?其实是去不掉的,因为对于 YUV 420 来说,4 个 Y 共用一组 UV,你去掉一排 Y,那么就会让剩下的数据不完整。所以,对于 YUV 420 来说,只能减去偶数个像素。而对于 422 来说,竖直方向上可以减去任意个像素,但是水平方向上只能减去偶数个像素。

而 H.264 的 Crop 量,在 420 上是要乘 2 的;在 422 上,水平方向是要乘 2 的。那么我们应该怎么计算每种格式正确的 Crop 信息呢?

chroma_format_idc

首先,我们得先知道我们的码流是什么格式的。在 SPS 中,有一个用来标记原始数据格式的语法元素叫做 chroma_format_idc,他的值如下表:

含义
chroma_format_idc = 0单色
chroma_format_idc = 1YUV 4:2:0
chroma_format_idc = 2YUV 4:2:2
chroma_format_idc = 3YUV 4:4:4

需要注意的时候,这个值不是所有的码流都是显式存在的。

chroma_format_idc

查看码表可以知道,只有当 profile_idc 等于这些值的时候,chroma_format_idc 才会被显式记录,那么如果 profile_idc 不是这些值,chroma_format_idc 就会取默认值。这里就很容易犯错误,大多数同学会把他的默认值制为 0,但是 0 表示的是只有 Y 通道的单色,而在默认情况下,实际上是 YUV 420。所以 chroma_format_idc 的默认值是 1。

这个问题一定要注意,否则会影响到后面的数据。

separate_colour_plane_flag

当在码流中读取到 chroma_format_idc 之后,我们紧接着就会看到另外一个量:

separate_colour_plane_flag

这个语法元素在 chroma_format_idc 等于 3,也就是 YUV 444 模式的时候才有。那么这个值是什么意思呢?

当图像是 YUV 444 的时候,YUV 三个分量的比重是相同的,那么就有两种编码方式了。第一种,就是和其他格式一样,让 UV 分量依附在 Y 分量上;第二种,就是把 UV 和 Y 分开,独立出来。separate_colour_plane_flag 这个值默认是 0,表示 UV 依附于 Y,和 Y 一起编码,如果 separate_colour_plane_flag 变成 1,则表示 UV 与 Y 分开编码。而对于分开编码的模式,我们采用和单色模式一样的规则。

ChromaArrayType

接下来,我们来看另外一个量,这个量在 H.264 标准文档中出现过很多次,却不是语法表格中的内容,这个值是由 separate_colour_plane_flag 和 chroma_format_idc 共同作用推导出来的,推导过程如下:

if (separate_colour_plance_flag == 0){
    ChromaArrayType = chroma_format_idc;
}
else{
    ChromaArrayType = 0;
}

这个变量在以后的内容中还有更多的用处,但是我们现在只想借用他推导出另外两个量:

SubWidthC 和 SubHeightC

SubWidthC 和 SubHeightC 表示的是 YUV 分量中,Y 分量和 UV 分量在水平和竖直方向上的比值。当 ChromaArrayType 等于 0 的时候,表示只有 Y 分量或者表示 YUV 444 的独立模式,所以 SubWidthC 和 SubHeightC 没有意义。

  • YUV 420 :

    水平和垂直方向上,都是 2 个 Y 共用 1 对 UV,所以 SubWidthC 为 2,SubHeightC 为 2

  • YUV 422 :

    水平方向上,是 2 个 Y 共用 1 对 UV,所以 SubWidthC 为 2;竖直方向上,是 1 个 Y 用 1 对 UV,所以 SubHeightC 为 1

  • YUV 444 :

    水平方向上,是 1 个 Y 用 1 对 UV,所以 SubWidthC 为 1;竖直方向上,是 1 个 Y 用 1 对 UV,所以 SubHeightC 为 1

完整的推导代码如下:

if (ChromaArrayType == 1) {
    SubWidthC = 2;
    SubHeightC = 2;
}
else if (ChromaArrayType == 2) {
    SubWidthC = 2;
    SubHeightC = 1;
}
else if (ChromaArrayType == 3) {
    SubWidthC = 1;
    SubHeightC = 1;
} 

// 当 ChromaArrayType = 0 的时候, SubWidthC 和 SubHeightC 无用

最终计算宽高的公式

有了这些信息之后,我们就可以推导出最终的计算公式了:

width = (pic_width_in_mbs_minus1 + 1) * 16;
height = (2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16;

if(frame_cropping_flag){
    int crop_unit_x = 0;
    int crop_unit_y = 0;

    if(ChromaArrayType == 0){
        crop_unit_x = 1;
        crop_unit_y = 2 - frame_mbs_only_flag;
    }
    else if(ChromaArrayType == 1 || ChromaArrayType == 2 || ChromaArrayType == 3){
        crop_unit_x = SubWidthC;
        crop_unit_y = SubHeightC * (2 - frame_mbs_only_flag);
    }

    width -= crop_unit_x * (frame_crop_left_offset + sps->frame_crop_right_offset);
    height -= crop_unit_y * (frame_crop_top_offset + sps->frame_crop_bottom_offset);
}

上一篇:3-6 *代码-开始读取 SPS 下一篇:解码重排序的问题
给我买个键盘吧。。。求打赏。。。
欢迎加群,一起交流~~~

zekun@sina.cn

frame_mbs_only_flag为0时,不是说是有可能有场编码吗,只是有可能吧,你这样直接*2了,合理吗?谢谢

2021-12-23 14:44:12

2456346488@qq.com

老师开个课程吧,就讲这个系列,一定支持!

2021-03-12 11:54:31

2456346488@qq.com

希望大佬快快更新,讲的很好!!!!!!!!!!!!!

2021-03-12 11:50:44

1055083441@qq.com

厉害

2021-03-08 11:59:57

1055083441@qq.com

厉害

2021-03-08 11:59:57

632212519@qq.com

讲的非常清晰,受益匪浅,感谢博主的分享。

2021-01-29 16:33:30

1079890643@qq.com

先赞后看,ʕ๑•ɷ•๑ʔ❀

2021-01-29 15:39:43

1079890643@qq.com

点赞

2021-01-29 15:36:26