【H264/AVC 句法和語義詳解】(六):C語言實(shí)現(xiàn)Exp-Golomb指數(shù)哥倫布編碼(編碼篇)

本篇隸屬于文集:《H264/AVC 句法和語義詳解》伍茄,查看文集全部文章淹朋,請(qǐng)點(diǎn)擊文字鏈接。
想看最新文章唱蒸,可以直接關(guān)注微信公眾號(hào):金架構(gòu)

上篇中我們介紹了Exp-Golomb的理論部分邦鲫,這一篇我們就使用C語言來實(shí)現(xiàn)它。

我們已經(jīng)知道神汹,在H.264中庆捺,指數(shù)哥倫布編碼有四個(gè)描述子,分別為ue(v)屁魏、se(v)滔以、me(v)、te(v)氓拼。其中me(v)是最簡(jiǎn)單的你画,它直接靠查表來實(shí)現(xiàn)。而剩余的se(v)和te(v)桃漾,是在ue(v)的基礎(chǔ)上來實(shí)現(xiàn)的坏匪。所以它們的利害關(guān)系不明而喻,ue(v)就代表了指數(shù)哥倫布編碼撬统。

下面我們就先重點(diǎn)介紹适滓,無符號(hào)指數(shù)哥倫布編碼:ue(v)。

1. ue(v)的編碼實(shí)現(xiàn)

由上一篇總結(jié)出恋追,指數(shù)哥倫布編碼的實(shí)現(xiàn)粒竖,可以分為以下幾步:

ue(v)編碼步驟

而考慮到是使用C語言,我們需要把步驟明確的更利于代碼實(shí)現(xiàn):

ue(v)編碼實(shí)現(xiàn)步驟

圖中的Buffer几于,通常稱為緩沖器,其實(shí)在這里就是一個(gè)數(shù)組沿后。在圖中整個(gè)過程最重要的沿彭,就是要有這么一個(gè)工具,它能向Buffer中尖滚,循環(huán)寫入n個(gè)比特值喉刘。并且編碼下一個(gè)數(shù)時(shí)瞧柔,它還能接著在上一個(gè)比特末尾,繼續(xù)寫入睦裳。

而在編程語言中呢造锅,我們通常是在字節(jié)的級(jí)別上進(jìn)行操作,C語言也不例外廉邑,比如數(shù)組的取值和賦值哥蔚。所以我們需要定義這樣一個(gè)結(jié)構(gòu)體,來保存當(dāng)前指針在Buffer中的位置蛛蒙,也即指針正在指向哪個(gè)字節(jié)糙箍,以及在這個(gè)字節(jié)中,剩余可用的比特?cái)?shù)牵祟。

1.1 比特流操作結(jié)構(gòu)體

這個(gè)結(jié)構(gòu)體如下:

typedef struct
{
  uint8_t*start; // 指向buf頭部指針
  uint8_t*p;     // 當(dāng)前指針位置
  uint8_t* end;   // 指向buf尾部指針
  int bits_left;  // 當(dāng)前讀取字節(jié)的剩余可用比特個(gè)數(shù)
} bs_t;
1.2 初始化對(duì)象

以面向?qū)ο蟮乃枷肷詈唬Y(jié)構(gòu)體就是一個(gè)類贡这。所以我們寫一個(gè)構(gòu)造器邑茄,使用Buffer來初始化這個(gè)比特流操作對(duì)象艾帐。

bs_t* bs_init(bs_t* b, uint8_t* buf, size_t size)
{
   b->start = buf;  // 指向buf起始位置
   b->p = buf;      // 初始位置與start保持一致
   b->end = buf + size;    // 指向buf末尾
   b->bits_left = 8;   // 默認(rèn)剩余8比特可用
   return b;
}

bs_t* bs_new(uint8_t* buf, size_t size)
{
   bs_t* b = (bs_t*)malloc(sizeof(bs_t));
   bs_init(b, buf, size);
   return b;
}

同時(shí)別忘了釋放:

void bs_free(bs_t* b)
{
   free(b);
}
1.3 寫入1個(gè)比特

然后我們可以拿這個(gè)對(duì)象(或句柄)荡陷,寫入1個(gè)比特换衬。

void bs_write_u1(bs_t* b, uint32_t v)
{
   // 1.剩余比特先減1
   b->bits_left--;
   if (! bs_eof(b))
   {
       // 2.見文章
       (*(b->p)) |= ((v & 0x01) << b->bits_left);
   }
   // 3.判斷是否寫到字節(jié)末尾定硝,如果是指針位置移向下一字節(jié)毫捣,比特位初始為8
   if (b->bits_left == 0) { b->p ++; b->bits_left = 8; }
}

/** 是否已讀到末尾(end_of_file) */
int bs_eof(bs_t* b) { if (b->p >= b->end) { return 1; } else { return 0; } }

每次調(diào)用這個(gè)函數(shù)搭综,會(huì)向Buffer中寫入1個(gè)比特值筹淫。其中第二步包含四步:

(1)(v & 0x01):按位與計(jì)算站辉,輸入?yún)?shù)v的末尾比特的值,就這個(gè)函數(shù)來講损姜,v為0或1饰剥,所以(v & 0x01)的值也即v的值
(2)((v & 0x01) << b->bits_left):將第(1)步計(jì)算出的比特值,移位至剩余可用比特位的最高位摧阅。
(3) (*(b->p)) | ((v & 0x01) << b->bits_left):按位或運(yùn)算汰蓉,保留b->p指向字節(jié)之前寫入比特值的同時(shí),把第(2)步新寫入的比特值加上
(4)把第(3)步結(jié)果賦值給*(b->p)
1.4 寫入n個(gè)比特
/**
寫入n個(gè)比特
@param b 比特流操作句柄
@param n 參數(shù)v所需的比特位個(gè)數(shù)
@param v 待寫入的值
*/
void bs_write_u(bs_t* b, int n, uint32_t v)
{
   int i;
   for (i = 0; i < n; i++)
   {
       // 循環(huán)調(diào)用bs_write_u1()棒卷,寫入n個(gè)比特
       bs_write_u1(b, (v >> ( n - i - 1 ))&0x01 );
   }
}

其中(v >> ( n - i - 1 ))&0x01分為兩步:

(1)v >> ( n - i - 1 ):把參數(shù)v向右移( n - i - 1 )位
(2)計(jì)算經(jīng)過第(1)步移位后顾孽,位于最低比特位的值
1.5 寫入指數(shù)哥倫布編碼后的值

這一步最重要的,就是需要計(jì)算指數(shù)哥倫布編碼后比规,所需要的比特位個(gè)數(shù)若厚,然后直接調(diào)用bs_write_u()寫入即可。

/**ue(v) 無符號(hào)指數(shù)哥倫布編碼*/
void bs_write_ue( bs_t *b, unsigned int val)
{
   // val + 1所需的比特個(gè)數(shù)
   int i_size = 0;
   // 1.值為0~255時(shí)蜒什,所需的比特位個(gè)數(shù)表
   static const int i_size0_255[256] =
   {
       1,      // 0的二進(jìn)制所需的比特個(gè)數(shù)
       1,      // 1的二進(jìn)制所需的比特個(gè)數(shù)
       2,2,    // 2~3的二進(jìn)制所需的比特個(gè)數(shù)
       3,3,3,3,    // 4~7的二進(jìn)制所需的比特個(gè)數(shù)
       4,4,4,4,4,4,4,4,  // 8~15的二進(jìn)制所需的比特個(gè)數(shù)
       5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,  // 16~31的二進(jìn)制所需的比特個(gè)數(shù)
       6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,  // 32~63的二進(jìn)制所需的比特個(gè)數(shù)
       6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
       7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  // 64~127的二進(jìn)制所需的比特個(gè)數(shù)
       7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
       7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
       7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
       8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,  // 128~255的二進(jìn)制所需的比特個(gè)數(shù)
       8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
       8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
       8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
       8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
       8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
       8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
       8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
   };
   
   if( val == 0 ) // 輸入為0测秸,直接編碼為1
   {
       bs_write_u1( b, 1 );
   }
   else
   {
       // 1.指數(shù)哥倫布編碼第一步,先把輸入值+1
       unsigned int tmp = ++val;

       // 2.判斷所需比特個(gè)數(shù)是否大于16位
       if( tmp >= 0x00010000 )
       {
           i_size += 16;
           tmp >>= 16;
       }

       // 3.判斷此時(shí)所需比特個(gè)數(shù)是否大于8位
       if( tmp >= 0x100 )
       {
           i_size += 8;
           tmp >>= 8;
       }

       // 4.最終tmp移位至8位以內(nèi),去查表
       i_size += i_size0_255[tmp];

       // 5.最終得出編碼val所需的總比特?cái)?shù):2 * i_size - 1
       // 寫入Buffer
       bs_write_u( b, 2 * i_size - 1, val );
   }
}

其中的表i_size0_255[]是個(gè)關(guān)鍵霎冯,在H.264的各個(gè)編解碼器中铃拇,查表是個(gè)很常見的優(yōu)化方法。而在這里沈撞,i_size0_255[]這個(gè)表慷荔,就包含了值為0~255時(shí),所對(duì)應(yīng)的二進(jìn)制值的比特位個(gè)數(shù)缠俺。

這樣我們肯定會(huì)有疑問显晶,既然表中數(shù)據(jù)只能查0~255以內(nèi)的,那超過255的該怎么辦晋修?

這就是if判斷中吧碾,else的處理步驟。我們都知道墓卦,2的8次方就是256倦春,而256的二進(jìn)制剛好占用(8+1)=9個(gè)比特,而小于256的值例如255落剪,占用最多8個(gè)比特睁本。

所以如果我們最終是想查表獲得,則需要把大于8比特的值忠怖,都移位至8比特以內(nèi)呢堰,這就是else處理的核心。

