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
}



