引言
更多系列文章請?jiān)L問自己動手寫 H.264 解碼器
上一小節(jié)左刽,我們闡述了無符號指數(shù)哥倫布熵編碼 ue(v)呜象,有符號指數(shù)哥倫布熵編碼 se(v)怜奖,映射指數(shù)哥倫布熵編碼 me(v)偎蘸,截?cái)嘀笖?shù)哥倫布熵編碼 te(v)四種指數(shù)哥倫布熵編碼的理論知識帚称。本小節(jié)嘹叫,就來看看怎么用代碼來解碼指數(shù)哥倫布編碼的數(shù)據(jù)嫩絮。
本小節(jié)主要介紹無符號指數(shù)哥倫布熵編碼和有符號指數(shù)哥倫布熵編碼的解碼丛肢,對于映射指數(shù)哥倫布熵編碼和截?cái)嘀笖?shù)哥倫布熵編碼,因?yàn)樯婕暗揭恍┣爸弥R還沒有講到剿干,本節(jié)先不做實(shí)現(xiàn)蜂怎,后續(xù)遇到的時(shí)候會實(shí)現(xiàn)。
BitStream
我們之前就說過怨愤,在 H.264 碼流中的操作單位是 bit派敷,所以,用普通的指針是無法達(dá)到這樣的操作顆粒度撰洗。
我們現(xiàn)在需要一個(gè)以 bit 為操作單位的流對象篮愉,我們把他叫做 BitStream。
接口設(shè)計(jì)
類的結(jié)構(gòu)
class BitStream {
public:
/**
* 構(gòu)造函數(shù)可以傳入一個(gè) buffer差导,這里可以直接把 nalu 的 buffer 傳入
*/
BitStream(unsigned char * buf, int size);
~BitStream();
private:
// 指向 buffer 開始的位置
unsigned char * start = nullptr;
// buffer 的長度(單位 Byte)
int size = 0;
// 當(dāng)前讀取到了哪個(gè)字節(jié)
unsigned char * p = nullptr;
// 當(dāng)前讀取到了字節(jié)中的第幾位
int bits_left = 8;
};
各個(gè)屬性的說明圖
函數(shù)的設(shè)計(jì)
-
從 Bitstream 中讀取 1 bit 的接口
在編寫從數(shù)據(jù)流里讀取指數(shù)哥倫布編碼的數(shù)據(jù)之前试躏,我們先來完成一個(gè)最基本的函數(shù)的封裝:先讀取 1 bit 的數(shù)據(jù)。
int ReadU1();
我們先定義一個(gè) ReadU1 的成員函數(shù)设褐,這個(gè)函數(shù)的作用是從 bitstram 讀取一個(gè) bit 的數(shù)據(jù)颠蕴,并把這一個(gè) bit 的數(shù)據(jù)轉(zhuǎn)換成一個(gè) int 類型泣刹,然后把數(shù)據(jù)指針向后移動一個(gè) bit(通過 bits_left 減 1 實(shí)現(xiàn),如果 bits_left 被減到了 0犀被,那么 unsigned char * p 將向后移動一個(gè)字節(jié))椅您。雖然 1 bit 的數(shù)據(jù)只會有 0 或者 1 兩種情況,但是我們?yōu)榱朔奖氵€是直接用一個(gè) int 來裝寡键。如果你覺得有些浪費(fèi)空間掀泳,可以自己修改一下。
這個(gè)函數(shù)的實(shí)現(xiàn)如下:
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 的接口
接下來西轩,我們再寫一個(gè)函數(shù)员舵,這個(gè)函數(shù)是 ReadU1 的擴(kuò)增,我們可以一下從 Bitstream 中讀取 n bit 的數(shù)據(jù)藕畔。
int ReadU(int n) { int r = 0; int i; for (i = 0; i < n; i++) { r |= ( ReadU1() << ( n - i - 1 ) ); } return r; }
-
從 Bitstream 中讀取一個(gè)無符號指數(shù)哥倫布熵編碼的數(shù)據(jù)
這個(gè)應(yīng)該是不需要做過多的解釋了马僻。我們在前一小結(jié)已經(jīng)專門講解了。
int ReadUE() { int r = 0; int i = 0; while((ReadU1() == 0) && (i < 32)){ i++; } r = ReadU(i); r += (1 << i) - 1; return r; }
-
從 Bitstream 中讀取一個(gè)有符號指數(shù)哥倫布熵編碼的數(shù)據(jù)
我們在讀取有符號的指數(shù)哥倫布熵編碼的時(shí)候注服,實(shí)際上是先按照無符號的方式去讀韭邓,然后讀出來之后再解析符號。
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;
}