由於幀間預測的需要,編碼當前幀時需要參考其他幀,這些被參考的幀稱爲當前幀的參考幀。在前面一節DPB中我們知道,DPB中包含參考圖像和非參考圖像。
參考圖像集(Reference Picture Set,RPS):RPS中包含DPB內的參考圖像,這些參考圖像可能被當前的圖像參考也可能被將來的圖像參考。
參考圖像列表( Reference Picture Lists,RPL):RPL中只包含當前圖像的參考圖像。
RPL是RPS的子集。
RPS和RPL中的參考圖像都是通過POC標識。
參考圖像可分爲短期參考圖像和長期參考圖像。可以參考文章《參考圖像》
Reference Picture Set
RPS中包含了當前或將來會使用的參考圖像。RPS中保存了每個參考圖像的POC,及爲每個參考圖像保存一個標識符表示是否被當前圖像參考。
根據RPS中參考圖像與當前圖像POC間的關係,RPS中參考圖像可被分爲5個子集。
-
RefPicSetStCurrBefore:由當前圖像的短期參考圖像,且POC小於當前圖像的圖像組成。
-
RefPicSetStCurrAfter:由當前圖像的短期參考圖像,且POC大於當前圖像的圖像組成。
-
RefPicSetStFoll:RPS中不被當前圖像參考的短期參考圖像。
-
RefPicSetLtCurr:當前圖像的長期參考圖像。
-
RefPicSetLtFoll:RPS中不被當前圖像參考的長期參考圖像。
下面是一個有三個時域子層的編碼結構和其參考圖像變化情況:
RPS傳輸
對於RPS中的每幅圖像都需要傳輸三部分信息:POC值、可用性、是長期還是短期參考圖像。
可用性:只需要1比特,1表示是當前圖像的參考圖像,0表示不是。
短期參考圖像的POC:分爲兩部分S0和S1。S0包含POC小於當前圖像的短期參考圖像,S1包含POC大於當前圖像的短期參考圖像。先傳輸S0,再傳輸S1。對每個部分按POC離當前圖像從近到遠排列,傳輸每個圖像與前一幅圖像POC的差值,第一幅圖像傳輸其與當前圖像POC的差值。
長期參考圖像的POC:長期參考圖像相距較遠,POC差值較大需要的碼字較長。所以長期參考圖像傳輸POC LSB。解碼時只需要將RPS中的POC LSB與DPB中所有圖像的POC LSB逐個比較。可能出現多個圖像POC LSB相同的情況,這時需要傳輸其POC MSB。
下表是上圖中B7的RPS的語法結構:
B7的POC=5,它的RPS包含3個短期參考圖像P1,B6,P5,P1屬於S0,B6、P5屬於S1。前兩個字段num_negative_pics和num_positive_pics分別表示S0和S1中圖像數量,分別是1和2。然後傳輸S0,S0只包含P1(POC=4),傳輸P1和當前圖像POC差值1。P1是當前圖像B7的參考圖像所以傳輸一個標識符1。
然後傳輸S1,S1包含B6(POC=6)和P5(POC=8)。傳輸B6和當前圖像B7的POC差值1,B6是當前圖像B7的參考圖像所以傳輸一個標識符1。傳輸P5和圖像B6的POC差值2,P5不是當前圖像B7的參考圖像所以傳輸一個標識符0。
然後傳輸RPS中長期參考圖像的數量,這裏只有1個長期參考圖像I0。然後傳輸長期參考圖像I0的POC LSB。I0不是當前圖像B7的參考圖像所以傳輸一個標識符0。這裏不需要POC MSB所以傳輸一個標識符0。
SPS和Slice header中的RPS信息
由於同一個圖像的多個slice、同一個GOP的多個圖像的RPS信息相似,如果每個slice header都傳輸完整的RPS會浪費大量比特。
爲了減少比特冗餘,可以將部分RPS信息放到SPS中,在slice header中引用相應信息。相關字段可以參考SPS和slice header的語法結構。
RPS相關定義如下:
/// Reference Picture Set class
class TComReferencePictureSet
{
private:
Int m_numberOfPictures;
Int m_numberOfNegativePictures; //!<S0中圖像數量
Int m_numberOfPositivePictures; //!<S1中圖像數量
Int m_numberOfLongtermPictures; //!<長期參考圖像數量
Int m_deltaPOC[MAX_NUM_REF_PICS]; //!<用於短期參考圖像
Int m_POC[MAX_NUM_REF_PICS]; //!<RPS中每個參考圖像的POC
Bool m_used[MAX_NUM_REF_PICS]; //!<表示每個參考圖像是否被當前圖像參考
Bool m_interRPSPrediction;
Int m_deltaRIdxMinus1;
Int m_deltaRPS;
Int m_numRefIdc;
Int m_refIdc[MAX_NUM_REF_PICS+1];
Bool m_bCheckLTMSB[MAX_NUM_REF_PICS];
Int m_pocLSBLT[MAX_NUM_REF_PICS];
Int m_deltaPOCMSBCycleLT[MAX_NUM_REF_PICS];
Bool m_deltaPocMSBPresentFlag[MAX_NUM_REF_PICS];
public:
TComReferencePictureSet();
virtual ~TComReferencePictureSet();
Int getPocLSBLT(Int i) const { return m_pocLSBLT[i]; }
Void setPocLSBLT(Int i, Int x) { m_pocLSBLT[i] = x; }
Int getDeltaPocMSBCycleLT(Int i) const { return m_deltaPOCMSBCycleLT[i]; }
Void setDeltaPocMSBCycleLT(Int i, Int x) { m_deltaPOCMSBCycleLT[i] = x; }
Bool getDeltaPocMSBPresentFlag(Int i) const { return m_deltaPocMSBPresentFlag[i]; }
Void setDeltaPocMSBPresentFlag(Int i, Bool x) { m_deltaPocMSBPresentFlag[i] = x; }
Void setUsed(Int bufferNum, Bool used);
Void setDeltaPOC(Int bufferNum, Int deltaPOC);
Void setPOC(Int bufferNum, Int deltaPOC);
Void setNumberOfPictures(Int numberOfPictures);
Void setCheckLTMSBPresent(Int bufferNum, Bool b );
Bool getCheckLTMSBPresent(Int bufferNum) const;
Int getUsed(Int bufferNum) const;
Int getDeltaPOC(Int bufferNum) const;
Int getPOC(Int bufferNum) const;
Int getNumberOfPictures() const;
Void setNumberOfNegativePictures(Int number) { m_numberOfNegativePictures = number; }
Int getNumberOfNegativePictures() const { return m_numberOfNegativePictures; }
Void setNumberOfPositivePictures(Int number) { m_numberOfPositivePictures = number; }
Int getNumberOfPositivePictures() const { return m_numberOfPositivePictures; }
Void setNumberOfLongtermPictures(Int number) { m_numberOfLongtermPictures = number; }
Int getNumberOfLongtermPictures() const { return m_numberOfLongtermPictures; }
Void setInterRPSPrediction(Bool flag) { m_interRPSPrediction = flag; }
Bool getInterRPSPrediction() const { return m_interRPSPrediction; }
Void setDeltaRIdxMinus1(Int x) { m_deltaRIdxMinus1 = x; }
Int getDeltaRIdxMinus1() const { return m_deltaRIdxMinus1; }
Void setDeltaRPS(Int x) { m_deltaRPS = x; }
Int getDeltaRPS() const { return m_deltaRPS; }
Void setNumRefIdc(Int x) { m_numRefIdc = x; }
Int getNumRefIdc() const { return m_numRefIdc; }
Void setRefIdc(Int bufferNum, Int refIdc);
Int getRefIdc(Int bufferNum) const ;
Void sortDeltaPOC();
Void printDeltaPOC() const;
};
Reference Picture Lists
從RPS中提取出當前圖像的參考圖像可以組成參考圖像列表RPL。對於雙向預測需要兩個列表list0和list1,單向預測只需要list0。
注意:同一個圖像的不同CTU參考圖像可以不同,雙向預測可以參考同一個圖像。
當P或B slice header解碼後,解碼器會爲當前slice構建RPL。會構建兩個RPL L0和L1,L0用於P和B slice,L1僅用於B slice。
下圖B7的RPL有5個參考圖像。
構建RPL首先要構建臨時RPL,然後通過臨時RPL構建最終RPL。
臨時RPL構建
臨時L0中首先按POC降序加入RPS RefPicSetStCurrBefore中當前圖像的參考圖像,這些都是POC小於當前圖像的短期參考圖像,上圖中即P1、B2。然後按POC升序加入RPS RefPicSetStCurrAfter中當前圖像的參考圖像,這些都是POC大於當前圖像的短期參考圖像,上圖中即B6、P5。最後加入長期參考圖像I0。則B7臨時L0爲{P1,B2,B6,P5,I0}。
臨時L1的構建和L0類似,交換RefPicSetStCurrBefore和RefPicSetStCurrAfter順序即可。則B7臨時L1爲{B6,P5,P1,B2,I0}。
對於L0和L1都是短期參考圖像在前,長期參考圖像在後。
最終RPL構建
在PPS中指定了L0和L1的長度,如果slice header中也指定了長度則覆蓋上面的值。最大允許長度爲15。如果L0和L1指定的長度分別小於臨時L0和L1,則最終L0和L1通過截斷臨時L0和L1實現。如果指定的長度大於臨時列表長度,則最終L0和L1通過重複臨時L0和L1中的值得到。例如臨時L0爲{P1,B2,B6,P5,I0},指定長度爲2,最終L0爲{P1,B2}。指定長度爲9,最終L0爲{P1,B2,B6,P5,I0,P1,B2,B6,P5}。
另一種通過臨時列表構建最終列表的方式是顯示傳輸用到的參考圖像索引。例如,L0指定的長度爲3,需要3個碼字指定參考圖像,如果3個碼字爲1,1,0則最終L0爲{B2,B2,P1}。
構建RPL相關代碼如下:
Void TComSlice::setRefPicList( TComList<TComPic*>& rcListPic, Bool checkNumPocTotalCurr )
{
if ( m_eSliceType == I_SLICE)
{
::memset( m_apcRefPicList, 0, sizeof (m_apcRefPicList));
::memset( m_aiNumRefIdx, 0, sizeof ( m_aiNumRefIdx ));
if (!checkNumPocTotalCurr)
{
return;
}
}
TComPic* pcRefPic= NULL;
static const UInt MAX_NUM_NEGATIVE_PICTURES=16;
TComPic* RefPicSetStCurr0[MAX_NUM_NEGATIVE_PICTURES];
TComPic* RefPicSetStCurr1[MAX_NUM_NEGATIVE_PICTURES];
TComPic* RefPicSetLtCurr[MAX_NUM_NEGATIVE_PICTURES];
UInt NumPicStCurr0 = 0;
UInt NumPicStCurr1 = 0;
UInt NumPicLtCurr = 0;
Int i;
//!<RefPicSetStCurrBefore中的圖像
for(i=0; i < m_pRPS->getNumberOfNegativePictures(); i++)
{
if(m_pRPS->getUsed(i))
{
pcRefPic = xGetRefPic(rcListPic, getPOC()+m_pRPS->getDeltaPOC(i));
pcRefPic->setIsLongTerm(0);
pcRefPic->getPicYuvRec()->extendPicBorder();
RefPicSetStCurr0[NumPicStCurr0] = pcRefPic;
NumPicStCurr0++;
pcRefPic->setCheckLTMSBPresent(false);
}
}
//!<RefPicSetStCurrAfter中的圖像
for(; i < m_pRPS->getNumberOfNegativePictures()+m_pRPS->getNumberOfPositivePictures(); i++)
{
if(m_pRPS->getUsed(i))
{
pcRefPic = xGetRefPic(rcListPic, getPOC()+m_pRPS->getDeltaPOC(i));
pcRefPic->setIsLongTerm(0);
pcRefPic->getPicYuvRec()->extendPicBorder();
RefPicSetStCurr1[NumPicStCurr1] = pcRefPic;
NumPicStCurr1++;
pcRefPic->setCheckLTMSBPresent(false);
}
}
//!<長期參考圖像
for(i = m_pRPS->getNumberOfNegativePictures()+m_pRPS->getNumberOfPositivePictures()+m_pRPS->getNumberOfLongtermPictures()-1; i > m_pRPS->getNumberOfNegativePictures()+m_pRPS->getNumberOfPositivePictures()-1 ; i--)
{
if(m_pRPS->getUsed(i))
{
pcRefPic = xGetLongTermRefPic(rcListPic, m_pRPS->getPOC(i), m_pRPS->getCheckLTMSBPresent(i));
pcRefPic->setIsLongTerm(1);
pcRefPic->getPicYuvRec()->extendPicBorder();
RefPicSetLtCurr[NumPicLtCurr] = pcRefPic;
NumPicLtCurr++;
}
if(pcRefPic==NULL)
{
pcRefPic = xGetLongTermRefPic(rcListPic, m_pRPS->getPOC(i), m_pRPS->getCheckLTMSBPresent(i));
}
pcRefPic->setCheckLTMSBPresent(m_pRPS->getCheckLTMSBPresent(i));
}
// ref_pic_list_init
TComPic* rpsCurrList0[MAX_NUM_REF+1];
TComPic* rpsCurrList1[MAX_NUM_REF+1];
Int numPicTotalCurr = NumPicStCurr0 + NumPicStCurr1 + NumPicLtCurr;
if (checkNumPocTotalCurr)
{
// The variable NumPocTotalCurr is derived as specified in subclause 7.4.7.2. It is a requirement of bitstream conformance that the following applies to the value of NumPocTotalCurr:
// - If the current picture is a BLA or CRA picture, the value of NumPocTotalCurr shall be equal to 0.
// - Otherwise, when the current picture contains a P or B slice, the value of NumPocTotalCurr shall not be equal to 0.
if (getRapPicFlag())
{
assert(numPicTotalCurr == 0);
}
if (m_eSliceType == I_SLICE)
{
return;
}
assert(numPicTotalCurr > 0);
// general tier and level limit:
assert(numPicTotalCurr <= 8);
}
//!<構建臨時L0
Int cIdx = 0;
for ( i=0; i<NumPicStCurr0; i++, cIdx++)
{
rpsCurrList0[cIdx] = RefPicSetStCurr0[i];
}
for ( i=0; i<NumPicStCurr1; i++, cIdx++)
{
rpsCurrList0[cIdx] = RefPicSetStCurr1[i];
}
for ( i=0; i<NumPicLtCurr; i++, cIdx++)
{
rpsCurrList0[cIdx] = RefPicSetLtCurr[i];
}
assert(cIdx == numPicTotalCurr);
if (m_eSliceType==B_SLICE)
{//!<構建臨時L1
cIdx = 0;
for ( i=0; i<NumPicStCurr1; i++, cIdx++)
{
rpsCurrList1[cIdx] = RefPicSetStCurr1[i];
}
for ( i=0; i<NumPicStCurr0; i++, cIdx++)
{
rpsCurrList1[cIdx] = RefPicSetStCurr0[i];
}
for ( i=0; i<NumPicLtCurr; i++, cIdx++)
{
rpsCurrList1[cIdx] = RefPicSetLtCurr[i];
}
assert(cIdx == numPicTotalCurr);
}
::memset(m_bIsUsedAsLongTerm, 0, sizeof(m_bIsUsedAsLongTerm));
for (Int rIdx = 0; rIdx < m_aiNumRefIdx[REF_PIC_LIST_0]; rIdx ++)
{
cIdx = m_RefPicListModification.getRefPicListModificationFlagL0() ? m_RefPicListModification.getRefPicSetIdxL0(rIdx) : rIdx % numPicTotalCurr;
assert(cIdx >= 0 && cIdx < numPicTotalCurr);
m_apcRefPicList[REF_PIC_LIST_0][rIdx] = rpsCurrList0[ cIdx ]; //!<構建最終L0
m_bIsUsedAsLongTerm[REF_PIC_LIST_0][rIdx] = ( cIdx >= NumPicStCurr0 + NumPicStCurr1 );
}
if ( m_eSliceType != B_SLICE )
{
m_aiNumRefIdx[REF_PIC_LIST_1] = 0;
::memset( m_apcRefPicList[REF_PIC_LIST_1], 0, sizeof(m_apcRefPicList[REF_PIC_LIST_1]));
}
else
{
for (Int rIdx = 0; rIdx < m_aiNumRefIdx[REF_PIC_LIST_1]; rIdx ++)
{
cIdx = m_RefPicListModification.getRefPicListModificationFlagL1() ? m_RefPicListModification.getRefPicSetIdxL1(rIdx) : rIdx % numPicTotalCurr;
assert(cIdx >= 0 && cIdx < numPicTotalCurr);
m_apcRefPicList[REF_PIC_LIST_1][rIdx] = rpsCurrList1[ cIdx ];//!<構建最終L1
m_bIsUsedAsLongTerm[REF_PIC_LIST_1][rIdx] = ( cIdx >= NumPicStCurr0 + NumPicStCurr1 );
}
}
}
感興趣的請關注微信公衆號Video Coding