H.264之CAVLC分析 .

1. CAVLC用于对残差或DCT变换后的系数块进行编码。
经预测、变换、量化后,4x4系数块的大部分数据可能是零,因此,CAVLC使用了游程编码。
经“之字形”扫描后的系数,其高频的非零值很可能是+/-1,CAVLC用T1s表示拖尾1。
相邻块的非零系数的个数是有相关性的。CAVLC对非零系数的个数编码用到了多个查找表,查找表的选择取决于相邻块的非零系数的个数。
非零系数的绝对值很可能是从大到小。

2. 编码过程
(1) 对非零系数和拖尾1的个数编码
coeff_token包括非零系数的个数(total_coeff)和拖尾1的个数(T1s)。
total_coeff的范围0-16。
T1s的范围0-3,当拖尾1的个数超过3个时,其余的+/-1当作普通系数处理。

对Luma块的coeff_token编码,有4个查找表。查找表的选择取决于相邻块的非零系数的个数。
假定当前块的左侧块和上方块的非零系数的个数分别是nA和nB,计算参数nC如下:
若左侧块和上方块都存在,nC = (nA+nB+1) >> 1
若只存在左侧块,nC = nA
若只存在上方块,nC = nB
若左侧块和上方块都存不在,nC = 0
-------------------------------------
nC         | coeff_token使用的查找表
-------------------------------------
0,1        | VLC table 1
2,3        | VLC table 2
4,5,6,7    | VLC table 3
大于等于8  | FLC
-------------------------------------

(2) 对T1s的符号编码
从高频开始,对T1s的符号编码,用bit 0表示+,用bit 1表示-。

(3) 对其余的非零系数编码
从高频开始,对其余的非零系数(level)进行编码,得到level VLC。
level VLC包含level_prefix和level_suffix。
level_prefix由n-bit前导0和1-bit 1构成
-------------------------------------
level_prefix | codeword
-------------------------------------
0            | 1
1            | 01
2            | 001
3            | 0001
4            | 0000 1
5            | 0000 01
6            | 0000 001
7            | 0000 0001
8            | 0000 0000 1
9            | 0000 0000 01
10           | 0000 0000 001
11           | 0000 0000 0001
12           | 0000 0000 0000 1
13           | 0000 0000 0000 01
14           | 0000 0000 0000 001
15           | 0000 0000 0000 0001
-------------------------------------

level_suffix的长度是suffixLength bits
- suffixLength初始化为0,当非零系数多于10个并且拖尾1少于3个,suffixLength初始化为1
- 当系数的绝对值超过阈值,suffixLength加1,suffixLength的最大值为6
--------------------------------------
当前suffixLength | 阈值
--------------------------------------
0                | 0
1                | 3  (3<<(1-1))
2                | 6  (3<<(2-1))
3                | 12 (3<<(3-1))
4                | 24 (3<<(4-1))
5                | 48 (3<<(5-1))
6                | 已是最大值
--------------------------------------

4) 对最后一个非零系数前的零的个数编码
对最后一个非零系数前的零的个数编码,得到TotalZeros。

5) 对零值用游程编码
从高频开始,对每一个非零系数之前的连续bit 0的个数编码,得到run_before。

3. 解码过程
1) coeff_token解码
使用VLC表解码coeff_token,根据变量nC的值,从5个VLC表选择相应的VLC表。其中,当chromaDCLevel的coeff_token解码时,nC取为-1,否则nC的计算过程如上所示。

2) level解码
初始化suffixLength
- total_coeff大于10并且T1s小于3,suffixLength = 1
- 否则,suffixLength = 0

i循环total_coeff-T1s次
- 根据level_prefix VLC表(表9-6)解码level_prefix
- 计算levelSuffixSize
  - 当level_prefix == 14并且suffixLength == 0时,levelSuffixSize = 4
  - 当level_prefix == 15时,levelSuffixSize = 12
  - 否则,levelSuffixSize = suffixLength
- 解码level_suffix
  - 当levelSuffixSize > 0时, 之后的levelSuffixSize-bit表示无符号的level_suffix
  - 当levelSuffixSize == 0时,隐含level_suffix为0
- 变量levelCode = (level_prefix << suffixLength) + level_suffix
- 当level_prefix == 15并且suffixLength == 0时,levelCode += 15
- 当i == T1s并且T1s < 3时,levelCode += 2
- 计算level[i]
  - 当levelCode是偶数,level[i] = (levelCode+2) >> 1
  - 当levelCode是奇数,level[i] = (-levelCode-1) >> 1
- 计算suffixLength
  - 若suffixLength == 0,suffixLength = 1
  - 若abs(level[i]) > (3<<(suffixLength)-1),suffixLength += 1,最大值为6

