x265对变换系数的编码(最后一个非零系数的位置编码部分)

对变换系数的编码是在codeCoeffNxN函数中完成的,先看参数列表。

void Entropy::codeCoeffNxN(const CUData& cu, const coeff_t* coeff, uint32_t absPartIdx, uint32_t log2TrSize, TextType ttype)

就看比较重要的吧,coeff是存储系数值的数组,log2TrSize表示TB的大小为NxN,这个参数值就为log2N,结构体TextType的定义为

typedef enum TextType
{
    TEXT_LUMA     = 0,  // luma
    TEXT_CHROMA_U = 1,  // chroma U
    TEXT_CHROMA_V = 2,  // chroma V
    MAX_NUM_COMPONENT = 3
}TextType;
表明数据类型,由彩色图像的YUV模型可以知道,TEXT_LUMA就表示灰度值,TEXT_CHROMA_U和TEXT_CHROMA_V表示的是色度分量。

由于这个函数的代码非常的长,这里就一部分一部分的贴吧。

    uint32_t trSize = 1 << log2TrSize;
    uint32_t tqBypass = cu.m_tqBypass[absPartIdx];
    // compute number of significant coefficients
    uint32_t numSig = primitives.cu[log2TrSize - 2].count_nonzero(coeff);
    X265_CHECK(numSig > 0, "cbf check fail\n");
    bool bHideFirstSign = cu.m_slice->m_pps->bSignHideEnabled && !tqBypass;

    if (log2TrSize <= MAX_LOG2_TS_SIZE && !tqBypass && cu.m_slice->m_pps->bTransformSkipEnabled)
        codeTransformSkipFlags(cu.m_transformSkip[ttype][absPartIdx], ttype);

    bool bIsLuma = ttype == TEXT_LUMA;

    // select scans
    TUEntropyCodingParameters codingParameters;
    cu.getTUEntropyCodingParameters(codingParameters, absPartIdx, log2TrSize, bIsLuma);

    uint8_t coeffNum[MLS_GRP_NUM];      // value range[0, 16]
    uint16_t coeffSign[MLS_GRP_NUM];    // bit mask map for non-zero coeff sign
    uint16_t coeffFlag[MLS_GRP_NUM];    // bit mask map for non-zero coeff

变量trSize存储的就是上面提到的TB大小原值N。中间的一部分代码涉及到了SDH技术,就是一种简化编码的一种方法,这里就不详细介绍了。bIsLuma根据前面的介绍判断该TB是亮度块还是色度块。

然后定义了codingParameters变量,其变量类型是结构体,存储的是一些编码参数。

typedef struct TUEntropyCodingParameters
{
	uint16_t *scan;
	uint16_t *scanCG;
	enum ScanType        scanType;
	uint32_t        log2TrSizeCG;
	uint32_t        firstSignificanceMapContext;
}TUEntropyCodingParameters;
scan表示子块内部系数的扫描顺序(相对于整个TB中),scanCG表示TB内子块的扫描顺序,scanType表示扫描方式。
enum ScanType
{
    SCAN_DIAG = 0,     // up-right diagonal scan
    SCAN_HOR = 1,      // horizontal first scan
    SCAN_VER = 2,      // vertical first scan
    NUM_SCAN_TYPE = 3
};

分别是对角扫描、水平扫描、垂直扫描方式。

紧接着的就是getTUEntropyCodingParameters函数,实现对上述编码参数的设置。参数具体怎么确定的这里就不论述了,log2TrSizeCG被赋值为log2TrSize-2,暂且先不谈它的含义。

最后创建了三个大小为64的数组,用途下面会讲。

//----- encode significance map -----

    // Find position of last coefficient
    int scanPosLast = 0;
    uint32_t posLast;
    uint64_t sigCoeffGroupFlag64 = 0;
    //const uint32_t maskPosXY = ((uint32_t)~0 >> (31 - log2TrSize + MLS_CG_LOG2_SIZE)) >> 1;
    X265_CHECK((uint32_t)((1 << (log2TrSize - MLS_CG_LOG2_SIZE)) - 1) == (((uint32_t)~0 >> (31 - log2TrSize + MLS_CG_LOG2_SIZE)) >> 1), "maskPosXY fault\n");

    scanPosLast = primitives.scanPosLast(codingParameters.scan, coeff, coeffSign, coeffFlag, coeffNum, numSig, g_scan4x4[codingParameters.scanType], trSize);
    posLast = codingParameters.scan[scanPosLast];

    const int lastScanSet = scanPosLast >> MLS_CG_SIZE;

    // Calculate CG block non-zero mask, the latest CG always flag as non-zero in CG scan loop
    for(int idx = 0; idx < lastScanSet; idx++)
    {
        const uint8_t subSet = (uint8_t)codingParameters.scanCG[idx];
        const uint8_t nonZero = (coeffNum[idx] != 0);
        sigCoeffGroupFlag64 |= ((nonZero ? (uint64_t)1 : 0) << subSet);
    }

