HEVC中的CABAC

HEVC中的CABAC


    CABAC(上下文自适应的二进制算术编码)基于算术编码,在HEVC中,除了参数集、SEI和slice头部之外,其余的所有数据都使用CABAC来进行熵编码。


     CABAC有三个步骤:

    1、初始化,构建上下文概率模型
    2、根据上下文概率模型获取语法元素的概率,对语法元素进行熵编码
    3、根据编码结果更新上下文概率模型



初始化


    初始化上下文模型,就是指初始化和上下文模型有关的两个变量:MPS和δ。MPS是最大概率符号,它表示待编码符号可能出现符号,对于二进制算术编码来说,MPS是0或者1,相反,LPS表示待编码符号不可能出现的符号,对于二进制算术编码来说,LPS也是0或1;δ表示概率的状态索引,它的值与LPS的概率值是相对应的,δ值随着LPS概率的更新而变化。MPS和δ唯一的确定上下文模型的状态,以及后续如何对上下模型进行更新。
    计算MPS和δ需要一个初始值initValue,initValue的值与slice的类型、初始量化参数有关。HEVC为每一个语法元素都定义了不同的initValue,为了方便,可以通过slice的类型和量化参数查表来得到initValue的值。initValue表示起始概率值。

    我们以MPS和δ来表示上下文概率模型,或者说,上下文概率模型的主要参数是MPS和δ。


初始化的入口函数


    它的主要功能是:获取QP和slice的类型,然后调用ContextModel3DBuffer::initBuffer进行上下文概率模型的初始化。

Void TEncSbac::resetEntropy           ()
{
	Int  iQp              = m_pcSlice->getSliceQp();
	SliceType eSliceType  = m_pcSlice->getSliceType();

	Int  encCABACTableIdx = m_pcSlice->getPPS()->getEncCABACTableIdx();
	if (!m_pcSlice->isIntra() && (encCABACTableIdx==B_SLICE || encCABACTableIdx==P_SLICE) && m_pcSlice->getPPS()->getCabacInitPresentFlag())
	{
		eSliceType = (SliceType) encCABACTableIdx;
	}

	// 初始化各个模型的缓存

	// split标志的上下文
	m_cCUSplitFlagSCModel.initBuffer       ( eSliceType, iQp, (UChar*)INIT_SPLIT_FLAG );

	// *** 省略其他
	
	// new structure
	m_uiLastQp = iQp;

	// 二值化的一些操作
	m_pcBinIf->start();

	return;
}



根据slice的类型获取存放initValue的表格


    需要注意的是,initValue的值是和slice的类型有关的,因此计算initValue的时候需要使用slice的类型。为了提高计算速度,HEVC实现的时候,使用查表的方式来代替直接计算,ctxModel(例如:INIT_SPLIT_FLAG等)中存放了是与slice类型有关的initValue。

    得到存放initValue的表格之后,以QP和initValue为参数调用ContextModel::init,计算MPS和δ。

Void ContextModel3DBuffer::initBuffer( SliceType sliceType, Int qp, UChar* ctxModel )
{ 
	ctxModel += sliceType * m_sizeXYZ;   // 根据当前slice的类型(I,P,B)选择对应的context,为什么这么做,下面会解释 
	// 根据sliceType计算initType并将context指针移动到正确的位置上,这个initType用于索引context model,且由slice_type来决定

	for ( Int n = 0; n < m_sizeXYZ; n++ )
	{
		m_contextModel[ n ].init( qp, ctxModel[ n ] );		// 完成context的各个状态变量的初始化工作
		m_contextModel[ n ].setBinsCoded( 0 );
	}
}
    下面是存放initValue的表格,每个语法元素对应一个表格:

// initial probability for cu_transquant_bypass flag
static const UChar
	INIT_CU_TRANSQUANT_BYPASS_FLAG[3][NUM_CU_TRANSQUANT_BYPASS_FLAG_CTX] =
{
	{ 154 }, 
	{ 154 }, 
	{ 154 }, 
};

// initial probability for split flag
static const UChar 
	INIT_SPLIT_FLAG[3][NUM_SPLIT_FLAG_CTX] =  
{
	{ 107,  139,  126, },
	{ 107,  139,  126, }, 
	{ 139,  141,  157, }, 
};

