在繼續介紹CABAC之前,先穿插進另外幾種相對而言較爲簡單的熵編碼方式。下圖是截取自draft 7.3.2.1的關於VPS(Video Parameter Set)的句法元素描述:
關注表格中"Descriptor”這一欄,當中的描述符有u,ue,分別表示無符號整型、無符號整型0階指數哥倫布編碼方式,可以在HM中找到與表格相對應的一段代碼:
Void TEncCavlc::codeVPS( TComVPS* pcVPS )
{
WRITE_CODE( pcVPS->getVPSId(), 4, "video_parameter_set_id" );
WRITE_FLAG( pcVPS->getTemporalNestingFlag(), "vps_temporal_id_nesting_flag" );
#if VPS_REARRANGE
WRITE_CODE( 3, 2, "vps_reserved_three_2bits" );
#else
WRITE_CODE( 0, 2, "vps_reserved_zero_2bits" );
#endif
WRITE_CODE( 0, 6, "vps_reserved_zero_6bits" );
WRITE_CODE( pcVPS->getMaxTLayers() - 1, 3, "vps_max_sub_layers_minus1" );
#if VPS_REARRANGE
WRITE_CODE( 0xffff, 16, "vps_reserved_ffff_16bits" );
codePTL( pcVPS->getPTL(), true, pcVPS->getMaxTLayers() - 1 );
#else
codePTL( pcVPS->getPTL(), true, pcVPS->getMaxTLayers() - 1 );
WRITE_CODE( 0, 12, "vps_reserved_zero_12bits" );
#endif
#if SIGNAL_BITRATE_PICRATE_IN_VPS
codeBitratePicRateInfo(pcVPS->getBitratePicrateInfo(), 0, pcVPS->getMaxTLayers() - 1);
#endif
#if HLS_ADD_SUBLAYER_ORDERING_INFO_PRESENT_FLAG
const Bool subLayerOrderingInfoPresentFlag = 1;
WRITE_FLAG(subLayerOrderingInfoPresentFlag, "vps_sub_layer_ordering_info_present_flag");
#endif /* HLS_ADD_SUBLAYER_ORDERING_INFO_PRESENT_FLAG */
for(UInt i=0; i <= pcVPS->getMaxTLayers()-1; i++)
{
WRITE_UVLC( pcVPS->getMaxDecPicBuffering(i), "vps_max_dec_pic_buffering[i]" );
WRITE_UVLC( pcVPS->getNumReorderPics(i), "vps_num_reorder_pics[i]" );
WRITE_UVLC( pcVPS->getMaxLatencyIncrease(i), "vps_max_latency_increase[i]" );
#if HLS_ADD_SUBLAYER_ORDERING_INFO_PRESENT_FLAG
if (!subLayerOrderingInfoPresentFlag)
{
break;
}
#endif /* HLS_ADD_SUBLAYER_ORDERING_INFO_PRESENT_FLAG */
}
#if VPS_OPERATING_POINT
assert( pcVPS->getNumHrdParameters() <= MAX_VPS_NUM_HRD_PARAMETERS );
assert( pcVPS->getMaxNuhReservedZeroLayerId() < MAX_VPS_NUH_RESERVED_ZERO_LAYER_ID_PLUS1 );
WRITE_UVLC( pcVPS->getNumHrdParameters(), "vps_num_hrd_parameters" );
WRITE_CODE( pcVPS->getMaxNuhReservedZeroLayerId(), 6, "vps_max_nuh_reserved_zero_layer_id" );
for( UInt opIdx = 0; opIdx < pcVPS->getNumHrdParameters(); opIdx++ )
{
if( opIdx > 0 )
{
// operation_point_layer_id_flag( opIdx )
for( UInt i = 0; i <= pcVPS->getMaxNuhReservedZeroLayerId(); i++ )
{
WRITE_FLAG( pcVPS->getOpLayerIdIncludedFlag( opIdx, i ), "op_layer_id_included_flag[opIdx][i]" );
}
}
// TODO: add hrd_parameters()
}
#else
WRITE_UVLC( 0, "vps_num_hrd_parameters" );
// hrd_parameters
#endif
WRITE_FLAG( 0, "vps_extension_flag" );
//future extensions here..
return;
}
兩者相對照之後,可以發現,WRITE_CODE對應的就是表格中的"u",而WRITE_UVLC對應的就是表格中的"ue"
#define WRITE_CODE( value, length, name) xWriteCode ( value, length )
#define WRITE_UVLC( value, name) xWriteUvlc ( value )
WRITE_CODE和WRITE_UVLC都是宏,實際調用的是xWriteCode和xWriteUvlc,宏在這個地方的主要作用是爲在代碼中處理句法元素的名字提供方便,一方面提高代碼的可讀性,另一方面,這個句法元素的命名對實際編碼並沒用處,將其作爲函數輸入參數不合理。而定義了宏後,則巧妙地解決了這一矛盾。
不羅嗦,直接分析這兩個函數:
Void SyntaxElementWriter::xWriteCode ( UInt uiCode, UInt uiLength )
{
assert ( uiLength > 0 );
m_pcBitIf->write( uiCode, uiLength ); //!< 將uiCode以二進制的方式寫入m_fifo中(長度爲uiLength)
}
Void SyntaxElementWriter::xWriteUvlc ( UInt uiCode )
{
UInt uiLength = 1;
UInt uiTemp = ++uiCode;
assert ( uiTemp );
while( 1 != uiTemp )
{
uiTemp >>= 1;
uiLength += 2;
}
// Take care of cases where uiLength > 32
m_pcBitIf->write( 0, uiLength >> 1);
m_pcBitIf->write( uiCode, (uiLength+1) >> 1);
}
第一個函數比較簡單,就不分析了,對於第二個函數,分析它的最好方法就是代入具體值進行調試。比如,當uiCode=0時,經過分析可得到寫入m_fifo的數據爲1,當uiCode=1時,可得到寫入m_fifo的數據爲010,當uiCode=2時,可得到寫入m_fifo的數據爲011,... ... 以此類推。結果可以參考draft中的Table 9-2,如下所示:
與ue(v)相對應的,還有se(v),是帶符號的指數哥倫布編碼,其相關代碼如下所示:
#define WRITE_SVLC( value, name) xWriteSvlc ( value )
Void SyntaxElementWriter::xWriteSvlc ( Int iCode )
{
UInt uiCode;
uiCode = xConvertToUInt( iCode );
xWriteUvlc( uiCode );
}
UInt xConvertToUInt ( Int iValue ) { return ( iValue <= 0) ? -iValue<<1 : (iValue<<1)-1; }
xWriteSvlc先調用xConvertToUInt將帶符號的iCode轉換爲無符號的uiCode,再調用xWriteSvlc完成編碼工作,從帶符號到無符號的轉換過程可以參考draft 的 Table 9-3,如下圖所示: