H264宏块包含的各种语法信息详解
本文将以CABAC编码需要编码的语法元素的程序为例,分析H264宏块中所包含的各种语法元素的含义。
以上三种变量的其他情况分支,本文进行了省略处理,完整的,全分支的CABAC编码见参考资料【1】
1、 slice_data
slice_data( ) {
if( entropy_coding_mode_flag )
while( !byte_aligned( ) )
cabac_alignment_one_bit
CurrMbAddr = first_mb_in_slice * ( 1 + MbaffFrameFlag )
moreDataFlag = 1
prevMbSkipped = 0
do {
if( slice_type != I && slice_type != SI )
if( !entropy_coding_mode_flag ) {
mb_skip_run
prevMbSkipped = ( mb_skip_run > 0 )
for( i=0; i<mb_skip_run; i++ )
CurrMbAddr = NextMbAddress( CurrMbAddr )
if( mb_skip_run > 0 )
moreDataFlag = more_rbsp_data( )
} else {
mb_skip_flag
moreDataFlag = !mb_skip_flag
}
if( moreDataFlag ) {
if( MbaffFrameFlag && ( CurrMbAddr % 2 = = 0 | |
( CurrMbAddr % 2 = = 1 && prevMbSkipped ) ) )
mb_field_decoding_flag
macroblock_layer( )
}
if( !entropy_coding_mode_flag )
moreDataFlag = more_rbsp_data( )
else {
if( slice_type != I && slice_type != SI )
prevMbSkipped = mb_skip_flag
if( MbaffFrameFlag && CurrMbAddr % 2 = = 0 )
moreDataFlag = 1
else {
end_of_slice_flag
moreDataFlag = !end_of_slice_flag
}
}
CurrMbAddr = NextMbAddress( CurrMbAddr )
} while( moreDataFlag )
}
首先需要明确的是,H264将一张图像分为很多个slice,而以一个slice被划分为很多16x16的块,这个16x16的块成为宏块(MB),H264一般以宏块为单位进行压缩编码。
✐ entropy_coding_mode_flag: 是否采用熵编码,该标志为1时,采用CABAC编码;
✐ MbaffFrameFlag : 帧编码时,该标志位为0,(目前场编码用的少了);
✐ ChromaArrayType: chroma的类型,当该值为0时,图像格式为YUV400;该值为1时图像格式为YUV420;
✐ byte_aligned( ) 0 : 字节未对齐; 1: 字节对齐;
✐ mb_skip_flag: 0:非skip, 1:skip(可跳过不编码);
✐ macroblock_layer( ) : 宏块包含的信息;
✐ end_of_slice_flag: 1:该MB为当前slice的最后一个MB;
一个Slice由很多个宏块组成,部分宏块可以由其他宏块的内容推断出来,这种宏块完全可以由附近的宏块信息猜出来,没必要单独编码;
这种可以通过前后图像直接推断出来,都是可以不用编码的;我们给每个宏块一个标志位mb_skip_flag来表示是否可以不编码这个宏块(slice_type为I时,他里面包含的宏块只有I宏块肯定不能跳过,因此这两个类型的mb_skip_flag不需要单独编码);
slice type | 该类型slice中允许出现的宏块类型 |
---|---|
I | I |
P | P , I |
B | B, I |
宏块类型:
✑ I宏块: 编码解码时只和当前图像有关(帧内预测);
✑ P宏块:编码解码时需要参考前面的图像信息(帧间预测);
✑ B宏块:编码解码时需要参考前面和后面的图像信息(帧间预测);
2、macroblock_layer( )
macroblock_layer( ) {
mb_type
if( mb_type = = I_PCM ) {
while( !byte_aligned( ) ) //字节对齐
pcm_alignment_zero_bit
for( i = 0; i < 256; i++ )
pcm_sample_luma[ i ]
for( i = 0; i < 2 * MbWidthC * MbHeightC; i++ )
pcm_sample_chroma[ i ]
} else {
noSubMbPartSizeLessThan8x8Flag = 1
if( mb_type != I_NxN &&
MbPartPredMode( mb_type, 0 ) != Intra_16x16 &&
NumMbPart( mb_type ) = = 4 ) {
sub_mb_pred( mb_type )
for( mbPartIdx = 0; mbPartIdx < 4; mbPartIdx++ )
if( sub_mb_type[ mbPartIdx ] != B_Direct_8x8 ) {
if( NumSubMbPart( sub_mb_type[ mbPartIdx ] ) > 1 )
noSubMbPartSizeLessThan8x8Flag = 0
} else if( !direct_8x8_inference_flag )
noSubMbPartSizeLessThan8x8Flag = 0
} else {
if( transform_8x8_mode_flag && mb_type = = I_NxN )
transform_size_8x8_flag
mb_pred( mb_type )
}
if( MbPartPredMode( mb_type, 0 ) != Intra_16x16 ) {
coded_block_pattern
if( CodedBlockPatternLuma > 0 &&
transform_8x8_mode_flag && mb_type != I_NxN &&
noSubMbPartSizeLessThan8x8Flag &&
( mb_type != B_Direct_16x16 | | direct_8x8_inference_flag ) )
transform_size_8x8_flag
}
if( CodedBlockPatternLuma > 0 | | CodedBlockPatternChroma > 0 | |
MbPartPredMode( mb_type, 0 ) = = Intra_16x16 ) {
mb_qp_delta
residual( 0, 15 )
}
}
}
每个宏块都有mb_type表明该宏块的类型,若该宏块为I_PCM,则直接输出原始图像,即256(16*16)个luma(Y)分量pcm_sample_luma和512个chroma分量(U+V)pcm_sample_chroma;
mb_type所代表的含义如下所示:
- ➸ I slice :
H264一般以16X16的宏块为单位进行划分,其中I_16X16_a_b_c代表,该宏块的大小是1个16x16,_a代表帧内预测的方式;_b和_c 分别表示chroma和luma的 CBP;
H264一般是帧间预测信息或者帧内预测信息,再加上一些预测值和真实值的差值(残差块),CBP主要用于表示残差块的哪些信息为0,后面将再详细介绍;
-
➸ P Slice: Pslice中包含I宏块和P宏块,P宏块的mb_type如下所示,I宏块的mb_type – 5后和I slice中的I宏块含义相同,例如:Pslice的mb_type为6,则其为I宏块I_16X16_1_0_0
P宏块是前向参考,即根据前面的图像来恢复当前图像;16x16为单位,或者8x8,或者16*8等方式进行划分,参考前面的不同的图像; -
➸ B Slice: Bslice中包含I宏块和B宏块,B宏块的mb_type如下所示,I宏块的mb_type – 23后和I slice中的I宏块含义相同,例如:B slice的某个宏块的mb_type为24,则其为I宏块I_16X16_1_0_0; P_L0_L0_16X8表示该16X16的宏块被划分为2个16*8的块,两个块都参考L0系列里的图像进行帧间编码(帧间预测时的参考图像分L0和L1两个系列)
B_Bi_L0_16X8表示该16X16的宏块被划分为2个16X8的块,两个块分别参考L0,L1系列里的图像(Bi),和L0系列的某个图像进行帧间编码
H264对于P_8X8,P_8X8ref0和B_8X8,如图所示我们可以将其进一步细分,最小可以再划分成4个4X4的块,因此我们在编码时,将这三种和比较特殊的B_direct_16X16单独写一种编码函数sub_mb_pred()进行编码。对其他类型的宏块用mb_pred()进行编码;
此外,H264中宏块的信息是经过DCT变换处理的,这个DCT变换可以是4X4为单位的,也可以是8X8为单位的,因此还需要对transform_size_8x8_flag这个信息进行编码,这个信息表示该宏块是采用8X8的单位编码(1),还是4X4的单位编码(0)。部分类型的宏块不支持8*8变换,因此这里是有个选择语句,;看起来有点复杂,总的来说就是这些情况:
- ❂ 1. 首先,transform_8X8_mode_flag相当于一个总开关,若该标志为0,则一定是DCT一定是4X4为单位,此时不对transform_size_8x8_flag进行编码(默认是0);
- ❂ 2. I_N*N 对transform_size_8x8_flag进行编码;
- ❂ 3. I_16*16不对transform_size_8x8_flag进行编码;
- ❂ 4. P和B的宏块除了被划分为8X8的(sub_mb_pred处理),其他的若宏块的luma的CBP大于0,则均需要对transform_size_8x8_flag进行编码;mb_type 为B_Direct_16x16且direct_8x8_inference_flag为0,则不编码transform_size_8x8_flag;
- ❂ 5. 对于sub_mb_pred处理的这几种宏块,若不含比8X8小的子宏块,且若子宏块含B_direct_8*8(此时direct_8x8_inference_flag为1),则编码transform_size_8x8_flag,否则不编码该元素;
由上表可知,宏块类型为I (Intra16*16)时,我们直接从mb_type就能看出来CBP(coded_block_pattern),而宏块类型就P,B时,我们无法直接看出CBP,因此我们还需要对P,B的CBP进行编码;
CodedBlockPatternLuma 和CodedBlockPatternChroma 分别代表亮度和色度的残差块信息是否为0,若为0就不编码了;因为Intra16X16的CBP可以直接从mb_type中获取,因此对残差块信息的编码程序有那三个条件;其中mb_qp_delta是残差块的量化系数,residual()则是对残差块信息的编码;
3、sub_mb_pred()和mb_pred()
由上述内容可知,只有B和P的部分宏块用sub_mb_pred()
因此sub_mb_pred()包含如下系数:
✠ 4个子宏块的类型
✠ 4组 ref_idx_l0(L0参考图像的坐标),mvd_l0(参考图像的运动信息)
✠ 4组 ref_idx_l1(L1参考图像的坐标),mvd_l1(参考图像的运动信息)
✠ P_8x8ref0不编码ref_idx_l0, B_Direct_8X8只编码sub_mb_type;
mb_pred()需要编码I宏块,因此可能含有帧内编码的信息,包含如下系数:
✸ Intra4X4 则编码16组prev_intra4x4_pred_mode_flag(是否直接采用临近块的帧内编码模式)rem_intra4x4_pred_mode(帧内编码的模式);
✸ Intra8X8 则编码4组prev_intra8x8_pred_mode_flag(是否直接采用临近块的帧内编码模式)rem_intra8x8_pred_mode(帧内编码的模式);
✸ Intra 16 X16的可以通过mb_type看出来,这里不做编码;
✸ 若含有chroma分量,还需要编码Intra_chroma_pred_mode(chroma帧内编码的模式)
✸ 若是B,P宏块,则为帧间预测,和sub_mb_pred()一样,编码每个宏块的ref_idx_l0,mvd_l0,ref_idx_l1和mvd_l1;
4、残差块residual()
残差块就是16X16的大小的,帧内或者帧间预测后,和原始图像的误差信息。以4X4或8X8为单位进行DCT变换和量化,变换单位的左上角的数值记为DC系数,其余位置的数值记为AC系数;
❂ significant_coeff_flag:该位置的残差系数是否为0,是为0,否为1;
❂ last_significant_coeff_flag:该位置是否为最后一个不为0的系数,是为1,不是为0
❂ coeff_abs_level_minus1:该位置i上残差系数的绝对值减1
❂ coeff_sign_flag[i]:该位置残差系数的正负号,1为正,0为负
5、总结
H264编码可以看成是,先根据一些其他信息进行预测,预测后的图像和原始图像有差异;H264就将预测方式和与原始图像的差异值保留。解码端可以通过预测方式恢复图像,再将差异值相加,从而达到较好的恢复压缩效果;
一个宏块里包含的信息,
✸ 1. 是否skip? 宏块类型是什么,即采用哪种方式压缩;
✸ 帧内编码:采用哪种模式根据上方和左侧数据恢复图像
✸ 帧间编码:采用根据前面的哪张图像,或者后面的哪张图像的哪个位置来恢复当前图像,以及选取这个图像的具体哪个位置的16*16块来恢复当前图像;
✸ 4. DCT变化后的量化参数
✸ 5. 残差:通过预测恢复后的图像与原始图像的差异
6、参考资料
- 博客园的一个大佬,写的很全,对理解H264帮助很大
- CSDN : H.265/HEVC编码原理及其处理流程的分析
- H264官方标准协议下载链接(英)
- 新一代高效视频编码H.265/HEVC:原理、标准与实现,作者:万帅、杨付正