HEVC中的CAVLC

HEVC中的CAVLC


CAVLC 基于上下文的自适应变长编码


    首先,HEVC的熵编码使用了两种算术编码:CABAC和CAVLC。CAVLC主要用于编码SEI、参数集、片头等,剩下的所有数据和语法元素均使用CABAC来编码。

    HEVC标准文档中使用到的一些描述符(描述符也表示操作方法):
    1、ae(v) 使用cabac
    2、b(8) 读进连续的8 bit
    3、f(n) 读进连续的n bit
    4、u(n) 读进连续的n bit,解码为无符号整数
    5、se(v) 有符号指数哥伦布编码
    6、ue(n) 无符号哥伦布指数编码



HEVC中与CAVLC有关的类


    HEVC中与CAVLC有关的类:SyntaxElementWriter、SEIWriterTEncCavlc

    1、SyntaxElementWriter语法元素写入者,定义了CAVLC的几种基本算法
    2、SEIWriter,SEI写入者,由于SEI使用CAVLC来编码,因此SEIWriter继承自SyntaxElementWriter
    3、TEncCavlc,CAVLC编码器,主要用于编码参数集以及slice头部等,继承自SyntaxElementWriter



HEVC中CAVLC的几种基本的算法


    CAVLC只是一个统称,它包含了多个算法:
    1、零阶无符号哥伦布指数编码
    2、零阶有符号指数哥伦布编码
    3、不编码直接写入若干比特
    4、不编码直接写入一个比特

class SyntaxElementWriter
{
protected:
	TComBitIf*    m_pcBitIf;

	SyntaxElementWriter()
		:m_pcBitIf(NULL)
	{};
	virtual ~SyntaxElementWriter() {};

    // 设置比特流
	Void  setBitstream          ( TComBitIf* p )  { m_pcBitIf = p;  }

	Void  xWriteCode            ( UInt uiCode, UInt uiLength ); // 不编码直接写入若干比特
	Void  xWriteUvlc            ( UInt uiCode ); // 零阶无符号哥伦布指数编码
	Void  xWriteSvlc            ( Int  iCode   ); // 零阶有符号指数哥伦布编码
	Void  xWriteFlag            ( UInt uiCode ); // 不编码直接写入一个比特

	UInt  xConvertToUInt        ( Int iValue ) {  return ( iValue <= 0) ? -iValue<<1 : (iValue<<1)-1; }
};


指数哥伦布编码的理论


    指数哥伦布编码由前缀和后缀两部分构成,前缀和后缀都依赖于指数哥伦布码的阶数k。假设指数哥伦布码是N,阶数为k,下面是它的编码步骤:
    (1)把N转换为二进制数,去掉最低的k个比特位,然后加上1
    (2)计算留下的比特数,把这个数减去1,这就是需要增加的前缀0的个数
    (3)把步骤(1)中去掉的k个比特位补回比特串的尾部。



零阶指数哥伦布编码


    根据上面的定义,可以得到零阶指数哥伦布编码的计算方法,假设输入值是N:
    1、把N转换成二进制,假设转换后是bins,计算bins += 1
    2、计算bins的比特数,假设是M,那么在bins的前面添加上M-1个0就得到最终的结果
    或者
    1、计算N += 1
    2、把N转换成二进制串bins,计算二进制串的长度,假设是M,那么在bins的前面添加M-1个0

    假设输入值N对应的二进制串的长度是len,那么零阶哥伦布码的长度是2*len-1
    哥伦布码golomb = 前缀prefix + 后缀suffix
    前缀prefix : len - 1个0
    后缀suffix : (N+1)对应的二进制串



零阶指数哥伦布编码的实现


