导航

结合JM学习CAVLC编码

Posted on 2011-03-23 14:52  xrx  阅读(893)  评论(0)    收藏  举报

CAVLC全称Context-Adaptive Variable-Length Coding,中文译为“基于上下文自适应的可变长编码”。

编码流程为:image

一、初始化

图中,初始化包含了

  1. 将数据按 zig-zag 序列重新排序;
  2. 计算非零系数的数目(TotalCoeffs);
  3. 计算拖尾系数的数目(TrailingOnes);
  4. 计算最后一个非零系数前零的数目(TotalZeros)。

拖尾系数是指序列中±1的数目,最多3个。如果超过3个,则最后3个为拖尾系数,其余的当做普通的非零系数处理。

例1. 设有一个4×4块数据

0 3 -1 0
0 -1 1 0
1 0 0 0
0 0 0 0

将其按zig-zag排序后为:

0,3,0,1,-1,-1,0,1,0,0,0,0,0,0,0,0

可计算出:TotalCoeffs = 5,TrailingOnes = 3,TotalZeros = 3。

二、计算NC

NC (Number Current,当前块值)。对于色度的直流系数,NC = -1。其余的情况则根据当前块左边4×4块的非零系数数目NA和当前块上方4×4块的非零系数数目NB求得。当前块的值为:NANB中与其同属一个片且可用的块的值的平均值,若没有同属一个片且可用的块则为0。

根据NC的值查表获取编码值,表1存放在JM中文件vlc.c中函数 int writeSyntaxElement_NumCoeffTrailingOnesChromaDC(VideoParameters *p_Vid, SyntaxElement *se, DataPartition *dp) 内。对应的是色度的直流系数。