static const UChar 
	INIT_SKIP_FLAG[3][NUM_SKIP_FLAG_CTX] =  
{
	{ 197,  185,  201, }, 
	{ 197,  185,  201, }, 
	{ CNU,  CNU,  CNU, }, 
};

static const UChar
	INIT_MERGE_FLAG_EXT[3][NUM_MERGE_FLAG_EXT_CTX] = 
{
	{ 154, }, 
	{ 110, }, 
	{ CNU, }, 
};

static const UChar 
	INIT_MERGE_IDX_EXT[3][NUM_MERGE_IDX_EXT_CTX] =  
{
	{ 137, }, 
	{ 122, }, 
	{ CNU, }, 
};

static const UChar 
	INIT_PART_SIZE[3][NUM_PART_SIZE_CTX] =  
{
	{ 154,  139,  154,  154 },
	{ 154,  139,  154,  154 },
	{ 184,  CNU,  CNU,  CNU },
};

static const UChar
	INIT_PRED_MODE[3][NUM_PRED_MODE_CTX] = 
{
	{ 134, }, 
	{ 149, }, 
	{ CNU, }, 
};

static const UChar 
	INIT_INTRA_PRED_MODE[3][NUM_ADI_CTX] = 
{
	{ 183, }, 
	{ 154, }, 
	{ 184, }, 
};

static const UChar 
	INIT_CHROMA_PRED_MODE[3][NUM_CHROMA_PRED_CTX] = 
{
	{ 152,  139, }, 
	{ 152,  139, }, 
	{  63,  139, }, 
};

static const UChar 
	INIT_INTER_DIR[3][NUM_INTER_DIR_CTX] = 
{
	{  95,   79,   63,   31,  31, }, 
	{  95,   79,   63,   31,  31, }, 
	{ CNU,  CNU,  CNU,  CNU, CNU, }, 
};

static const UChar 
	INIT_MVD[3][NUM_MV_RES_CTX] =  
{
	{ 169,  198, }, 
	{ 140,  198, }, 
	{ CNU,  CNU, }, 
};

static const UChar 
	INIT_REF_PIC[3][NUM_REF_NO_CTX] =  
{
	{ 153,  153 }, 
	{ 153,  153 }, 
	{ CNU,  CNU }, 
};

static const UChar 
	INIT_DQP[3][NUM_DELTA_QP_CTX] = 
{
	{ 154,  154,  154, }, 
	{ 154,  154,  154, }, 
	{ 154,  154,  154, }, 
};

static const UChar 
	INIT_QT_CBF[3][2*NUM_QT_CBF_CTX] =  
{
	{ 153,  111,  CNU,  CNU,   149,   92,  167,  154 },
	{ 153,  111,  CNU,  CNU,   149,  107,  167,  154 },
	{ 111,  141,  CNU,  CNU,    94,  138,  182,  154 },
};

static const UChar 
	INIT_QT_ROOT_CBF[3][NUM_QT_ROOT_CBF_CTX] = 
{
	{  79, }, 
	{  79, }, 
	{ CNU, }, 
};

static const UChar 
	INIT_LAST[3][2*NUM_CTX_LAST_FLAG_XY] =  
{
	{ 125,  110,  124,  110,   95,   94,  125,  111,  111,   79,  125,  126,  111,  111,   79,
	108,  123,   93,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU, 
	}, 
	{ 125,  110,   94,  110,   95,   79,  125,  111,  110,   78,  110,  111,  111,   95,   94,
	108,  123,  108,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,
	}, 
	{ 110,  110,  124,  125,  140,  153,  125,  127,  140,  109,  111,  143,  127,  111,   79, 
	108,  123,   63,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU,  CNU, 
	}, 
};

static const UChar 
	INIT_SIG_CG_FLAG[3][2 * NUM_SIG_CG_FLAG_CTX] =  
{
	{ 121,  140,  
	61,  154, 
	}, 
	{ 121,  140, 
	61,  154, 
	}, 
	{  91,  171,  
	134,  141, 
	}, 
};

static const UChar 
	INIT_SIG_FLAG[3][NUM_SIG_FLAG_CTX] = 
{
	{ 170,  154,  139,  153,  139,  123,  123,   63,  124,  166,  183,  140,  136,  153,  154,  166,  183,  140,  136,  153,  154,  166,  183,  140,  136,  153,  154,  170,  153,  138,  138,  122,  121,  122,  121,  167,  151,  183,  140,  151,  183,  140,  }, 
	{ 155,  154,  139,  153,  139,  123,  123,   63,  153,  166,  183,  140,  136,  153,  154,  166,  183,  140,  136,  153,  154,  166,  183,  140,  136,  153,  154,  170,  153,  123,  123,  107,  121,  107,  121,  167,  151,  183,  140,  151,  183,  140,  }, 
	{ 111,  111,  125,  110,  110,   94,  124,  108,  124,  107,  125,  141,  179,  153,  125,  107,  125,  141,  179,  153,  125,  107,  125,  141,  179,  153,  125,  140,  139,  182,  182,  152,  136,  152,  136,  153,  136,  139,  111,  136,  139,  111,  }, 
};

static const UChar 
	INIT_ONE_FLAG[3][NUM_ONE_FLAG_CTX] = 
{
	{ 154,  196,  167,  167,  154,  152,  167,  182,  182,  134,  149,  136,  153,  121,  136,  122,  169,  208,  166,  167,  154,  152,  167,  182, }, 
	{ 154,  196,  196,  167,  154,  152,  167,  182,  182,  134,  149,  136,  153,  121,  136,  137,  169,  194,  166,  167,  154,  167,  137,  182, }, 
	{ 140,   92,  137,  138,  140,  152,  138,  139,  153,   74,  149,   92,  139,  107,  122,  152,  140,  179,  166,  182,  140,  227,  122,  197, }, 
};

static const UChar 
	INIT_ABS_FLAG[3][NUM_ABS_FLAG_CTX] =  
{
	{ 107,  167,   91,  107,  107,  167, }, 
	{ 107,  167,   91,  122,  107,  167, }, 
	{ 138,  153,  136,  167,  152,  152, }, 
};

static const UChar 
	INIT_MVP_IDX[3][NUM_MVP_IDX_CTX] =  
{
	{ 168 },
	{ 168 },
	{ CNU }, 
};

static const UChar 
	INIT_SAO_MERGE_FLAG[3][NUM_SAO_MERGE_FLAG_CTX] = 
{
	{ 153,  }, 
	{ 153,  }, 
	{ 153,  }, 
};

static const UChar 
	INIT_SAO_TYPE_IDX[3][NUM_SAO_TYPE_IDX_CTX] = 
{
	{ 160, },
	{ 185, },
	{ 200, },
};

static const UChar
	INIT_TRANS_SUBDIV_FLAG[3][NUM_TRANS_SUBDIV_FLAG_CTX] =
{
	{ 224,  167,  122, },
	{ 124,  138,   94, },
	{ 153,  138,  138, },
};

static const UChar
	INIT_TRANSFORMSKIP_FLAG[3][2*NUM_TRANSFORMSKIP_FLAG_CTX] = 
{
	{ 139,  139}, 
	{ 139,  139}, 
	{ 139,  139}, 
};
//! \}




根据initValue和量化参数计算MPS和δ


    在下面的函数中,slope、offset、initState都是中间变量,mpState表示MPS,m_ucState表示δ

Void ContextModel::init( Int qp, Int initValue )
{
    // 选取中间值
	qp = Clip3(0, 51, qp);

	// 与draft 9.3.1.1基本呈一一对应关系
	Int  slope      = (initValue>>4)*5 - 45;			// m
	Int  offset     = ((initValue&15)<<3)-16;		// n
	Int  initState  =  min( max( 1, ( ( ( slope * qp ) >> 4 ) + offset ) ), 126 );		// preCtxState  
	UInt mpState    = (initState >= 64 );				// valMPS  
	m_ucState       = ( (mpState? (initState - 64):(63 - initState)) <<1) + mpState;		// pStateIdx,与(9-5)式略有不同,这里的m_ucState的值实际上是draft中pStateIdx<<1+valMPS,这么做的目的应该是为了节省内存
}





