理论
熵编码
熵编码是一种无损编码,按照熵原理不丢失任何信息的编码。
常见的熵编码有:香浓(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+ 2 k 2^k 2k-1 使用 0阶指数哥伦布编码;
- 数suffix的位数,用M表示;
- prefix 由M-1个0组成;
以codenum=5, k=1为例,
- 5 + 2 1 − 1 = 6 5+2^1-1=6 5+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 ⌊ l o g 2 x ⌋ + 1 2\lfloor log_2^{x}\rfloor + 1 2⌊log2x⌋+1个bit来编码表示数值。例如,数字5 使用 5个bit来表示;数字10使用7个bit来表示。
编码规则
x 为 待编码数
- 找N,N为x的2的指数的最大幂次; N = f l o o r ( l o g 2 x ) 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 = 2 2 + 1 x = 2^2+1 x=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对应的数值;再按照公式 c o d e N u m = 2 l e a d i n g Z e r o B i t s − 1 + r e a d _ b i t s ( l e a d i n g Z e r o B i t s ) 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;所以 c o d e N u m = 2 2 − 1 + 2 = 5 codeNum = 2^2-1+2=5 codeNum=22−1+2=5 ,这样就解出原数值5了。
从table9-1可以看出,"prefix"即是leadingZeroBits对应的前导0个数,"suffix"即是codeNum对应的比特值,由 x i x_i xi表示,i的范围是[0, leadingZeroBits-1], x i x_i xi可以是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)的实现原理即是前面提到的 c o d e N u m = 2 l e a d i n g Z e r o B i t s − 1 + r e a d _ b i t s ( l e a d i n g Z e r o B i t s ) codeNum=2^{leadingZeroBits}-1+read\_bits(leadingZeroBits) codeNum=2leadingZeroBits−1+read_bits(leadingZeroBits)。ue(v)的值即是codeNum。
se(v)
se(v)的值 就是 在ue(v)的值的基础上,按照 ( − 1 ) k + 1 C e i l ( k / 2 ) , k = u e ( 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