1. 基本原理
CAVLC属于熵编码。熵编码是一种无损压缩编码方法,它生成的码流可以经解码无失真地恢复出原数据。熵编码是建立在随机过程的统计特性基础上的,因此它主要为了降低数据的统计冗余。
在 H.264 的 CAVLC(基于上下文自适应的可变长编码)相比于huffman编码,它可以通过根据已编码句法元素的情况自适应调整当前编码中使用的码表,从而取得了极高的压缩比。
下面举例说明CAVLC的基于上下文特性。
下图是色度分量DC系数解析要用到的码表,其中tzVlcIndex指的是非零DC系数个数(色度DC系数最大值为4),该值在前面已解析过,当前可以知道具体值。total_zeros指最后一个非零系数前系数为0的个数。
假设tzVlcIndex=3,total_zeros当前可能出现的值只有两种情况:0或1,因此只需要1bit就能表示。而如果我们还是选择tzVlcIndex=1的码表,则需要2bit才能表示这两种情况。
CAVLC 用于4x4块亮度和色度残差数据的编码。残差经过变换量化后的数据表现出如下特性:
- 4*4 块数据经过预测、变换、量化后,非零系数主要集中在低频部分,而高频系数大部分是零;
- 量化后的数据经过 zig-zag 扫描,DC 系数附近的非零系数值较大,而高频位置上的非零系数值大部分是+1 和-1;
- 非零系数幅值变化有一定的规律性和相关性;
- 相邻的 4*4 块的非零系数的数目是相关的。
CAVLC 充分利用残差经过整数变换、量化后数据的特性进行压缩,进一步减少数据中的冗余信息,为 H.264 卓越的编码效率奠定了基础。
2. CAVLC编码过程
2.1 编码过程中的语法元素
- 非零系数的个数(TotalCoeffs):取值范围为[0, 16],即当前系数矩阵中包含多少个非0值的元素;
- 拖尾系数的个数(TrailingOnes):取值范围为[0, 3],表示最高频的几个值为±1的系数的个数。拖尾系数最多不超过3个,若超出则只有最后3个被认为是拖尾系数,其他被作为普通的非0系数;
- 拖尾系数的符号:以1 bit表示,0表示+,1表示-;
- 当前块值(numberCurrent):用于选择TotalCoeffs/TrailingOnes编码码表(Table 9-5),由上方和左侧的相邻块的非零系数个数计算得到。设当前块值为nC,上方相邻块非零系数个数为nA,左侧相邻块非零系数个数为nB,计算公式为nC = round((nA + nB)/2);对于色度的直流系数,nC = -1;
- 普通非0系数的幅值(level):幅值的编码分为prefix和suffix两个部分进行编码。编码过程按照反序编码,即从最高频率非零系数开始。
- 最后一个非0系数之前的0的个数(TotalZeros);
- 每个非0系数之前0的个数(RunBefore):按照反序编码,即从最高频非零系数开始;对于最后一个非零系数(即最低频的非零系数)前的0的个数,以及没有剩余的0系数需要编码时,不需要再继续进行编码。
2.2 编码过程
2.2.1 编码非零系数的数目(TotalCoeffs)以及拖尾系数的数目(TrailingOnes)
对非零系数个数和拖尾系数个数的编码是通过查表的方式,共有 4 个变长表格和 1 个定长表格可供选择(Table 9-5)。其中的定长表格(nC>=8时选择)的码字是 6个比特长,高 4 位表示非零系数的个数(TotalCoeffs),最低两位表示拖尾系数的个数(TrailingOnes)。
表格的选择由变量 nC值决定,在求变量 NC 值的过程中,体现了基于上下文的思想。除了色度的直流系数外,其它系数类型的 NC 值是根据当前块左边 44 块的非零系数数目(NA)和当前块上面 44 块的非零系数数目(NB)求得的。当输入的系数是色度的直流系数时,NC= -1。
2.2.2 编码每个拖尾系数的符号
对于每个拖尾系数(±1)只需要指明其符号,其符号用一个比特表示(0 表示+,1 表示-)。编码的顺序是按照反向扫描的顺序,从高频数据开始。
2.2.3 编码除了拖尾系数之外的非零系数的幅值(Levels)
非零系数幅值(Levels)编码码流由两个部分组成,前缀(level_prefix)和后缀(level_suffix)。levelSuffixsSize 和 suffixLength 是编码过程中需要使用的两个变量。后缀level_suffix是长度为 LevelSuffixsSize的无符号整数。通常情况下变量 levelSuffixsSize 的值等于变量 suffixLength 的值,有两种情况例外:
- 当前缀等于 14 时,suffixLength 等于 0,levelSuffixsSize 等于 4。
- 当前缀等于 15 时,levelSuffixsSize 等于 12。
变量 suffixLength 是基于上下文模式自适应更新的,suffixLength 的更新与当前的 suffixLength的值以及已经解码好的非零系数的值(Level)有关。
Level的编码过程如下:
- 初始化suffixLength ,一般情况下,suffixLength初值为0;当TotalCoeffs>0 && TrailingOnes<=1 时,suffixLength初值为1.
- 将有符号level转换为无符号levelCode
if level >0
levelCode = (level<<1)-2
else if level <0
levelCode = -(level<<1)-1
上面的公式也就是把整数转换为偶数,负数转换为奇数。比如level=1时,levelcode=0,level=-1,levelcode=1 - 计算level_prefix,并查表Table 9-6得到对应比特流。
level_prefix = levelCode / (1<<suffixLength ) - 计算level_suffix,用二进制无符号数表示。
level_suffix= levelCode % (1<<suffixLength ) - level_prefix和 level_suffix 拼接在一起就是该level的编码bit
- 更新suffixLength,如果编码系数level大于给定阈值(如下表)则suffixLength
- 跳到步骤2编码下一个level,直到所有level都编码完成。
2.2.4 编码最后一个非零系数前零的数目(TotalZeros)
TotalZeros 指的是在最后一个非零系数前零的数目,此非零系数指的是按照正向扫描的最后一个非零系数。TotalZeros 的编码通过查表Table9-7 得到。
例如:已知一串系数 0 0 5 0 3 0 0 0 1 0 0 -1 0 0 0 0,最后一个非零系数是-1,TotalZeros的值等于 2+3+1+2=8。因为非零系数数目(TotalCoeffs)是已知,这就决定了 TotalZeros 可能的最大值。根据这一特性,CAVLC 在编排 TotalZeros 的码表时做了进一步的优化。
2.2.5 编码每个非零系数前零的个数(RunBefore)
每个非零系数前零的个数(RunBefore)是按照反序来进行编码的,从最高频的非零系数开始。
RunBefore 在以下两种情况下是不需要编码的:
- 最后一个非零系数(在低频位置上)前零的个数。
- 如果没有剩余的零需要编码(Σ[RunBefore]=TotalZeros)时,没有必要再进行 RunBefore 的编码。
在 CAVLC 中,对每个非零系数前零的个数的编码是依赖于 ZerosLeft 的值(table 9-10),ZerosLeft 表示当前非零系数左边的所有零的个数,ZerosLeft 的初始值等于 TotalZeros,在每个非零系数的 RunBefore值编码后进行更新。这种基于上下文的编码方法,有助于进一步压缩编码的比特数目。当zerosleft小时,可以使用较少的bit表示RunBefore。
例如:如果当前 ZerosLeft等于 1,就是只剩下一个零没有编码,下一个非零系数前零的数目只可能是 0 或 1, 编码只需要一个比特。
3. CAVLC编解码实例分析
4x4块数据为
经过zigzag扫描,数据重排后为:
12,-1,0,0,0,-2,1,0,2,0,-1,-1,0,0,0,1
编码过程:
- 初始值设定:
非零系数个数TotalCoeff=8,拖尾系数个数TrailingOnes=3,nC=0
TotalZeros=8,suffixLength=0 - 编码coeff_token,查表9-5,可知编码为 0000000100
- 编码拖尾系数符号,三个拖尾系数符号逆序依次为±-,所以编码为011
- 按逆序编码非零系数Level
1)编码level(2): suffixLength=0, levelCode=2,level_prefix=2,level_suffix=0.level_prefix编码通过查表9-6,可知2对应的比特流为001,level_suffix=0,没有后缀,所以最终编码为001
2)编码level(-1): suffixLength=1, levelCode=1,level_prefix=0,level_suffix=1,编码为11
3)编码level(-2): suffixLength=1, levelCode=3,level_prefix=1,level_suffix=1,编码为011
4)编码level(-1): suffixLength=1, levelCode=1,level_prefix=0,level_suffix=1,编码为11
5)编码level(12): suffixLength=1, levelCode=22,level_prefix=11,level_suffix=0,编码为0000000000010 - 编码totalZeros,tzVlcIndex=TotalCoeff=8,totalZeros=8,查表9-8可知编码为000000
- 逆序编码runBefore。
1)zeroleft=8,runbefore=3,查表9-10,可知编码为100
2)zeroleft=5,runbefore=0,编码为11
3)zeroleft=5,runbefore=1,编码为10
4)zeroleft=4,runbefore=1,编码为10
5)zeroleft=3,runbefore=0,编码为11
6)zeroleft=3,runbefore=3,编码为001
把所有编码bit按顺序连接就是该4x4块残差数据最终编码的码流
4. 代码实现
#!/usr/bin/python
# -*- coding:utf8 -*-
# nc >= 0 and nc < 2 , coeff_token_map
coeffTokenMap = [
["1", "000101", "01", "00000111", "000100", "001", "000000111", "00000110", "0000101", "00011", "0000000111",
"000110", "00000101", "000011", "00000000111", "0000000110", "000000101", "0000100", "0000000001111",
"00000000110", "0000000101", "00000100", "0000000001011", "0000000001110", "00000000101", "000000100",
"0000000001000", "0000000001010", "0000000001101", "0000000100", "00000000001111", "00000000001110",
"0000000001001", "00000000100", "00000000001011", "00000000001010", "00000000001101", "0000000001100",
"000000000001111", "000000000001110", "00000000001001", "00000000001100", "000000000001011", "000000000001010",
"000000000001101", "00000000001000", "0000000000001111", "000000000000001", "000000000001001", "000000000001100",
"0000000000001011", "0000000000001110", "0000000000001101", "000000000001000", "0000000000000111",
"0000000000001010", "0000000000001001", "0000000000001100", "0000000000000100", "0000000000000110",
"0000000000000101", "0000000000001000"]
]
# table 9-7 & 9-8
# totalZerosMap[total_zeros][totalCoeffs]
totalZerosMap = [
["", "1", "111", "0101", "00011", "0101", "000001", "000001", "000001", "000001", "00001", "0000", "0000", "000", "00", "0" ],
[ "", "011", "110", "111", "111", "0100", "00001", "00001", "0001", "000000", "00000", "0001", "0001", "001", "01", "1" ],
[ "", "010", "101", "110", "0101", "0011", "111", "101", "00001", "0001", "001", "001", "01", "1", "1" ],
[ "", "0011", "100", "101", "0100", "111", "110", "100", "011", "11", "11", "010", "1", "01" ],
[ "", "0010", "011", "0100", "110", "110", "101", "101", "11", "10", "10", "1", "001" ],
[ "", "00011", "0101", "0011", "101", "101", "100", "11", "10", "001", "01", "011" ],
[ "", "00010", "0100", "100", "100", "100", "011", "010", "010", "01", "0001" ],
[ "", "000011", "0011", "011", "0011", "011", "010", "0001", "001", "0000", "1" ],
[ "", "000010", "0010", "0010", "011", "0010", "0001", "001", "000000" ],
[ "", "0000011", "00011", "00011", "0010", "00001", "001", "000000" ],
[ "", "0000010", "00010", "00010", "00010", "0001", "000000" ],
[ "", "00000011", "000011", "000001", "00001", "00000" ],
[ "", "00000010", "000010", "00001", "00000" ],
[ "", "000000011", "000001", "000000" ],
[ "", "000000010", "000000" ],
[ "", "000000001" ]
]
runBeforeMap= [
[ "", "1", "1", "11", "11", "11", "11", "111" ],
[ "", "0", "01", "10", "10", "10", "000", "110" ],
[ "", "", "00", "01", "01", "011", "001", "101" ],
[ "", "", "", "00", "001", "010", "011", "100" ],
[ "", "", "", "", "000", "001", "010", "011" ],
[ "", "", "", "", "", "000", "101", "010" ],
[ "", "", "", "", "", "", "100", "001" ],
[ "", "", "", "", "", "", "", "0001" ],
[ "", "", "", "", "", "", "", "00001" ],
[ "", "", "", "", "", "", "", "000001" ],
[ "", "", "", "", "", "", "", "0000001" ],
[ "", "", "", "", "", "", "", "00000001" ],
[ "", "", "", "", "", "", "", "000000001" ],
[ "", "", "", "", "", "", "", "0000000001" ],
[ "", "", "", "", "", "", "", "00000000001" ]
]
# 获取非零值个数
def get_total_coeffs(num_list):
ret = 0
for i in range(len(num_list)):
if num_list[i]:
ret += 1
return ret
# 获取拖尾个数以及拖尾符号
# 拖尾系数:高频部分系数为+/-1的个数
# coeffs【input】: 需要编码的字符
# trailingSign【out】: 拖尾系数符号
def get_trailings_ones(coeffs):
ret = 0
trailing_sign = []
for i in range(15, -1, -1):
if abs(coeffs[i]) > 1 or ret == 3:
break
elif abs(coeffs[i]) == 1:
trailing_sign.append(1 if coeffs[i] == 1 else -1)
ret += 1
return ret, trailing_sign
# 返回普通非0系数列表
def get_levels(coeffs, levelCnt):
level_idx = levelCnt-1
levels = []
for i in range(len(coeffs)):
if coeffs[i] :
levels.insert(0, coeffs[i])
level_idx -= 1
if level_idx < 0 :
break
return levels
# 返回最后一个非0系数之前的0个数
def get_total_zeros(coeffs):
totalZeros = 0
last_num = 0
for i in range(len(coeffs)-1, -1, -1):
if coeffs[i]:
last_num = 1
if last_num == 1 and coeffs[i] == 0:
totalZeros += 1
return totalZeros
# 每个非零系数前零的个数
def get_run_before(coeffs, totalCoeffs):
run_idx = 0
runBefore = [0]*totalCoeffs
zerosLeft = [0]*totalCoeffs
for idx in range(len(coeffs)-1, -1, -1):
if coeffs[idx] == 0:
continue
for run in range(idx):
if coeffs[idx-1-run] == 0:
runBefore[run_idx] += 1
zerosLeft[run_idx] += 1
else:
run_idx += 1
break
for a in range(run_idx):
for b in range(a+1, run_idx):
zerosLeft[a] += zerosLeft[b]
return runBefore, zerosLeft
def encode_level(level, suffixLength):
ret = ''
level_code = 0
if level > 0:
level_code = (level << 1) -2
else:
level_code = -(level << 1) -1
level_prefix = int(level_code / (1 << suffixLength))
level_suffix = level_code % (1 << suffixLength)
print("level_code: %d, level_prefix: %d, level_suffix: %d, suffixLength: %d" % (level_code, level_prefix, level_suffix, suffixLength))
for i in range(level_prefix):
ret += '0'
ret += '1'
for i in range(suffixLength):
if (level_suffix >> (suffixLength-i-1) &1) == 1:
ret += '1'
else:
ret += '0'
return ret
if __name__ == '__main__':
num_list = [12, -1, -2, 1, 0, 0, 0, 0, 0, 2, -1, 0, 0, -1, 0, 1]
totalCoeffs = get_total_coeffs(num_list)
trailingOnes, trailingSign = get_trailings_ones(num_list)
print("trailingOnes: %d" % trailingOnes)
print(trailingSign)
level_conut = totalCoeffs - trailingOnes
levels = get_levels(num_list, level_conut)
print(levels)
totalZeros = get_total_zeros(num_list)
print("totalZeros: %d" % totalZeros)
runBefore, zerosLeft = get_run_before(num_list, totalCoeffs)
print(runBefore)
print(zerosLeft)
cavlcCode = ''
# coeff_token
if totalCoeffs >= 3:
cavlcCode += (coeffTokenMap[0][(totalCoeffs - 3) * 4 + trailingOnes + 6])
elif totalCoeffs <= 1:
cavlcCode += (coeffTokenMap[0][totalCoeffs + trailingOnes])
else:
# totalCoeffs == 2
cavlcCode += (coeffTokenMap[0][totalCoeffs + trailingOnes+1])
print("coeff_token: %s" % cavlcCode)
# trailing_sign
for i in range(trailingOnes):
if trailingSign[i] == 1:
cavlcCode +=("0")
elif trailingSign[i] == -1:
cavlcCode +=("1")
print("trailing_sign: %s" % cavlcCode)
# level
suffixLength = 0
if totalCoeffs >10 and trailingOnes < 3:
suffixLength = 1
for idx in range(level_conut):
tmp = encode_level(levels[idx], suffixLength)
print(tmp)
cavlcCode += tmp
if abs(levels[idx] > (0 if suffixLength==0 else (3 << (suffixLength - 1)))) \
and suffixLength < 6 :
suffixLength += 1
# totalZeros
cavlcCode += totalZerosMap[totalZeros][totalCoeffs];
# runBefore
for idx in range(totalCoeffs):
if zerosLeft[idx] == 0:
break
t = zerosLeft[idx]
if zerosLeft[idx] > 6:
t = 7
cavlcCode += runBeforeMap[runBefore[idx]][t]
print(cavlcCode)