3) run解码
当total_coeff等于非零系数的最大数目maxNumCoeff,变量zeros_left取值0,否则根据相应的total_zeors VLC解码(表9-7, 9-8, 9-9)得到total_zeors,即zeros_left。

i循环total_coeff-1次
- 计算run[i]
  - 当zeros_left大于0时,基于表9-10解码得run_before,run[i] = run_before
  - 否则,run[i] = 0

6) 联合level和run信息,计算coeffLevel
- 变量coeffNum初始化为-1
- i初始化为total_coeff-1
- coeffNum += run[i]+1
- coeffLevel[coeffNum] = level[i]
- i-=1

例子1
4x4系数块经扫描后是:-2,4,3,-3,0,0,-1,...(全是0)
total_coeff = 5
T1s = 1
TotalZeros = 2
编码过程
------------------------------------------------------------------------------
Element       | Value                                      | code
------------------------------------------------------------------------------
coeff_token   | total_coeff = 5, T1s = 1 (use VLC table 1) | 0000 0001 10
T1符号        | -                                          | 1
Level(3)      | -3, suffixLength = 0                       | 0001  (Table 9-6)
Level(2)      | 3, suffixLength = 1                        | 0010  (Table 9-6)
Level(1)      | 4, suffixLength = 1                        | 00010 (Table 9-6)
Level(0)      | -2, suffixLength = 2                       | 111   (Table 9-6)
TotalZeros    | 2                                          | 0011  (Table 9-7)
run_before(4) | ZerosLeft = 2; run_before = 2              | 00    (Table 9-10)
run_before    | 0                                          | No code required
------------------------------------------------------------------------------
编码输出结果:0000000110 1 0001 0010 00010 111 0011 00

解码过程
------------------------------------------------------------------------------
code         | Element      | Value                    | Output
------------------------------------------------------------------------------
0000 0001 10 | coeff_token  | total_coeff = 5, T1s = 1 | Empty
1            | T1符号       | -                        | -1
0001         | Level        | -3                       | -3,-1
0010         | Level        | 3                        | 3,-3,-1
00010        | Level        | 4                        | 4,3,-3,-1
111          | Level        | -2                       | -2,4,3,-3,-1
0011         | TotalZeros   | 2                        | -2,4,3,-3,-1
00           | runbefore(4) | 2                        | -2,4,3,-3,0,0,-1
------------------------------------------------------------------------------
解码输出结果:-2,4,3,-3,0,0,-1,...(全是0)

例子2
4x4系数块经扫描后是:0,3,0,1,-1,-1,0,1,0...(全是0)
total_coeff = 5
T1s = 3
TotalZeros = 3
编码过程
------------------------------------------------------------------------------
Element       | Value                                      | code
------------------------------------------------------------------------------
coeff_token   | total_coeff = 5, T1s = 3 (use VLC table 1) | 0000 100
T1符号(4)     | +                                          | 0
T1符号(3)     | -                                          | 1
T1符号(2)     | -                                          | 1
Level(1)      | 1, suffixLength = 0                        | 1     (Table 9-6)
Level(0)      | 3, suffixLength = 1                        | 0010  (Table 9-6)
TotalZeros    | 3                                          | 111   (Table 9-7)
run_before(4) | ZerosLeft = 3; run_before = 1              | 10    (Table 9-10)
run_before(3) | ZerosLeft = 2; run_before = 0              | 1     (Table 9-10)
run_before(2) | ZerosLeft = 2; run_before = 0              | 1     (Table 9-10)
run_before(1) | ZerosLeft = 2; run_before = 1              | 01    (Table 9-10)
run_before(0) | ZerosLeft = 1; run_before = 1              | No code required
------------------------------------------------------------------------------
编码输出结果:0000100 0 1 1 1 0010 111 10 1 1 01

解码过程
------------------------------------------------------------------------------
code         | Element      | Value                    | Output
------------------------------------------------------------------------------
0000 100     | coeff_token  | total_coeff = 5, T1s = 3 | Empty
0            | T1符号       | +                        | 1
1            | T1符号       | -                        | -1,1
1            | T1符号       | -                        | -1,-1,1
1            | Level        | 1                        | 1,-1,-1,1
0010         | Level        | 3                        | 3,1,-1,-1,1
111          | TotalZeros   | 3                        | 3,1,-1,-1,1
10           | runbefore(4) | 1                        | 3,1,-1,-1,0,1
1            | runbefore(3) | 0                        | 3,1,-1,-1,0,1
1            | runbefore(2) | 0                        | 3,1,-1,-1,0,1
01           | runbefore(1) | 1                        | 3,0,1,-1,-1,0,1
------------------------------------------------------------------------------
由于TotalZeros等于3,还要在系数3的前面插入1个0,所以解码器的输出是:0,3,0,1,-1,-1,0,1,...(全是0)

