摸一下H.264应该不会被打吧 (二)

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
其他还参考了很多的博客,学完之后会慢慢补上来的。

你可能感兴趣的:(h.264)