最近重构代码, 移植了ffmpeg源码, 有人问了两个问题:
1. MIN_CACHE_BITS的含义, 读取码值时为什么要判断该值
2. get_ue_golomb_long()的含义
这里简要分析下代码(原理性东西比如什么是哥伦布编码就不说了).
先来看下show_bits_long()(defined in libavcodec/get_bits.h), 为什么此处需要判断n的位数并走入两条分支?
1 static inline unsigned int show_bits_long(GetBitContext *s, int n) 2 { 3 if (n <= MIN_CACHE_BITS) { 4 return show_bits(s, n); 5 } else { 6 GetBitContext gb = *s; 7 return get_bits_long(&gb, n); 8 } 9 } 10 static inline unsigned int show_bits(GetBitContext *s, int n) 11 { 12 register int tmp; 13 OPEN_READER_NOSIZE(re, s); 14 av_assert2(n>0 && n<=25); 15 UPDATE_CACHE(re, s); 16 tmp = SHOW_UBITS(re, s, n); 17 return tmp; 18 } 19 static inline unsigned int get_bits_long(GetBitContext *s, int n) 20 { 21 av_assert2(n>=0 && n<=32); 22 if (!n) { 23 return 0; 24 } else if (n <= MIN_CACHE_BITS) { 25 return get_bits(s, n); 26 } else { 27 #ifdef BITSTREAM_READER_LE 28 unsigned ret = get_bits(s, 16); 29 return ret | (get_bits(s, n - 16) << 16); 30 #else 31 unsigned ret = get_bits(s, 16) << (n - 16); 32 return ret | get_bits(s, n - 16); 33 #endif 34 } 35 }
因为show_bits()一次读入int(4byte, 32bit)大小数据, 再根据当前读取bit数做移位操作(去除已读取的bit). 最坏的情况下需要左移7位(如果8位以上就是字节偏移了), 因此最少能保证读取25bit有效数据. 因此在已知读取位数少于25bit时走上面的fast path, 如果读取位数大于25bit呢? 那就走下面的slow path, 即先取地址两个字节, 再取高地址剩余的位. 那变长编码如何得知我要读取的位数呢? 再看下get_ue_golomb_long()(defined in libavcodec/golomb.h):
1 static inline unsigned get_ue_golomb_long(GetBitContext *gb) 2 { 3 unsigned buf, log; 4 buf = show_bits_long(gb, 32); 5 log = 31 - av_log2(buf); 6 skip_bits_long(gb, log); 7 return get_bits_long(gb, log + 1) - 1; 8 }
get_ue_golomb_long()先调用show_bits_long(), 传入的长度是32, 其逻辑是假定不存在32个连零的情况(否则实际值最小也是0xFFFFFFFF), 返回的值是当前读取位所在字节起始的连续四字节组成的unsigned int. av_log2()(defined in libavutil/intmath.h)是解析前导零个数的关键, 其思想分两步: 通过二分查找最高有效位所在的字节, 再通过查表得知最高有效位所在位的位数. 通过一个256字节的数组(ff_log2_tab[])节约了循环查找的时间开销. 最后用31减去最高位所在位数即得到前导零的个数.
1 #define av_log2 ff_log2 2 #define ff_log2 ff_log2_c 3 static av_always_inline av_const int ff_log2_c(unsigned int v) 4 { 5 int n = 0; 6 if (v & 0xffff0000) { 7 v >>= 16; 8 n += 16; 9 } 10 if (v & 0xff00) { 11 v >>= 8; 12 n += 8; 13 } 14 n += ff_log2_tab[v]; 15 return n; 16 }
回到get_ue_golomb_long(), 得到前导零个数后将其跳过再读n+1位即得到码值, 减去1即实际哥伦布编码数据.