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<=cMax2、把语法元素值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
};
前面看到了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;
}
}
}