熵編碼部分看的:http://blog.csdn.net/nb_vol_1/article/details/71374859,因博主禁止轉載,請點鏈接去看原博主文章,寫的很清晰明瞭,學習後整理內容如下,方便以後查看。
熵編碼:
消息發生的概率越小,攜帶的信息量就越大。確定的消息沒有信息量,因爲你已經知道要發生什麼了。信息量定義:
由以上性質可知,序列中依賴性越強,不確定性就越弱。
二進制編碼:
變長編碼給大概率分配短碼字,給小概率分配長碼字,目標是使平均長最小。
指數哥倫布編碼:
編碼流程:
解碼流程:記錄0個數爲m,哥倫布編碼階數爲k,第一個1之後的值記爲value,
解碼值= (2的m+k次冪) - (2的m次冪) + value
零階指數哥倫布編碼:
假設輸入值是N:
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)對應的二進制串
HEVC中的熵編碼:
HEVC的熵編碼使用了兩種算術編碼:CABAC和CAVLC。CAVLC主要用於編碼SEI、參數集、片頭等,剩下的所有數據和語法元素均使用CABAC來編碼。
CAVLC中的編碼方法:
1、零階無符號哥倫布指數編碼
2、零階有符號指數哥倫布編碼
3、不編碼直接寫入若干比特
4、不編碼直接寫入一個比特
//無符號指數哥倫布編碼
Void SyntaxElementWriter::xWriteUvlc ( UInt uiCode )
{
UInt uiLength = 1;//哥倫布碼的長度
UInt uiTemp = ++uiCode;//執行UITemp= UICode + 1
assert ( uiTemp );
while( 1 != uiTemp )//計算長度,若uiTemp長度爲len,則uiLength=2*len-1
{
uiTemp >>= 1;
uiLength += 2;
}
// Take care of cases where uiLength > 32
m_pcBitIf->write( 0, uiLength >> 1);//寫入前綴,len-1個零
m_pcBitIf->write( uiCode, (uiLength+1) >> 1);//寫入後綴,uiTemp的二進制。
}
將哥倫布碼寫入比特流:
寫入比特流中的流程:
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 );
/* any modulo 8 remainder of num_total_bits cannot be written this time,
* and will be held until next time. */
//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;
/* form a byte aligned word (write_bits), by concatenating any held bits
* with the new bits, discarding the bits that will form the next_held_bits.
* eg: H = held bits, V = n new bits /---- next_held_bits
* len(H)=7, len(V)=1: ... ---- HHHH HHHV . 0000 0000, next_num_held_bits=0
* len(H)=7, len(V)=2: ... ---- HHHH HHHV . V000 0000, next_num_held_bits=1
* if total_bits < 8, the value of v_ is not used */
//將數據執行移位操作,放入next_held_bits中
UChar next_held_bits = uiBits << (8 - next_num_held_bits);
//判斷比特數是否大於8,若大於,表示緩衝區滿,要先寫入比特流中
if (!(num_total_bits >> 3))
{//不大於8,寫入緩衝區尾部
/* insufficient bits accumulated to write out, append new_held_bits to
* current held_bits */
/* NB, this requires that v only contains 0 in bit positions {31..n} */
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;
}
CABAC:
CABAC(上下文自適應的二進制算術編碼)基於算術編碼,在HEVC中,除了參數集、SEI和slice頭部之外,其餘的所有數據都使用CABAC來進行熵編碼。
CABAC有三個步驟:
1、初始化,構建上下文概率模型
2、根據上下文概率模型獲取語法元素的概率,對語法元素進行熵編碼
3、根據編碼結果更新上下文概率模型
1.初始化
初始化上下文模型,就是指初始化和上下文模型有關的兩個變量: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();
//幀內,Bslice,
if (!m_pcSlice->isIntra() && (encCABACTableIdx==B_SLICE || encCABACTableIdx==P_SLICE) && m_pcSlice->getPPS()->getCabacInitPresentFlag())
{
eSliceType = (SliceType) encCABACTableIdx;
}
//初始化各個模型的緩存
m_cCUSplitFlagSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_SPLIT_FLAG );
//split標誌的上下文
m_cCUSkipFlagSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_SKIP_FLAG );
//skip標誌的上下文
//merge標誌、索引上下文
m_cCUMergeFlagExtSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_MERGE_FLAG_EXT);
m_cCUMergeIdxExtSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_MERGE_IDX_EXT);
//PartSize上下文
m_cCUPartSizeSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_PART_SIZE );
//預測上下文
m_cCUPredModeSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_PRED_MODE );
//幀內預測
m_cCUIntraPredSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_INTRA_PRED_MODE );
//色度預測
m_cCUChromaPredSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_CHROMA_PRED_MODE );
//幀間角度
m_cCUInterDirSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_INTER_DIR );
//mv殘差
m_cCUMvdSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_MVD );
m_cCURefPicSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_REF_PIC );
m_cCUDeltaQpSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_DQP );
m_cCUQtCbfSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_QT_CBF );
m_cCUQtRootCbfSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_QT_ROOT_CBF );
//符號上下文
m_cCUSigCoeffGroupSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_SIG_CG_FLAG );
m_cCUSigSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_SIG_FLAG );
//最後一個X
m_cCuCtxLastX.initBuffer ( eSliceType, iQp, (UChar*)INIT_LAST );
//最後一個Y
m_cCuCtxLastY.initBuffer ( eSliceType, iQp, (UChar*)INIT_LAST );
m_cCUOneSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_ONE_FLAG );
m_cCUAbsSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_ABS_FLAG );
m_cMVPIdxSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_MVP_IDX );
m_cCUTransSubdivFlagSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_TRANS_SUBDIV_FLAG );
m_cSaoMergeSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_SAO_MERGE_FLAG );
m_cSaoTypeIdxSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_SAO_TYPE_IDX );
m_cTransformSkipSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_TRANSFORMSKIP_FLAG );
m_CUTransquantBypassFlagSCModel.initBuffer( eSliceType, iQp, (UChar*)INIT_CU_TRANSQUANT_BYPASS_FLAG );
// new structure
m_uiLastQp = iQp;
//二值化的操作
m_pcBinIf->start();
return;
}
根據initValue和量化參數計算MPS和δ
在下面的函數中,slope、offset、initState都是中間變量,mpState表示MPS,m_ucState表示δ
獲取概率進行熵編碼
語法元素對應的上下文模型初始化完成之後,開始進行二進制算術編碼。二進制算術編碼是對語法元素對應的二進制比特串進行算術編碼。二進制算術編碼包含兩種方式:旁路方式和常規方式。在旁路編碼方式中,二進制串的符號的概率是相同的,也不需要更新上下文概率模型;在常規方式中,二進制串中符號的概率可以由上下文模型中得到,對每一個符號編碼完成之後都需要對上下文模型進行更新。使用常規方式還是旁路方式是由語法元素決定的,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時)
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;
}
k階指數哥倫布碼
它由前綴和後綴構成,前綴和一元碼有點相似,由p個1和一個0構成,其中p=log2[x/(2^k)+1];後綴是q對應的二進制數,其中q=x+2^k*(1-2^p);HEVC中最常用的是1階指數哥倫布碼。
//指數哥倫布編碼
Void TEncSbac::xWriteEpExGolomb( UInt uiSymbol, UInt uiCount )
{//uiSymbol是元素值,uiCount 是階數
UInt bins = 0;
Int numBins = 0;
while( uiSymbol >= (UInt)(1<<uiCount) )
{//去掉後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、更新上下文模型。上下文模型的更新主要是對上下文模型的δ和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。
encodeBin中的編碼流程:
編碼流程:
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
//1.該比特對應的上下文模型設置爲已編碼
//2.計算LPS(最低概率值)
//3.計算新的範圍
//4.根據比特值是否和MPS相等,更新上下文模式(最大概率值)。
Void TEncBinCABAC::encodeBin( UInt binValue, ContextModel &rcCtxModel )
{
//調試的打印信息
{
DTRACE_CABAC_VL( g_nSymbolCounter++ )
DTRACE_CABAC_T( "\tstate=" )
DTRACE_CABAC_V( ( rcCtxModel.getState() << 1 ) + rcCtxModel.getMps() )
DTRACE_CABAC_T( "\tsymbol=" )
DTRACE_CABAC_V( binValue )
DTRACE_CABAC_T( "\n" )
}
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() )
{//binVal != valMPS 索引概率值將減小,即LPS的概率增大
//numBit用於歸一化
Int numBits = TComCABACTables::sm_aucRenormTable[ uiLPS >> 3 ];
//更新low=low+length_mps,使用<<numBits的目的是重歸一化
m_uiLow = ( m_uiLow + m_uiRange ) << numBits;
m_uiRange = uiLPS << numBits;
rcCtxModel.updateLPS();//用LPS對δ進行更新
m_bitsLeft -= numBits;
}
else//binVal == valMPS,概率索引值將增大,即LPS的概率減小
{//下界low不變,length更新爲length_mps(m_uiRange已經等於length_mps)
rcCtxModel.updateMPS();//更新δ
if ( m_uiRange >= 256 )//大於256不用歸一化
{
return;
}
m_uiLow <<= 1;
m_uiRange <<= 1;
m_bitsLeft--;
}
// 嘗試寫到比特流中,先判斷當前緩衝區中的空閒空間是否足夠,不足的話就寫到比特流中,騰出空間
testAndWriteOut();
}