熵编码--mpeg12video huffman编码

背景介绍

====

最近打算做一些沉淀,学习mpeg音视频编解码的一些东西。发现ffmpeg里面有很多值得反复品味的地方。特此记录。


熵编码

====

huffman 码表是标准mpeg-13818-2-video.pdf规定好了的,编码和解码只需查表即可,但是查表有点技巧。。。

mpeg12video DC表示为幅度差值diff所用的比特数size, 差值diff。

一个AC表示为幅度所用的比特数run,幅度值level,正负号s。


EOB 表示后面的都是零值。
escape 后面接 run 固定6bit, level 固定12bit

IS   --     ffmpeg
B.12 -- vlc_dc_lum_code/len 
B.13 -- vlc_dc_chroma_code/len
B.14 -- mpeg1_vlc/run/level
B.15 -- mpeg2_vlc/run/level
B.16 -- escape编码规则
7.2.1 -- decode_dc/mpeg2_decode_block_intra

(xx_code是码字的值,xx_len是该码字的长度)

比如code=0x7本身既可以表示码字'111',也可以'0111',或者'00000111'等,所以我们还需要一个长度。
code/len组合就可以唯一表示一个码字了。

编码:
对于diff=4, 所需比特数size=3,
查vlc_dc_lum_code/bits数组第3个得知size编码为'101', diff的二进制表示为'100',最后写入比特流'101100'。

解码:
读取到比特是'101',查表B.12得知表示size=3,然后读size比特,就是diff值='100'。

对于AC,
比如遇到的比特是'00101',查表B.14得知表示run = 0, level =3。再读一个比特如果是1,表示AC值是-3。

B.16 只需level = (level << 20) >> 20, 不需要查表。

这里注意ffmpeg处理是很有技巧的,编码直接对vlc_dc_lum_code[]下标引用。
解码如果按标准的B.12来一个个对比查找的话,就实在是太慢太慢了。ffmpeg在解码开始时调
ff_mpeg12_init_vlcs()做了一个逆向索引。

逆向索引的构造
====
对于第k个code/len,计算 code << (table_nb_bits - len) 作为下标j,
建一个表RI, RI[j] 赋值为k,RI[j+1, ..., j + (1<<(table_nb_bits-len))-1]也赋值为k。
(为什么?见下文)
 
如果len > table_nb_bits ?
====
例如ff_mpeg12_mbAddrIncrTable[0][1]最大的是0x23, 而MBINCR_VLC_BITS=9。
能不能把9增大呢?注释里面说:
'nb_bits' set thee decoding table size (2^nb_bits) entries. The
   bigger it is, the faster is the decoding. But it should not be too
   big to save memory and L1 cache. '9' is a good compromise.
即使你内存再多到8G,L1才是速度限制性因素。

则对code的高table_nb_bits计算下标j, RI[j] = table_nb_bits - len,再递归计算剩下的bits。
由build_table实现,使用
table[j][0]表示k(或者递归开始下标)
table[j][1]表示len (负值表示需要递归)

例如对于table_nb_bits = 8:
'111' --> '111 00000'
'0111' --> '0111 0000'
'101' --> '101 00000'
'100' --> '100 00000' 
'10' --> '10 000000' 
(码字'10'不可能存在,为什么?因为huffman在树叶子上编码,不会有重复前缀。)

解码时,以解码DC size 为例:
decode_dc-->
int get_vlc2(s = gb, table = ff_dc_lum_vlc.table, bits = 9,  max_depth = 2)
{
 int code;
 unsigned int re_index = (gb)->index;
 unsigned int re_cache;
 re_cache = (*(uint32_t*)((gb)->buffer + (re_index >> 3)))  << (re_index & 7);

 int n, nb_bits;
 unsigned int index;
 index = ((uint32_t)re_cache)>>(32-bits);
 code = table[index][0];
 n = table[index][1];
 if(n < 0)递归查找
 re_cache >>= n;
 re_index += n;

 (gb)->index = re_index;
 return code;
}
以上是我宏展开的样子,re_cache忽略了小端序需要byteswap。
gb->buffer 的类型是uint8_t*, 读到re_cache是大端序。
可以看出比特流是怎么一个一个比特地读的,怎么精妙地处理字节边界的。
比如当前re_index = 17,表示当前读指针指向buffer第17比特,现在要读取9比特数据。
re_cache = buffer+2 开始的32比特,再左移1比特。
index = re_cache 右移32-9=23比特。
假设 index = '101xxxxxx',x表示值为任一0或1,为了在table里面一次性查出,这就解释了上面的疑问。
假设table已经按上面的计划根据B.12初始化好的,我们得到
code = 3, n = 3,则DC_size = code = 3,当前re_index 后移n比特,指向20。
接下来,DC_diff = get_xbits(s = gb, n = 3)
{
 int sign; 
 int32_t cache;
 unsigned int re_index = (gb)->index;
 unsigned int re_cache;
 re_cache = (*(uint32_t*)((gb)->buffer + (re_index >> 3)))  << (re_index & 7);
 cache = re_cache;
 sign = ~cache >> 31; /*若最高位为0,则符号扩展sign一定为0xffffffff*/ 
 re_index += n;
 (gb)->index = re_index;
 
 int tmp = (sign ^ cache) >> (32-n); /*取高n位, tmp是原始值的绝对值*/ 
 return (tmp ^ sign) - sign; /*若为负则取反加一*/
//这里括号是必须的,算术运算符真应该放在||,?:后面。大概就是Rithe说的设计缺陷吧。
}
假设读到cache = '100x xxxx xxxx xxxx xxxx xxxx xxxx xxxx',sign = 0,tmp = '100', return '100'。
最后两行为什么要这么绕呢?
起初我以为是统一处理encode_dc(int diff)的两个分支,反复想了下,不是的!
这里引出另一个问题,为什么有两个分支呢,只使用mpeg1_lum_dc_uni且不是更有效率?
原因是mpeg1_lum_dc_uni数组不能太大!(太大了你怎么移植到小内存的硬件呢?)
我们来看 encode_dc:
 adiff = FFABS(diff);
 if (diff < 0)diff--;
 index = av_log2(2 * adiff);
 code = diff & ((1 << index) - 1);

