CABAC基础四-CABAC熵解码算法FFmpeg实现

1.  CABAC熵解码算法FFmpeg实现

本章主要讲解FFmpeg中对CABAC熵解码算法的实现和优化思路。由于算术编码算术在H264和H265中没有发生变化,所以FFmpeg中H265的熵解码仍然采用了H264中的实现方式,所以,在H265的CABAC部分,仍然可以看到有的数组的定义中带有H264的名字,如ff_h264_lps_range和ff_h264_mlps_state等等。

1.1.  概率状态初始化

  ff_h264_init_cabac_states(const H264Context *h, H264SliceContext *sl)
  {
      int i;
      const int8_t (*tab)[2];
      const int slice_qp = av_clip(sl->qscale - 6*(h->sps.bit_depth_luma-8), 0, 51);
      if (sl->slice_type_nos == AV_PICTURE_TYPE_I) tab = cabac_context_init_I;
      else                                 tab = cabac_context_init_PB[sl->cabac_init_idc];
      /* calculate pre-state */
      for( i= 0; i < 1024; i++ ) {
          int pre = 2*(((tab[i][0] * slice_qp) >>4 ) + tab[i][1]) - 127;
          pre^= pre>>31;
          if(pre > 124)
              pre= 124 + (pre&1);
          sl->cabac_state[i] =  pre;
      }
  }
  cabac_init_state(HEVCContext *s)
  {
      int init_type = 2 - s->sh.slice_type;
      int i;
      if (s->sh.cabac_init_flag && s->sh.slice_type != I_SLICE)
          init_type ^= 3;
      for (i = 0; i < HEVC_CONTEXTS; i++) {
          int init_value = init_values[init_type][i];
          int m = (init_value >> 4) * 5 - 45;
          int n = ((init_value & 15) << 3) - 16;
          int pre = 2 * (((m * av_clip(s->sh.slice_qp, 0, 51)) >> 4) + n) - 127;
          pre ^= pre >> 31;
          if (pre > 124)
              pre = 124 + (pre & 1);
          s->HEVClc->cabac_state[i] = pre;
      }
      for (i = 0; i < 4; i++)
          s->HEVClc->stat_coeff[i] = 0;
  }

ff_h264_init_cabac_states函数中第5~9行根据slice_type选择该slice所需要初始化1024的概率状态。在标准里preCtxState = Clip3( 1,126, ( ( m*Clip3( 0, 51, SliceQPY ) ) >> 4 ) + n ),第5行有slice_qp = av_clip(sl->qscale - 6*(h->sps.bit_depth_luma-8),0, 51),所以preCtxState= Clip3( 1, 126, ( ( m*slice_qp ) >> 4 ) + n ),其中m是第10行的tab[i][0]、n是第10行的tab[i][1],理论上最终的preCtxState∈[1, 126],因此2* preCtxState∈[2, 252],其中间值为127,减去127后pre∈[-125, 125],pre中每个数之间的间隔为2,即pre∈{-125,-123,…,-1,1,3,…,123, 125}。在FFmpeg里,pre是一个int型的32位整数,因此第11行pre>>31相当于将pre的符号位算术右移31位,如果pre是负数,那么pre>>31的结果是0xFFFFFFFF,如果pre是正数,那么pre>>31的结果是0x00000000;pre ^= pre >> 31相当于将一个负数变成比他的相反数小1的数,而正数保持不变。如果pre本身是正数,那么pre>>31的结果为全0,任何数和0异或,结果保持不变,比如0x7fXOR 0x00 = 0x7f;如果pre本身是负数,那么pre>>31的结果为全1,任何数和1异或,变为其相反数,也就是1XOR 1 = 0、0 XOR 1 = 1,0x9f  XOR 0xff = 0x60,其中有符号数0x9f = -97,有符号数0x60=96比0x9f = -97的相反数小1;或者可以从有符号数的补码和原码的转换关系也可以得出同样的结论:使有符号负数补码的符号位保持不变,其余位变反加1,即可得到其原码;如果对有符号数负数的补码全部位取反则得到的结果比其相反数小1。通过以上分析可知:pre^= pre >> 31会将负数转换为比其相反数小1的数,而正数保持不变,也就是pre将由{-125,-123,…,-1,1,3,…,123,125}转换为{124,122,…,0,1,3,…,123,125},即pre∈[0, 125],其中偶数{124,122,…,0}属于MPS为0的情况,奇数{1,3,…,123,125}属于MPS=1的情况,也就是标准算法中的valMPS和pStateIdx可以通过如下公式valMPS=pre&1,pStateIdx=pre>>1获得。由于标准限制preCtxState∈[1, 126],因此也将限制pre∈[0, 125],pre=124时,valMPS=0、pStateIdx=62,pre=125时,valMPS=1、pStateIdx=62,因此当pre>125时pre=124+(pre&1),FFmpeg第12和13行判断条件pre>124时,pre=124+(pre&1),其结果是一致的。最终的结果cabac_state=pre=( pStateIdx<< 1) + valMPS。从函数cabac_init_state 和函数ff_h264_init_cabac_states 我们看出,H265相比于H264概率上下文的存储方式有了一些改变,主要体现在三个方面:第一是针对每个语法元素不在是直接提供m和n,而是提供一个initValue值,然后根据公式计算对应的m和n;第二是H265中概率状态以语法元素为单位进行组织,H264中不同语法元素的概率状态会被其他语法元素的概率状态割裂而分散在不同的区域,比如H264中I帧中coded_block_flag语法元素的概率状态被割裂为三部分:85..104、460..483和1012..1023;第三就是H265中概率状态数量较少。cabac_init_state函数实现的原理和ff_h264_init_cabac_states函数基本一致,区别已经在上面讲述。

1.2.  字节填充

  ff_init_cabac_decoder(CABACContext *c, const uint8_t *buf, int buf_size)
  {
      c->bytestream_start=
      c->bytestream= buf;
      c->bytestream_end= buf + buf_size;
      c->low =  (*c->bytestream++)<<18;
      c->low+=  (*c->bytestream++)<<10;
      // Keep our fetches on a 2-byte boundry as this should avoid ever having to
      // do unaligned loads if the compiler (or asm) optimises the double byte
      // load into a single instruction
      if(((uintptr_t)c->bytestream & 1) == 0)
      {
          c->low += (1 << 9);
      }
      else
      {
          c->low += ((*c->bytestream++) << 2) + 2;
      }
      c->range= 0x1FE;
      if ((c->range<<(CABAC_BITS+1)) < c->low)
          return AVERROR_INVALIDDATA;
      return 0;
  }
  refill2(CABACContext *c)
  {
      int i;
      unsigned x;
      x= c->low ^ (c->low-1);
      i = 7 - ff_h264_norm_shift[x >> (CABAC_BITS - 1)];
      x = -CABAC_MASK;
      x += (c->bytestream[0] << 9) + (c->bytestream[1] << 1);
      c->low += x<<i;
      if (c->bytestream < c->bytestream_end)
          c->bytestream += CABAC_BITS/8;
  }
   refill(CABACContext *c)
   {
       c->low+= (c->bytestream[0]<<9) + (c->bytestream[1]<<1);
       c->low -= CABAC_MASK;
       if (c->bytestream < c->bytestream_end)
           c->bytestream += CABAC_BITS / 8;
   }
相比于标准的常规算术解码实现,FFmpeg中的常规算术解码实现中规避了很多分支判断等,从一定程度上应该能减少branch miss。其实现代码如下图所示。       FFmpeg中字节填充分为两个部分,第一个部分是在CABAC解码器初始化的时候,第二部分是在CABAC解码过程中缓冲的字节无法满足算术解码的要求的时候。

