H.264 一点点解读

h264具体解析过程

残差块解析过程分析:

前注:代码是我自己实现的,类似但不是标准中的,写代码不要参照这个。
残差块解码调用条件:

if(CodedBlockPatternLuma > 0 || CodedBlockPatternChroma > 0 ||\
            Get_MbPartPredMode(mb_type, 0) == Intra_16x16){
            mb_qp_delta  = parser_g->read_ae(24);
            residual(0, 15);
        }

也就是解码模式(亮度、色度)中任一大于0,或者是帧内16x16时调用residual(0, 15);同时还读取了一个宏块的量化参数偏移。
从之前的PPS的 pic_init_qp 到Slice头部的 slice_qp_delta 再到这里的 mb_qp_delta 则完全确定了这个宏块的量化参数QP。
指定解码函数指针
这个比较简单,就是用之前的解码参数entropy_coding_mode_flag来确定函数指针是CABAC还是CAVLC

if(!parser_g->pS->pps->entropy_coding_mode_flag) residual_block = ¯oblock::residual_block_cavlc;
else residual_block = ¯oblock::residual_block_cabac;

而至于这两个函数指针的具体实现不用太关心,
他的参数表为:(int32_t* coeffLevel, uint8_t startIdx, uint8_t endIdx, uint8_t length)
就简单理解为从 startIdx 到 endIdx 读入 length 个数据组成一个矩阵(或者说数组),放到第一个参数中。
亮度残差块解码
亮度块解码紧跟在函数指针的下一步。一共6个参数:AC系数返回值、DC系数返回值、4x4块系数、8x8块系数、开始索引、结束索引。
在宏块中residual(0, 15);所以开始索引是0,结束索引是15,一共16是个,也就是。然后进入亮度块的残差解码函数中。

亮度块的残差解码函数:

大体上是两个部分:DC系数、两级for循环读入其他系数。
DC系数就只有两句话,基本就是为了帧内16x16准备的,这也是毕老师书上提到的非帧内16x16宏块不单独编码DC系数。

if(startIdx == 0 && Get_MbPartPredMode(mb_type, 0) == Intra_16x16)
    (this->*residual_block)(i16x16DClevel, 0, 15, 16);

然后是一个下面所示的两级for循环,两级循环索引分别是8x8索引i8x8和4x4块索引i4x4。这样就是4个8x8块和每一个8x8里面的4个4x4块。

//不完整的代码
for(uint8_t i8x8 = 0; i8x8 < 4; i8x8++)
        for(uint8_t i4x4 = 0; i4x4 < 4; i4x4++) 

第一个for下面的三个条件分别是:

if(!transform_size_8x8_flag || !parser_g->pS->pps->entropy_coding_mode_flag)
else if(CodedBlockPatternLuma & (1 << i8x8))
else

1、标准句法表第一句里面很绕,可以反过来想: 非( transform_size_8x8_flag 且 entropy_coding_mode_flag )
也就是二者不能同时为真。同时为真是什么意思:是算术熵编码CABAC 并且 是8x8变换解码 ,
再继续说就是:如果这个if 不成立,应该用 CABAC 去解8x8块残差得到总的残差。
2、CodedBlockPatternLuma从最低位也就是2^0位上开始,按照4个8x8块索引依次往高位,移位求并得到CodedBlockPatternLuma当前位上的值。四个值从低到高分别是四个8x8块(0 1 2 3)的系数。也就是8x8块系数。
也即:如果编码模式为1,那么解8x8块。
3、其他:除了上面两种情况之外的其他情况,当前的8x8块的所有系数为0。
第一种情况的反面:(transform_size_8x8_flag && entropy_coding_mode_flag)同时为真,也就是:是算术熵编码CABAC并且是8x8变换解码,那么就是用8x8块的系数来决定整个宏块的亮度残差。
第二种情况的反面:该8x8块上的亮度编码模式为0,也就是第三种情况里面的全部为0不编码。

全代码如下:

void macroblock::residual_luma(int32_t i16x16DClevel[16], int32_t i16x16AClevel[16][15], int32_t level4x4[16][16], int level8x8[4][64], uint8_t startIdx, uint8_t endIdx)
{
    uint8_t i = 0;
    if(startIdx == 0 && Get_MbPartPredMode(mb_type, 0) == Intra_16x16)
        (this->*residual_block)(i16x16DClevel, 0, 15, 16);
    for(uint8_t i8x8 = 0; i8x8 < 4; i8x8++) 
        if(!transform_size_8x8_flag || !parser_g->pS->pps->entropy_coding_mode_flag) 
            for(uint8_t i4x4 = 0; i4x4 < 4; i4x4++) 
            { 
                if(CodedBlockPatternLuma & (1 << i8x8)) 
                    if(Get_MbPartPredMode(mb_type, 0) == Intra_16x16) 
                        (this->*residual_block)(i16x16AClevel[i8x8*4+ i4x4], (0 > startIdx - 1)?0: startIdx - 1, endIdx - 1, 15);
                    else
                        (this->*residual_block)(level4x4[i8x8 * 4 + i4x4], startIdx, endIdx, 16);
                else if(Get_MbPartPredMode(mb_type, 0) == Intra_16x16)
                    for(i = 0; i < 15; i++)
                        i16x16AClevel[i8x8 * 4 + i4x4][i] = 0;
                else
                    for(i = 0; i < 16; i++)
                        level4x4[i8x8 * 4 + i4x4][i] = 0;
                if(!parser_g->pS->pps->entropy_coding_mode_flag && transform_size_8x8_flag)
                    for(i = 0; i < 16; i++)
                        level8x8[i8x8][4 * i + i4x4] = level4x4[i8x8 * 4 + i4x4][i];
            }
        else if(CodedBlockPatternLuma & (1 << i8x8))
            (this->*residual_block)(level8x8[i8x8], 4 * startIdx, 4 * endIdx + 3, 64);
        else
            for(i = 0; i < 64; i++) 
                level8x8[i8x8][i] = 0;
}

OK,搞清楚之后可以进入第一种情况的详细解释了:

for(uint8_t i4x4 = 0; i4x4 < 4; i4x4++) 
{ 
    if(CodedBlockPatternLuma & (1 << i8x8)) 
        if(Get_MbPartPredMode(mb_type, 0) == Intra_16x16) 
            (this->*residual_block)(i16x16AClevel[i8x8*4+ i4x4], (0 > startIdx - 1)?0: startIdx - 1, endIdx - 1, 15);
        else
            (this->*residual_block)(level4x4[i8x8 * 4 + i4x4], startIdx, endIdx, 16);
    else if(Get_MbPartPredMode(mb_type, 0) == Intra_16x16)
        for(i = 0; i < 15; i++)
            i16x16AClevel[i8x8 * 4 + i4x4][i] = 0;
    else
        for(i = 0; i < 16; i++)
            level4x4[i8x8 * 4 + i4x4][i] = 0;
    if(!parser_g->pS->pps->entropy_coding_mode_flag && transform_size_8x8_flag)
        for(i = 0; i < 16; i++)
            level8x8[i8x8][4 * i + i4x4] = level4x4[i8x8 * 4 + i4x4][i];
}

这里有两套逻辑:
第一套if:
1、如果当前8x8块的编码模式为1:是帧内16x16就解AC系数,否则就解4x4系数
2、如果当前8x8块的编码模式为0 并且 是帧内16x16,那么所有的AC系数为0
3、否则:所有的4x4系数为0。用上面的事件取逻辑反就得当前条件:当前8x8块编码模式为0 并且 不是帧内16x16宏块
这三个也是很绕的三个逻辑,主要抓住两个判断条件:当前8x8块编码模式是不是1,当前宏块是不是帧内16x16。
总结:帧内16x16宏块,那么用AC系数;不是,那么用4x4系数
第二套if:
这个比较简单只有一个条件:如果不是熵编码 并且 是8x8变换解码。那么所有的4x4系数给到相应的8x8系数矩阵里面去。

二次总结:

单独的一个是DC系数,只有16个,如果不是帧内16x16宏块,那么没有编码DC系数。
其他三个分别是AC系数、8x8系数、4x4系数
AC系数还是给帧内16x16用的,
8x8系数是给8x8变换解码用的,
4x4是给除了帧内16x16之外的其他解码(包括8x8变换解码)用的。
在这里还要注意到子宏块等等的分法的不同而不同。
其中:AC系数只有16x15个,另外两个一共都是16x16个。
维度上面的分法有所不同:
8x8:[ 4][64]
4x4:[16][16]
AC :[16][15]

