算术编码JM实现
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
}




浙公网安备 33010602011771号