CABAC熵解码初始化的时候使用ff_init_cabac_decoder函数。其中第3~5行初始化相关的bytestream信息。第6和7行初始化c->low,需要注意的是:这里对c->low使用了26比特,而大多数其他的相关实现只使用了24比特,之所以如此,是因为FFmpeg在初始化时候在码字后面添加了一个比特的结束标记“1”和一个或者九个拖比特的拖尾“0”,来确定在解码过程中填充的时候需要移动的位数,而其他的相关实现都是通过增加一个变量来指明剩余的比特数。第11~18行判断c->bytestream的地址,为的是在编译器或者汇编优化的时候能够从偶数地址加载数据,如果当前c->bytestream已经在偶数地址,则不需要读取下一个字节,而是直接将1左移9位(此时c->low的组成为:xxxxxxxx xxxxxxxx 10 00000000,其中“x”表示一个二进制比特,其中前两字节是从c->bytestream中读取的,比特“1”是结束标志,后面包含九个拖尾的比特“0”,此时c->low预读了16比特),如果当前c->bytestream在奇数位置,则继续读取下一个字节,以使c->bytestream处于偶数位置,然后加上2(此时c->low的组成为:xxxxxxxx xxxxxxxx xxxxxxxx 10,加上2就相当于在末尾添加一个结束标志“1”和一个比特的拖尾“0”,此时c->low预读了24比特),用于后面判断当前c->low中剩余的可用比特数:c->low剩余可用比特数=26-1-拖尾“0”的个数。同时需要注意的是:c->low中预读了16或者24比特,实际在熵解码的时候用的是c->low的高9位,相当于将9比特的low左移了17位,所以在get_cabac_inline中第7行比较c->low和c->range时需要将c->range左移CABAC_BITS+1=17位。

       CABAC熵解码过程中进行字节填充时使用refill2函数。第5和6行利用c->low末尾的比特“1”来确定c->low中剩余的可用比特数,假设当前c->low=0x2380000(其二进制串为:100011 100 00000000000000000,可以发现高9位为100011 100,其中低三比特“100”中的“1”是CABAC添加的结束标记,后两比特的“0”是拖尾0,所以可以知道当前c->low只剩下“100011”六位有效比特可用,不满足c->low九比特的要求,需要添加至少三比特的数据才能满足要求),则(c->low-1)=0x237ffff,x=c->low^(c->low-1)=0x00fffff,x >> (CABAC_BITS - 1)=0x1f=31,查表ff_h264_norm_shift[31]=4,则7- ff_h264_norm_shift[31]=3(这个“3”就是上面要求的需要添加的比特数,在该关系式中7 - ff_h264_norm_shift[x >> (CABAC_BITS - 1)]不便于理解,可以换成9 - ff_h264_norm_shift[x>> (CABAC_BITS + 1)],因为进入refill2函数的条件是c->low的第16位为全0,则c->low^(c->low-1)的低17位肯定是全1,此时高9位中比特“0”的个数就是剩余的有效比特数,而ff_h264_norm_shift[x >> (CABAC_BITS + 1)]计算的结果表示的是需要左移多少位才能使(x >>(CABAC_BITS + 1))>=256,所以ff_h264_norm_shift[x >> (CABAC_BITS + 1)]表示的就是高9位中比特“0”的个数),则9 -ff_h264_norm_shift[x >> (CABAC_BITS + 1)]表示的就是为了使c->low达到9比特,还需要多少比特。第7和8行是假设c->low高9位正常时,读入接下来的两字节所需要的操作;假设c->low当前高9位正常,后17位有一个结束符比特“1”和十六个比特“0”组成,即c->low当前的状态为:xxxxxxxxx 1 0000000000000000,此时如果要读入两字节,那么新的c->low将变成xxxxxxxxx  xxxxxxxx xxxxxxxx 1,由旧c->low变成新的c->low所需要的操作就是减去第17位的比特“1”,并在最低位加上作为结束符的比特“1”,也就是-(1<<CABAC_BITS)+1=- CABAC_MASK,并将读入的两个字节左移1位(两个字节单独读入时也就是将第一个字节左移九位,第二个字节左移一位)。第7行和8行是建立在假设c->low的高9位正常的情况下的结果,可实际情况下c->low的低7 - ff_h264_norm_shift[x >> (CABAC_BITS - 1)]=7-ff_h264_norm_shift[31]=3(或者9- ff_h264_norm_shift[x >> (CABAC_BITS + 1)]=3)比特是缺失了,需要填补上,所以第9行c->low += x<<i,通过移位和相加将c->low中的有效位和读入的两字节链接起来,并在添加了结束符比特“1”。

       refill与refill2函数原理一致,在算术解码的bypass模式下每次解码只消耗一个比特,而不会像decision模式一次最多可以消耗6个比特,所以在bypass模式下将refill2简化为refill函数。