/*
** 无符号指数哥伦布编码
*/
Void SyntaxElementWriter::xWriteUvlc     ( UInt uiCode )
{
	UInt uiLength = 1; // 哥伦布码的长度
	UInt uiTemp = ++uiCode; // 执行uiTemp = uiCode + 1

	assert ( uiTemp );

	while( 1 != uiTemp ) // 假设uiTemp(即uiCode + 1)对应的二进制长度是len,那么哥伦布码的长度 = 2 * len - 1
	{
		uiTemp >>= 1;
		uiLength += 2;
	}

	m_pcBitIf->write( 0, uiLength >> 1); // 写入前缀: len - 1个0
	m_pcBitIf->write( uiCode, (uiLength+1) >> 1); // 写入后缀:uiTemp对应的二进制
}
    上面算法的步骤:
    1、计算N+=1

    2、计算N对应的二进制串bins的比特数,假设是len

    3、把前缀(M-1个0)写入比特流中

    4、把后缀(N对应的二进制串)写入比特流流中


把零阶指数哥伦布码写入比特流中

    写入比特流中的流程:

    1、TComOutputBitstream包含两个部分:缓冲区和比特流
    2、比特流就是已经处理完成的数据,存放在一个vector中
    3、缓冲区暂存还没有写入比特流中的数据,TComOutputBitstream使用一个uchar类型的数据(8 bit)作为缓存区,因为很多时候写入的数据长度只有若干比特,不能直接写入比特流中,需要等到缓冲区满(达到8bit),才写入
    3、m_num_held_bits表示缓冲区中已有的比特数
    4、num_total_bits表示写入数据之后,缓冲区中总的比特数(可能会溢出,后面会解决这个问题)
    5、next_num_held_bits有两种意思:
        (1)如果缓冲区没有溢出,那么它表示缓冲区中总的比特数(已有+新增)
        (2)如果缓冲区溢出,那么它表示数据占用完缓冲区后还需要的比特数,只能存放数据的一部分,剩下那部分需要等缓冲区的数据写入比特流之后再存放
    6、next_held_bits是格式化之后的数据
        (1)如果缓冲区不溢出,那它表示将要写入缓冲区中的数据
        (2)如果缓冲区溢出,那它表示将一部分数据写入缓冲区之后,剩下的那部分数据
    7、判断num_total_bits是否大于8,即判断新增数据之后,缓冲区是否会溢出
    8、如果缓冲区不溢出,那么把数据写入缓冲区中,然后返回
    9、如果缓冲区溢出,那么先写一部分数据到缓冲区中,然后把缓冲区写入比特流中,清空缓冲区,继续把剩余的数据写入缓冲区中

Void TComOutputBitstream::write   ( UInt uiBits, UInt uiNumberOfBits )
{
	assert( uiNumberOfBits <= 32 );
	assert( uiNumberOfBits == 32 || (uiBits & (~0 << uiNumberOfBits)) == 0 );

	// m_num_held_bits缓存区中已经持有的比特数
	// num_total_bits表示写入uiBits之后,缓存区中的总比特数
	UInt num_total_bits = uiNumberOfBits + m_num_held_bits;
	
	// next_num_held_bits表示缓存区中经使用的比特数
	UInt next_num_held_bits = num_total_bits % 8;
	
	// 把数据执行位移操作之后,存放进一个临时变量中,
	UChar next_held_bits = uiBits << (8 - next_num_held_bits);

	// 判断当前持有的比特数是否大于8,如果大于8,表示缓冲区已经满了,需要先写入比特流中
	if (!(num_total_bits >> 3))
	{
		// 把数据写入缓冲区的尾部
		m_held_bits |= next_held_bits;
		m_num_held_bits = next_num_held_bits;
		return;
	}

	/* topword serves to justify held_bits to align with the msb of uiBits */
	// 把一部分数据写入
	UInt topword = (uiNumberOfBits - next_num_held_bits) & ~((1 << 3) -1);
	UInt write_bits = (m_held_bits << topword) | (uiBits >> next_num_held_bits);

	// 判断num_total_bits的长度:32,24,16,8
	switch (num_total_bits >> 3)
	{
	case 4: m_fifo->push_back(write_bits >> 24);
	case 3: m_fifo->push_back(write_bits >> 16);
	case 2: m_fifo->push_back(write_bits >> 8);
	case 1: m_fifo->push_back(write_bits);
	}

	m_held_bits = next_held_bits;
	m_num_held_bits = next_num_held_bits;
}



  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值