由于帧间预测的需要,编码当前帧时需要参考其他帧,这些被参考的帧称为当前帧的参考帧。在前面一节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