int writeSyntaxElement_NumCoeffTrailingOnesChromaDC(VideoParameters *p_Vid, SyntaxElement *se, DataPartition *dp)
{
  static const byte lentab[3][4][17] =
  {
    //YUV420
   {{ 2, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 1, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 0, 3, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 0, 0, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
    //YUV422
   {{ 1, 7, 7, 9, 9,10,11,12,13, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 2, 7, 7, 9,10,11,12,12, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 0, 3, 7, 7, 9,10,11,12, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 0, 0, 5, 6, 7, 7,10,11, 0, 0, 0, 0, 0, 0, 0, 0}},
    //YUV444
   {{ 1, 6, 8, 9,10,11,13,13,13,14,14,15,15,16,16,16,16},
    { 0, 2, 6, 8, 9,10,11,13,13,14,14,15,15,15,16,16,16},
    { 0, 0, 3, 7, 8, 9,10,11,13,13,14,14,15,15,16,16,16},
    { 0, 0, 0, 5, 6, 7, 8, 9,10,11,13,14,14,15,15,16,16}}
  };

  static const byte codtab[3][4][17] =
  {
    //YUV420
   {{ 1, 7, 4, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 1, 6, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 0, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
    //YUV422
   {{ 1,15,14, 7, 6, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 1,13,12, 5, 6, 6, 6, 5, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 0, 1,11,10, 4, 5, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0},
    { 0, 0, 0, 1, 1, 9, 8, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0}},
    //YUV444
   {{ 1, 5, 7, 7, 7, 7,15,11, 8,15,11,15,11,15,11, 7, 4},
    { 0, 1, 4, 6, 6, 6, 6,14,10,14,10,14,10, 1,14,10, 6},
    { 0, 0, 1, 5, 5, 5, 5, 5,13, 9,13, 9,13, 9,13, 9, 5},
    { 0, 0, 0, 3, 3, 4, 4, 4, 4, 4,12,12, 8,12, 8,12, 8}}

  };  
  int yuv = p_Vid->yuv_format - 1;

  // se->value1 : numcoeff
  // se->value2 : numtrailingones
  se->len = lentab[yuv][se->value2][se->value1];
  se->inf = codtab[yuv][se->value2][se->value1];

  ....

}

NC ≠ –1时查表2,表2对应函数如下:

int writeSyntaxElement_NumCoeffTrailingOnes(SyntaxElement *se, DataPartition *dp)
{
  static const byte lentab[3][4][17] =
  {
    {   // 0702
      { 1, 6, 8, 9,10,11,13,13,13,14,14,15,15,16,16,16,16},
      { 0, 2, 6, 8, 9,10,11,13,13,14,14,15,15,15,16,16,16},
      { 0, 0, 3, 7, 8, 9,10,11,13,13,14,14,15,15,16,16,16},
      { 0, 0, 0, 5, 6, 7, 8, 9,10,11,13,14,14,15,15,16,16},
    },
    {
      { 2, 6, 6, 7, 8, 8, 9,11,11,12,12,12,13,13,13,14,14},
      { 0, 2, 5, 6, 6, 7, 8, 9,11,11,12,12,13,13,14,14,14},
      { 0, 0, 3, 6, 6, 7, 8, 9,11,11,12,12,13,13,13,14,14},
      { 0, 0, 0, 4, 4, 5, 6, 6, 7, 9,11,11,12,13,13,13,14},
    },
    {
      { 4, 6, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9, 9,10,10,10,10},
      { 0, 4, 5, 5, 5, 5, 6, 6, 7, 8, 8, 9, 9, 9,10,10,10},
      { 0, 0, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,10},
      { 0, 0, 0, 4, 4, 4, 4, 4, 5, 6, 7, 8, 8, 9,10,10,10},
    },

  };

  static const byte codtab[3][4][17] =
  {
    {
      { 1, 5, 7, 7, 7, 7,15,11, 8,15,11,15,11,15,11, 7,4},
      { 0, 1, 4, 6, 6, 6, 6,14,10,14,10,14,10, 1,14,10,6},
      { 0, 0, 1, 5, 5, 5, 5, 5,13, 9,13, 9,13, 9,13, 9,5},
      { 0, 0, 0, 3, 3, 4, 4, 4, 4, 4,12,12, 8,12, 8,12,8},
    },
    {
      { 3,11, 7, 7, 7, 4, 7,15,11,15,11, 8,15,11, 7, 9,7},
      { 0, 2, 7,10, 6, 6, 6, 6,14,10,14,10,14,10,11, 8,6},
      { 0, 0, 3, 9, 5, 5, 5, 5,13, 9,13, 9,13, 9, 6,10,5},
      { 0, 0, 0, 5, 4, 6, 8, 4, 4, 4,12, 8,12,12, 8, 1,4},
    },
    {
      {15,15,11, 8,15,11, 9, 8,15,11,15,11, 8,13, 9, 5,1},
      { 0,14,15,12,10, 8,14,10,14,14,10,14,10, 7,12, 8,4},
      { 0, 0,13,14,11, 9,13, 9,13,10,13, 9,13, 9,11, 7,3},
      { 0, 0, 0,12,11,10, 9, 8,13,12,12,12, 8,12,10, 6,2},
    },
  };
  int vlcnum = se->len;

  // se->value1 : numcoeff
  // se->value2 : numtrailingones

  if (vlcnum == 3) // nC >= 8
  {
    se->len = 6;  // 4 + 2 bit FLC
    if (se->value1 > 0)
    {
      se->inf = ((se->value1-1) << 2) | se->value2;
    }
    else
    {
      se->inf = 3;
    }
  }
  else    // nC >=0 && nC < 8
  {
    se->len = lentab[vlcnum][se->value2][se->value1];
    se->inf = codtab[vlcnum][se->value2][se->value1];
  }

    ....

}

例2. 接例1,若NC = 0时,求TotalCoeffs和TrailingOnes的编码以及每个拖尾系数的符号。

TotalCofeffs = 5,TraillingOnes = 3,NC = 0,查表2,得编码为0000100。

拖尾系数从高频到低频(逆序)分别为:+1,-1,-1。正号用0表示,负号用1表示得编码为011。

所以至此编码为 0000100 011。

三、非零系数幅值编码

只编码不包含拖尾系数的非零系数。计算方法为:

1. 将有符号的Level[i]转化为无符号的LevelCode:

if(Level[i] > 0)    // +
    LevelCode = (Level[i] << 1) - 2;
else    // -
    LevelCode = (Level[i] << 1) - 1;

2. 计算前缀与后缀:

LevelPrefix = LevelCode / (1 << SuffixLength);
LevelSuffix = LevelCode % (1 << SuffixLength)

3. 根据前缀计算bit string :

bitStringLen = LevelPrefix + 1; // length of bit string
bitStringCode = 1;    // value of bit string, constant 1

4. 更新SuffixLength,根据SuffixLength确定后缀长度,初始状态SuffixLength = 0:

if(SuffixLength == 0)
    SuffixLength++;
else if((LevelCode > (3 << SuffixLength - 1)) && (SuffixLength < 6))
    SuffixLength++;

例3. 接例2,对非零系数幅值编码。

序列中非零系数有:3,1。从高频开始,先编码1。

初始状态SuffixLength = 0,Level[0] = 1,可得LevelCode = 0,计算前缀LevelPrefix = 0,得bit string = 1。由于Level[0]为第一个数,没有后缀。

更新SuffixLength,SuffixLength = 1。同样可得3的LevelCode = 4,LevelPrefix = 2,得bit string = 001。LevelSuffix = 0,SuffixLength = 1,所以后缀为0。编码为0010。

至此编码为 0000100 011 10010。

四、最后一个非零系数前零数目编码

根据最后一个非零系数前零数目TotalZeros与非零系数数目可以查表3获得编码值,表3对应下面函数。

int writeSyntaxElement_TotalZeros(SyntaxElement *se, DataPartition *dp)
{
  static const byte lentab[TOTRUN_NUM][16] =
  {
    { 1,3,3,4,4,5,5,6,6,7,7,8,8,9,9,9},
    { 3,3,3,3,3,4,4,4,4,5,5,6,6,6,6},
    { 4,3,3,3,4,4,3,3,4,5,5,6,5,6},
    { 5,3,4,4,3,3,3,4,3,4,5,5,5},
    { 4,4,4,3,3,3,3,3,4,5,4,5},
    { 6,5,3,3,3,3,3,3,4,3,6},
    { 6,5,3,3,3,2,3,4,3,6},
    { 6,4,5,3,2,2,3,3,6},
    { 6,6,4,2,2,3,2,5},
    { 5,5,3,2,2,2,4},
    { 4,4,3,3,1,3},
    { 4,4,2,1,3},
    { 3,3,1,2},
    { 2,2,1},
    { 1,1},
  };

  static const byte codtab[TOTRUN_NUM][16] =
  {
    {1,3,2,3,2,3,2,3,2,3,2,3,2,3,2,1},
    {7,6,5,4,3,5,4,3,2,3,2,3,2,1,0},
    {5,7,6,5,4,3,4,3,2,3,2,1,1,0},
    {3,7,5,4,6,5,4,3,3,2,2,1,0},
    {5,4,3,7,6,5,4,3,2,1,1,0},
    {1,1,7,6,5,4,3,2,1,1,0},
    {1,1,5,4,3,3,2,1,1,0},
    {1,1,1,3,3,2,2,1,0},
    {1,0,1,3,2,1,1,1,},
    {1,0,1,3,2,1,1,},
    {0,1,1,2,1,3},
    {0,1,1,1,1},
    {0,1,1,1},
    {0,1,1},
    {0,1},
  };
  int vlcnum = se->len;

  // se->value1 : TotalZeros
  se->len = lentab[vlcnum][se->value1];
  se->inf = codtab[vlcnum][se->value1];

  ....

}

色度的直流系数则为表4,对应下面函数:

int writeSyntaxElement_TotalZerosChromaDC(VideoParameters *p_Vid, SyntaxElement *se, DataPartition *dp)
{
  static const byte lentab[3][TOTRUN_NUM][16] =
  {
    //YUV420
   {{ 1,2,3,3},
    { 1,2,2},
    { 1,1}},
    //YUV422
   {{ 1,3,3,4,4,4,5,5},
    { 3,2,3,3,3,3,3},
    { 3,3,2,2,3,3},
    { 3,2,2,2,3},
    { 2,2,2,2},
    { 2,2,1},
    { 1,1}},
    //YUV444
   {{ 1,3,3,4,4,5,5,6,6,7,7,8,8,9,9,9},
    { 3,3,3,3,3,4,4,4,4,5,5,6,6,6,6},
    { 4,3,3,3,4,4,3,3,4,5,5,6,5,6},
    { 5,3,4,4,3,3,3,4,3,4,5,5,5},
    { 4,4,4,3,3,3,3,3,4,5,4,5},
    { 6,5,3,3,3,3,3,3,4,3,6},
    { 6,5,3,3,3,2,3,4,3,6},
    { 6,4,5,3,2,2,3,3,6},
    { 6,6,4,2,2,3,2,5},
    { 5,5,3,2,2,2,4},
    { 4,4,3,3,1,3},
    { 4,4,2,1,3},
    { 3,3,1,2},
    { 2,2,1},
    { 1,1}}
  };

  static const byte codtab[3][TOTRUN_NUM][16] =
  {
    //YUV420
   {{ 1,1,1,0},
    { 1,1,0},
    { 1,0}},
    //YUV422
   {{ 1,2,3,2,3,1,1,0},
    { 0,1,1,4,5,6,7},
    { 0,1,1,2,6,7},
    { 6,0,1,2,7},
    { 0,1,2,3},
    { 0,1,1},
    { 0,1}},
    //YUV444
   {{1,3,2,3,2,3,2,3,2,3,2,3,2,3,2,1},
    {7,6,5,4,3,5,4,3,2,3,2,3,2,1,0},
    {5,7,6,5,4,3,4,3,2,3,2,1,1,0},
    {3,7,5,4,6,5,4,3,3,2,2,1,0},
    {5,4,3,7,6,5,4,3,2,1,1,0},
    {1,1,7,6,5,4,3,2,1,1,0},
    {1,1,5,4,3,3,2,1,1,0},
    {1,1,1,3,3,2,2,1,0},
    {1,0,1,3,2,1,1,1,},
    {1,0,1,3,2,1,1,},
    {0,1,1,2,1,3},
    {0,1,1,1,1},
    {0,1,1,1},
    {0,1,1},
    {0,1}}
  };
  int vlcnum = se->len;
  int yuv = p_Vid->yuv_format - 1;  

  // se->value1 : TotalZeros
  se->len = lentab[yuv][vlcnum][se->value1];
  se->inf = codtab[yuv][vlcnum][se->value1];

  ....

}

例4. 接例3,对最后一个非零系数前零数目编码。

TotalCoeffs = 5,TotalZeros = 3,查表3得TotalZeros的编码为:111。

至此,编码为:0000100 011 10010 111。

五、对每个非零系数前零数目编码

  1. 从高频开始,逆序编码。
  2. 最后一个非零系数前零数目不需要编码;
  3. ZerosLeft储存还没编码的零数目,RunBefore储存当前非零系数前零数目。
  4. 查表5获得,表5对应函数如下:
int writeSyntaxElement_Run(SyntaxElement *se, DataPartition *dp)
{
  static const byte lentab[TOTRUN_NUM][16] =
  {
    {1,1},
    {1,2,2},
    {2,2,2,2},
    {2,2,2,3,3},
    {2,2,3,3,3,3},
    {2,3,3,3,3,3,3},
    {3,3,3,3,3,3,3,4,5,6,7,8,9,10,11},
  };

  static const byte codtab[TOTRUN_NUM][16] =
  {
    {1,0},
    {1,1,0},
    {3,2,1,0},
    {3,2,1,1,0},
    {3,2,3,2,1,0},
    {3,0,1,3,2,5,4},
    {7,6,5,4,3,2,1,1,1,1,1,1,1,1,1},
  };
  int vlcnum = se->len;

  // se->value1 : RunBefore
  // vlcnum : ZerosLeft - 1, means {1,1} correspond to vlcnum = 0, zerosLeft = 1
  se->len = lentab[vlcnum][se->value1];
  se->inf = codtab[vlcnum][se->value1];

  ....

}

例5. 接例4,序列为:0,3,0,1,-1,-1,0,1,0,0,0,0,0,0,0,0。

根据表5的信息可得:

Source ZeroLeft RunBefore Code
1 3 1 10
-1 2 0 1
-1 2 0 1
1 2 1 01
3 1 1 N/A

至此,编码为:0000100 011 10010 111 101101。