4. FFMpeg的实现
FFMpeg针对CAVLC做了大量的优化,主要是码表的构建和查找。
1) 码表的构建
H.264标准中的Table 9-5到Table 9-10,是CAVLC的码表。针对每个CAVLC码表,FFMpeg都定义了两个数组:xxx_len和xxx_bits。

以Table 9-5中nC=-1的VLC表为例,FFMpeg定义了chroma_dc_coeff_token_len和chroma_dc_coeff_token_bits这两个数组。
static const uint8_t chroma_dc_coeff_token_len[4*5] =
{
    2, 0, 0, 0,
    6, 1, 0, 0,
    6, 6, 3, 0,
    6, 7, 7, 6,
    6, 8, 8, 7,
};
static const uint8_t chroma_dc_coeff_token_bits[4*5] =
{
    1, 0, 0, 0,
    7, 1, 0, 0,
    4, 6, 1, 0,
    3, 3, 2, 5,
    2, 3, 2, 0,
};
这两个数组设计的颇为巧妙,有以下几点:
- 数组的索引(用key表示)是total_coeff和T1s的组合,等于(total_coeff<<2 | T1s)
- 数组len的值是相应码字的长度
- 数组bits的值是相应码字的值
------------------------------------------------
codeword | len | bits | total_coeff | T1s | key
------------------------------------------------
01       | 2   | 1    | 0           | 0   | 0
000111   | 6   | 7    | 1           | 0   | 4
1        | 1   | 1    | 1           | 1   | 5
000100   | 6   | 4    | 2           | 0   | 8
000110   | 6   | 6    | 2           | 1   | 9
001      | 3   | 1    | 2           | 2   | 10
000011   | 6   | 3    | 3           | 0   | 12
0000011  | 7   | 3    | 3           | 1   | 13
0000010  | 7   | 2    | 3           | 2   | 14
000101   | 6   | 5    | 3           | 3   | 15
000010   | 6   | 2    | 4           | 0   | 16
00000011 | 8   | 3    | 4           | 1   | 17
00000010 | 8   | 2    | 4           | 2   | 18
0000000  | 7   | 0    | 4           | 3   | 19
------------------------------------------------
构建码表时只需保存len和key,由于码字的最大长度是8,需要256个索引,即chroma_dc_coeff_token_vlc包含两个大小为256的数组,分别保存len和key。len和key保存的位置与codeword的值有关。
以codeword[0]=01为例,len[0]=2,key[0]=0,从(codeword[0]<<(8-len[0]))位置开始,连续(1<<(8-len[0]))位置都保存len[0]和key[0]。
因此,在解码chroma_dc_coeff_token时,从当前位置读取8-bit数据x,以x为下标,在chroma_dc_coeff_token_vlc的两个数组中找到len和key,即可得到total_coeff和T1s。

当码字的长度超过8时,如Table 9-5中的nC=0的情况,若直接映射需要65536个索引,则存储空间占用过大,FFMpeg采用分级映射的方式来构建码表。
在build_table()函数中,第一步,若码字的长度不超过已分配的码表尺寸table_nb_bits,处理方法同上;若码字的长度大于table_nb_bits,则用码字的高table_nb_bits位的值作为索引,保存table_nb_bits-len(该值为负数),表示该码字还有多少位没映射。第二部,遍历已分配的码表,若发现需要分级映射的码字,则递归调用build_table()函数,生成新的码表完成分级映射。

尽管用直接映射和静态存储的方式效率更高,但是由于码长不同,若直接映射需要大量的存储空间,所以FFMpeg采用了折中的方式。

2) 码表的查找
对于不超过8-bit的码字,只需查找一次码表即可;对于大于8-bit的码字,需要查找多次(最长是16-bit,需查找两次)。
在get_vlc2()函数中实现了CAVLC解码:
static always_inline int get_vlc2(GetBitContext *s, VLC_TYPE (*table)[2],
                                  int bits, int max_depth)
{
    int code;

    OPEN_READER(re, s)
    UPDATE_CACHE(re, s)

    GET_VLC(code, re, s, table, bits, max_depth)

    CLOSE_READER(re, s)
    return code;
}
bits定义了需读取的比特数,通过这个值在码表中查找len和key;max_depth定义了映射的级数,若max_depth大于1,表示分级映射。

你可能感兴趣的:(H.264之CAVLC分析 .)