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








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