色度残差块解码

if(parser_g->pV->ChromaArrayType == 1 || parser_g->pV->ChromaArrayType == 2)
{
    uint8_t iCbCr = 0;
    NumC8x8 = 4 / (parser_g->pV->SubWidthC * parser_g->pV->SubHeightC);
    for(iCbCr = 0; iCbCr < 2; iCbCr++) // read chroma DC level
        if((CodedBlockPatternChroma & 3) && startIdx == 0) (this->*residual_block)(ChromaDCLevel[iCbCr], 0, 4 * NumC8x8 - 1, 4 * NumC8x8);
        else for(uint8_t i = 0; i < 4 * NumC8x8; i++) ChromaDCLevel[iCbCr][i] = 0;
    for(iCbCr = 0; iCbCr < 2; iCbCr++) // read chroma AC level
        for(uint8_t i8x8 = 0; i8x8 < NumC8x8; i8x8++)
            for(uint8_t i4x4 = 0; i4x4 < 4; i4x4++)
                if(CodedBlockPatternChroma & 2) (this->*residual_block)(ChromaACLevel[iCbCr][i8x8*4+i4x4], startIdx - 1 >= 0 ? (startIdx - 1) : 0 , endIdx - 1, 15);
                else for(uint8_t i = 0; i < 15; i++) ChromaACLevel[iCbCr][i8x8*4+i4x4][i] = 0;
}

色度块就简单一些,注意其中的条件 ChromaArrayType:
上篇文章提到 ChromaArrayType 指示了亮度和色度的分组情况,这里的1和2分别就是4:2:0 4:2:2
注意到是先读完所有的(Cb Cr)DC系数,然后再读所有的AC系数。
然后也就是 CodedBlockPatternChroma 这个变量对是否编码系数的影响,从if-else就可以看出来。

后面还有 ChromaArrayType == 3 的解码,因为就是用的亮度的解码方法,这里也不多说了。

2020年05月26日16:49:31 残差块认知结束。(未完)

CABAC在解残差块的流程

void residual::residual_block_cabac(block* bl, int requestVlaue, uint8_t startIdx, uint8_t endIdx, uint8_t length)
{
    int16_t i = 0, numCoeff = 0;
    int32_t* coeffLevel                  = new int32_t[endIdx];
    uint8_t* significant_coeff_flag =      new uint8_t[endIdx];
    uint8_t* last_significant_coeff_flag = new uint8_t[endIdx];
    int16_t a= 0 ,b = 0;

    if(length != 64 || parser->pV->ChromaArrayType == 3) 
        bl->coded_block_flag = parser->read_ae(0x61000 + requestVlaue);
    for(i = 0; i < length; i++) 
        coeffLevel[i] = 0;
    if(bl->coded_block_flag)
    {
        int32_t* coeff_abs_level_minus1 = new int32_t[endIdx];
        int32_t* coeff_sign_flag        = new int32_t[endIdx];
        numCoeff = endIdx + 1 ;
        i = startIdx ;
        while(i < numCoeff - 1) 
        {
            significant_coeff_flag[i] = parser->read_ae(0x62000 + requestVlaue + (((int32_t)i) << 20));
            if(significant_coeff_flag[i]) {
                last_significant_coeff_flag[i] = parser->read_ae(0x63000);  
                if(last_significant_coeff_flag[i]) 
                    numCoeff = i + 1;
            }
            i++;
        }
        coeff_abs_level_minus1[numCoeff - 1] = parser->read_ae(0x64000 + requestVlaue + (((int)a) << 24) + (((int)b )<< 20));
        coeff_sign_flag[numCoeff - 1] = parser->read_ae(0);//使用的是旁路编码模式
        coeffLevel[numCoeff - 1] = (coeff_abs_level_minus1[numCoeff - 1] + 1) * (1 - 2 * coeff_sign_flag[numCoeff - 1]) ;
        for(i = numCoeff - 2; i >= startIdx; i--){
            if(significant_coeff_flag[i])
            {    
                if(coeffLevel[i] == 1 || coeffLevel[i] == -1) a++;
                if(coeffLevel[i] > 1  || coeffLevel[i] <  -1) b++;
                coeff_abs_level_minus1[i] = parser->read_ae(0x64000 + (((int)a) << 24) + (((int)b )<< 20));  
                coeff_sign_flag[i] = parser->read_ae(0);  
                coeffLevel[i] = (coeff_abs_level_minus1[i] + 1) * (1 - 2 * coeff_sign_flag[i]);

            }
        }
        delete[] coeff_abs_level_minus1;
        delete[] coeff_sign_flag       ;
    }
    for (uint16_t i = 0; i < length; i++) {bl->set_blockValue(i, coeffLevel[i]);}
    printf(">>residu:result of cabac is: \n");
    for (uint16_t i = 0; i < length; i++) {printf(" index %2d, value : %d\n", i, coeffLevel[i]);}
    delete[] coeffLevel                 ;
    delete[] significant_coeff_flag     ;
    delete[] last_significant_coeff_flag;
}