获取概率进行熵编码

    语法元素对应的上下文模型初始化完成之后,开始进行二进制算术编码。二进制算术编码是对语法元素对应的二进制比特串进行算术编码。二进制算术编码包含两种方式:旁路方式和常规方式。在旁路编码方式中,二进制串的符号的概率是相同的,也不需要更新上下文概率模型;在常规方式中,二进制串中符号的概率可以由上下文模型中得到,对每一个符号编码完成之后都需要对上下文模型进行更新。使用常规方式还是旁路方式是由语法元素决定的,HEVC文档指明了哪些语法元素使用旁路方式哪些语法元素使用常规方式。

    在对语法元素进行编码之前需要对它进行二进制化。


二进制化

    理论上,HEVC的二进制方法有:
    1、一元码 
    2、截断一元码 
    3、指数哥伦布码
    4、截断莱斯码
    5、定长码

    由于在实际中,由于很多语法元素的值都是0或者1,因此,很多语法元素不需要二进制化就可以直接进行编码,只有少部分才会进行二进制化。例如mvp-index、delta-qp等语法元素使用截断一元码进行二进制化;mvd等语法元素使用指数哥伦布来进行二进制化。


一元码

    假设语法的元素值是x,那么它对应的一元码由前缀x个1和后缀一个0构成:11...10。假设x=5,那么它的一元码是111110

/*
** 一元码的实现
*/
Void TEncSbac::xWriteUnarySymbol( UInt uiSymbol, ContextModel* pcSCModel, Int iOffset )
{
	m_pcBinIf->encodeBin( uiSymbol ? 1 : 0, pcSCModel[0] );

	if( 0 == uiSymbol)
	{
		return;
	}

	while( uiSymbol-- )
	{
		m_pcBinIf->encodeBin( uiSymbol ? 1 : 0, pcSCModel[ iOffset ] );
	}

	return;
}

截断一元码

    1、把语法元素之转换成一元码,假如语法元素值是x,那么它的一元码由起始的x个1和最后一个0组成。
    2、给定一个最大的可能值cMax,bins的长度不能超过cMax,如果超过,那么就对bins的尾部进行截断
    3、例如,给定一个语法元素的值是5,cMax是4
        (1)5对应的一元码是111110
        (2)由于一元码的长度大于cMax,因此需要对它进行截断
        (3)截断之后为1111,因此5对应的截断一元码是1111(当cMax等于4时)

/*
** uiSymbol:语法元素值
** uiMaxSymbol:cMax
*/
Void TEncSbac::xWriteUnaryMaxSymbol( UInt uiSymbol, ContextModel* pcSCModel, Int iOffset, UInt uiMaxSymbol )
{
	if (uiMaxSymbol == 0)
	{
		return;
	}

	// 先编码第一个编码一元码的第一个比特
	m_pcBinIf->encodeBin( uiSymbol ? 1 : 0, pcSCModel[ 0 ] );

	if ( uiSymbol == 0 )
	{
		return;
	}

	// 判断是否需要截断
	Bool bCodeLast = ( uiMaxSymbol > uiSymbol );

	// 编码x个1(一元码)
	while( --uiSymbol )
	{
		m_pcBinIf->encodeBin( 1, pcSCModel[ iOffset ] );
	}
	
	// 不需要截断,直接在后面添加0
	if( bCodeLast )
	{
		m_pcBinIf->encodeBin( 0, pcSCModel[ iOffset ] );
	}

	return;
}	

截断莱斯码
    HEVC的实现中好像没有用到,这里不再介绍


k阶指数哥伦布码
    它由前缀和后缀构成,前缀和一元码有点相似,由p个1和一个0构成,其中p=log2[x/(2^k)+1];后缀是q对应的二进制数,其中q=x+2^k*(1-2^p);HEVC中最常用的是1阶指数哥伦布码。