首先凡泣,我們需要判斷val+1的值枉疼,是否大于16位,也即大于0x10000鞋拟。如果是骂维,則把所需比特個(gè)數(shù)i_size加16,然后向右移16位贺纲。

然后航闺,再判斷處理后的值,是否大于8位猴誊,也即大于0x100潦刃。如果是,則把所需比特個(gè)數(shù)i_size加8懈叹,然后向右移8位乖杠。

這樣一來,只要是32位以內(nèi)的數(shù)澄成,肯定能縮短至8位以內(nèi)滑黔,然后嗨皮的去查表就可以了笆包。當(dāng)然會(huì)有人說,那大于32位的數(shù)該怎么辦略荡?這個(gè)請(qǐng)放寬心,這個(gè)是絕對(duì)不可能出現(xiàn)的歉胶,要知道2的32次方是多少汛兜?是四十多億,沒錯(cuò)通今,是億級(jí)別的粥谬!所以這個(gè)是幾乎不可能的!

而且從上一篇我們就知道辫塌,指數(shù)哥倫布編碼漏策,只適合小數(shù)值比大數(shù)值概率高的編碼,數(shù)值越大占得位數(shù)越大臼氨。所以除非不懂指數(shù)哥倫布編碼掺喻,否則我們不可能用這么大的數(shù)。

所以最終呢储矩,我們就得到ue(v)所需的總比特?cái)?shù)2 * i_size - 1感耙,然后調(diào)用bs_write_u()寫入buffer就可以了。

2. se(v)的編碼實(shí)現(xiàn)

實(shí)現(xiàn)了ue(v)持隧,再實(shí)現(xiàn)se(v)就很簡(jiǎn)單了即硼。因?yàn)閟e(v)也稱有符號(hào)指數(shù)哥倫布編碼,也即把ue(v)無符號(hào)指數(shù)哥倫布編碼拓展至了負(fù)數(shù)屡拨。它的編碼方式是:

當(dāng)待編碼整數(shù)x≤0時(shí)只酥,映射為偶數(shù)-2x,然后對(duì)-2x使用無符號(hào)指數(shù)哥倫布編碼
當(dāng)待編碼整數(shù)x>0時(shí)呀狼,映射為奇整數(shù)2x-1裂允,然后對(duì)2x-1使用無符號(hào)指數(shù)哥倫布編碼

所以se(v)的實(shí)現(xiàn)如下:

/**se(v) 有符號(hào)指數(shù)哥倫布編碼*/
void bs_write_se(bs_t* b, int32_t v)
{
   if (v <= 0)
   {
       bs_write_ue(b, -v*2);
   }
   else
   {
       bs_write_ue(b, v*2 - 1);
   }
}
3. te(v)的編碼實(shí)現(xiàn)
/** te(v) 截?cái)嘀笖?shù)哥倫布編碼 */
void bs_write_te( bs_t *b, int x, int val)
{
  if( x == 1 )
  {
      bs_write_u1( b, 1&~val );
  }
  else if( x > 1 )
  {
      bs_write_ue( b, val );
  }
}

其中x為語法元素范圍的上限,如果上限為1赠潦,則為輸入值val最低比特位的取反叫胖,否則使用ue(v)進(jìn)行編碼。

最后注意她奥,給Buffer開辟存儲(chǔ)空間時(shí)瓮增,要使用calloc(),不要用malloc()哩俭,否則會(huì)出現(xiàn)初始值不為0的情況绷跑。

本文源碼地址如下:

1、GitHub:https://github.com/Gosivn/Exp-Golomb-C-implementation

2凡资、CSDN:https://download.csdn.net/download/u011399342/10321706

[注]:本文的實(shí)現(xiàn)嚴(yán)重參考x264源碼砸捏!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谬运,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子垦藏,更是在濱河造成了極大的恐慌梆暖,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掂骏,死亡現(xiàn)場(chǎng)離奇詭異轰驳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)弟灼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門级解,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人田绑,你說我怎么就攤上這事勤哗。” “怎么了掩驱?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵芒划,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我昙篙,道長(zhǎng)腊状,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任苔可,我火速辦了婚禮缴挖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焚辅。我一直安慰自己映屋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布同蜻。 她就那樣靜靜地躺著棚点,像睡著了一般。 火紅的嫁衣襯著肌膚如雪湾蔓。 梳的紋絲不亂的頭發(fā)上瘫析,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音默责,去河邊找鬼贬循。 笑死,一個(gè)胖子當(dāng)著我的面吹牛桃序,可吹牛的內(nèi)容都是我干的杖虾。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼媒熊,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼奇适!你這毒婦竟也來了坟比?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤嚷往,失蹤者是張志新(化名)和其女友劉穎葛账,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皮仁,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡注竿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了魂贬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡裙顽,死狀恐怖付燥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情愈犹,我是刑警寧澤键科,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站漩怎,受9級(jí)特大地震影響勋颖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勋锤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一饭玲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧叁执,春花似錦茄厘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吆录,卻和暖如春窑滞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恢筝。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工哀卫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人滋恬。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓聊训,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親恢氯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子带斑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容