H.264分幀-3.哥倫布編碼

 上文說到根據slice頭中的first_mb_in_slice來分幀。於是,查看了標準中的slice_header中first_mb_in_slice的描述符,發現爲ue(v)。對ue(v)展開學習。

Golomb編碼

 通過查詢描述符的定義:

 — ue(v):無符號整數指數哥倫布碼編碼的語法元素,左位在先。

 該描述符涉及了一個新的名詞,指數哥倫布編碼,於是摘取了一段介紹哥倫布編碼的文字。

 Golomb編碼是一種無損的數據壓縮方法,由數學家Solomon W.Golomb在1960年代發明。Golomb編碼只能對非負整數進行編碼,符號表中的符號出現的概率符合幾何分佈(Geometric Distribution)時,使用Golomb編碼可以取得最優效果,也就是說Golomb編碼比較適合小的數字比大的數字出現概率比較高的編碼。它使用較短的碼長編碼較小的數字,較長的碼長編碼較大的數字。

 Golomb編碼是一種分組編碼,需要一個正整數參數m,然後以m爲單位對待編碼的數字進行分組,如下圖,
哥倫布編碼分組
 對於任一待編碼的非負正整數N,Golomb編碼將其分爲兩個部分:所在組的編號GroupID以及分組後餘下的部分,GroupID實際是待編碼數字N和參數m的商,餘下的部分則是其商的餘數,具體計算如下:
 Q = N / m,
 R = N % m
 對於得到的組號Q使用一元編碼,餘下部分R則使用固定長度的二進制編碼。
 一元編碼(Unary coding)是一種簡單的只能對非負整數進行編碼的方法,對於任意非負整數num,它的一元編碼就是num個1後面緊跟着一個0。
 有了以上Golomb編碼的介紹,可以進一步分析指數Golomb編碼。

指數Golomb編碼

 相對於普通的Golomb編碼,指數Golomb編碼有一個較大的改進,就是在分組的時候,不再使用固定的m作爲大小,而組的大小是呈指數增長,m必須爲2的次冪,見下圖,
指數哥倫布編碼分組
 指數Golomb編碼的碼元結構是:** [M zeros prefix] [1] [Offset] **,其中M是分組的編號GroupID,1可以看着是分隔符,Offset是組內的偏移量。
 指數Golomb需要一個非負整數K作爲參數,稱之爲K階指數Golomb。其中當K = 0時,稱爲0階指數Golomb,我們需要用到的的H.264視頻編碼標準中使用的就是0階的指數Golomb,並且可以將任意的階數K轉爲0階Exp-Golomb編碼。
 我摘取了一張表來表現0階的指數Golomb(表中是前導1與分隔符0),見下表
0階指數哥倫布編碼表
 由上表可知,對於m組(Group ID)的Golomb編碼,可以表示的值的範圍是
2m1n<2m+112^m-1 \leq n<2^{m+1}-1
 由此可得出對於值爲n的非負整數,進行Golomb編碼,組號爲
m=log2n+1m = \left \lfloor log_{2}^{n+1} \right \rfloor
 有組號m之後,對於數值n的編碼過程就可以表示出來了。
 - 對組號m進行一元編碼,連續寫入m個0
 - 計算出碼元結構中的偏移量,offset = n - 2^m
 - 取偏移量offset二進制的低m位
 0階指數Golomb編碼,編碼後的長度爲2*m+1,其解碼過程摘取建議書中9.1的介紹:

 這些語法元素的解析過程是由比特流當前位置比特開始讀取,包括非0 比特,直至leading_bits 的數量爲0。具體過程如下所示:

	leadingZeroBits = -1;
	for(b = 0; !b; leadingZeroBits++)
    	b = read_bits(1);

變量codeNum 按照如下方式賦值:
codeNum=2leadingZeroBits1+read_bits(leadingZeroBits)codeNum = 2^{leadingZeroBits} - 1 + read\_bits(leadingZeroBits)
這裏read_bits( leadingZeroBits )的返回值使用高位在先的二進制無符號整數表示。

優化:
 通過上述分析發現,要通過判斷first_mb_in_slice的值來判斷是否爲新的一幀,只需要判斷ue(v)是否爲0即可,並不需要對整個Golomb編碼進行反解析。反解析涉及較多運算,會消耗較長的時間。
 故在本例中實際運用時,只需要對slice_header中第一個字節進行一次與0x80的與運算即可。但爲了後續拓展,在編寫該函數時對Golomb是否計算具體值增加了一個開關參數bIsZero。
 結合上述,最終編寫出指數Golomb代碼如下:

unsigned int GolombCode(unsigned char *pbyData, int nBytePosition, bool bIsZero)
{
    int nLeadingZeroBits = -1;  // 前導零的位數
    unsigned int dwCodeNum = 0;          // CodeNum值
    unsigned int dwTail = 0;             // 編碼後綴
    unsigned int dwOffset = 0;           // 移位偏移量
    unsigned char byPos = 0x80;           // & 變量
    unsigned int dwOffsetByte  = 0;      // 偏移字節數

    if (bIsZero)
    {
        return !(pbyData[nBytePosition+5] & byPos);
    }
    /* 得到前導零的數量 */
    for(int b = 0; !b; nLeadingZeroBits++)
    {
        if (!((nLeadingZeroBits + 1) % 8) && (nLeadingZeroBits + 1))
        {
            dwOffsetByte++;
            byPos = 0x80;
        }
        b = pbyData[nBytePosition + 5 + dwOffsetByte] & byPos;
        byPos >>= 1;
    }

    byPos = 0x80;

    /* 後綴計算 */
    for(int k = nLeadingZeroBits; k >= 0; k--)
    {
        dwOffset = 2 * nLeadingZeroBits - k + 1;
        dwTail += (u32)pow(2.0, k - 1) * (pbyData[nBytePosition + 5 + dwOffset / 8] & (byPos >> ((dwOffset % 8) ? 1 : 0)));
    }

    /* ue(v)的值計算 */
    dwCodeNum = (u32)pow(2.0, (s32)nLeadingZeroBits) - 1 + dwTail;

    return dwCodeNum;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章