/*
** K阶指数哥伦布 二进制化
** uiSymbol:语法元素值
** uiCount:阶数K
** HEVC最常用的是1阶
*/
Void TEncSbac::xWriteEpExGolomb( UInt uiSymbol, UInt uiCount )
{
	UInt bins = 0;
	Int numBins = 0;

	while( uiSymbol >= (UInt)(1<<uiCount) )
	{
		bins = 2 * bins + 1;
		numBins++;
		uiSymbol -= 1 << uiCount;
		uiCount  ++;
	}
	bins = 2 * bins + 0;
	numBins++;

	bins = (bins << uiCount) | uiSymbol;
	numBins += uiCount;

	assert( numBins <= 32 );
	m_pcBinIf->encodeBinsEP( bins, numBins );
}


定长码
    1、给定一个与语法元素值x和一个参数cMax,限定0<=x<=cMax
    2、把语法元素值x转换成二进制bins,这就是它的定长码
    3、HEVC中没有给出具体的实现,因为使用定长码的语法元素大部分值都是0或者1,可以直接编码



常规编码


    对于常规编码方式,编码完一个比特符号之后需要更新上下文模型和编码区间。为了减少更新上下文模型时候的计算量,HEVC定义了几个表格,分别是transMPS、transLPS和rangeLPS,transMPS和transLPS的作用是更新δ,rangeLPS存储了LPS对应的子区间。是当为了方便描述,假设当前编码的符号是bin,把当前的编码区间的长度和下界设置为length和low。

    上下文模型的更新分为两个步骤:
    1、更新上下文模型。上下文模型的更新主要是对上下文模型的δ和MPS变量进行更新。如果bin等于MPS,那么δ_new=transMPS[δ];否则δ_new=transLPS[δ],而且如果δ等于0,需要互换MPS和LPS
    2、更新编码区间。主要是移动编码区间的下界low或者重新计算区间的长度length。首先计算LPS的子区间的长度length_lps=rangeLPS[δ][(length?6)&3],对应的MPS的子区间的长度length_mps=length-length_lps;如果比特符号x等MPS,那么区间下界low不变,length更新为length_mps;否则,low=low+length_mps,length更新为length_lps。

Void TEncBinCABAC::encodeBin( UInt binValue, ContextModel &rcCtxModel )
{
	// 调试的打印信息,省略***
	
	// 比特计数,如果开启了计数功能就计数
	m_uiBinsCoded += m_binCountIncrement;

	// 设置已经编码的标志
	rcCtxModel.setBinsCoded( 1 );

	// 查表获取LPS对应的子区间的长度
	UInt  uiLPS   = TComCABACTables::sm_aucLPSTable[ rcCtxModel.getState() ][ ( m_uiRange >> 6 ) & 3 ];

	// m_uiRange表示MPS对应的子区间的长度
	m_uiRange    -= uiLPS;

	// 如果二进制符号不等于MPS
	if( binValue != rcCtxModel.getMps() )		
	{
		// numBits用于重归一化
		Int numBits = TComCABACTables::sm_aucRenormTable[ uiLPS >> 3 ];		// RenormE 
		
		// 更新low=low+length_mps,使用“<< numBits”的目的是重归一化
		m_uiLow     = ( m_uiLow + m_uiRange ) << numBits;		// codILow = codILow + codIRange
		
		// 更新length = length_lps,“<< numBits”的目的是重归一化
		m_uiRange   = uiLPS << numBits;									// codIRange = codIRangeLPS

		// 使用rangeLPS表格对δ进行更新
		rcCtxModel.updateLPS();													// pStateIdx = transIdxLPS[pStateIdx] 
			
		// 
		m_bitsLeft -= numBits;  
	}
	else// binVal == valMPS,概率索引值将增大,即LPS的概率减小
	{
		/*
		** 下界low不变,length更新为length_mps(m_uiRange已经等于length_mps)
		*/
		
		// 使用transMPS表格对δ进行更新
		rcCtxModel.updateMPS();			// pStateIdx = transIdxLPS[pStateIdx] 
		
		// 如果length大于等于256,那么不用重归一化,直接返回
		if ( m_uiRange >= 256 )
		{
			return;
		}

		// 重归一化
		m_uiLow <<= 1;
		m_uiRange <<= 1;
		m_bitsLeft--;
	}

	// 尝试写到比特流中,先判断当前缓冲区中的空闲空间是否足够,不足的话就写到比特流中,腾出空间
	testAndWriteOut();
}
    编码流程:
    1、首先计算LPS对应的子区间的长度,通过查表得到:length_lps=rangeLPS[δ][(length?6)&3],对应的代码是:
UInt  uiLPS   = TComCABACTables::sm_aucLPSTable[ rcCtxModel.getState() ][ ( m_uiRange >> 6 ) & 3 ];
其中rcCtxModel.getState()返回δ
    2、计算MPS对应的子区间的长度:m_uiRange    -= uiLPS;
    3、如果二进制符号不等于MPS,low=low+length_mps,length更新为length_lps

    4、如果二进制符号等于MPS,下界low不变,length更新为length_mps


transMPS表格

// 即transMPS表格
const UChar ContextModel::m_aucNextStateMPS[ 128 ] =
{
	2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
	18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
	34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
	50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
	66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
	82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97,
	98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
	114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 124, 125, 126, 127
};

transLPS表格

// 即transLPS表格
const UChar ContextModel::m_aucNextStateLPS[ 128 ] =
{
	1, 0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 9, 8, 9, 10, 11,
	12, 13, 14, 15, 16, 17, 18, 19, 18, 19, 22, 23, 22, 23, 24, 25,
	26, 27, 26, 27, 30, 31, 30, 31, 32, 33, 32, 33, 36, 37, 36, 37,
	38, 39, 38, 39, 42, 43, 42, 43, 44, 45, 44, 45, 46, 47, 48, 49,
	48, 49, 50, 51, 52, 53, 52, 53, 54, 55, 54, 55, 56, 57, 58, 59,
	58, 59, 60, 61, 60, 61, 60, 61, 62, 63, 64, 65, 64, 65, 66, 67,
	66, 67, 66, 67, 68, 69, 68, 69, 70, 71, 70, 71, 70, 71, 72, 73,
	72, 73, 72, 73, 74, 75, 74, 75, 74, 75, 76, 77, 76, 77, 126, 127
};

rangeLPS表格

// 即rangeLPS
const UChar TComCABACTables::sm_aucLPSTable[64][4] =
{
	{ 128, 176, 208, 240},
	{ 128, 167, 197, 227},
	{ 128, 158, 187, 216},
	{ 123, 150, 178, 205},
	{ 116, 142, 169, 195},
	{ 111, 135, 160, 185},
	{ 105, 128, 152, 175},
	{ 100, 122, 144, 166},
	{  95, 116, 137, 158},
	{  90, 110, 130, 150},
	{  85, 104, 123, 142},
	{  81,  99, 117, 135},
	{  77,  94, 111, 128},
	{  73,  89, 105, 122},
	{  69,  85, 100, 116},
	{  66,  80,  95, 110},
	{  62,  76,  90, 104},
	{  59,  72,  86,  99},
	{  56,  69,  81,  94},
	{  53,  65,  77,  89},
	{  51,  62,  73,  85},
	{  48,  59,  69,  80},
	{  46,  56,  66,  76},
	{  43,  53,  63,  72},
	{  41,  50,  59,  69},
	{  39,  48,  56,  65},
	{  37,  45,  54,  62},
	{  35,  43,  51,  59},
	{  33,  41,  48,  56},
	{  32,  39,  46,  53},
	{  30,  37,  43,  50},
	{  29,  35,  41,  48},
	{  27,  33,  39,  45},
	{  26,  31,  37,  43},
	{  24,  30,  35,  41},
	{  23,  28,  33,  39},
	{  22,  27,  32,  37},
	{  21,  26,  30,  35},
	{  20,  24,  29,  33},
	{  19,  23,  27,  31},
	{  18,  22,  26,  30},
	{  17,  21,  25,  28},
	{  16,  20,  23,  27},
	{  15,  19,  22,  25},
	{  14,  18,  21,  24},
	{  14,  17,  20,  23},
	{  13,  16,  19,  22},
	{  12,  15,  18,  21},
	{  12,  14,  17,  20},
	{  11,  14,  16,  19},
	{  11,  13,  15,  18},
	{  10,  12,  15,  17},
	{  10,  12,  14,  16},
	{   9,  11,  13,  15},
	{   9,  11,  12,  14},
	{   8,  10,  12,  14},
	{   8,   9,  11,  13},
	{   7,   9,  11,  12},
	{   7,   9,  10,  12},
	{   7,   8,  10,  11},
	{   6,   8,   9,  11},
	{   6,   7,   9,  10},
	{   6,   7,   8,   9},
	{   2,   2,   2,   2}
};