比如diff=-2, index = 2, code = 0xfffffffd & '11' = '01'
    diff=+2, index = 2, code = 0x2 & '11' = '10'
    diff=+3, index = 2, code = 0x3 & '11' = '11'
    diff=-3, index = 2, code = 0xfffffffc & '11' = '00'
诸位注意到了么,恰好是反码!
假设cache = '00xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx', 则sign = 1, tmp = 
通过在get_xbits()里面加打印也验证了这点:
'0':-1, '01':-2 , '00':-3, '011':-4, '010':-5, '001':-6。

注意put_bits()是_网络比特序_,即'011'应该表示为'011x'接28个x。

测试代码:


后续打算: 量化--> 变换--> 最复杂的预测。

附录
====
调试ffmpeg代码时需要关掉优化和内联,否则gdb里面bt,n等命令行号异常:
../ffmpeg/configure --prefix=$HOME/ffmpeg \
 --enable-version3 --enable-nonfree \
 --extra-cflags="-DDEBUG -fno-inline-small-functions"  --disable-optimizations 


下面给一个mpeg1解码的栈帧:
(gdb) bt
#0  get_xbits (s=0x1d69f00, n=6) at ../ffmpeg/libavcodec/get_bits.h:229
#1  0x00000000009d7f97 in decode_dc (gb=0x1d69f00, component=0)
    at ../ffmpeg/libavcodec/mpeg12.h:65
#2  0x00000000009d834e in mpeg1_decode_block_intra (s=0x1d67e20, block=0x1d93ee0, n=0)
    at ../ffmpeg/libavcodec/mpeg12dec.c:150
#3  0x00000000009db335 in mpeg_decode_mb (s=0x1d67e20, block=0x1d93ee0)
    at ../ffmpeg/libavcodec/mpeg12dec.c:852
#4  0x00000000009df666 in mpeg_decode_slice (s=0x1d67e20, mb_y=0, buf=0x7fffffffd9f0, buf_size=31469)
    at ../ffmpeg/libavcodec/mpeg12dec.c:1806
#5  0x00000000009e279b in decode_chunks (avctx=0x1d673a0, picture=0x1d6a4a0, got_output=0x7fffffffdc3c, 
    buf=0x1d7f0b0 "", buf_size=31501) at ../ffmpeg/libavcodec/mpeg12dec.c:2665
#6  0x00000000009e2b3e in mpeg_decode_frame (avctx=0x1d673a0, data=0x1d6a4a0, got_output=0x7fffffffdc3c, 
    avpkt=0x7fffffffdaf0) at ../ffmpeg/libavcodec/mpeg12dec.c:2742
#7  0x0000000000b79139 in avcodec_decode_video2 (avctx=0x1d673a0, picture=0x1d6a4a0, 
    got_picture_ptr=0x7fffffffdc3c, avpkt=0x7fffffffdbb0)
    at ../ffmpeg/libavcodec/utils.c:2286
#8  0x000000000064c8ad in try_decode_frame (s=0x1d66a80, st=0x1d670a0, avpkt=0x1d6a3a0, options=0x1d5e340)
    at ../ffmpeg/libavformat/utils.c:2593
#9  0x000000000064f026 in avformat_find_stream_info (ic=0x1d66a80, options=0x1d5e340)
    at ../ffmpeg/libavformat/utils.c:3219
#10 0x000000000040f2e5 in open_input_file (o=0x7fffffffe100, filename=0x7fffffffe924 "b3e.mpeg")
    at ../ffmpeg/ffmpeg_opt.c:887
#11 0x0000000000416cae in open_files (l=0x1d4e0d8, inout=0x10f1bd7 "input", open_file=0x40eb4f <open_input_file>)
    at ../ffmpeg/ffmpeg_opt.c:2650
#12 0x0000000000416e07 in ffmpeg_parse_options (argc=5, argv=0x7fffffffe688)
    at ../ffmpeg/ffmpeg_opt.c:2687
#13 0x0000000000428447 in main (argc=5, argv=0x7fffffffe688)
    at ../ffmpeg/ffmpeg.c:3859

Ref
[1] http://www.chinavideo.org/viewthread.php?action=printable&tid=1966

你可能感兴趣的:(Codec)