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,表示分级映射。