第一章 前言
第三章 有关 SPS 和 PPS 的一切
第四章 有关 Slice 的一切
第七章 帧间编码
第八章 残差的熵编码: CAVLC 和 CABAC
上一小结,我们对照着标准文档中的语法表格,从原始码流中将语法元素一个一个得解码了出来。解码出来的语法元素众多,大部分我们在后面使用的时候再详细介绍。我们在这一小结,介绍一些比较重要和马上就要用到的信息,然后用这些信息来解析出这个视频的宽高。
开始之前,我们先来介绍一个概念:宏块。以后的课程中,我们将会频繁得用到它,我们先来对他进行一个简单的介绍。
在 H.264 中,我们通常不会逐个像素去处理图像,而是把图像按照固定的长宽划分成一个一个小块,然后按照小块为单位去处理,而这些小块,我们就称之为宏块。
假如我们有一幅图像,宽度为 176 个像素,高度为 144 个像素。按照 H.264 的标准的规定,一个宏块宽为 16 个像素,高为 16 个像素,那么这个图像就会按照下图划分:
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 倍数的视频的呢?
在 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。
那么是不是用宏块数乘上 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 的时候,表示有可能存在场编码。
那么是不是把场编码考虑进去就可以了呢?
也不是,我们还需要考虑更多情况。
我们知道,H.264 压缩前的数据是 YUV 格式的原始数据,一般情况下,我们使用 4 个 Y 分量公共 1 组 UV 分量的 YUV 420 模式。但是 H.264 同样是支持 YUV 422,YUV 444 还有只有 Y 分量的单色模式的。我们先来看一下三种 YUV 的像素排列情况。
YUV 420 的像素排列如下:
YUV 422 的像素排列如下:
YUV 444 的像素排列如下:
那么问题来了,对于 YUV 420 的图像,你可以减去一排像素吗?
例如这张图,你可以把红框中的 Y 分量去掉吗?其实是去不掉的,因为对于 YUV 420 来说,4 个 Y 共用一组 UV,你去掉一排 Y,那么就会让剩下的数据不完整。所以,对于 YUV 420 来说,只能减去偶数个像素。而对于 422 来说,竖直方向上可以减去任意个像素,但是水平方向上只能减去偶数个像素。
而 H.264 的 Crop 量,在 420 上是要乘 2 的;在 422 上,水平方向是要乘 2 的。那么我们应该怎么计算每种格式正确的 Crop 信息呢?
首先,我们得先知道我们的码流是什么格式的。在 SPS 中,有一个用来标记原始数据格式的语法元素叫做 chroma_format_idc,他的值如下表:
值 | 含义 |
---|---|
chroma_format_idc = 0 | 单色 |
chroma_format_idc = 1 | YUV 4:2:0 |
chroma_format_idc = 2 | YUV 4:2:2 |
chroma_format_idc = 3 | YUV 4:4:4 |
需要注意的时候,这个值不是所有的码流都是显式存在的。
查看码表可以知道,只有当 profile_idc 等于这些值的时候,chroma_format_idc 才会被显式记录,那么如果 profile_idc 不是这些值,chroma_format_idc 就会取默认值。这里就很容易犯错误,大多数同学会把他的默认值制为 0,但是 0 表示的是只有 Y 通道的单色,而在默认情况下,实际上是 YUV 420。所以 chroma_format_idc 的默认值是 1。
这个问题一定要注意,否则会影响到后面的数据。
当在码流中读取到 chroma_format_idc 之后,我们紧接着就会看到另外一个量:
这个语法元素在 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 分开编码。而对于分开编码的模式,我们采用和单色模式一样的规则。
接下来,我们来看另外一个量,这个量在 H.264 标准文档中出现过很多次,却不是语法表格中的内容,这个值是由 separate_colour_plane_flag 和 chroma_format_idc 共同作用推导出来的,推导过程如下:
if (separate_colour_plance_flag == 0){
ChromaArrayType = chroma_format_idc;
}
else{
ChromaArrayType = 0;
}
这个变量在以后的内容中还有更多的用处,但是我们现在只想借用他推导出另外两个量:
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);
}
zekun@sina.cn
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