1.3.  常规算术解码

  get_cabac_inline(CABACContext *c, uint8_t * const state)
  {
      int s = *state;
      int RangeLPS= ff_h264_lps_range[2*(c->range&0xC0) + s];
      int bit, lps_mask;
      c->range -= RangeLPS;
      lps_mask= ((c->range<<(CABAC_BITS+1)) - c->low)>>31;
      c->low -= (c->range<<(CABAC_BITS+1)) & lps_mask;
      c->range += (RangeLPS - c->range) & lps_mask;
      s^=lps_mask;
      *state= (ff_h264_mlps_state+128)[s];
      bit= s&1;
      lps_mask= ff_h264_norm_shift[c->range];
      c->range<<= lps_mask;
      c->low  <<= lps_mask;
      if(!(c->low & CABAC_MASK))
          refill2(c);
      return bit;
  }

第10、11和12行主要根据lps_mask的值计算新的状态和本次常规算术解码解出来的比特值;第10行中输入概率状态s始终为正(从概率状态初始化过程的结果可以知道),那么当lps_mask为0时s的结果保持不变,当lps_mask为0xffffffff时,执行s^=lps_mask后s将变为比其相反数小1的数(比如由2变成-3等等,原因已经在概率状态初始化的部分解释过);第11行中表ff_h264_mlps_state中前128个元素存储了标准中的transIdxLPS表,后128个元素transIdxMPS表,由于标准中是64种状态,而在FFmpeg中状态s已经被扩大为128种了(对应valMPS=0和valMPS=1各64种),ff_h264_mlps_state+128首先指向第129个元素,也就是ff_h264_mlps_state表中的元素“2”,2的二进制为“10”,也就是说下一个状态为1,valMPS为0,经过第10行的计算后,如果lps_mask为0xffffffff,状态s将变成了负值,所以(ff_h264_mlps_state+128)[s]将指向前128个元素中的某一个,否则将指向后128个元素中的某一个;需要注意的是:ff_h264_mlps_state表中的第127和128个元素分别为0和1与ff_h264_mlps_state表中前126个状态的排布规律由大到小不符合,是由于在LPS这种情况下,如果状态为0,将会互换MPS和LPS的值(如果在LPS分支中,当概率状态为0是,如果MPS=0,则输出为1且设置CABAC状态种的MPS=1,如果MPS=1,则输出为0且设置CABAC状态种的MPS=0,其他情况下输出结果与MPS保持一致且CABAC状态种的MPS值保持不变),如果当前状态s为0,二进制表示为00,经过第10行的计算后s=-1,经过第11行的计算将会得到*state=1,二进制表示为01,可以看出最低位已经有0变成了1,说明MPS的值已经发生了交换,如果当前状态s为1,二进制表示为01,经过第10行的计算后s=-2,经过第11行的查表将会得到*state=0,二进制表示为00,同样也说明MPS的值和LPS的值已经发生了交换;第12行bit=s&1获取本次算术解码将要输出的二进制比特,该行也可以写成bit=*state&1,当lps_mask为0是,s&1的结果就是概率状态中存储的MPS,当lps_mask=0xffffffff时,假设s没有发生变化,设a=s^lps_mask,则a=-s-1,则a&1=!(s&1),也就是说a&1的结果是概率状态种存储的MPS的相反数。第4行中的ff_h264_lps_range表格与标准中rangeTabLPS表格排列方式不同。表格ff_h264_lps_range中的元素按列排列(标准按行排列),如下图所示,且每个元素重复两次。根据前面对FFmpeg中概率状态初始化的分析可知,第3行代码中的局部变量s和参数state表示的是( pStateIdx << 1) + valMPS,一个用来表示valMPS=0的RangeLPS,另一个用来表示valMPS=1是的RangeLPS,所以需要对每个RangeLPS重复两次。如下图所示(由于空间原因只显示了前两列元素的排布),由于ff_h264_lps_range是无符号的8比特数,所以图中的负数都会自动转换为相对应的正数,也就是说-128表示的是+128,-80表示的是+176。在标准算法中qCodIRangeIdx=( codIRange >> 6 ) & 0x03,而在FFmpeg中 c->range&0xC0相当于将qCodIRangeIdx扩大了64倍,2*(c->range&0xC0)相当于将qCodIRangeIdx扩大128倍,之所以扩大128倍,一个是因为FFmpeg中的rangeTabLPS表按列存储(每列64个),另一个是因为每一个RangeLPS要被重复两次,所以需要扩大128倍;2*(c->range&0xC0) + s就可以得到c->range状态s在ff_h264_lps_range表中对应的RangeLPS。第7行lps_mask得到的就是c->range和c->low的大小关系的表示,如果c->range>=c->low,则lps_mask=0(对应标准算法流程中MPS分支),否则lps_mask=0xffffffff(对应标准算法流程中LPS分支),第8和第9行则根据lps_mask的值,计算新的c->low和c->range值。第13、14和15行进行重归一化操作,左移的位数与c->range的大小有关,左移的位数要保证c->range>=256。第16和17行,根据c->low值的低16位是否是全0,决定是否需要进行字节填充,前面已有详细介绍。

 

标准rangeTabLPS表格

    -128,    -128,    -128,    -128,    -128,    -128,    123,     123,

    116,     116,     111,     111,     105,     105,     100,     100,

    95,      95,      90,      90,      85,      85,      81,      81,

    77,      77,      73,      73,      69,      69,      66,      66,

    62,      62,      59,      59,      56,      56,      53,      53,

    51,      51,      48,      48,      46,      46,      43,      43,

    41,      41,      39,      39,      37,      37,      35,      35,

    33,      33,      32,      32,      30,      30,      29,      29,

    27,      27,      26,      26,      24,      24,      23,      23,

    22,      22,      21,      21,      20,      20,      19,      19,

    18,      18,      17,      17,      16,      16,      15,      15,

    14,      14,      14,      14,      13,      13,      12,      12,

    12,      12,      11,      11,      11,      11,      10,      10,

    10,      10,      9,       9,       9,       9,       8,       8,

    8,       8,       7,       7,       7,       7,       7,       7,

    6,       6,       6,       6,       6,       6,       2,       2,

    -80,     -80,     -89,     -89,     -98,     -98,     -106,    -106,

    -114,    -114,    -121,    -121,    -128,    -128,    122,     122,

    116,     116,     110,     110,     104,     104,     99,      99,

    94,      94,      89,      89,      85,      85,      80,      80,

    76,      76,      72,      72,      69,      69,      65,      65,

    62,      62,      59,      59,      56,      56,      53,      53,

    50,      50,      48,      48,      45,      45,      43,      43,

    41,      41,      39,      39,      37,      37,      35,      35,

    33,      33,      31,      31,      30,      30,      28,      28,

    27,      27,      26,      26,      24,      24,      23,      23,

    22,      22,      21,      21,      20,      20,      19,      19,

    18,      18,      17,      17,      16,      16,      15,      15,

    14,      14,      14,      14,      13,      13,      12,      12,

    12,      12,      11,      11,      11,      11,      10,      10,

    9,       9,       9,       9,       9,       9,       8,       8,

    8,       8,       7,       7,       7,       7,       2,       2,

FFmpeg中对rangeTabLPS表格存储顺序

    127,     126,     77,      76,      77,      76,      75,      74,

    75,      74,      75,      74,      73,      72,      73,      72,

    73,      72,      71,      70,      71,      70,      71,      70,

    69,      68,      69,      68,      67,      66,      67,      66,

    67,      66,      65,      64,      65,      64,      63,      62,

    61,      60,      61,      60,      61,      60,      59,      58,

    59,      58,      57,      56,      55,      54,      55,      54,

    53,      52,      53,      52,      51,      50,      49,      48,

    49,      48,      47,      46,      45,      44,      45,      44,

    43,      42,      43,      42,      39,      38,      39,      38,

    37,      36,      37,      36,      33,      32,      33,      32,

    31,      30,      31,      30,      27,      26,      27,      26,

    25,      24,      23,      22,      23,      22,      19,      18,

    19,      18,      17,      16,      15,      14,      13,      12,

    11,      10,      9,       8,       9,       8,       5,       4,

    5,       4,       3,       2,       1,       0,       0,       1,

    2,       3,       4,       5,       6,       7,       8,       9,

    10,      11,      12,      13,      14,      15,      16,      17,

    18,      19,      20,      21,      22,      23,      24,      25,

    26,      27,      28,      29,      30,      31,      32,      33,

    34,      35,      36,      37,      38,      39,      40,      41,

    42,      43,      44,      45,      46,      47,      48,      49,

    50,      51,      52,      53,      54,      55,      56,      57,

    58,      59,      60,      61,      62,      63,      64,      65,

    66,      67,      68,      69,      70,      71,      72,      73,

    74,      75,      76,      77,      78,      79,      80,      81,

    82,      83,      84,      85,      86,      87,      88,      89,

    90,      91,      92,      93,      94,      95,      96,      97,

    98,      99,      100,     101,     102,     103,     104,     105,

    106,     107,     108,     109,     110,     111,     112,     113,

    114,     115,     116,     117,     118,     119,     120,     121,

    122,     123,     124,     125,     124,     125,     126,     127,

                     FFmpeg中ff_h264_mlps_state表

1.4.  旁路算术解码

  get_cabac_bypass(CABACContext *c)
  {
      int range;
      c->low += c->low;
      if(!(c->low & CABAC_MASK))
          refill(c);
      range= c->range<<(CABAC_BITS+1);
      if(c->low < range)
      {
          return 0;
      }
      else
      {
          c->low -= range;
          return 1;
      }
  }

  旁路算术编码与普通算术编码的不同之处在于:编码时low使用的是10比特,而普通算术编码模式下只使用了9比特。所以,在旁路算术解码时low也是使用10比特,因此c->low的第27~18比特之间的10比特作为当前有效的比特。

1.5.  终止符算术解码

  get_cabac_terminate(CABACContext *c)
  {
      c->range -= 2;
      if(c->low < c->range<<(CABAC_BITS+1))
      {
          renorm_cabac_decoder_once(c);
          return 0;
      }
      else
      {
          return c->bytestream - c->bytestream_start;
      }
  }
   renorm_cabac_decoder_once(CABACContext *c)
   {
       int shift= (uint32_t)(c->range - 0x100)>>31;
       c->range<<= shift;
       c->low  <<= shift;
       if(!(c->low & CABAC_MASK))
           refill(c);
   }

相比于常规CABAC解码,终止符算术解码就相对比较简单了,和标准的算法基本如出一辙,不同的地方主要有两个:第一个是get_cabac_terminate函数的第11行,标准中直接返回1,FFmpeg中返回两个指针的差值;第二个地方主要是renorm_cabac_decoder_once函数,在标准中调用的是重归一化过程(标准里重归一化过程使用while循环,而在实现时使用的是查表操作),而FFmpeg中没有使用查表操作,第3行shift= (uint32_t)(c->range -0x100)>>31计算c->range和0x100的大小,当c->range>=0x100是shift为0,否则为1,由于第5行可能会对low进行左移操作(当shift为1时左移一位),所以第6和7行需要对其进行refill操作。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值