一、引言
由于视频的传输和存贮是十分在乎体积的,对于每一个比特(bit)都要格外珍惜,所以H.264中用到了多种熵编码来对原本的数据进行压缩。
比如Sequence Paramater Set(sps / 序列参数集)中,seq_parameter_set_id这个属性用到了无符号指数哥伦布编码ue(v):
offset_for_non_ref_pic这个属性用到了有符号指数哥伦布编码se(v):
要拿到sps中的上述属性,需要对H.264码流对应的位置进行指数哥伦布编码的解码。
二、指数哥伦布编码简介
哥伦布编码(又译作格伦布编码,英语:Golomb coding)是一种无失真资料压缩方法,由数学家所罗门·格伦布在1960年代提出。其优点为易于编码与解码,目前广泛用于无损影像压缩。它是一种变长编码。
其具体原理可以参考《百度百科:指数哥伦布码》
《维基百科:格伦布编码》
《Golomb Codes》
三、FFmpeg源码中 无符号指数哥伦布编码的解码实现
FFmpeg源码中通过get_ue_golomb、get_ue_golomb_long、get_ue_golomb_31等函数实现 对无符号指数哥伦布编码的解码。下面以get_ue_golomb_31函数为例,进行讲解。
get_ue_golomb_31函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为5.0.3)的头文件libavcodec/golomb.h中:
/*** read unsigned exp golomb code, constraint to a max of 31.* If the value encountered is not in 0..31, the return value* is outside the range 0..30.*/
static inline int get_ue_golomb_31(GetBitContext *gb)
{unsigned int buf;#if CACHED_BITSTREAM_READERbuf = show_bits_long(gb, 32);buf >>= 32 - 9;skip_bits_long(gb, ff_golomb_vlc_len[buf]);
#elseOPEN_READER(re, gb);UPDATE_CACHE(re, gb);buf = GET_CACHE(re, gb);buf >>= 32 - 9;LAST_SKIP_BITS(re, gb, ff_golomb_vlc_len[buf]);CLOSE_READER(re, gb);
#endifreturn ff_ue_golomb_vlc_code[buf];
}
形参gb:既是输入型参数也是输出型参数。指向已经被初始化的GetBitContext类型的变量。执行get_ue_golomb_31函数之前必须确保已经使用init_get_bits函数进行初始化。(关于GetBitContext结构体可以参考《FFmpeg中位操作相关的源码:GetBitContext结构体,init_get_bits函数、get_bits1函数和get_bits函数分析》)
如果是使用get_ue_golomb_31函数对某个NALU(比如sps)中的属性进行读取。
执行get_ue_golomb_31函数之前:
gb->buffer需指向存放该NALU的“NALU Header + RBSP 的缓冲区”。
gb->buffer_end需指向上述缓冲区的末尾,也就是RBSP的最后一个字节。
gb->index的值需等于:当前读取到该缓冲区的第几位了(单位为bit)。要对以该位为起始的数据进行无符号指数哥伦布编码的解码。
gb->size_in_bit 的值需等于NALU Header + SODB的位数,单位为bit。
gb->size_in_bits_plus8的值需等于 s->size_in_bit 的值 加 8。
执行get_ue_golomb_31函数后:
gb->index的值会加上 “读取到的无符号指数哥伦布编码后的位数”。gb的其它成员的值不变。
get_ue_golomb_31函数返回值为:对gb->index的位置 进行无符号指数哥伦布解码后得到的数据。
注意:get_ue_golomb_31函数有读取范围的限制!!!只能读取0到31的数据,所以如果get_ue_golomb_31函数的返回值大于31表示出错了。
所以FFmpeg源码中(源文件libavcodec/h264_ps.c)对sps进行解码的函数ff_h264_decode_seq_parameter_set中有这样的一段逻辑:
#define MAX_SPS_COUNT 32int ff_h264_decode_seq_parameter_set(GetBitContext *gb, AVCodecContext *avctx,H264ParamSets *ps, int ignore_truncation){//...sps_id = get_ue_golomb_31(gb);if (sps_id >= MAX_SPS_COUNT) {av_log(avctx, AV_LOG_ERROR, "sps_id %u out of range\n", sps_id);goto fail;}//...
}
如果读取到的sps_id值不小于32,表示out of range了。
如果想要更大的读取范围可以用get_ue_golomb函数和get_ue_golomb_long函数。它们的用法跟get_ue_golomb_31函数一样,只是读取范围更大而已。
四、FFmpeg源码中 有符号指数哥伦布编码的解码实现
FFmpeg源码中通过get_se_golomb和get_se_golomb_long等函数实现对 有符号指数哥伦布编码的解码。它们都定义在libavcodec/golomb.h中。
其形参跟get_ue_golomb_31函数相同,不同的地方为返回值是:对gb->index的位置 进行有符号指数哥伦布解码后得到的数据。