首先我还是贴一下我写的代码,这里就简单略过解析器指针parser的read方法的实现,单单就这个过程做一些学习。
首先是这个块是不是被编码,coded_block_flag这个句法元素指明了,如果说 coded_block_flag == 0,那么后面读取的操作都不会发生。所有系数都是0.
然后我们需要分别new 4个数组出来用来存储中间计算和读取的数据。
首先是初始化两个变量,一个是系数的总数量,另一个是系数的读取索引。

numCoeff = endIdx + 1 ;
i = startIdx ;

然后看第一个while

while(i < numCoeff - 1) 
{
    significant_coeff_flag[i] = parser->read_ae(0x62000 + requestVlaue + (((int32_t)i) << 20));
    if(significant_coeff_flag[i]) {
        last_significant_coeff_flag[i] = parser->read_ae(0x63000);  
        if(last_significant_coeff_flag[i]) 
            numCoeff = i + 1;
    }
    i++;
}

因为i是从0开始的,所以判断条件的右边是系数减一。
在中间的一段先不看,直接到结尾的i++,所以这个循环可以说是针对于整个块的。
然后里面第一个句法元素,significant_coeff_flag,这是一个数组,对于每一个i,记录这个位置上的数据是不是0,如果是significant_coeff_flag[i] == 0,那么这个数据位上是0,否则是非零的数。
如果当前位置上不是0,那么指定 last_significant_coeff_flag 来说明这是不是在扫描顺序中最后一个非零的数。
last_significant_coeff_flag == 1,接下来从i+1到结尾扫描顺序之后全是都是0。为0说明后面还有非零系数。

如果这个位置是最后一个非零系数的话,那么之后的数据也不用读了。直接全部为0,而之前已经完成这个工作了,所以就直接退出循环。

接下来的三句把退出的时候最后一个非零系数位置上的数据读出来。
coeff_abs_level_minus1 指系数绝对值减去1。
coeff_sign_flag 指明符号,0为正数,1为负数。
coeffLevel 就是这个系数

coeff_abs_level_minus1[numCoeff - 1] = parser->read_ae(0x64000 + requestVlaue + (((int)a) << 24) + (((int)b )<< 20));
coeff_sign_flag[numCoeff - 1] = parser->read_ae(0);//使用的是旁路编码模式
coeffLevel[numCoeff - 1] = (coeff_abs_level_minus1[numCoeff - 1] + 1) * (1 - 2 * coeff_sign_flag[numCoeff - 1]) ;

第二个循环

for(i = numCoeff - 2; i >= startIdx; i--)
{
    if(significant_coeff_flag[i])
    {    
        if(coeffLevel[i] == 1 || coeffLevel[i] == -1) a++;
        if(coeffLevel[i] > 1  || coeffLevel[i] <  -1) b++;
        coeff_abs_level_minus1[i] = parser->read_ae(0x64000 + (((int)a) << 24) + (((int)b )<< 20));  
        coeff_sign_flag[i] = parser->read_ae(0);  
        coeffLevel[i] = (coeff_abs_level_minus1[i] + 1) * (1 - 2 * coeff_sign_flag[i]);

    }
}

总结:先从0遍历到最后一个非零的系数,然后再从最后一个非零系数往前依次读取数值。

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