3.关于FMO(多片组)
FMO,指灵活块映射,也即多片组模式。
关于片组的概念:一个片组由若干个宏块组成,可以按照某种规则将一个图像中的某些宏块(这些宏块可以在图像中的任意位置,不是必须相邻)划分成一个组,就是一个片组。
我们已经知道,H.264对图像进行编解码时是以片为单位的,一个片也包含一系列的宏块。在有了片组的概念后,划分片时还有一个规则:
一个片中的所有宏块,必须属于同一个片组。
我们先把一个图像中的所有宏块划分成几个片组(每个片组都有一个组号,以相互区别),然后把这些片组再各自划分成若干个片。
这也就是说,图像由若干“片组”组成,片组由若干“片”组成。而我们之前一直说的“图像由若干‘片’组成”,其实是针对不使用FMO的情况,
当不使用FMO时,一个图像中默认只有一个片组,该片组包含整个图像的所有宏块(一般都是只有一个片组)。
在一个片组中,每个片的 MB都是按光栅扫描次序被编码的,这个“光栅扫描顺序”并不是相对于整个图像来说的,而是在“片组内部”进行光栅扫描,不会扫描到当前片组之外的宏块。
在图像参数集中,并没有哪个元素显式地指明是否对当前图像使用FMO,但有一个元素隐含了这个信息,
num_slice_groups_minus1,这个元素指定了一个图像中片组的数目。
num_slice_groups_minus1等于0,图像只有一个片组时,则不启用FMO;其他情况下,一个图像中有多个片组,这时都使用FMO。
了解了片组的概念之后,下面的主要问题就是宏块到片组的映射了。即如何指定某个宏块属于哪个片组?
在H.264中,有六种默认的映射规则供选择,通过图像参数集中的slice_group_map_type可以指定使用哪个规则为当前图像划分片组。如果这六种默认的规则都不能满足要求,还可以定制自己的映射规则。
下面详细介绍这几种映射规则。
在这之前,先回顾一下映射单元的概念,片组的映射单元规定如下:
–— 如果 frame_mbs_only_flag 等于0 ,mb_adaptive_frame_field_flag等于1 ,而且编码图像是一个帧,那么条带组映射单元就是宏块对单元。
–— 否则,如果frame_mbs_only_flag 等于1或者一个编码图像是一个场,条带组映射单元就是宏块的单元。
–— 否则(frame_mbs_only_flag 等于0,mb_adaptive_frame_field_flag 等于0,并且编码图像是一个帧),条带组映射单元就是像在一个MBAFF帧中的一个帧宏块对中一样垂直相邻的两个宏块的单元。
在实际进行映射时,都是以“mapUnit(映射单元)”为单位进行映射。
当需要知道某个宏块属于哪个片组时,先找出这个宏块所属的“mapUnit(映射单元)”,再找出这个“mapUnit(映射单元)”所对应的片组号即可。
图像中每个“mapUnit(映射单元)”属于哪个组号,需要一个数组来记录,将这个数据定义为mapUnitToSliceGroupMap[],数组中的第i个元素表示以光栅顺序扫描时,第i个“mapUnit(映射单元)”对应哪个组(组号)。
当我们得到mapUnitToSliceGroupMap[]后,就可以根据宏块与“mapUnit(映射单元)”的对应关系,
得出“宏块-片组映射”的数组MbToSliceGroupMap[]。
在几种不同的映射模式下,mapUnit分别是以下面图标所描述的方式被映射到某个片组中的。
注:下表中,对于3、4、5三种类型,都是默认一个图像中只有两个片组。
slice_group_map_type | 片组类型名称 | 描述 |
0 | 交错 | mapUnit的游程被依次分配给每一块组 |
1 | 散乱 | 每一片组中的mapUnit被分散在整个图象中 |
2 | 前景和背景 | 由多层方框相互覆盖,每一层的方框中包含的mapUnit归为一个片组;位于上层的方框(前景)将覆盖位于下层的方框(背景)。最底层的方框要涵盖整个图像。具体见下面的图示。 |
3 | Box-out | 从帧的中心开始,产生一个箱子,其mapUnit属于片组 0 ,其它mapUnit属于片组1。 |
4 | 光栅扫描 | 片组0 包含按光栅扫描次序从顶-左的所有mapUnit,其余mapUnit属片组1 |
5 | 手绢 | 片组0 包含从顶-左垂直扫描次序的mapUnit,其余mapUnit属片组1 |
6 | 定制 | 自定义块组映射。这种模式下,在图像参数集中为每一个宏块单独指定其所属的片组ID。 |
下面是相应的图示,通过图示可以更直观地理解各种映射模式。
在使用交错型片组时(即slice_group_map_type=0时),图像参数集中的run_length_minus1[i] 指定了第i个片组的游程长度。
在使用交错型片组时(即slice_group_map_type=1时),只需按照上图中的规律(每行第一个映射单元设置成与上一行最后一个映射单元同片组,随后的映射单元片组号循环递增)对映射单元进行映射即可,不需要额外的参数。
在使用前景背景型片组时(即slice_group_map_type=2时),图像参数集中的top_left[i]和bo ttom_right[i]分别指定了第i个片组所占据的方框的左上角和右下角的映射单元在图像中的序号(这个序号是以光栅扫描顺序来说的)。
slice_group_map_type等于3、4 和5 时,片组的映射还与图像参数集slice_group_change_direction_flag有关。
slice_group_map_type | slice_group_change_direction_flag | 精确条带组映射类型 |
3 | 0 | Box-out clockwise顺时针盒状展开 |
3 | 1 | Box-out counter-clockwise逆时针盒状展开 |
4 | 0 | Raster scan光栅扫描 |
4 | 1 | Reverse raster scan逆光栅扫描 |
5 | 0 | Wipe right从左向右擦除 |
5 | 1 | Wipe left从右向左擦除 |
这三种映射方式都需要一个变量MapUnitsInSliceGroup0,来标记第一个片组SliceGroup0中mapUnit的数目。
按照上表所示的扫描方式扫描时(参考之前的图示有助于理解),当扫描了MapUnitsInSliceGroup0个映射单元后,停止扫描,这时已经被扫描到的属于group0,未被扫描的属于group1。
对于扫描方式4、5来说,slice_group_change_direction_flag为1和0时,扫描方式恰好反向。
对于扫描方式3来说,它扫描时起始的映射单元的位置是 (图像左上角的映射单元位置设为(0,0),这个坐标系统要注意一下,他跟数学中的笛卡尔坐标不一致,y轴的正方向在这里是向下而不是向上):
x= ( PicWidthInMbs − slice_group_change_direction_flag ) / 2
y= ( PicHeightInMapUnits − slice_group_change_direction_flag ) / 2
第一次扫描时的方向向量是(即扫描的第二个mapUnit与起始mapUnit的相对位置):
(xDir, yDir ) = (slice_group_change_direction_flag − 1, slice_group_change_direction_flag )
之后的扫描就是按照规定的顺或逆时针方向来,直到扫描够MapUnitsInSliceGroup0个单元。
如果slice_group_map_type等于6,这时片组的映射方式完全由用户自定义。图像参数集中的slice_group_id[i]表示图像中第i个映射单元(按照光栅扫描顺序)所属的片组ID,这时数组slice_group_id[]的长度PicSizeInMapUnits,应由图像参数集中的pic_size_in_map_units_minus1确定,图像参数集中只有在slice_group_map_type=6时才会出现pic_size_in_map_units_minus1这个元素。而当slice_group_map_type不是6时,PicSizeInMapUnits由下面方式确定:
PicSizeInMapUnits = PicWidthInMbs* PicHeightInMapUnits (等号右边的两个数都由序列参数集中的pic_width_in_mbs_minus1和pic_height_in_map_units_minus1得到)。
这个我在前面介绍slice头中的slice_group_change_cycle元素时已提到过一次。
当映射完这些mapUnits后,要得到某个宏块与片组的对应关系,只需按我前面介绍“映射单元”时提到的宏块与“映射单元”的对应关系,先找出宏块对应的mapUnit,再找出这个mapUnit对应的片组即可。
到这里,宏块到片组的映射方法就算介绍完了。那我们为什么要费这么大劲来进行映射呢?为什么要使用FMO?
使用FMO是为了提高解码端错误纠正能力。比如,如果我们现在使用的是散乱型映射方式的FMO,而且在一幅图像的解码过程中发生了丢包现象,假如我们丢失了某一个片的数据,但本图像中其他片都是完好的,这时由于丢失的这个片所包含的宏块都是分散开的,因此每个丢失的宏块其四周的其他宏块都属于其他片,也就是说都是完整可用的,这样就可以根据它周围宏块的信息来较好得“还原”丢失的宏块,这就起到了错误隐藏的作用。
而如果不用FMO,丢失的宏块将都是连续的,这样在某个宏块信息丢失时其他未丢失的宏块就无法起到较好的参考作用,错误纠正就很困难。
3.关于FMO(多片组)
FMO,指灵活块映射,也即多片组模式。
关于片组的概念:一个片组由若干个宏块组成,可以按照某种规则将一个图像中的某些宏块(这些宏块可以在图像中的任意位置,不是必须相邻)划分成一个组,就是一个片组。
我们已经知道,H.264对图像进行编解码时是以片为单位的,一个片也包含一系列的宏块。在有了片组的概念后,划分片时还有一个规则:
一个片中的所有宏块,必须属于同一个片组。
我们先把一个图像中的所有宏块划分成几个片组(每个片组都有一个组号,以相互区别),然后把这些片组再各自划分成若干个片。
这也就是说,图像由若干“片组”组成,片组由若干“片”组成。而我们之前一直说的“图像由若干‘片’组成”,其实是针对不使用FMO的情况,
当不使用FMO时,一个图像中默认只有一个片组,该片组包含整个图像的所有宏块(一般都是只有一个片组)。
在一个片组中,每个片的 MB都是按光栅扫描次序被编码的,这个“光栅扫描顺序”并不是相对于整个图像来说的,而是在“片组内部”进行光栅扫描,不会扫描到当前片组之外的宏块。
在图像参数集中,并没有哪个元素显式地指明是否对当前图像使用FMO,但有一个元素隐含了这个信息,
num_slice_groups_minus1,这个元素指定了一个图像中片组的数目。
num_slice_groups_minus1等于0,图像只有一个片组时,则不启用FMO;其他情况下,一个图像中有多个片组,这时都使用FMO。
了解了片组的概念之后,下面的主要问题就是宏块到片组的映射了。即如何指定某个宏块属于哪个片组?
在H.264中,有六种默认的映射规则供选择,通过图像参数集中的slice_group_map_type可以指定使用哪个规则为当前图像划分片组。如果这六种默认的规则都不能满足要求,还可以定制自己的映射规则。
下面详细介绍这几种映射规则。
在这之前,先回顾一下映射单元的概念,片组的映射单元规定如下:
–— 如果 frame_mbs_only_flag 等于0 ,mb_adaptive_frame_field_flag等于1 ,而且编码图像是一个帧,那么条带组映射单元就是宏块对单元。
–— 否则,如果frame_mbs_only_flag 等于1或者一个编码图像是一个场,条带组映射单元就是宏块的单元。
–— 否则(frame_mbs_only_flag 等于0,mb_adaptive_frame_field_flag 等于0,并且编码图像是一个帧),条带组映射单元就是像在一个MBAFF帧中的一个帧宏块对中一样垂直相邻的两个宏块的单元。
在实际进行映射时,都是以“mapUnit(映射单元)”为单位进行映射。
当需要知道某个宏块属于哪个片组时,先找出这个宏块所属的“mapUnit(映射单元)”,再找出这个“mapUnit(映射单元)”所对应的片组号即可。
图像中每个“mapUnit(映射单元)”属于哪个组号,需要一个数组来记录,将这个数据定义为mapUnitToSliceGroupMap[],数组中的第i个元素表示以光栅顺序扫描时,第i个“mapUnit(映射单元)”对应哪个组(组号)。
当我们得到mapUnitToSliceGroupMap[]后,就可以根据宏块与“mapUnit(映射单元)”的对应关系,
得出“宏块-片组映射”的数组MbToSliceGroupMap[]。
在几种不同的映射模式下,mapUnit分别是以下面图标所描述的方式被映射到某个片组中的。
注:下表中,对于3、4、5三种类型,都是默认一个图像中只有两个片组。
slice_group_map_type | 片组类型名称 | 描述 |
0 | 交错 | mapUnit的游程被依次分配给每一块组 |
1 | 散乱 | 每一片组中的mapUnit被分散在整个图象中 |
2 | 前景和背景 | 由多层方框相互覆盖,每一层的方框中包含的mapUnit归为一个片组;位于上层的方框(前景)将覆盖位于下层的方框(背景)。最底层的方框要涵盖整个图像。具体见下面的图示。 |
3 | Box-out | 从帧的中心开始,产生一个箱子,其mapUnit属于片组 0 ,其它mapUnit属于片组1。 |
4 | 光栅扫描 | 片组0 包含按光栅扫描次序从顶-左的所有mapUnit,其余mapUnit属片组1 |
5 | 手绢 | 片组0 包含从顶-左垂直扫描次序的mapUnit,其余mapUnit属片组1 |
6 | 定制 | 自定义块组映射。这种模式下,在图像参数集中为每一个宏块单独指定其所属的片组ID。 |
下面是相应的图示,通过图示可以更直观地理解各种映射模式。
在使用交错型片组时(即slice_group_map_type=0时),图像参数集中的run_length_minus1[i] 指定了第i个片组的游程长度。
在使用交错型片组时(即slice_group_map_type=1时),只需按照上图中的规律(每行第一个映射单元设置成与上一行最后一个映射单元同片组,随后的映射单元片组号循环递增)对映射单元进行映射即可,不需要额外的参数。
在使用前景背景型片组时(即slice_group_map_type=2时),图像参数集中的top_left[i]和bo ttom_right[i]分别指定了第i个片组所占据的方框的左上角和右下角的映射单元在图像中的序号(这个序号是以光栅扫描顺序来说的)。
slice_group_map_type等于3、4 和5 时,片组的映射还与图像参数集slice_group_change_direction_flag有关。
slice_group_map_type | slice_group_change_direction_flag | 精确条带组映射类型 |
3 | 0 | Box-out clockwise顺时针盒状展开 |
3 | 1 | Box-out counter-clockwise逆时针盒状展开 |
4 | 0 | Raster scan光栅扫描 |
4 | 1 | Reverse raster scan逆光栅扫描 |
5 | 0 | Wipe right从左向右擦除 |
5 | 1 | Wipe left从右向左擦除 |
这三种映射方式都需要一个变量MapUnitsInSliceGroup0,来标记第一个片组SliceGroup0中mapUnit的数目。
按照上表所示的扫描方式扫描时(参考之前的图示有助于理解),当扫描了MapUnitsInSliceGroup0个映射单元后,停止扫描,这时已经被扫描到的属于group0,未被扫描的属于group1。
对于扫描方式4、5来说,slice_group_change_direction_flag为1和0时,扫描方式恰好反向。
对于扫描方式3来说,它扫描时起始的映射单元的位置是 (图像左上角的映射单元位置设为(0,0),这个坐标系统要注意一下,他跟数学中的笛卡尔坐标不一致,y轴的正方向在这里是向下而不是向上):
x= ( PicWidthInMbs − slice_group_change_direction_flag ) / 2
y= ( PicHeightInMapUnits − slice_group_change_direction_flag ) / 2
第一次扫描时的方向向量是(即扫描的第二个mapUnit与起始mapUnit的相对位置):
(xDir, yDir ) = (slice_group_change_direction_flag − 1, slice_group_change_direction_flag )
之后的扫描就是按照规定的顺或逆时针方向来,直到扫描够MapUnitsInSliceGroup0个单元。
如果slice_group_map_type等于6,这时片组的映射方式完全由用户自定义。图像参数集中的slice_group_id[i]表示图像中第i个映射单元(按照光栅扫描顺序)所属的片组ID,这时数组slice_group_id[]的长度PicSizeInMapUnits,应由图像参数集中的pic_size_in_map_units_minus1确定,图像参数集中只有在slice_group_map_type=6时才会出现pic_size_in_map_units_minus1这个元素。而当slice_group_map_type不是6时,PicSizeInMapUnits由下面方式确定:
PicSizeInMapUnits = PicWidthInMbs* PicHeightInMapUnits (等号右边的两个数都由序列参数集中的pic_width_in_mbs_minus1和pic_height_in_map_units_minus1得到)。
这个我在前面介绍slice头中的slice_group_change_cycle元素时已提到过一次。
当映射完这些mapUnits后,要得到某个宏块与片组的对应关系,只需按我前面介绍“映射单元”时提到的宏块与“映射单元”的对应关系,先找出宏块对应的mapUnit,再找出这个mapUnit对应的片组即可。
到这里,宏块到片组的映射方法就算介绍完了。那我们为什么要费这么大劲来进行映射呢?为什么要使用FMO?
使用FMO是为了提高解码端错误纠正能力。比如,如果我们现在使用的是散乱型映射方式的FMO,而且在一幅图像的解码过程中发生了丢包现象,假如我们丢失了某一个片的数据,但本图像中其他片都是完好的,这时由于丢失的这个片所包含的宏块都是分散开的,因此每个丢失的宏块其四周的其他宏块都属于其他片,也就是说都是完整可用的,这样就可以根据它周围宏块的信息来较好得“还原”丢失的宏块,这就起到了错误隐藏的作用。
而如果不用FMO,丢失的宏块将都是连续的,这样在某个宏块信息丢失时其他未丢失的宏块就无法起到较好的参考作用,错误纠正就很困难。