变量scanPosLast表示的是TB经过扫描后第一个非零系数在CG数组中位置,即当前TB中最后一个非零系数的位置,具体的值是通过primitives.scanPosLast函数的执行得到的。

int scanPosLast_c(const uint16_t *scan, const coeff_t *coeff, uint16_t *coeffSign, uint16_t *coeffFlag, uint8_t *coeffNum, int numSig, const uint16_t* /*scanCG4x4*/, const int /*trSize*/)
{
    memset(coeffNum, 0, MLS_GRP_NUM * sizeof(*coeffNum));
    memset(coeffFlag, 0, MLS_GRP_NUM * sizeof(*coeffFlag));
    memset(coeffSign, 0, MLS_GRP_NUM * sizeof(*coeffSign));

    int scanPosLast = 0;
    do
    {
        const uint32_t cgIdx = (uint32_t)scanPosLast >> MLS_CG_SIZE;

        const uint32_t posLast = scan[scanPosLast++];

        const int curCoeff = coeff[posLast];
        const uint32_t isNZCoeff = (curCoeff != 0);
        numSig -= isNZCoeff;

        // TODO: optimize by instruction BTS
        coeffSign[cgIdx] += (uint16_t)(((uint32_t)curCoeff >> 31) << coeffNum[cgIdx]);
        coeffFlag[cgIdx] = (coeffFlag[cgIdx] << 1) + (uint16_t)isNZCoeff;
        coeffNum[cgIdx] += (uint8_t)isNZCoeff;
    }
    while (numSig > 0);
    return scanPosLast - 1;
}

前三个语句初始化了三个数组。

该函数遍历CG系数组中从第一个非零系数到最后一个系数,同时统计了TB块中的sig_coeff_flag语法元素(表示当前位置上的系数是否为0)、coeff_sign_flag语法元素(用于表示非零系数是正值还是负值)和非零系数的数目,分别记录在coeffFlag、coeffSign、coeffNum数组中。

通过这个函数可以推断出x265对系数位置的表示,以对角扫描为例,首先因为它是从CG系数组中的最后一个系数开始统计的,所以scanPosLast=0表示的位置就是左上角的位置。

  

再看之前提到的scan参量,该数组所存储的对于8x8对角扫描顺序的数据为

{ 0,   8,  1, 16,  9,  2, 24, 17, 10,  3, 25, 18, 11, 26, 19, 27, 32, 40, 33, 48, 41, 34, 56, 49, 42, 35, 57, 50, 43, 58, 51, 59,
      4,  12,  5, 20, 13,  6, 28, 21, 14,  7, 29, 22, 15, 30, 23, 31, 36, 44, 37, 52, 45, 38, 60, 53, 46, 39, 61, 54, 47, 62, 55, 63 }

因此,未扫描前,TB中的变换系数是从左上角开始,向右,逐行向下进行标序的。scanPosLast也是对CG中系数位置的特殊表述。

回到函数中,do循环就是遍历的过程,scanPosLast >> MLS_CG_SIZE(常数4),除16,表示当前系数所在的CG块。根据前面所述,posLast变量就表示扫描前系数在TB中的位置,curCoeff就存储该系数的值,isNZCoeff = (curCoeff != 0)判断该系数是否为非零系数,numSig存储的是剩余非零系数的个数。

 coeffSign[cgIdx] += (uint16_t)(((uint32_t)curCoeff >> 31) << coeffNum[cgIdx]);
该式先将curCoeff右移31位,也就是取出符号位,再左移当前所记录的非零系数数目,添加进数组值中,可见数组中的一个数表示一个CG,其中有16位数,分别表示CG中16个数的coeff_sign_flag值,TB中位置靠前的在低位。

coeffFlag[cgIdx] = (coeffFlag[cgIdx] << 1) + (uint16_t)isNZCoeff;                                           

采用同样的存储方法,但在该数组中,TB中位置靠前的在高位。coeffNum就不用说了,最后scanPosLast-1对应循环中的scanPosLast++,返回正确值。

