h.264標准中,CABAC的算術編碼部分(9.3.4)只是一個參考,實際編碼器中並不一定會按照它來實現,像JM中就有自己的算術編碼實現方案。
在上篇文章CABAC中有詳細的算術編碼描述,在了解算術編碼原理的基礎上,下面分析JM18.6中的算術編碼實現。
下圖是JM方案編碼的一個例子
結合上圖的例子分析,JM的方案在下面幾部分跟標准有差異
1. 初始化
把$[0,1)$用$[0,2^{26})$來表示,其中有9個bit為$R$,也就是說在初始化時,有
$R \cdot 2^{17} = (R_{MPS}+R_{LPS})\cdot 2^{17} = 2^{26}$
void arienco_start_encoding(EncodingEnvironmentPtr eep, unsigned char *code_buffer, int *code_len ) { eep->Elow = 0; // low eep->Echunks_outstanding = 0; //count of consecutive 0xffff eep->Ebuffer = 0; //store the word eep->Epbuf = -1; // to remove redundant chunk ^^ count of bytes to output eep->Ebits_to_go = BITS_TO_LOAD + 1; // to swallow first redundant bit //n = 16 + 1 eep->Ecodestrm = code_buffer; eep->Ecodestrm_len = code_len; eep->Erange = HALF; //0x1fe 510 }
2. 重歸一化
當輸入的是LPS時,會選擇$R_{LPS}$作為下次進行符號編碼的$R$,但是由於標准規定了$R \in [2^8,2^9)$,因此如果$R_{LPS}$小於$2^8$的話,需要對$R_{LPS}$向左移位。不同大小$R_{LPS}$需要移動不同的位數才能符號區間$[2^8,2^9)$,下面是$R_{LPS}$的范圍對應的移位表格
RangeLPS | Renorm Left Shift Bits |
[0,7] | 6 |
[8,15] | 5 |
[16,31] | 4 |
[32,63] | 3 |
[64,127] | 2 |
[128,255] | 1 |
$R_{LPS}$進行左移,意味着作為增量的$2^{n}$需要減去相應的位,即
${R^{i}}_{LPS} \times 2^n = ({R^i}_{LPS}<<k) \times 2^{n-k}$
void biari_encode_symbol(EncodingEnvironmentPtr eep, int symbol, BiContextTypePtr bi_ct ) { ... else //LPS { unsigned int renorm = renorm_table_32[(rLPS >> 3) & 0x1F]; //get k low += range << bl; range = (rLPS << renorm); bl -= renorm; // n = n - k if (!bi_ct->state) bi_ct->MPS ^= 0x01; // switch MPS if necessary bi_ct->state = AC_next_state_LPS_64[bi_ct->state]; // next state if (low >= ONE) // output of carry needed { low -= ONE; propagate_carry(eep); } if( bl > MIN_BITS_TO_GO ) // n > 0 ,no need to save a word yet { eep->Elow = low; eep->Erange = range; eep->Ebits_to_go = bl; return; } } ... }
當輸入的是MPS時,會選擇$R_{MPS}$作為下次進行符號編碼的$R$,但是標准規定了$R\in [2^8,2^9)$,因此如果$R_{MPS}$小於$2^8$的話,需要對$R_{LPS}$左移,不過這里只需要左移一位,因為MPS出現的概率是大於等於0.5的,所以有$2^8 \leqslant 2 \times R_{MPS} < 2^{9}$。最后還需要對n減去1。如果$R_{MPS}$大於或等於$2^8$的話就不需要執行這一步了。
void biari_encode_symbol(EncodingEnvironmentPtr eep, int symbol, BiContextTypePtr bi_ct ) { ... if ((symbol != 0) == bi_ct->MPS) //MPS { bi_ct->state = AC_next_state_MPS_64[bi_ct->state]; // next state if( range >= QUARTER ) // no renorm { eep->Erange = range; return; } else { range<<=1; if( --bl > MIN_BITS_TO_GO ) // renorm once, no output //n = n - 1, n>0, no need to save a word yet { eep->Erange = range; eep->Ebits_to_go = bl; return; } } } ... }
3. 區間起點的計算方法
最終編碼輸出的是區間起點$L$,由上圖可以看出,只有當輸入符號位LPS時,L才會增大,有
$L_{i+1} = L_i + {R^i}_{MPS} \cdot 2^n$
在前面我們已經知道,隨着編碼的推進,$n$由17往0遞減,當$n$為0時,由於$R$只有9bit,對於一共有26bit的$L$,除了進位之外,后面的計算是不會修改到$L$的高位17個bit的部分了。此時可以保存$L$高位的16bit。
保存下來的16bit數據可能會由於后續計算的進位而+1。需要注意的是,如果保存下來的16bit數據是0xffff,就會由於進位而溢出。解決方法是:每當需要保存的16bit數據為0xffff時,用一個計數器記錄0xffff連續出現的次數,一旦碰到進位就把這些0xffff對應的位置置零,並且對它們前面的那個非0xffff的16bit數據+1
如果在連續出現的0xffff后並沒有進位,而是接着保存非0xffff的16bit數據,這時就能將0xffff及其前面的數據一起輸出
$L$的26bit數據中,剩下的10bit數據會被再次進行16bit的左移位(n=16),在下次編碼符號時作為$L$繼續處理。
void biari_encode_symbol(EncodingEnvironmentPtr eep, int symbol, BiContextTypePtr bi_ct ) { ... else //LPS { low += range << bl; ... if (low >= ONE) // output of carry needed { low -= ONE; propagate_carry(eep); //process carry "+1" } ... } ... //n = 0, save a word //renorm needed eep->Elow = (low << BITS_TO_LOAD )& (ONE_M1); //ONE_M1 = 2^26 - 1 low = (low >> B_BITS) & B_LOAD_MASK; // mask out the 8/16 MSBs for output //B_BITS=10 if (low < B_LOAD_MASK) // no carry possible, output now// B_LOAD_MASK=0xFFFF { put_last_chunk_plus_outstanding(eep, low); //low != 0xFFFF } else // low == "FF.."; keep it, may affect future carry { ++(eep->Echunks_outstanding); //low == 0xFFFF } } static forceinline void propagate_carry(EncodingEnvironmentPtr eep) { ++(eep->Ebuffer); //+1 while (eep->Echunks_outstanding > 0) { put_one_word(eep, 0); //set 0xFFFF 0 --(eep->Echunks_outstanding); } } static inline void put_last_chunk_plus_outstanding(EncodingEnvironmentPtr eep, unsigned int l) { while (eep->Echunks_outstanding > 0) { put_one_word(eep, 0xFFFF); //it is 0xFFFF, no carry would affect them --(eep->Echunks_outstanding); } put_one_word(eep, l); //new word }