H.264 句法元素分析
本来只是为了自己理解然后偷懒查表和复制替换写代码写的,后来发现注释着注释着就发现有了好多的东西。
这期间走了好多的弯路,当然,本人也挺菜的(指垃圾学习能力),应该有很多错误。
我自己还在不断学的过程中,会不断更正错误的。
当然了,主要还是想记载自己学习的过程,特别不想最后只能面对一堆err的代码。
正文
所以当进行到这一步的时候,应该已经从媒体文件中(MP4)得到了一个连续数据流h.264。
这一个连续的数据流可以分为一个一个的NAL单元(NAL unit),这个单元可以理解为解码器应该接收的最小的数据单元。
解码器在接收到NAL Unit单元开始的符号时,就认为这是一个新的NAL unit。
在网络传输环境中这个开始的符号都是0x 00 00 01,这便标识了一个NALU的开头。接下来就是按照句法解读这个NAL的过程。
其实句法元素分析这个过程较为简单,按照句法元素表来依次读入就行。
所以首先需要按照句法表进行分析,主要是算出一些常用的变量和参量。
参量是指按照句法表从数据流中读出来的数据,变量是指通过参量计算出来的数据。
在ISO/IEC的句法表中,参量是用全小写字母和下划线组成,变量是有大写的字母、没有下划线。
描述子是用什么样的方式读入数据,可以将他们简单理解为从数据流中读出多少位,解释成什么数据。
(这个方法说明了数据的读入方式、说明了数据的解释方式)
b(8) 读入8个bit
f(n) 读进n个bit
i(n)/i(v) 读进n个bit 解释为有符号数
u(n)/u(v) 读进n个bit 解释为无符号数
ae(v) 基于上下文自适应的二进制算术熵编码
ce(v) 基于上下文自适应的可变长熵编码
ue(v) 无符号 指数Golomb编码
se(v) 有符号 指数Golomb编码
me(v) 映射 指数Golomb编码
te(v) 截断 指数Golomb编码
上面有的是需要参数的,有的是不用的,需要解码参数的在参量中都会有指明(这个不是很多),
不需要参数的是因为本身解码方法就能知道数据有多少bit。
在下边把描述子写在数据的前面了(模仿了C的数据写法,为了粘贴代码的方便/懒~)。
这里只是进行了分析,没有对解析方法进行说明,没有实现。
这里也不用太关心具体的数据读取是怎么实现的,主要是理清句法元素之间的相关关系,
能做到需要用到哪个参量时,知道在哪个参数集或者哪个头部中去找。
句法元素分析
NAL层
这层完成读取NAL头,将NAL数据读入rbsp_bytes中。
//NalUnit 读入以及取出数据
0x 00 00 01 //nal unit 起始码
f(1) forbidden_zero_bit //保留位
u(2) nal_ref_idc //nal的重要性,在参考帧标记中会用到
u(5) nal_unit_type //nal的类型判断
//这里还有一个辅助nal判断函数
NumBytesInRBSP=0;
//下面这个for循环完成了将NAL header后面的数据读入到rbsp_bytes数组中
//并且去掉了0x 03这个“防止竞争”位,rbsp的字节大小在NumBytesInRBSP中
for(i = nalUnitHeaderBytes; i < NumBytesInUnit; i++)
{
if(i+2
RBSP层
RBSP层是NAL的数据块,根据nal_unit_type的不同分别使用不同的rbsp,
这一层主要完成原始数据流的读入
SPS
序列参数集
1、指定了序列参数集的一些基本属性
2、指定了需要满足的限制条件
3、指定了最大帧数量,指定了计算POC的一些变量
4、指定了图像的宽(宏块为单位)、高(地图单元为单位)
5、指出帧、场的一些参量,用以计算帧、场模式
//SPS类型的NALU
//sequence parameter set即序列参数集
seq_parameter_set_rbsp() //SPS 的rbsp
{
seq_parameter_set_data(); //
rbsp_trailing_bits(); //
}
seq_parameter_set_data() //SPS的rbsp的data
{
u(8) profile_idc //配置ID
u(1) constraint_set0_flag; //从0到5为约束条件
u(1) constraint_set1_flag;
u(1) constraint_set2_flag;
u(1) constraint_set3_flag;
u(1) constraint_set4_flag;
u(1) constraint_set5_flag;
u(2) reserved_zero_2bits;
u(8) level_idc; //级别ID
ue(v) seq_parameter_set_id; //SPSID
if(profile_idc==100||profile_idc==110||profile_idc==122\
||profile_idc==244||profile_idc==44 ||profile_idc==83\
||profile_idc==86 ||profile_idc==118||profile_idc==128\
||profile_idc==138||profile_idc==139||profile_idc==134)
{
ue(v) chroma_format_idc //4个取值:0代表4:0:0 1代表4:2:0 2代表4:2:2 3代表4:4:4
//如果没出现这个值,那么它应该为1
if(chroma_format_idc==3) u(1) separate_colour_plane_flag
//4:4:4的时候将使用3个NAL分别储存Y,Cb,Cr
//依赖上面两个元素可以计算出ChromaArrayType:
//if(separate_colour_plane_flag = 0) ChromaArrayType = chroma_format_idc;
//else ChromaArrayType = 0;
ue(v) bit_depth_luma_minus8; //亮度分量的解码参数的参量
ue(v) bit_depth_chroma_minus8; //色度分量的解码参数的参量
u(1) qpprime_y_zero_transform_bypass_flag;
u(1) seq_scaling_matrix_present; //
if(seq_scaling_matrix_prsent_flag)
{
for(i=0;i<((chroma_format_idc!=3)?8:12);i++)
{
u(1) seq_scaling_list_present_flag[i];
if(seq_scaling_list_present_flag[i])
{
if(i<6) scaling_list(ScalingList4x4[i],16,UseDefaultScalingMatrix4x4Flag[i]);
else scaling_list(ScalingList8x8[i-6],64,UseDefaultScalingMatrix8x8Flag[i-6]);
}
}
}
}
ue(v) log2_max_frame_num_minus4;
ue(v) pic_order_cnt_type;
if(pic_order_cnt_type==0) ue(v) log2_max_pic_order_cnt_lsb_minus4;
else if(pic_order_cnt_type==1)
{
u(1) delta_pic_order_always_zero_flag;
se(v) offset_for_non_ref_pic;
se(v) ffset_for_top_to_bottom_field;
ue(v) num_ref_frames_in_pic_order_cnt_cycle;
for(i=0;i
u(1) vui_parameters_present_flag;
if(vui_parameters_present_flag)
vui_pamareters();
}
//SPS 的补充函数
scaling_list()
在这里能计算出来的变量:
ChromaArrayType:像素的亮度和色度数组比例的值
if (separate_colour_plane_flag = 0) ChromaArrayType = chroma_format_idc;
else ChromaArrayType = 0;
MaxFrameNm:最大的帧数量
MaxFrameNum = 2 ^ (log2_max_frame_num_minus4 + 4);
PicWidthInMbs:图片以宏块表示的宽度:
PicWidthInMbs = pic_width_in_mbs_minus1 + 1;
PicHeightInMapUnits:图片以map unit表示的高度:
PicHeightInMapUnits = pic_width_in_map_units_minus1 + 1;
FrameHeightInMbs:将PicHeightInMapUnits化成FrameHeightInMbs,在Slice Header中用于计算PicHeightInMbs
FrameHeightInMbs = ( 2 − frame_mbs_only_flag ) * PicHeightInMapUnit
PicSizeInMapUnits:
PicSizeInMapUnits = PicWidthInMbs * PicHeightInMapUnits
帧号frame_num的解码参数v:
v = log2_max_frame_num_minus4 + 4;
亮度样点值的解码参数v:
BitDepthY = 8 + bit_depth_luma_minus8
亮度量化参数偏移范围
QpBdOffsetY = 6 * bit_depth_luma_minus
色度的相关参量和上面一样的计算方法
bit_depth_luma_minus8这个参量没有读出来的话就默认为0。
得到FrameHeightInMbs就可以计算出图像的分辨率,
每个宏块都是16x16像素点,再用上面的(PicWidthInMbs - 裁剪宽度) 、 (FrameHeightInMbs - 裁剪宽度)
就能计算出分辨率了。
PPS
图像参数集
在这个参数集中,
1、确定了后面的解码模式
2、确定了宏块-片组映射图的关系,由此可以确定每一个宏块的位置(映射图有被我称为地图了)。
3、指定了两个参考帧队列的长度,由此可以在队列中开辟存储空间
4、其他的参数
pic_parameter_set_rbsp()
{
ue(v) pic_parameter_set_id //图像参数集ID
ue(v) seq_parameter_set_id //引用的序列参数集ID
u(1) entropy_coding_mode_flag //熵编码模式标志:0表示CAVLC 1表示CABAC
//CAVLC:基于上下文自适应熵编码 CABAC:基于上下文自适应二进制编码
//之后出现两种编码方式时(如ue(v)|ae(v)):此项等于0选择左边,否则选择右边
ue(v) num_slice_groups_minus1 //片组数量减一
if(num_slice_groups_minus1 > 0) //如果大于一个片组,则需要读入宏块片组映射图
//片组内部的宏块按照光栅扫描排序
{
ue(v) slice_group_map_type; //片组的地图类型
if(slice_group_map_type == 0) //交错型片组,在读入片组地图时只需要读入相应的参数即可
//按光栅扫描顺序依次把下面长度的宏块分配给这个片组,如下面
//1111122
//2222333 //所以指定的是每个片组的的长度
//3333444
for(iGroup=0;iGroup<=num_slice_group_minus1;iGroup++)
ue(v) run_length_minus1[iGroup];
//slice_group_map_type == 1//散乱分布,
//此时只要用上面片组的数量和图片用宏块表示的宽高
//就可以直接拿到地图,
//比如:下面是4个片组组成的地图,宽4个宏块,高3个宏块地图如下
//01234
//40123 每一行的开始和上一行的结尾是相同的片组
//34012
else if(slice_group_map_type == 2) //背景型片组,左上划分在片组1,右下在片组2
for(iGroup=0;iGroup
se(v) chroma_qp_index_offset; //用于计算色度分量的量化参数
u(1) deblocking_fliter_control_present_flag; //去方块滤波的强度控制
u(1) constrained_intra_pred_flag; //帧内预测限制标志
//1:帧内编码宏块不能用帧间编码宏块作为预测 0:无这种限制
u(1) redundant_pic_cnt_present_flag;
if(more_rbsp_data()) //如果Rbsp里面还有数据,那么按照如下读取
{ //先不看这里
u(1) transform_8x8_mode_flag;
u(1) pic_scaling_matrix_present_flag;
if(pic_scaling_matrix_present_flag)
{
for(i=0;i<6+((chroma_formal_idc!=3)?2:6) * transform_8x8_mode_flag;i++)
{
u(1) pic_scaling_list_present_flag[i];
if(pic_scaling_list_present_flag[i])
{
if(i<6) scaling_list(ScalingList4x4[i],16,UseDefaultScalingMatrix4x4Flag[i]);
else scaling_list(ScalingList8x8[i-6],64,UseDefaultScalingMatrix8x8Flag[i-6]);
}
}
}
se(v) secong_chroma_qp_index_offset;
}
rbsp_trailing_bits()
}
在这里能计算出来的变量:
前向参考帧队列长度
后向参考帧队列长度
亮度分量初始化参数
其他
总之,作为较为重要的两个参数集,SPS和PPS记载了许多在计算中需要用到的参量,由这些参量又可以计算出许多的变量。
SEI
Supplement enhancement information
这是作为额外的信息补充,没怎么看
sei_rbsp()
{
do
sei_message();
while(more_rbsp_data())
rbsp_trailing_bits();
}
sei_message()
{
payloadType=0;
while(next_bits(8)==0xFF)
{
f(8) ff_byte;
payloadType+=255;
}
u(8) last_payload_type_byte;
payLoadType+=last_payload_type_byte;
payloadSize=0;
while(next_bits(8)==0xFF)
{
f(8) ff_byte;
payLoadSize+=255;
}
last_payLoadSize+=last_payLoad_size_byte;
sei_payload(payLoadType, payLoadSize);
}
Slice Layer rbsp
这一层开始进入到slice层面,这个rbsp有两种形式:数据分区和数据不分区
//slice_layer_without_partition_rbsp
//数据不分区slice
slice_layer_without_partition_rbsp()
{
slice_header();
slice_data();
rbsp_slice_trailing_bits(); //slice类型的rbsp拖尾系数
}
片头
下文中:片 == Slice
片头完成了较多的任务
首先读取了许多作为参数的量:
1、第一个宏块的位置
2、片的类型
3、片所引用的图像参数集的ID。在上面的图像参数集PPS中继续存在引用到了SPS的ID,
4、如果片数据是分割开的,则会有一个表示同一个片分开的ID用来找齐一起的三个片数据
5、帧号(较重要)标记了当前帧的序号(在视频中)
6、编码场的相关
其次完成了较为重要的三个功能:
1、POC计算:POC也就是picture order count,解码顺序不等于图像顺序,使用的是POC来标记而非frame_num
2、参考帧列表重排序:在正式开始解码之前就需要对之前的参考帧列表进行重排序
3、解码图片参考标记:
slice_header()
{
ue(v) first_mb_in_slice; //片中第一个宏块的位置,这里还有一个限制在Annex A
ue(v) slice_type; //片类型P(0,5) B(1,6) I(2,7) SP(3,8) SI(4,9)
ue(v) pic_parameter_set_id; //使用的图像参数集的ID
if(separate_colour_plane_flag==1) u(2) colour_plane_id; //如果使用了色彩分割,那么这里出现ID号以查询0-Y,1-Cr, 2-Cb
u(v) frame_num; //也即原始视频中该帧的序号(注意之前提到过的最大帧号)
if(!frame_mbs_only_flag) //如果不是只有帧--即存在“场”
{
u(1) field_pic_flags; //判断是不是编码场
if(field_pic_flag) u(1) bottom_field_flag;//如果是编码场,确定当前是编码顶场还是编码底场
} //这个if完全确定了这个片的帧场属性
三个参数:frame_mbs_only_flag
field_pic_flags
mb_adaptive_frame_filed_flag
那么就可以完全确定这个图像的编码模式
帧、场、帧场自适应
if(idrPicFlag) ue(v) idr_pic_id; //如果是IDR图像,这里还会有一个IDR图像标记ID
//读取计算Pic Order Count(POC)的变量
if(pic_order_cnt_type==0)
{
u(v) pic_order_cnt_lsb;
if(bottom_field_pic_order_in_frame_present_flag && !field_pic_flag)
se(v) delta_pic_order_cnt_bottom;
}
if(pic_order_cnt_type==1 && !delta_pic_order_always_zero_flag)
{
se(v) delta_pic_order_cnt[0];
if(bottom_field_pic_order_in_frame_present_flag && !field_pic_flag)
se(v) delta_pic_order_cnt[1]
}
//读取计算POC的变量到此结束
if(redundant_pic_cnt_present_flag) ue(v) redundant_pic_cnt;
if(slice_type==B) u(1) direct_spatial_mv_pred_flag;
if(slice_type==P||slice_type==SP||slice_type==B)
{
u(1) num_ref_idx_active_override_flag; //指明是否会重载下面的两个元素
if(num_ref_idx_active_override_flag){ //如果重载
ue(v) num_ref_idx_10_active_minus1; //重载的参考帧队列1
if(slice_type==B) //如果是B片
ue(v) num_ref_idx_10_active_minus1; //还会重载一个参考帧队列0
} //说是重载,感觉用C++的引用&来描述更好
}
//参考帧重排序的语法
if(nal_unit_type==20||nal_unit_type==21)
//参考附表知道20号nal是扩展nal,21号nal是3D_AVC view,可能这两个nal的重排序方式不同
ref_pic_list_mvc_modification(); //扩展参考帧列表的重排序
else
ref_pic_list_modification() //这里进入参考帧列表重排序
//加权预测语法
if(weight_pred_flag &&\ //加权预测的判断条件
(slice_type==P||slice_type==SP) ||\
(weighted_pred_idc==1&&slice_type==B)
)
{
pred_weight_table(); //这里进入加权预测
}
//nal_ref_ice != 0表示此图片会用于参考,需要做参考标记
if(nal_ref_idc!=0) dec_ref_pic_marking(); //这里进入解码参考图片标记
if(entropy_coding_mode_flag && slice_type!=I && slice_type != SI)
//PPS中entropy_coding_mode_flag为1时指定CABAC编码,基于上下文自适应二进制编码
ue(v) cabac_init_idc; //CABAC初始化表格的选择
se(v) slice_qp_delta; //所有宏块量化参数的初始值
if(slice_type==SP || slice_type==SI)
{
if(slcie_type==SP)
u(1) sp_for_switch_flag; //是否会用到SP的switch解码方式
ue(v) slice_qs_delta; //专用于SP和SI的宏块量化参数初始值
}
//去方块滤波控制(先不看,依照句法元素读取)
if(deblocking_fliter_control_present_flag)
{
ue(v) disable_deblocking_fliter_idc; //去方块滤波是否使用,默认为0[0,2]
if(disable_deblocking_fliter_idc!=1)
{
se(v) slice_alpha_c0_offset_dic2;
se(v) slice_beta_offset_div2;
}
}
if(num_slice_groups_minus1>0 && slice_group_map_type>=3 && slice_group_map_type<=5)//片组地图循环
//如果是3、4、5也即光栅扫描,盒形展开,手绢
u(v) slice_group_change_cycle;
//读取参量v由Ceil(log2( PicSizeInMapUnits / SliceGroupChangeRate + 1 ))指定
//SliceGroupChangeRate = slice_group_change_rate_minus1 + 1//这个变量在PPS中指定
}
这里可以计算出来的变量
PicHeightInMbs:图片的高(单位:宏块)
PicHeightInMbs = FrameHeightInMbs / ( 1 + field_pic_flag )
PicHeightInSamplesL:图片的高(单位:样点)后缀L表示luma即色度,后缀C为chroma亮度
PicHeightInSamplesL = PicHeightInMbs * 16
PicHeightInSamplesC = PicHeightInMbs * MbHeightC
PicSizeInMbs:图片面积(单位:宏块)
PicSizeInMbs = PicWidthInMbs * PicHeightInMbs
MbaffFrameFlag:帧场自适应标记
MbaffFrameFlag = ( mb_adaptive_frame_field_flag && !field_pic_flag );
MaxPicNum:
if(field_pic_flag == 0) MaxPicNum = MaxFrameNum;
else MaxPicNum = 2 * MaxFrameNum;
CurrPicNum:
if( field_pic_flag == 0 )
CurrPicNum = frame_num;
else
CurrPicNum = 2 * frame_num - 1;
片数据
从片数据这一块可以看出,大体上是一个循环读取宏块层,
读取宏块前先做mb_skip_run的计算和准备
读取宏块进入宏块层语法
读取宏块后做流的后面是否还有剩余数据
slice_data()
{
if(entropy_coding_mode_flag) //如果是CABAC编码
while(!byte_aligned()) //如果字节没有对齐,用cabac的字节对齐数据来对齐
f(1) cabac_alignment_one_bit;
CurrMbAddr=first_mb_in_slice * (1+MbaffFrameFlag);
//计算当前第一个宏块的地址,如果有帧场自适应的话,地址乘以2(宏块对的问题)
moreDataFlag=1;//流中的片数据是否结束
prevMbSkipped=0;
do //循环 读取数据流直到(对于这个NAL来说)中没有多的数据
{ //这个while结束的时候这个NAL也就结束了
//应该注意的是这个循环是用moreDataFlag的状态作为退出条件的
if(slice_type!=I && slice_type!=SI) //
if(!entropy_coding_mode_flag) //如果不是使用CABAC编码
{ //这里使用mb_skip_run跳过连续的N个不编码宏块
ue(v) mb_skip_run; //mb_skip_run以ue描述子读取
prevMbSkipped=(mb_skip_run>0)
for(i=0;i0) //如果需要跳过的宏块数大于0
moreDataFlag=more_rbsp_data( )//那么用rbsp是否有更多数据来判断是否应该结束
}
else
{ //这里使用mb_skip_flag来标记宏块是否应该被跳过
ae(v) mb_skip_flag; //mb_skip_flag以ae描述子读取
moreDataFlag=!mb_skip_flag;
}
在上面这个if-else中读取了跳跃编码模式,如果存在跳跃编码模式,那么推出当前宏块为Skip宏块,进一步:
如果片是P、SP,那么就是P_Skip宏块,如果片是B,那么就是B_Skip宏块
//
if(moreDataFlag)
{
if(MbaffFrameFlag && (CurrMbAddr%2==0 || (currMbAddr%2==1 && prevMbSkipped)))
u(1)|ae(v) mb_field_decoding_flag;
//这个if的意思是如果存在上述条件,那么会有一个标志记录当前宏块要使用场解码
macroblock_layer(); //从这里进入宏块层
}
//
//这个if-else在宏块读取完成之后,用来判断数据流后面还有没有数据
if(!entropy_coding_mode_flag) //如果是CABAC编码,用more_rbsp_data()来判断是否结束
moreDataFlag=more_rbsp_data();
else
{
if(slice_type != I && slice!= SI)
prevMbSkipped=mb_skip_flag;
if(MbaffFrameFlag && CurrMbAddr%2==0)
moreDataFlag=1; //
else
{
ae(v) end_of_slice_flag; //片结束标记
moreDataFlag = !end_of_slice_flag; //如果片结束,那么moreDataFlag为0,此循环之后停止运行
}
}
}while(moreDataFlag)
}
宏块层
宏块层主要完成了三个工作
1、I_PCM宏块无损传输
2、划分4个子宏块以及子宏块预测 或者 整个宏块预测
3、确定残差编码方案以及解码
以上三点可以简单理解为:
1、实际图像宏块
2、预测图像宏块
3、宏块对应残差
其中:预测图像宏块+宏块对应残差=重建的图像宏块
所有的:重建图像宏块+实际图像宏块 = 去方块滤波前的重建图像
macroblock_layer()
{
ue(v)|ae(v) mb_type; //宏块类型
if(mb_type == I_PCM) //I_PCM宏块直接传输样点值而不预测和变换
{
while(!byte_aligned()) //如果字节没有对齐
f(1) pcm_alignment_zero_bit; //使用pcm对齐位对齐
//这里的V由前面SPS的BitDepth指定,色度同理。
for(i = 0; i < 256; i++) //一个I_PCM宏块16X16=256个像素点每个点一个亮度值
u(v) pcm_sample_luma[i]; //循环读取亮度样点值Y
for(i = 0;i < 2 * MbWidth * MbHeight; i++)//这里的宏块宽高由色度亮度比等计算出来
//例如:4:2:0的色度宏块宽高均为16x16的1/2,
//那么这里就为64个Cb样点值后面接上64个Cr样点值
u(v) pcm_sample_chroma[i]; //循环读取色度样点值Cb Cr(所有的Cb在所有的Cr前面)
}
else
{
//I_NxN表示I_4x4或者I_8x8
//NumMbPart()表示宏块的分区,由mb_type的完整属性给出
noSubMbPartSizeLessThan8x8Flag=1; //这个标志位标志没有小于8x8大小的子宏块
if(mb_type!=I_NxN && MbPartPredMode(mb_type,0) != Intra_16x16 && NumMbPart(mb_type)==4)
//如果 宏块类型!=I_NxN 并且 宏块预测模式 != 帧内16x16模式 并且 宏块分区数目==4
//这个if的意思是这个宏块包含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)
//如果是8x8变换解码 并且 宏块类型 == I_NxN
u(1)|ae(v) transform_size_8x8_flag;
//
//这里进入宏块预测
mb_pred(mb_type);
}
//
//
if(MbPartPredMode(mb_type,0) != Intra_16x16)
//Intra_16x16直接指明了CodedBlockPatternLuma和CodedBlockPatternLuma
//在mb_type属性表中指明的
//所以对于非Intra_16x16宏块来说还需要coded_block_pattern来指明上面两个值
//这两个值只会出现在I片和SI片中
{
me(v)|ae(v) coded_block_pattern; //残差的编码方案:
如果coded_block_pattern出现在数据流中
CodedBlockPatternLuma和CodedBlockPatternLuma都由等式7-36指明
在ISO/IEC 14496-10的106页:
CodedBlockPatternLuma = coded_block_pattern % 16
CodedBlockPatternChroma = coded_block_pattern / 16
if(CodedBlockPatternLuma > 0 && transform_8x8_mode_flag &&\
mb_type !=I_NxN && noSubMbPartSizeLessThan8x8Flag &&\
(mb_type != B_Direct_16x16 || direct_8x8_inference_flag))
u(1)|ae(v) transform_size_8x8_flag;
//1:对8x8残差块的变换系数解码和图像重建的过程应该对于亮度块使用,
//如果ChromaArrayType==3色度块也是
//0:对4x4残差块的变换系数解码和图像重建的过程应该对于亮度块使用,
//如果ChromaArrayType==3色度块也是
//Intra_16x16不会出现这个元素
}
//
//以下进入残差语法
//CodedBlockPatternLuma和CodedBlockPatternLuma指的是残差编码方案
//
if(CodedBlockPatternLuma > 0\
|| CodedBlockPatternChroma > 0\
|| MbPartPredMode(mb_type,0) == Intra_16x16)
{
se(v)|ae(v) mb_qp_delta;
//
//从这里进入残差的解码
residual(0,15);
}
}
}
宏块预测
宏块预测层完成了两个工作:
1、如果是帧内预测:确定帧内预测模式
2、如果是帧间预测:循环读入每一个宏块的参考图像序列号和运动矢量
mb_pred( mb_type )
{
//宏块是帧内4x4,8x8,16x16时就不需要参考图像,直接读入帧内预测模式。
if(MbPartPredMode(mb_type, 0) == Intra_4x4 ||\
MbPartPredMode(mb_type, 0) == Intra_8x8 ||\
MbPartPredMode(mb_type, 0) == Intra_16x16)
{
if(MbPartPredMode(mb_type,0) == Intra_4x4) //确定帧内4x4预测模式
for(luma4x4BlkIdx=0; luma4x4BlkIdx<16; luma4x4BlkIdx++)
{
u(1)|ae(v) prev_intra4x4_pred_mode_flag[luma4x4BlkIdx];//亮度分量的预测模式的预测值是否是实际的预测值
if(!prev_intra4x4_pred_mode_flag[luma4x4BlkIdx])
u(1)|ae(v) rem_intra4x4_pred_mode[luma4x4BlkIdx];//如果不是,由此句法指定实际预测模式
}
if(MbPartPredMode(mb_type, 0) == Intra_8x8 ) //确定帧内8x8预测模式
for( luma8x8BlkIdx=0; luma8x8BlkIdx<4; luma8x8BlkIdx++)
{
u(1)|ae(v) prev_intra8x8_pred_mode_flag[luma8x8BlkIdx]; //
if(!prev_intra8x8_pred_mode_flag[luma8x8BlkIdx])
u(1)|ae(v) rem_intra8x8_pred_mode[luma8x8BlkIdx]; //
}
/16x16的帧内预测模式可以直接由mb_type读出
if( ChromaArrayType == 1 || ChromaArrayType == 2 )
ue(v)|ae(v) intra_chroma_pred_mode
//帧内色度的预测模式
//0:DC 1:horizontal 2:Vertical 3:Plane
}
//下面这个else if完成了将每一个宏块的子块的参考图像序列和运动矢量读入的工作
//包括前向和后向
else if(MbPartPredMode(mb_type, 0) != Direct)
{
for( mbPartIdx = 0; mbPartIdx < NumMbPart( mb_type ); mbPartIdx++)
if((num_ref_idx_l0_active_minus1 > 0\
|| mb_field_decoding_flag != field_pic_flag)\
&& MbPartPredMode( mb_type, mbPartIdx) != Pred_L1)
te(v)|ae(v) ref_idx_l0[mbPartIdx]; //前向参考帧队列中所参考图像的序号
for(mbPartIdx = 0; mbPartIdx < NumMbPart(mb_type); mbPartIdx++)
if((num_ref_idx_l1_active_minus1 > 0 || mb_field_decoding_flag != field_pic_flag)\
&& MbPartPredMode( mb_type, mbPartIdx ) != Pred_L0)
te(v)|ae(v) ref_idx_l1[mbPartIdx]; //后向参考帧队列中所参考图像的序号
for(mbPartIdx = 0; mbPartIdx < NumMbPart(mb_type); mbPartIdx++)
if( MbPartPredMode(mb_type,mbPartIdx) != Pred_L1)
for(compIdx = 0; compIdx < 2; compIdx++)
se(v)|ae(v) mvd_l0[mbPartIdx][0][compIdx];
//运动矢量的前线预测值与实际值之间的差
//compIdx为0时代表
for(mbPartIdx = 0; mbPartIdx < NumMbPart(mb_type); mbPartIdx++)
if(MbPartPredMode(mb_type,mbPartIdx) != Pred_L0)
for(compIdx = 0; compIdx < 2; compIdx++)
se(v)|ae(v) mvd_l1[mbPartIdx][0][compIdx]; //运动矢量的后向预测值与实际值之间的差
}
}
子宏块预测语法
sub_mb_pred(mb_type) {
for(mbPartIdx = 0; mbPartIdx < 4; mbPartIdx++) //在宏块层我们提到宏块可以继续划分4个子宏块
ue(v)|ae(v) sub_mb_type[mbPartIdx] //子宏块的类型直接在子宏块预测中指出
for(mbPartIdx = 0; mbPartIdx < 4; mbPartIdx++)
if((num_ref_idx_l0_active_minus1 > 0\
|| mb_field_decoding_flag != field_pic_flag)\
&& mb_type != P_8x8ref\
&& sub_mb_type[mbPartIdx] != B_Direct_8x8\
&& SubMbPredMode(sub_mb_type[mbPartIdx]) != Pred_L1)
te(v)|ae(v) ref_idx_l0[mbPartIdx];
for(mbPartIdx = 0; mbPartIdx < 4; mbPartIdx++)
if((num_ref_idx_l1_active_minus1 > 0\
|| mb_field_decoding_flag != field_pic_flag)\
&& sub_mb_type[mbPartIdx] != B_Direct_8x8\
&& SubMbPredMode(sub_mb_type[mbPartIdx]) != Pred_L0)
te(v)|ae(v) ref_idx_l1[mbPartIdx];
for(mbPartIdx = 0; mbPartIdx < 4; mbPartIdx++)
if(sub_mb_type[mbPartIdx] != B_Direct_8x8\
&& SubMbPredMode(sub_mb_type[mbPartIdx]) != Pred_L1)
for(subMbPartIdx = 0; subMbPartIdx < NumSubMbPart(sub_mb_type[mbPartIdx]); subMbPartIdx++)
for(compIdx = 0; compIdx < 2; compIdx++)
se(v)|ae(v) mvd_l0[mbPartIdx][subMbPartIdx][compIdx];
for(mbPartIdx = 0; mbPartIdx < 4; mbPartIdx++)
if(sub_mb_type[mbPartIdx] != B_Direct_8x8\
&& SubMbPredMode(sub_mb_type[mbPartIdx]) != Pred_L0)
for(subMbPartIdx = 0; subMbPartIdx < NumSubMbPart(sub_mb_type[mbPartIdx]); subMbPartIdx++)
for(compIdx = 0; compIdx < 2; compIdx++) //
se(v)|ae(v) mvd_l1[mbPartIdx][subMbPartIdx][compIdx];
}
残差数据语法
可以看到,残差数据句法中是没有使用到描述子的。
只是不停地在对AC、DC等数据计算和赋值。
在宏块层中残差解码使用的是:residual(0,15)
residual ( startIdx, endIdx )
{
if ( ! entropy_coding_mode_flag ) //如果没有使用CABAC编码
residual_block = residual_block_cavlc; //那么残差块为CAVLC编码
else
residual_block = residual_block_cabac; //否则使用CABAC编码
//residual_block不是在数据流中的句法,而是计算出来的一个句法,用来解析变换系数等级的函数指针
//
//这一块进入残差亮度块句法,前面的四个元素是这个句法的输出值
residual_luma ( i16x16DClevel, i16x16AClevel, level4x4, level8x8, startIdx, endIdx ) ;
//经过上一个过程之后,四个输出值分别被赋予相应的变量用于后面的计算
Intra16x16DCLevel = i16x16DClevel;//
Intra16x16ACLevel = i16x16AClevel;//
LumaLevel4x4 = level4x4;//
LumaLevel8x8 = level8x8;//
//至此亮度块的残差参量结束
//4:2:0 和 4:2:2的情况
if ( ChromaArrayType == 1 || ChromaArrayType == 2 )
{
NumC8x8 = 4 / ( SubWidthC * SubHeightC );//
for ( iCbCr = 0; iCbCr < 2; iCbCr++ ) //
if ( ( CodedBlockPatternChroma & 3 ) && startIdx == 0 ) // chroma DC residual present
residual_block ( ChromaDCLevel[iCbCr], 0, 4 * NumC8x8 − 1, 4 * NumC8x8 );//
else
for ( i = 0; i < 4 * NumC8x8; i++ ) //
ChromaDCLevel[iCbCr][i] = 0; //
for ( iCbCr = 0; iCbCr < 2; iCbCr++ ) //
for ( i8x8 = 0; i8x8 < NumC8x8; i8x8++ ) //
for ( i4x4 = 0; i4x4 < 4; i4x4++ ) //
if ( CodedBlockPatternChroma & 2 ) // chroma AC residual present
residual_block ( ChromaACLevel[iCbCr][i8x8*4+i4x4],\
Max ( 0, startIdx − 1 ) , endIdx − 1, 15 );
else
for ( i = 0; i < 15; i++ ) //
ChromaACLevel[iCbCr][i8x8*4+i4x4][i] = 0;//
}
//4:4:4
//对于4:4:4来说,Y、Cb、Cr具有相同的大小,所以可以直接使用亮度残差的函数来得到色度残差的变换系数
else if ( ChromaArrayType == 3 )
{
residual_luma ( i16x16DClevel, i16x16AClevel , level4x4, level8x8, startIdx, endIdx );
CbIntra16x16DCLevel = i16x16DClevel;//
CbIntra16x16ACLevel = i16x16AClevel;//
CbLevel4x4 = level4x4;//
CbLevel8x8 = level8x8;//
residual_luma ( i16x16DClevel, i16x16AClevel, level4x4, level8x8, startIdx, endIdx )
CrIntra16x16DCLevel = i16x16DClevel;//
CrIntra16x16ACLevel = i16x16AClevel;//
CrLevel4x4 = level4x4; //
CrLevel8x8 = level8x8; //
}
}
残差解码完成之后,宏块层的任务到此完全结束,下一步回到片数据的后处理中(数据流后面是否还有多余的数据)
片数据解码完成之后,回到NAL层,读入字节对齐等位后,整个NAl到此读取结束
但是在这个宏块层完成解码之后,应该已经得到预测值、残差值,接下来进行图像的复原和重建,并将样点值输出到实际的图像输出矩阵中,然后对整个图像进行去方块滤波得到实际的输出图像样点值。
(注意到重排序和图片标记在片头中就已经完成)
至此当前图像解码完成。
有引用到这篇垃圾文章的话,贴一下作者和原文地址就行了,感激不尽。
参考文献:
新一代视频编码标准-H.264/AVC
Information technology — Coding of audio-visual objects — Part 10: Advanced Video Coding
其他还参考了很多的博客,学完之后会慢慢补上来的。