重归一化的表格

// 归一化的时候使用到的表格
const UChar TComCABACTables::sm_aucRenormTable[32] =
{
	6,  5,  4,  4,
	3,  3,  3,  3,
	2,  2,  2,  2,
	2,  2,  2,  2,
	1,  1,  1,  1,
	1,  1,  1,  1,
	1,  1,  1,  1,
	1,  1,  1,  1
};



旁路编码


    旁路编码也叫等概率编码,它不使用上下文概率模型,二进制符号0和1的概率都是1/2。为了是区间划分操作更加简便,不直接对区间长度进行二等分,而是采用保存编码区间长度不变,使区间下限low的值加倍的方法来实现区间的划分,效果是一样的。

Void TEncBinCABAC::encodeBinEP( UInt binValue )
{
	// 调试的打印信息,省略***
	
	m_uiBinsCoded += m_binCountIncrement;
	
	// low的值加倍
	m_uiLow <<= 1;
	
	// 如果符号值等于1
	if( binValue )
	{
		// 更新low=low + length
		m_uiLow += m_uiRange;
	}
	
	// 重归一化
	m_bitsLeft--;

	testAndWriteOut();
}


把数据写到比特流中

    先看看TEncBinCABAC的定义

class TEncBinCABAC : public TEncBinIf
{
	// *** 省略了类的函数和接口
	
	Void testAndWriteOut();
	Void writeOut();

	// 比特处理接口:比特流的处理
	TComBitIf*          m_pcTComBitIf;
	// 二进制算数编码的下限
	UInt                m_uiLow;
	// 二进制算数编码的范围
	UInt                m_uiRange;
	// 缓冲区
	UInt                m_bufferedByte; 
	// 已经缓存的数据的长度
	Int                 m_numBufferedBytes;
	// 缓冲区中剩余的比特数
	Int                 m_bitsLeft;
	// 已经编码的比特数
	UInt                m_uiBinsCoded;
	// 增长的比特数,和m_uiBinsCoded一起进行计数
	Int                 m_binCountIncrement;

#if FAST_BIT_EST
	UInt64 m_fracBits;
#endif
};


    TEncSbac表示CABAC编码类,它定义了怎么样把一个语法元素编码成比特;TEncBinCABAC是TEncSbac使用的二进制编码类,是TEncSbac的工具类或者辅助类。

    前面看到了TEncBinCABAC::encodeBin函数中会调用testAndWriteOut函数,testAndWriteOut函数的作用是尝试往比特流中写入数据。

/*
** 尝试写到比特流中
** 先判断当前缓冲区中的空闲空间是否足够,不足的话就写到比特流中,腾出空间
*/
Void TEncBinCABAC::testAndWriteOut()
{
	if ( m_bitsLeft < 12 )
	{
		writeOut();
	}
}


    把数据写入比特流中。这个函数的细节不太懂,希望哪位大神告知一声。
/*
** 把已经编码的数据写到比特流中
*/
Void TEncBinCABAC::writeOut()
{
	UInt leadByte = m_uiLow >> (24 - m_bitsLeft);
	m_bitsLeft += 8;
	m_uiLow &= 0xffffffffu >> m_bitsLeft;

	if ( leadByte == 0xff )
	{
		m_numBufferedBytes++;
	}
	else
	{
		if ( m_numBufferedBytes > 0 )
		{
			UInt carry = leadByte >> 8;
			UInt byte = m_bufferedByte + carry;
			m_bufferedByte = leadByte & 0xff;
			m_pcTComBitIf->write( byte, 8 );

			byte = ( 0xff + carry ) & 0xff;
			while ( m_numBufferedBytes > 1 )
			{
				m_pcTComBitIf->write( byte, 8 );
				m_numBufferedBytes--;
			}
		}
		else
		{
			m_numBufferedBytes = 1;
			m_bufferedByte = leadByte;
		}      
	}    
}








  • 9
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值