理论
熵编码
熵编码是一种无损编码,按照熵原理不丢失任何信息的编码。
常见的熵编码有:香浓(Shannon)编码、哈夫曼(Huffman)编码、算术(arithmetic)编码、指数哥伦布(exponential Golomb)编码、基于上下文的自适应变长编码(CAVLC:Context-based Adaptive Variable Length Coding )、基于上下文的自适应二进制算术编码(CABAC:Context-based Adaptive Binary Arithmetic Coding )。
在H264中,主要使用三种编码方式,exp-golomb、CAVLC、CABAC。
因为在项目中,需要解析sps,而sps RBSP语法元素主要用到了0阶exp-golomb,下面介绍0阶exp-golomb。
指数哥伦布编码
指数哥伦布码(Exponential-Golomb coding)是一种无损数据压缩方法。
Exp-golomb编码规则(文字描述版本)
说明:
codenum 表示 待编码的数 x;
codeword 表示 编码后的数;codeword的构成=[prefix][suffix]
prefix 也叫做 leadingzerobits; 二进制数
suffix 也是二进制数
编码步骤
- codenum+1 并将其值写成二进制形式,用suffix表示;
- 数suffix的位数,用M表示;
- prefix 由M-1个0组成;
以codenum=5为例,
- codenum+1=6,即suffix = 0b110;
- prefix = 0b00;
- codeword = 0b00110;
我们知道,在计算机中,以字节作为最小存储单位(字节对齐),而字节由8个bit组成。10进制数5在计算机中的存储为0b00000101,可以看出实际的有效位数只有3位,另外的5位没有用,但还是占用了5个bit的存储空间。所以为了更高效地利用物理存储空间,工程师们就想,有没有什么方式既能正确的表示数据又能更省存储空间呢?那就需要设计某种“规则”,使用这种“规则”重新表示数据。而这种“规则”用专业术语表达就叫做编码。
前面提到的指数哥伦布编码就是万千编码方式中的一种,通过exp-golomb coding,将0b00000101压缩成了0b00110,从8位压缩到了5位,节省了3位。
从上面讲解Exp-golomb编码规则中,并没有看到和指数相关的东西。那为什么这玩意叫指数哥伦布编码呢?
其实,上面讲解的编码规则是一种特殊情况(指数k=0)。k阶指数哥伦布编码的一般规则是
- x+2k2^k2k-1 使用 0阶指数哥伦布编码;
- 数suffix的位数,用M表示;
- prefix 由M-1个0组成;
以codenum=5, k=1为例,
- 5+21−1=65+2^1-1=65+21−1=6,6的0阶指数哥伦布编码为00111;
- 删除 1 个前导 0;
- codeword 为 0b0111;
Elias gamma coding
Elias γ code or Elias gamma code is a universal code encoding positive integers developed by Peter Elias.[1]:197, 199 It is used most commonly when coding integers whose upper-bound cannot be determined beforehand.
elias gamma coding 使用 2⌊log2x⌋+12\\lfloor log_2^{x}\\rfloor + 12⌊log2x⌋+1个bit来编码表示数值。例如,数字5 使用 5个bit来表示;数字10使用7个bit来表示。
编码规则
x 为 待编码数
- 找N,N为x的2的指数的最大幂次;N=floor(log2x)N=floor(log_2^{x})N=floor(log2x),即$2^N\\le x < 2^{N+1} $;
- N个前导0 bits;
- 接着将x表示成2进制;
- 将#2和#3的二进制数组合 即为编码后的数;
以x=5为例:
- N = 2;x=22+1x = 2^2+1x=22+1;
- 2个前导0,00;
- x = 0b101
- codeword = 0b00101
实践
sps的数据语法格式如下
从sps的语法中可以看到,主要使用了4种描述子,u(1),u(n),ue(v),se(v)。
接下来,分别介绍这四种描述子及其代码实现。
在h264标准中,有一个读取比特流的语法函数,read_bits(n)
read_bits( n ) reads the next n bits from the bitstream and advances the bitstream pointer by n bit positions. When n is
equal to 0, read_bits( n ) is specified to return a value equal to 0 and to not advance the bitstream pointer
read_bits的作用就是 从当前比特流位置开始,读取n个比特,同时比特流指针向前移动n位。如果n为0,read_bits返回0,且比特流指针不移动。
Syntax elements coded as ue(v), me(v), or se(v) are Exp-Golomb-coded. Syntax elements coded as te(v) are truncated ExpGolomb-coded. The parsing process for these syntax elements begins with reading the bits starting at the current location in the bitstream up to and including the first non-zero bit, and counting the number of leading bits that are equal to 0.
This process is specified as follows:
leadingZeroBits = -1
for( b = 0; !b; leadingZeroBits++ )
b = read_bits( 1 )
在h264的语法元素中,ue(v),me(v),se(v)都是使用指数哥伦布编码;te(v)使用截断指数哥伦布编码。解码过程为 从当前比特流位置开始读,直到遇到第一个非0bit位,即1位,然后计算前导0的个数N(注意:比特流的指针移动到了第一个非0bit的下一位);接着再向后读取N个bit并得到这个N个bit对应的数值;再按照公式codeNum=2leadingZeroBits−1+read_bits(leadingZeroBits)codeNum=2^{leadingZeroBits}-1+read\\_bits(leadingZeroBits)codeNum=2leadingZeroBits−1+read_bits(leadingZeroBits)解出原数值。
例如,5的exp-golomb coded number是 00110,第一个比特值1的前面有2个前导0,即leadingZeroBits=2;接着,第一个比特值1后的两个比特值为10,也就是10进制的2;所以codeNum=22−1+2=5codeNum = 2^2-1+2=5codeNum=22−1+2=5 ,这样就解出原数值5了。
从table9-1可以看出,\”prefix\”即是leadingZeroBits对应的前导0个数,\”suffix\”即是codeNum对应的比特值,由xix_ixi表示,i的范围是[0, leadingZeroBits-1],xix_ixi可以是0也可以是1。
u(1)
u(1)就相当于 read_bits(1)。它的值是0或1。
u(n)
unsigned integer using n bits. When n is “v” in the syntax table, the number of bits varies in a manner
dependent on the value of other syntax elements. The parsing process for this descriptor is specified by the return
value of the function read_bits( n ) interpreted as a binary representation of an unsigned integer with most
significant bit written first.
u(n) 就相当于 read_bits(n)。u(n)的返回值是n个bit对应的正整数。注意:most significant bit written first。
ue(v)
unsigned integer Exp-Golomb-coded syntax element with the left bit first. The parsing process for this
descriptor is specified in clause 9.1
ue(v)是使用0阶指数哥伦布编码的值,所以解析sps相应的语法元素时,要使用0阶指数哥伦布对应的解码规则进行解码。ue(v)的实现原理即是前面提到的codeNum=2leadingZeroBits−1+read_bits(leadingZeroBits)codeNum=2^{leadingZeroBits}-1+read\\_bits(leadingZeroBits)codeNum=2leadingZeroBits−1+read_bits(leadingZeroBits)。ue(v)的值即是codeNum。
se(v)
se(v)的值 就是 在ue(v)的值的基础上,按照(−1)k+1Ceil(k/2),k=ue(v)(-1)^{k+1}Ceil(k/2),k=ue(v)(−1)k+1Ceil(k/2),k=ue(v)计算得到。
更直观点的描述就是,se(v)对应的语法元素的值 ,从1开始,每相邻的两个数为一对,低序号的值为正数,高序号的值为负数,依次升序排列,如下表所示。
reference:
https://en.wikipedia.org/wiki/Exponential-Golomb_coding
ITU-T H.264 (V12) 2017-04-13
https://baike.baidu.com/item/%E7%86%B5%E7%BC%96%E7%A0%81/3303605