上文說到根據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),見下表
由上表可知,對於m組(Group ID)的Golomb編碼,可以表示的值的範圍是
由此可得出對於值爲n的非負整數,進行Golomb編碼,組號爲
有組號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 按照如下方式賦值:
這裏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;
}