在继续介绍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,如下图所示: