VP8二进制熵编码器

以下内容主要参考了《VP8 Data Format and Decoding Guide》第七章Boolean Entropy Decoder

7.1 Underlying Theory of Coding

在编码时,假设:a <= x < b,p是出现0的概率。那么,编0的时候上式变为:a <= x < a + ( p * (b - a) );编1的时候,上式变为:a + ( p * (b - a) ) <= x < b。

在解码时,假设:a <= x < b,p是出现0的概率。那么,如果a <= x < a + ( p * (b - a) ),说明应该输出0,且在输出后要更新上式;如果a + ( p * (b - a) ) <= x < b,说明应该输出1,且在输出后要更新上式。

理解用于编0的更新公式a <= x < a + ( p * (b - a) )和用于编1的更新公式a + ( p * (b - a) ) <= x < b可以这样记忆:

  1. 0 <= x < 1,且0出现的概率为p
  2. 当出现0时,更新为0 <= x < p。更新了上边界。
  3. 当出现1时,更新为p <= x < 1。更新了下边界。

7.2 Practical Algorithm Implementation

VP8使用0 <= p <= 255来表示概率,此时真实的概率就是p / 256(等价于右移8位)。在编码时并没有像上一节记录边界a和b。而是记录bottom(其实就是a)和range(就是b - a)。那么,p * (b - a)就可以表示为split = 1 + ( ( (range - 1) * prob ) >> 8 )。此时,1 <= split <= range - 1。

如果编0,那么bottom不动,range = split。参考:a <= x < a + ( p * (b - a) ),range = a + ( p * (b - a) ) - a = p * (b - a) = split。

如果编1,那么bottom = bottom + split,且range = range - split。参考:a + ( p * (b - a) ) <= x < b,range = b - a - ( p * (b - a) ) = range - split。

下面代码展示了二进制熵编码器的具体编码过程。代码和英文注释依然来自《VP8 Data Format and Decoding Guide》。中文注释是我加入的。

/* Encoding very rarely produces a carry that must be propagated
 to the already-written output. The arithmetic guarantees that
 the propagation will never go beyond the beginning of the
 output. Put another way, the encoded value x is always less
 than one. */
// 对前面已经写过的数据进行加一
// 考虑这样一个例子:0x 01 FF FF 08
// 当前在写最后的08。此时发现bottom已经大于等于 1 << 31,此时要向0x 01 FF FF加一。
// 最后的结果就是0x 02 FF FF
void add_one_to_output(uint8_t *q)
{
    // *(--q),这里注意有指针移动操作
    // 255就是FF。当前值是255的时候,需要向前进位,同时把FF置为0。
    while (*--q == 255)
        *q = 0;
    // ++(*q)
    ++*q;
}
/* Main function writes a bool_value whose probability of being
 zero is (expected to be) prob/256. */
void write_bool(bool_encoder *e, Prob prob, int bool_value)
{
    /* split is approximately (range * prob) / 256 and,
 crucially, is strictly bigger than zero and strictly
 smaller than range */
    uint32_t split = 1 + (((e->range - 1) * prob) >> 8);
    if (bool_value)
    {
        e->bottom += split; /* move up bottom of interval */
        e->range -= split;  /* with corresponding decrease in range */
    }
    else
        e->range = split; /* decrease range, leaving bottom alone */
    while (e->range < 128)
    {
        e->range <<= 1;
        // 等价于e->bottom >= (1 << 31)
        if (e->bottom & (1 << 31)) /* detect carry */
            // 因为下面要对e->bottom左移,而此时e->bottom >= (1 << 31),再左移一位会溢出。
            // 所以,此时要向前面已经写过的bytes“进位”。add_one_to_output就是对前面已经写过的bytes进行加一操作的。
            add_one_to_output(e->output);
        // 编码器要输出的值不会每次输出一位,而是不断左移到bottom的高位。积累到一定时候一起处理。
        e->bottom <<= 1; /* before shifting bottom */
        // !( --(e->bit_count) )
        // 当e->bit_count == 1时,下面的判断为真。也即,本次是e->bottom左移的第24次,或者是第32次、40次、48次......
        if (!--e->bit_count)
        { /* write out high byte of bottom ... */
            // *e->output = (uint8_t)(e->bottom >> 24);
            // e->output++; 
            // 把当前output指向的地址赋值为bottom的高8位
            // 再将output地址加一
            *e->output++ = (uint8_t)(e->bottom >> 24);
            e->bottom &= (1 << 24) - 1; /* ... keeping low 3 bytes */
            e->bit_count = 8;           /* 8 shifts until next output */
        }
    }
}

你可能感兴趣的:(编解码,算法,熵编码)