回到最先前的函数,posLast和lastScanSet与上述具有相同的含义,subSet记录当前子块在TB中的位置,其位置的排序与扫描前系数在TB中的排序相同。nonZero = (coeffNum[idx] != 0)表示当前子块中是否有非零系数,并通过移位和或运算将该值存入sigCoeffGroupFlag64变量中,位置靠前的在低位。

    // Code position of last coefficient
    {
        // The last position is composed of a prefix and suffix.
        // The prefix is context coded truncated unary bins. The suffix is bypass coded fixed length bins.
        // The bypass coded bins for both the x and y components are grouped together.
        uint32_t packedSuffixBits = 0, packedSuffixLen = 0;
        uint32_t pos[2] = { (posLast & (trSize - 1)), (posLast >> log2TrSize) };
        // swap
        if (codingParameters.scanType == SCAN_VER)
            std::swap(pos[0], pos[1]);

        int ctxIdx = bIsLuma ? (3 * (log2TrSize - 2) + ((log2TrSize - 1) >> 2)) : NUM_CTX_LAST_FLAG_XY_LUMA;
        int ctxShift = bIsLuma ? ((log2TrSize + 1) >> 2) : log2TrSize - 2;
        uint32_t maxGroupIdx = (log2TrSize << 1) - 1;

        uint8_t *ctx = &m_contextState[OFF_CTX_LAST_FLAG_X];
        for (uint32_t i = 0; i < 2; i++, ctxIdx += NUM_CTX_LAST_FLAG_XY)
        {
            uint32_t temp = g_lastCoeffTable[pos[i]];
            uint32_t prefixOnes = temp & 15;
            uint32_t suffixLen = temp >> 4;

            for (uint32_t ctxLast = 0; ctxLast < prefixOnes; ctxLast++)
                encodeBin(1, *(ctx + ctxIdx + (ctxLast >> ctxShift)));

            if (prefixOnes < maxGroupIdx)
                encodeBin(0, *(ctx + ctxIdx + (prefixOnes >> ctxShift)));

            packedSuffixBits <<= suffixLen;
            packedSuffixBits |= (pos[i] & ((1 << suffixLen) - 1));
            packedSuffixLen += suffixLen;
        }

        encodeBinsEP(packedSuffixBits, packedSuffixLen);
    }

pos数组存储的就是第一个非零系数位置的xy坐标,trSize就是一行的长度,posLast&(trSize-1)取出仅表示行的二进制位数,而posLast>>log2TrSize就取出表示列的位数。

确定了最后一个非零系数的位置之后,就需要开始熵编码的步骤,xy值分别被分解为前缀和后缀,last_sig_coeff_x_prefix、last_sig_x_suffix、last_sig_coeff_y_prefix、last_sig_coeff_y_suffix,先编码前缀值,经TR(截断莱斯)二元化后进行常规编码(编码参数cMax = ( log2TrafoSize << 1 ) − 1, cRiceParam = 0),采用的上下文模型索引见下表,再编码后缀值,FL(定长)二元化后进行旁路编码。

ctxIdx和ctxShift两个变量是用于确定下表的


Bin索引表示二进制位数,其他数字表示的是上下文索引值,用于得到对应的上下文模型,ctxIdx就表示基础值,对应Bin索引为0的列,ctxShift表示相邻列之间取值的偏移量。

ctx数组存储了不同的上下文模型,maxGroupIdx = (log2TrSize << 1) - 1就等于编码参数cMax。参数前后缀的分解方法如下(以32x32的TB为例)


一种分组表示的方法吧,前缀值就是每组的起始数,而偏移量就是后缀,中间的二进制码是已经完成二元化后的值,该分解方法全部记录在g_lastCoeffTable数组中。

const uint8_t g_lastCoeffTable[32] =
{
    0x00, 0x01, 0x02, 0x03, 0x14, 0x14, 0x15, 0x15,
    0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27,
    0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
    0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
};

数组中参数的低四位表示前缀值,高四位表示后缀的二进制位数,且这两个值分别存储在prefixOnes和suffixLen变量中。对前缀的TR中,必定有P=前缀值=prefixOnes个‘1’,因此也就编码相应个数的‘1’,上下文模型的计算就是还原上述所讲的索引表,如果P<(cMax>>R)需要多加一个‘0’,代码中就是通过prefixOnes < maxGroupIdx体现的。

然后是后缀值,pos[i] & ((1 << suffixLen) - 1)取出后缀,通过或和移位运算将其二进制值添加进packedSuffixBits变量中。

last_sig_coeff_x_prefix共有18个不同的上下文初始化模型,且都存储在entropy->m_contextState数组中,last_sig_coeff_y_prefix也相同,因此从x到y上下文模型的切换只要在基值上加18就行,for循环中ctxIdx += NUM_CTX_LAST_FLAG_XY(18)该式就完成了切换功能。

最后是执行对后缀码流的旁路编码。




  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值