首先要說明的是CABAC的生命期是SLICE,因此本篇所講的也是一個SLICE里CABAC的流程,其次對于我們來說場模式幾乎用不到,所以本文的編碼流程只使用幀模式,因此實際上用到的表只有277個, 當然如果我寫成399, 不是說里面所有表都用到的. 這里只是聲明一下這個問題, 如果大家實際操作的時候發現模型表序號始終不過276那是很正常的. 本文參考了T264的代碼, 應此一幀里只有一個SLICE. 而本文用的變量則采用標準里的變量.本文不會講CABAC的原理, 想要了解原理請參考FTP上的<<Context-based adaptive binary arithmetic coding in the H.264AVC video compression standard>>
?????? 注意:我用的標準是BS的正式標準,可能里面的序號和大家的不一樣,但是內容應該是一樣.大家對照著就可以了
片級: 即以下步驟在片期間只做1次
?????? 1, 在一幀的開始的時候, 先看是否字節對齊, 沒有對齊則補1, 直到字節對齊
?????? 2, 先根據SliceQP算出399個模型表里的pStateIdx和valMPS, 構成一張初始表,根據標準9.3.1.1里的公式, 同時可以參考T264_cabac_context_init函數. 這張表不要和模型表弄混,雖然都是399維的, 但我們宏塊級編碼過程中實際用的只是這張表, 而不是標準里的那張模型表Table 9-23, 9-23這張表是用來算由pStateIdx和valMPS構成的初始表的.
?????? 3, 然后就是初始化CABAC的初值, 下界指針,區間范圍 (0x1FE)等等,可參考T264_cabac_encode_init函數.
宏塊級: 以下則是每個宏塊都要做一次的, 這一級中會處理很多的語法元素, 這里我只用前2個語法元素做為例子: mb_skip_flag, mb_type
??????? 4, 重整區間, 確保在區間在28-29內.這里的區間概念來自BAC,如果不明白先google算術編碼,然后再看上面那個參考文獻.
??????? 5, 首先mb_skip_flag標志進行CABAC編碼, 由于這個元素本身就是2值的,所以直接就可以進行上下文模型選擇了:
??????????????? 1. 由標準Table 9-24知道, P幀(這里要注意是slice_type==P, 不是mb_type)的這個元素用11號表, B幀用24號表. 可以參考T264中的T264_cabac_mb_skip函數.
??????????????? 2. 由于這個元素只有1個bit, 因此只要算第一個bit的ctxIdxInc就可以了, 參考標準Table 9-29, 可以看到表中11 和 24號表確實只有第一個bit是可用的, 根據9.3.3.1.1子條款可以知道這一位的ctxIdxInc可能是0, 1, 2中的一個, 在T264中簡單的說就是看左邊和上邊宏塊是否是Skip模式, 有一個是skip模式ctxIdxInc就加1. 也就是全不是skip為0, 有一個skip則ctxIdxInc就是1,全是skip則ctxIdxInc就是2.
??????????????? 3. 下來就是算術編碼部分了, 簡單提一下基本原理: 在CABAC中為了減少RLPS = R*pLPS 這個區間變換公式的開銷, 用128個有限狀態(實際可用的為126)代替pLPS , 用rangeTab這張表代替了RLPS, 見標準Table 9-33. 可以參考T264中的T264_cabac_encode_decision函數, 看一下具體流程:
????????? 獲得當前bin的pStateIdx和valMPS(來自片級計算的那張初始表)
????????? 根據標準9.3.3.2.1子條款, 求得qCodIRangeIdx用來索引表rangeTab, 即可以求得變換后的區間了.
????????? 修正區間codIRange = codIRange – codIRangeLPS
????????? 判斷當前的bin是否為最有可能的值, 如果不是(binVal!= valMPS),則更新區間下邊界codILow = codILow + codIRange, 同時修正區間codIRange = codIRangeLPS;然后判斷當前狀態, 如果已經達到狀態0, 是則把valMPS的值取反(即0,1互換), 如果還有沒達到0, 則進行LPS的狀態遷移,具體參看標準Table 9-34的狀態遷移表中的transIdxLPS
????????? 如果當前的binVal值等于valMPS, 就比較簡單了, 直接進行狀態遷移, MPS的狀態遷移也很簡單, 直接數據+1就可以了(最大63),具體參考標準Table 9-34的狀態遷移表中的transIdxMPS, 狀態的轉移其實就是修改了399個模型的初始表.
????????? 區間重整, 如果區間過小則輸出一些bit, 這樣可以不用把數據全部編碼完再輸出, 可以編一部分輸出一些.
????????? 已編碼的2進制值bin總數+1, 即SymCnt+1, 這個值用于字節填充, 可以參考標準9.3.4.6, 其中的BinCountsInNALunits就是指這個值,至此算術編碼部分就結束了.
??????? 6, 下面就是開始編碼第二個語法元素了: mb_type
????????????????? 1)???? 不同于上一個語法元素mb_skip_flag, mb_type這個語法元素本身并不是二值化的, 因此編碼的第一步是進行二值化, 查閱標準Table 9-24可知mb_type的二值化方法需要參考9.3.2.5子條款, 由該條款可知mb_type的二值化相對簡單, 可以直接參考表得到, 例如I片中某宏塊mb_type為I_16x16時則二值化后bin0=1, 且非I_PCM則bin1=0, 亮度AC無非0系數則bin2=0, 色度有非0系數則bin3=1, 色度DC,AC都無非0系數時,bin4=0, 幀內預測模式為0時則bin5=0, bin6=0, 因此我們就得到了標準中I_16x16_0_1_0的二值化串為1001000
???????????????? 2)???? 下來是上下文模型選擇, 由Table 9-24可知 I的mb_type元素起始表是3號表, 然后參考標準表Table 9-29, 計算bin串中每個bin的ctxIdxInc值,由表可知bin0要參考9.3.3.1.1.3才能確定ctxIdxInc值, 簡單的說就是如果上邊和左邊的塊模式有一個不是I_4x4則ctxIdxInc++, 這樣就有0,1,2這3種可能的結果了; bin1的ctxIdx固定是276, 直接就調用了encoder_terminal模塊, 即標準Figure 9-11的右分支;bin2的ctxIdxInc是3;bin3的ctxIdxInc是4,; bin4和bin5要參考9.3.3.1.2子條款, 由該條款可知如果bin3是1則bin4的ctxIdxInc=5,bin5的ctxIdxInc=6,如果bin3=0則bin4的ctxIdxInc =6,bin5的ctxIdxInc =7,由于I_16x16_0_1_0的二值化串為1001000, 其中bin3=1, 由此可知此時bin4的ctxIdxInc =5, bin5的ctxIdxInc =6; 最后由表9-29 中bin6的ctxIdxInc為7.至此二進制串所有位的表號可以用3+的ctxIdxInc來得到了.
??????????????? 3)???? 下來就是算術編碼部分,這個部分于mb_skip_flag的算術編碼部分步驟一樣.這里要提的是, 第一步獲得當前bin的pStateIdx和valMPS的模型表已經被更新了(是表被更新,但不一定是當前表), 還記得么? 是在上一個語法元素的狀態轉移的時候更新的.
??????? 7, 下面的語法元素就是按照標準7.3.5的語法一一編碼的, 其過程和上面2個語法元素的編碼過程大同小異,就不一一細述了.
?
……如此做完所有的宏塊里的語法元素
又回到片級:
?????? 8, 寫入end_of_slice_flag的標志,即完成標準Figure 9-11的左分支, 然后調用T264_cabac_encode_flush函數, 輸出bit
?????? 9, 進行byte stuffing, 參考標準9.3.4.6 Byte stuffing process里的公式可以求得k, 在碼流中填充k次的0x000003
?????? 10, 最后是模型更新,更換模型表(更改PB幀所用的模型表,而I幀所用的不變)