H.264分帧-3.哥伦布编码

 上文说到根据slice头中的first_mb_in_slice来分帧。于是,查看了标准中的slice_header中first_mb_in_slice的描述符,发现为ue(v)。对ue(v)展开学习。

Golomb编码

 通过查询描述符的定义:

 — ue(v):无符号整数指数哥伦布码编码的语法元素,左位在先。

 该描述符涉及了一个新的名词,指数哥伦布编码,于是摘取了一段介绍哥伦布编码的文字。

 Golomb编码是一种无损的数据压缩方法,由数学家Solomon W.Golomb在1960年代发明。Golomb编码只能对非负整数进行编码,符号表中的符号出现的概率符合几何分布(Geometric Distribution)时,使用Golomb编码可以取得最优效果,也就是说Golomb编码比较适合小的数字比大的数字出现概率比较高的编码。它使用较短的码长编码较小的数字,较长的码长编码较大的数字。

 Golomb编码是一种分组编码,需要一个正整数参数m,然后以m为单位对待编码的数字进行分组,如下图,
哥伦布编码分组
 对于任一待编码的非负正整数N,Golomb编码将其分为两个部分:所在组的编号GroupID以及分组后余下的部分,GroupID实际是待编码数字N和参数m的商,余下的部分则是其商的余数,具体计算如下:
 Q = N / m,
 R = N % m
 对于得到的组号Q使用一元编码,余下部分R则使用固定长度的二进制编码。
 一元编码(Unary coding)是一种简单的只能对非负整数进行编码的方法,对于任意非负整数num,它的一元编码就是num个1后面紧跟着一个0。
 有了以上Golomb编码的介绍,可以进一步分析指数Golomb编码。

指数Golomb编码

 相对于普通的Golomb编码,指数Golomb编码有一个较大的改进,就是在分组的时候,不再使用固定的m作为大小,而组的大小是呈指数增长,m必须为2的次幂,见下图,
指数哥伦布编码分组
 指数Golomb编码的码元结构是:** [M zeros prefix] [1] [Offset] **,其中M是分组的编号GroupID,1可以看着是分隔符,Offset是组内的偏移量。
 指数Golomb需要一个非负整数K作为参数,称之为K阶指数Golomb。其中当K = 0时,称为0阶指数Golomb,我们需要用到的的H.264视频编码标准中使用的就是0阶的指数Golomb,并且可以将任意的阶数K转为0阶Exp-Golomb编码。
 我摘取了一张表来表现0阶的指数Golomb(表中是前导1与分隔符0),见下表
0阶指数哥伦布编码表
 由上表可知,对于m组(Group ID)的Golomb编码,可以表示的值的范围是
2 m 1 n < 2 m + 1 1 2^m-1 \leq n<2^{m+1}-1
 由此可得出对于值为n的非负整数,进行Golomb编码,组号为
m = l o g 2 n + 1 m = \left \lfloor log_{2}^{n+1} \right \rfloor
 有组号m之后,对于数值n的编码过程就可以表示出来了。
 - 对组号m进行一元编码,连续写入m个0
 - 计算出码元结构中的偏移量,offset = n - 2^m
 - 取偏移量offset二进制的低m位
 0阶指数Golomb编码,编码后的长度为2*m+1,其解码过程摘取建议书中9.1的介绍:

 这些语法元素的解析过程是由比特流当前位置比特开始读取,包括非0 比特,直至leading_bits 的数量为0。具体过程如下所示:

	leadingZeroBits = -1;
	for(b = 0; !b; leadingZeroBits++)
    	b = read_bits(1);

变量codeNum 按照如下方式赋值:
c o d e N u m = 2 l e a d i n g Z e r o B i t s 1 + r e a d _ b i t s ( l e a d i n g Z e r o B i t s ) codeNum = 2^{leadingZeroBits} - 1 + read\_bits(leadingZeroBits)
这里read_bits( leadingZeroBits )的返回值使用高位在先的二进制无符号整数表示。

优化:
 通过上述分析发现,要通过判断first_mb_in_slice的值来判断是否为新的一帧,只需要判断ue(v)是否为0即可,并不需要对整个Golomb编码进行反解析。反解析涉及较多运算,会消耗较长的时间。
 故在本例中实际运用时,只需要对slice_header中第一个字节进行一次与0x80的与运算即可。但为了后续拓展,在编写该函数时对Golomb是否计算具体值增加了一个开关参数bIsZero。
 结合上述,最终编写出指数Golomb代码如下:

unsigned int GolombCode(unsigned char *pbyData, int nBytePosition, bool bIsZero)
{
    int nLeadingZeroBits = -1;  // 前导零的位数
    unsigned int dwCodeNum = 0;          // CodeNum值
    unsigned int dwTail = 0;             // 编码后缀
    unsigned int dwOffset = 0;           // 移位偏移量
    unsigned char byPos = 0x80;           // & 变量
    unsigned int dwOffsetByte  = 0;      // 偏移字节数

    if (bIsZero)
    {
        return !(pbyData[nBytePosition+5] & byPos);
    }
    /* 得到前导零的数量 */
    for(int b = 0; !b; nLeadingZeroBits++)
    {
        if (!((nLeadingZeroBits + 1) % 8) && (nLeadingZeroBits + 1))
        {
            dwOffsetByte++;
            byPos = 0x80;
        }
        b = pbyData[nBytePosition + 5 + dwOffsetByte] & byPos;
        byPos >>= 1;
    }

    byPos = 0x80;

    /* 后缀计算 */
    for(int k = nLeadingZeroBits; k >= 0; k--)
    {
        dwOffset = 2 * nLeadingZeroBits - k + 1;
        dwTail += (u32)pow(2.0, k - 1) * (pbyData[nBytePosition + 5 + dwOffset / 8] & (byPos >> ((dwOffset % 8) ? 1 : 0)));
    }

    /* ue(v)的值计算 */
    dwCodeNum = (u32)pow(2.0, (s32)nLeadingZeroBits) - 1 + dwTail;

    return dwCodeNum;
}
发布了60 篇原创文章 · 获赞 18 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/BadAyase/article/details/103489291