引言
更多系列文章请访问自己动手写 H.264 解码器
上一小节,我们阐述了无符号指数哥伦布熵编码 ue(v),有符号指数哥伦布熵编码 se(v),映射指数哥伦布熵编码 me(v),截断指数哥伦布熵编码 te(v)四种指数哥伦布熵编码的理论知识。本小节,就来看看怎么用代码来解码指数哥伦布编码的数据。
本小节主要介绍无符号指数哥伦布熵编码和有符号指数哥伦布熵编码的解码,对于映射指数哥伦布熵编码和截断指数哥伦布熵编码,因为涉及到一些前置知识还没有讲到,本节先不做实现,后续遇到的时候会实现。
BitStream
我们之前就说过,在 H.264 码流中的操作单位是 bit,所以,用普通的指针是无法达到这样的操作颗粒度。
我们现在需要一个以 bit 为操作单位的流对象,我们把他叫做 BitStream。
接口设计
类的结构
class BitStream {
public:
/**
* 构造函数可以传入一个 buffer,这里可以直接把 nalu 的 buffer 传入
*/
BitStream(unsigned char * buf, int size);
~BitStream();
private:
// 指向 buffer 开始的位置
unsigned char * start = nullptr;
// buffer 的长度(单位 Byte)
int size = 0;
// 当前读取到了哪个字节
unsigned char * p = nullptr;
// 当前读取到了字节中的第几位
int bits_left = 8;
};
各个属性的说明图
函数的设计
-
从 Bitstream 中读取 1 bit 的接口
在编写从数据流里读取指数哥伦布编码的数据之前,我们先来完成一个最基本的函数的封装:先读取 1 bit 的数据。
int ReadU1();
我们先定义一个 ReadU1 的成员函数,这个函数的作用是从 bitstram 读取一个 bit 的数据,并把这一个 bit 的数据转换成一个 int 类型,然后把数据指针向后移动一个 bit(通过 bits_left 减 1 实现,如果 bits_left 被减到了 0,那么 unsigned char * p 将向后移动一个字节)。虽然 1 bit 的数据只会有 0 或者 1 两种情况,但是我们为了方便还是直接用一个 int 来装。如果你觉得有些浪费空间,可以自己修改一下。
这个函数的实现如下:
int ReadU1() { int r = 0; bits_left--; r = ((*(p)) >> bits_left) & 0x01; if (bits_left == 0) { p++; bits_left = 8; } return r; }
-
从 Bitstream 中读取 n bit 的接口
接下来,我们再写一个函数,这个函数是 ReadU1 的扩增,我们可以一下从 Bitstream 中读取 n bit 的数据。
int ReadU(int n) { int r = 0; int i; for (i = 0; i < n; i++) { r |= ( ReadU1() << ( n - i - 1 ) ); } return r; }
-
从 Bitstream 中读取一个无符号指数哥伦布熵编码的数据
这个应该是不需要做过多的解释了。我们在前一小结已经专门讲解了。
int ReadUE() { int r = 0; int i = 0; while((ReadU1() == 0) && (i < 32)){ i++; } r = ReadU(i); r += (1 << i) - 1; return r; }
-
从 Bitstream 中读取一个有符号指数哥伦布熵编码的数据
我们在读取有符号的指数哥伦布熵编码的时候,实际上是先按照无符号的方式去读,然后读出来之后再解析符号。
int ReadSE() { int r = ReadUE(); if (r & 0x01) { r = (r+1)/2; } else { r = -(r/2); } return r; }
完整代码
BitStream.hpp
#ifndef EYERLIB_BITSTREAM_HPP
#define EYERLIB_BITSTREAM_HPP
class BitStream {
public:
BitStream(unsigned char * buf, int size);
~BitStream();
int ReadU1();
int ReadU(int n);
int ReadUE();
int ReadSE();
private:
unsigned char * start = nullptr;
int size = 0;
unsigned char * p = nullptr;
int bits_left;
};
#endif //EYERLIB_BITSTREAM_HPP
BitStream.cpp
#include "BitStream.hpp"
BitStream::BitStream(unsigned char * _buf, int _size)
{
start = _buf;
p = _buf;
size = _size;
bits_left = 8;
}
BitStream::~BitStream()
{
}
int BitStream::ReadU1()
{
int r = 0;
bits_left--;
r = ((*(p)) >> bits_left) & 0x01;
if (bits_left == 0) {
p++;
bits_left = 8;
}
return r;
}
int BitStream::ReadU(int n)
{
int r = 0;
int i;
for (i = 0; i < n; i++) {
r |= ( ReadU1() << ( n - i - 1 ) );
}
return r;
}
int BitStream::ReadUE()
{
int r = 0;
int i = 0;
while((ReadU1() == 0) && (i < 32)){
i++;
}
r = ReadU(i);
r += (1 << i) - 1;
return r;
}
int BitStream::ReadSE()
{
int r = ReadUE();
if (r & 0x01) {
r = (r+1)/2;
}
else {
r = -(r/2);
}
return r;
}