首页 > 代码库 > H.264学习笔记5——熵编码之CAVLC
H.264学习笔记5——熵编码之CAVLC
H.264中,4x4的像素块经过变换和量化之后,低频信号集中在左上角,大量高频信号集中在右下角。左边的低频信号相对数值较大,而右下角的大量高频信号都被量化成0、1和-1;变换量化后的残差信息有一定的统计特性和规律。
CAVLC(Context-based Adaptive Variable-Length Code):基于上下文的可变长度编码,是H.264中进行4x4像素块进行熵编码的方法,基本(baseline)档次中只能使用CAVLC,只有主要档次和扩展档次才能使用CABAC(见笔记:熵编码之CABAC)。
一、对于经过Zigzag扫描(Z扫描)后的4x4像素残差,编码过程包括:
A、非零系数数目(TotalCoeffs)和拖尾系数数目(TrailingOnes)的编码:
拖尾系数:就是指Z扫描后,末尾高频信号中出现连续1或-1的个数(中间可以隔任意多个0),拖尾系数最多有5个。当连续1或-1的个数超过3个,只有最后3个1或-1是拖尾系数,其他的当作普通的非零系数。
TotalCoeffs和TrailingOnes通过查表方式进行编码,H.264针对TotalCoeffs和TrailingOnes,提供了4张变长表和1张定长表(部分见附表9-5)。编码表的选择是由NC确定的,NC的值是由上下文信息确定的。对于色度直流信号,NC=-1;对于其他的NC,根据当前块左边4x4块和上面4x4块的非零系数的个数A和B决定。如表一:其中X表示该块与当前块属于同一slice且可用。根据NC选择编码表的策略如表二:
A(左边块) | B(上面块) | NC |
X | X | (A+B)/2 |
X | — | A |
— | X | B |
— | — | 0 |
NC | 编码表编号 |
0,1 | 变长表1 |
2,3 | 变长表2 |
4,5,6,7 | 变长表3 |
>=8 | 定长表 |
-1 | 变长表4 |
通过NC确定所选择的表之后,将编码的二进制输出。
B、每一个拖尾系数的符号正负性编码(按照Z扫面结果的逆序编码):
按照逆序对每一个拖尾系数的符号进行编码,用0表示1(正)、1表示-1(负),将编码结果输出
C、除拖尾系数外的每一个非零系数幅值(Level,包含正负号信息)编码(按照Z扫描结果的逆序编码):
拖尾系数幅值的编码包括前缀Level_prefix和后缀Level_suffix。另外编码过程中suffixLength基于上下文信息,根据Level_suffix和Level实时更新。具体过程如下:
1、设置初始SuffixLength:
当TotalCoeffs>10且TrailingOnes<=1时,SuffixLength设为1;否则设为0;
2、将有符号的Level转换成无符号的LevelCode:
若Level > 0:LevelCode = (Level << 1)-2;
若Level < 0:LevelCode = -(Level << 1) - 1;
这样解码时,就可以根据LevelCode的奇偶性判断Level的正负性,从在根据LevelCode解码出有符号的Level。
3、计算Level_prefix 和 Level_suffix:
Level_prefix = LevelCode / (1 << SuffixLength);
Level_suffix = LevelCode% (1 << SuffixLength);
4、编码Level_prefix和Level_suffix:
编码Level_prefix是通过查标准表9-6(部分见附表9-6),编码得到的是前缀码,所以解码时可以即使、唯一译码。Level_suffix的编码就是Level_suffix的二进制无符号形式。然后将Level_prefix和Level_suffix的编码结果依次输出,但当SuffixLength=0时,没有Level_suffix,不需要输出。
5、更新SuffixLength的值,回到步骤2继续编码下一个非零系数。更新过程可以用下面代码表示:
1 if(SuffixLength == 0)2 SuffixLength++;3 else if (abs(Level) > (3 << (SuffixLength -1)) && SuffixLength < 6)4 SuffixLength++;
即:当SuffixLength为0时,SuffixLength加1;当SuffixLength达到6之后不再更新SuffixLength;当SuffixLength在1和6之间,如果当前已编码的非零系数的绝对值(abs(Level))大于给定的阈值S,那么SuffixLength增1,其中阈值S的大小为:S = 3 * 2 ^ (SuffixLength-1) = 3<<(SuffixLength-1)。
D、最后一个非零系数前0的数目(TotalZeros)编码:查找标准表9-7(部分见附表9-7)~9-9
E、每一个非零系数前连续0的数目(RunBefore)编码(按照Z扫面结果的逆序编码):查找标准表9-10(部分见附表9-10)
编码过程中,ZerosLeft表示当前编码非零系数左边所有0的个数,对于最后一个(逆序的最后一个)非零系数前0的个数不需要编码。
二、对于编码后的4x4残差块,解码过程包括:
三、例如:对于4x4的残差块,如下图:
0 | 3 | -1 | 0 |
0 | -1 | 1 | 0 |
1 | 0 | 0 | 0 |
0 | 0 | 0 | 0 |
经过Z扫描得到序列:0,3,0,1,-1,-1,0,1,0,0,0,0,0,0,0,0。
对该序列进行编码如下:
1、初始值设定:
TotalCoeffs = 5;TrailingOnes = 3;TotalZeros = 3;假设 NC = 1;SuffixLength = 0;最终编码输出码流为out。
2、编码TotalCoeffs和TrailingOnes:
查附表9-5得,TotalCoeffs = 5、TrailingOnes = 3和0 <= NC <2时;编码结果为:0000 100。
此时out = 0000 100。
3、编码拖尾系数符号:
拖尾系数为1,-1,-1(扫描逆序),对应的符号编码为0,1,1。所以此时out = 0000 1000 11。
4、编码每个非拖尾非零系数的幅值Level:
需要编码的非零系数有1,3(Z扫描逆序),初始i=2,过程如下:
Level[i--] = 1; 得到LevelCode = 0,Level_prefix = 0,没有Level_suffix(因为SuffixLength=0),查表9-6得编码结果为1。此时out = 0000 1000 111。
然后更新SuffixLength,SuffixLength++得SuffixLength=1;
Level[i--] = 3;得到LevelCode=4,Level_prefix = 2,Level_suffix = 0,查表9-6得Level_prefix编码结果为001,然后Level_suffix的二进制表示为0。
out = 0000 1000 1110 010。此时i=0,此步骤编码结束。
5、编码TotalZeros:查表9-7得编码结果为编码结果为111,此时out = 0000 1000 1110 0101 11。
6、编码每一个非零系数前的连续o的数目:有四个非零系数前连续0的个数要编码,初始i=5。查表9-10的编码过程如下:
ZerosLeft = 3, RunBefore = 1,Level[i--]=1,编码结果为10;
ZerosLeft = 2, RunBefore = 0,Level[i--]=-1,编码结果为1;
ZerosLeft = 2, RunBefore = 0,Level[i--]=-1,编码结果为1;
ZerosLeft = 2, RunBefore = 1,Level[i--]=1,编码结果为01;
ZerosLeft = 1, RunBefore = 1,Level[i--]=3,此时对应第一个非零系数,不需要编码;
最后输出结果为 out = 0000 1000 1110 0101 1110 1101。
相应解码如下:
四、附表:
H.264学习笔记5——熵编码之CAVLC