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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章