x265源码分析:sao.cpp 自适应样点补偿

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nk_wavelet/article/details/52554537
/* 对|num / den|四舍五入,然后前面添加符号 */
inline int32_t roundIBDI(int32_t num, int32_t den)
{
    return num >= 0 ? ((num * 2 + den)/(den * 2)) : -((-num * 2 + den)/(den * 2));
}

/* 获取输入变量x的符号 */
inline int8_t signOf(int x)
{
    return (x >> 31) | ((int)((((uint32_t)-x)) >> 31));
}

/* a等于b返回0, a小于b就返回-1,a大于b就返回1 */
inline int signOf2(const int a, const int b)
{
    int r = 0;
    if (a < b)  r = -1;
    if (a > b)  r = 1;
    return r;
}

/**
 * @brief 计算D_post和 D_pre的差值,其中D_pre和D_post分别表示原始像素与重构像素(SAO补偿前、补偿后)之间的失真。
 * @param count  : 一个CTB内某个特定SAO类型样本的个数
 * @param offset : 一个CTB内某个特定SAO类型样本的补偿值
 * @param offsetOrg : 原始像素与重构像素(SAO补偿前)之间的差值之和
 */
inline int64_t estSaoDist(int32_t count, int32_t offset, int32_t offsetOrg)
{
    return (count * offset - offsetOrg * 2) * offset;
}

/**
 * @brief 边界补偿模式下像素的5种分类 :
 *        第1类谷点和第2类凹拐点,需要加上一个正补偿值;
 *        第4类峰点和第3类凸拐点,需要加上一个负补偿值;
 *        第0类像素不进行补偿。
 */
const uint32_t SAO::s_eoTable[NUM_EDGETYPE] =
{
    1, // 0
    2, // 1
    0, // 2
    3, // 3
    4  // 4
};

/**
 * @brief 创建SAO的部分参数
 */
bool SAO::create(x265_param* param, int initCommon)
{
    m_param = param;                        // 编码器参数集
    m_chromaFormat = param->internalCsp;    // 内部图像颜色空间,此处只考虑 I420

    // 色度水平和垂直方向移动的位数,对于I420格式的图像,此处都是1
    m_hChromaShift = CHROMA_H_SHIFT(param->internalCsp);
    m_vChromaShift = CHROMA_V_SHIFT(param->internalCsp);

    // 计算水平和垂直方向CU(编码单元)的个数,长度不足 g_maxCUSize 也算一个;
    // maxCUSize 表示CU的最大尺寸,此处取值为 64.
    m_numCuInWidth =  (m_param->sourceWidth + g_maxCUSize - 1) / g_maxCUSize;
    m_numCuInHeight = (m_param->sourceHeight + g_maxCUSize - 1) / g_maxCUSize;

    // maxY表示亮度的最大值,对于8位深度的图像来说,该最大值为255;
    // rangeExt 扩展范围为最大值的一半,此处为127;
    // numCtu 表示一帧中 CU (编码单元)的个数.
    const pixel maxY = (1 << X265_DEPTH) - 1;
    const pixel rangeExt = maxY >> 1;
    int numCtu = m_numCuInWidth * m_numCuInHeight;

    // 为当前CU的左边和上面CU申请空间,备份左边和上面CU主要用于预测当前CU;
    for (int i = 0; i < (param->internalCsp != X265_CSP_I400 ? 3 : 1); i++)
    {
        CHECKED_MALLOC(m_tmpL1[i], pixel, g_maxCUSize + 1);
        CHECKED_MALLOC(m_tmpL2[i], pixel, g_maxCUSize + 1);

        // SAO asm code will read 1 pixel before and after, so pad by 2
        // NOTE: m_param->sourceWidth+2 enough, to avoid condition check in 
        // copySaoAboveRef(), I alloc more up to 63 bytes in here
        CHECKED_MALLOC(m_tmpU[i], pixel, m_numCuInWidth * g_maxCUSize + 2 + 32);
        m_tmpU[i] += 1;
    }

    if (initCommon)
    {
        // 选择SAO方法处理去方块边界像素,如果开启则处理所有边界像素,
        // 关闭则不处理右边和下面边界的像素;缺省是关闭。
        if (m_param->bSaoNonDeblocked)
        {
            CHECKED_MALLOC(m_countPreDblk, PerPlane, numCtu);
            CHECKED_MALLOC(m_offsetOrgPreDblk, PerPlane, numCtu);
        }
        CHECKED_MALLOC(m_depthSaoRate, double, 2 * SAO_DEPTHRATE_SIZE);

        m_depthSaoRate[0 * SAO_DEPTHRATE_SIZE + 0] = 0;
        m_depthSaoRate[0 * SAO_DEPTHRATE_SIZE + 1] = 0;
        m_depthSaoRate[0 * SAO_DEPTHRATE_SIZE + 2] = 0;
        m_depthSaoRate[0 * SAO_DEPTHRATE_SIZE + 3] = 0;
        m_depthSaoRate[1 * SAO_DEPTHRATE_SIZE + 0] = 0;
        m_depthSaoRate[1 * SAO_DEPTHRATE_SIZE + 1] = 0;
        m_depthSaoRate[1 * SAO_DEPTHRATE_SIZE + 2] = 0;
        m_depthSaoRate[1 * SAO_DEPTHRATE_SIZE + 3] = 0;

        CHECKED_MALLOC(m_clipTableBase,  pixel, maxY + 2 * rangeExt);
        m_clipTable = &(m_clipTableBase[rangeExt]);

        // 创建一个快速查找表m_clipTable(用于补偿,限制越界),即:
        // {0, 0, ..., 0 (127个); 0, 1, 2, ..., 255; 255, 255, ..., 255(127个)}
        for (int i = 0; i < rangeExt; i++)
            m_clipTableBase[i] = 0;
        for (int i = 0; i < maxY; i++)
            m_clipTable[i] = (pixel)i;
        for (int i = maxY; i < maxY + rangeExt; i++)
            m_clipTable[i] = maxY;
    }
    else
    {
        // must initialize these common pointer outside of function
        m_countPreDblk = NULL;
        m_offsetOrgPreDblk = NULL;
        m_clipTableBase = NULL;
        m_clipTable = NULL;
    }
    return true;
fail:
    return false;
}

/* 为当前CTU的SAO参数分配空间并初始化 */
void SAO::allocSaoParam(SAOParam* saoParam) const
{
    int planes = (m_param->internalCsp != X265_CSP_I400) ? 3 : 1;
    saoParam->numCuInWidth  = m_numCuInWidth;

    for (int i = 0; i < planes; i++)
        saoParam->ctuParam[i] = new SaoCtuParam[m_numCuInHeight * m_numCuInWidth];
}

/**
 * @brief  根据SAO补偿模式对重构像素值进行补偿.
 * @param addr : 从上到下、从左到右,当前CTU的序号
 * @param typeIdx : SAO补偿模式,取值SAO_EO_X 或 SAO_BO
 * @param plane : 颜色空间平面的序号,亮度平面为0,两个色度平面分别为1和2. 
 */
void SAO::applyPixelOffsets(int addr, int typeIdx, int plane)
{
    // reconPic为YUV重构图像,rec为当面颜色平面当前CTU的在重构图像中起始地址
    PicYuv* reconPic = m_frame->m_reconPic; 
    pixel* rec = reconPic->getPlaneAddr(plane, addr);

    // 获取重构图像颜色平面对应的跨度,亮度和色度的跨度不一样
    intptr_t stride = plane ? reconPic->m_strideC : reconPic->m_stride;
    uint32_t picWidth  = m_param->sourceWidth;      // 原始图像的宽
    uint32_t picHeight = m_param->sourceHeight;     // 原始图像的高
    const CUData* cu = m_frame->m_encData->getPicCTU(addr);
    int ctuWidth  = g_maxCUSize;        // 当前CU的宽度
    int ctuHeight = g_maxCUSize;        // 当前CU的高度

    // 当前CU最左边的横坐标和最上面的纵坐标
    uint32_t lpelx = cu->m_cuPelX;
    uint32_t tpely = cu->m_cuPelY;

    // 如果是色度平面,相应的宽度和高度都要减半,即左移一位
    if (plane)
    {
        picWidth  >>= m_hChromaShift;
        picHeight >>= m_vChromaShift;
        ctuWidth  >>= m_hChromaShift;
        ctuHeight >>= m_vChromaShift;
        lpelx     >>= m_hChromaShift;
        tpely     >>= m_vChromaShift;
    }

    // 获取当前CU最右边和最下面的边界值,不超出原始图像的最右边和最下面
    uint32_t rpelx = x265_min(lpelx + ctuWidth,  picWidth);
    uint32_t bpely = x265_min(tpely + ctuHeight, picHeight);

    // 当前CU实际的宽度和高度,除了最右边和最下面的CU外,其他都是64x64
    ctuWidth  = rpelx - lpelx;
    ctuHeight = bpely - tpely;

    int8_t _upBuff1[MAX_CU_SIZE + 2], *upBuff1 = _upBuff1 + 1, signLeft1[2];
    int8_t _upBufft[MAX_CU_SIZE + 2], *upBufft = _upBufft + 1;

    memset(_upBuff1 + MAX_CU_SIZE, 0, 2 * sizeof(int8_t)); 

    pixel* tmpL = m_tmpL1[plane];
    pixel* tmpU = &(m_tmpU[plane][lpelx]);
    int8_t* offsetEo = m_offsetEo[plane];

    // 根据边界或边带类型进行相应的SAO补偿
    switch (typeIdx)
    {
        case SAO_EO_0: // dir: -
            {... ...}
        case SAO_EO_1: // dir: |
            {... ...}
        case SAO_EO_2: // dir: 135
            {... ...}
        case SAO_EO_3: // dir: 45
            {... ...}
        case SAO_BO:   // 边带补偿
            {... ...}
        default: break;
    }
}

/* 生成亮度CTU的各种模式下的SAO补偿值并进行补偿 */
void SAO::generateLumaOffsets(SaoCtuParam* ctuParam, int idxY, int idxX)
{
    PicYuv* reconPic = m_frame->m_reconPic;
    intptr_t stride = reconPic->m_stride;
    int ctuWidth  = g_maxCUSize;
    int ctuHeight = g_maxCUSize;

    // 根据idxX和idxY得到CTU的序号,再根据序号获取CTU在重构图像缓冲区中起始位置
    int addr = idxY * m_numCuInWidth + idxX;
    pixel* rec = reconPic->getLumaAddr(addr);

    // 如果是水平方向第一个CTU,则用m_tmpL1[0]保存CTU左边一列(即左边CTU最右边的一列,
    // 不属于该CTU)的重构值
    if (idxX == 0)
    {
        for (int i = 0; i < ctuHeight + 1; i++)
        {
            m_tmpL1[0][i] = rec[0];
            rec += stride;
        }
    }

    // 判断当前CTU是否与左边CTU的SAO模式一样
    bool mergeLeftFlag = (ctuParam[addr].mergeMode == SAO_MERGE_LEFT);
    int typeIdx = ctuParam[addr].typeIdx;

    // 当前CTU不是水平方向的最后一个CTU,则用m_tmpL2[0]来保存当前CTU最右边的一列
    //(属于该CTU)重构值,后续跟m_tmpL1[0]交换可以用于下一个CTU的SAO模式计算
    if (idxX != (m_numCuInWidth - 1))
    {
        rec = reconPic->getLumaAddr(addr);
        for (int i = 0; i < ctuHeight + 1; i++)
        {
            m_tmpL2[0][i] = rec[ctuWidth - 1];
            rec += stride;
        }
    }

    // SAO补偿模式总共五种,取值0 – 4.
    if (typeIdx >= 0)
    {
        // 如果跟左边的CTU相同的SAO模式,则 m_offsetEo 直接采用左边CTU的值
        if (!mergeLeftFlag)
        {
            if (typeIdx == SAO_BO)
            {
                memset(m_offsetBo[0], 0, sizeof(m_offsetBo[0]));

                for (int i = 0; i < SAO_NUM_OFFSET; i++)
                    m_offsetBo[0][((ctuParam[addr].bandPos + i) & (MAX_NUM_SAO_CLASS - 1))] = 
                                            (int8_t)(ctuParam[addr].offset[i] << SAO_BIT_INC);
            }
            else // 边界补偿,即SAO_EO_X, X = 0,1,2,3
            {
                int offset[NUM_EDGETYPE];
                offset[0] = 0;
                for (int i = 0; i < SAO_NUM_OFFSET; i++)
                    offset[i + 1] = ctuParam[addr].offset[i] << SAO_BIT_INC;

                for (int edgeType = 0; edgeType < NUM_EDGETYPE; edgeType++)
                    m_offsetEo[0][edgeType] = (int8_t)offset[s_eoTable[edgeType]];
            }
        }

        // m_offsetEo[0]保存了亮度平面各种边界或边带需要补偿的值,将该值用到SAO补偿中
        applyPixelOffsets(addr, typeIdx, 0);
    }

    // 交换m_tmpL1[0]与m_tmpL2[0],就得到下一个CTU左边一列的重构值,即:m_tmpL1[0]
    std::swap(m_tmpL1[0], m_tmpL2[0]);
}

/* 生成色度CTU的各种模式下的SAO补偿值并进行补偿*/
void SAO::generateChromaOffsets(SaoCtuParam* ctuParam[3], int idxY, int idxX);

/* 统计当前CTU在BO和EO各模式下的像素归类,包括重构像素与原始像素差值之和,以及对classIdx的计数 */
void SAO::calcSaoStatsCTU(int addr, int plane)
{
    const PicYuv* reconPic = m_frame->m_reconPic;
    const CUData* cu = m_frame->m_encData->getPicCTU(addr);
    const pixel* fenc0 = m_frame->m_fencPic->getPlaneAddr(plane, addr);
    const pixel* rec0  = reconPic->getPlaneAddr(plane, addr);
    const pixel* fenc;
    const pixel* rec;

    // 亮度和色度平面的跨度不一样,plane为0表示亮度,非0表示色度
    intptr_t stride = plane ? reconPic->m_strideC : reconPic->m_stride;
    uint32_t picWidth  = m_param->sourceWidth;
    uint32_t picHeight = m_param->sourceHeight;
    int ctuWidth  = g_maxCUSize;
    int ctuHeight = g_maxCUSize;
    uint32_t lpelx = cu->m_cuPelX;      // 当前CTU最左边像素的横坐标
    uint32_t tpely = cu->m_cuPelY;      // 当前CTU最上面像素的纵坐标

    // 色度平面,相应的值都要减半,即左移一位
    if (plane)
    {
        picWidth  >>= m_hChromaShift;
        picHeight >>= m_vChromaShift;
        ctuWidth  >>= m_hChromaShift;
        ctuHeight >>= m_vChromaShift;
        lpelx     >>= m_hChromaShift;
        tpely     >>= m_vChromaShift;
    }

    // 当前CTU最右边像素的横坐标、最下面像素的纵坐标
    uint32_t rpelx = x265_min(lpelx + ctuWidth,  picWidth);
    uint32_t bpely = x265_min(tpely + ctuHeight, picHeight);

    // 当前CTU实际的宽度和高度,除了最右边和最下面的CTU外,其他CTU一般都是64x64
    ctuWidth  = rpelx - lpelx;
    ctuHeight = bpely - tpely;

    int startX, startY, endX, endY;
    const int plane_offset = plane ? 2 : 0;
    int skipB = 4, skipR = 5;

    int8_t _upBuff[2 * (MAX_CU_SIZE + 16 + 16)], *upBuff1 = _upBuff + 16, 
            *upBufft = upBuff1 +(MAX_CU_SIZE + 16 + 16);
    ALIGN_VAR_32(int16_t, diff[MAX_CU_SIZE * MAX_CU_SIZE]);

    // 计算 (fenc - frec),结果放入diff中,即原始像素与重构像素间的失真
    if ((lpelx + ctuWidth <  picWidth) & (tpely + ctuHeight < picHeight))
    {
        // WARNING: *) May read beyond bound on video than ctuWidth or 
        // ctuHeight is NOT multiple of cuSize
        X265_CHECK((ctuWidth == ctuHeight) || (m_chromaFormat != X265_CSP_I420), 
                    "video size check failure\n");

        // 对于square的CU可以采用SIMD流指令计算(fenc - frec),
        // 此处 MAX_CU_SIZE = 64 可以看作 diff的跨度,fenc0和rec0的跨度都是stride
        if (plane)
            primitives.chroma[m_chromaFormat].cu[g_maxLog2CUSize - 2].
                        sub_ps(diff, MAX_CU_SIZE, fenc0, rec0, stride, stride);
        else
           primitives.cu[g_maxLog2CUSize - 2].sub_ps(diff, MAX_CU_SIZE, fenc0, rec0, stride, stride);
    }
    else
    {
        // path for non-square area (most in edge)
        // 最右边或最下面的CTU可能不是square,另外单独计算 (fenc - frec)
        for(int y = 0; y < ctuHeight; y++)
        {
            for(int x = 0; x < ctuWidth; x++)
            {
                diff[y * MAX_CU_SIZE + x] = (fenc0[y * stride + x] – rec0[y * stride + x]);
            }
        }
    }

    // SAO_BO:
    {
        // 缺省是disable,表示右边和底部边界不做去方块滤波
        if (m_param->bSaoNonDeblocked)
        {
            skipB = 3;
            skipR = 4;
        }

        endX = (rpelx == picWidth) ? ctuWidth : ctuWidth - skipR + plane_offset;
        endY = (bpely == picHeight) ? ctuHeight : ctuHeight - skipB + plane_offset;

        // 当前CTU按照BO补偿模式对像素进行归类,
        // 包括每个条带像素个数、原始像素与重构像素差值之和
        primitives.saoCuStatsBO(diff, rec0, stride, endX, endY, 
                        m_offsetOrg[plane][SAO_BO], m_count[plane][SAO_BO]);
    }

    // SAO_EO_0: // dir: -
    {
        if (m_param->bSaoNonDeblocked)  // 缺省是disable, 忽略
        {
             skipB = 3;
             skipR = 5;
        }

        startX = !lpelx;
        endX   = (rpelx == picWidth) ? ctuWidth - 1 : ctuWidth - skipR + plane_offset;

        // 当前CTU按照 EO_0 模式对像素进行归类
        primitives.saoCuStatsE0(diff + startX, rec0 + startX, stride, endX - startX, ctuHeight - skipB + 
                                plane_offset, m_offsetOrg[plane][SAO_EO_0], m_count[plane][SAO_EO_0]);
    }

    // SAO_EO_1: // dir: |
    {
        if (m_param->bSaoNonDeblocked)      // 缺省是disable, 忽略
        {
             skipB = 4;
             skipR = 4;
        }

        rec  = rec0;

        // 如果tpely = 0,就表示当前CTU位于最上方,因此从CTU的第二行开始进行统计
        startY = !tpely;
        endX   = (rpelx == picWidth) ? ctuWidth : ctuWidth - skipR + plane_offset;
        endY   = (bpely == picHeight) ? ctuHeight - 1 : ctuHeight - skipB + plane_offset;
        if (!tpely)
            rec += stride;      // 当前CTU第二行起始地址,为下面的sign计算做准备

        // 计算当前CTU第二行与第一行的像素差值,保存在 upBuff1 中
        primitives.sign(upBuff1, rec, &rec[- stride], ctuWidth);

        // 当前CTU按照 EO_1 模式对像素进行归类
        primitives.saoCuStatsE1(diff + startY * MAX_CU_SIZE, rec0 + startY * stride, stride, upBuff1, 
                           endX, endY - startY, m_offsetOrg[plane][SAO_EO_1], m_count[plane][SAO_EO_1]);
    }

    // SAO_EO_2: // dir: 135
    {
        if (m_param->bSaoNonDeblocked)      // 缺省是disable, 忽略
        {
             skipB = 4;
             skipR = 5;
        }

        fenc = fenc0;
        rec  = rec0;

        // 要计算某个像素与左上方像素(即135度方向)的差值,要确保左上方像素存在,
        // 因此如果CTU位于图像的最左边或最上方,startX、startY需为1
        startX = !lpelx;
        endX   = (rpelx == picWidth) ? ctuWidth - 1 : ctuWidth - skipR + plane_offset;
        startY = !tpely;
        endY   = (bpely == picHeight) ? ctuHeight - 1 : ctuHeight - skipB + plane_offset;
        if (!tpely)
        {
           fenc += stride;  // 原始图像第二行起始地址 
           rec  += stride;  // 当前CTU第二行起始地址,为下面的sign计算做准备
        }

        // 计算当前CTU第二行与第一行的像素差值(即与左上方像素的差值),保存在 upBuff1 中
        primitives.sign(upBuff1, &rec[startX], &rec[startX - stride - 1], (endX - startX));

        // 当前CTU按照 EO_2 模式对像素进行归类
        primitives.saoCuStatsE2(diff + startX + startY * MAX_CU_SIZE, rec0  + startX + startY * stride, 
                            stride, upBuff1, upBufft, endX - startX, endY - startY, 
                            m_offsetOrg[plane][SAO_EO_2], m_count[plane][SAO_EO_2]);
    }

    // SAO_EO_3: // dir: 45
    {
        if (m_param->bSaoNonDeblocked)  // 缺省是disable, 忽略
        {
           skipB = 4;
           skipR = 5;
        }

        fenc = fenc0;
        rec  = rec0;

        startX = !lpelx;
        endX   = (rpelx == picWidth) ? ctuWidth - 1 : ctuWidth - skipR + plane_offset;
        startY = !tpely;
        endY   = (bpely == picHeight) ? ctuHeight - 1 : ctuHeight - skipB + plane_offset;

        if (!tpely)
        {
           fenc += stride;      // 原始图像第二行起始地址
           rec  += stride;      // 当前CTU第二行起始地址,为下面的sign计算做准备
        }

        // 计算当前CTU第二行与第一行的像素差值(即与右上方像素的差值),保存在 upBuff1 中
        primitives.sign(upBuff1, &rec[startX - 1], &rec[startX - 1 - stride + 1],(endX - startX + 1));

        // 当前CTU按照 EO_3 模式对像素进行归类
        primitives.saoCuStatsE3(diff + startX + startY * MAX_CU_SIZE, rec0  + startX + startY * stride, 
                            stride, upBuff1 + 1, endX - startX, endY - startY, 
                            m_offsetOrg[plane][SAO_EO_3], m_count[plane][SAO_EO_3]);
    }
}

/* 去方块滤波前对CTU的像素统计归类,只有当SAO和bSaoNonDeblocked都开启的情况下才使用,因此暂时忽略 */
void SAO::calcSaoStatsCu_BeforeDblk(Frame* frame, int idxX, int idxY);

/* 计算CTU在各种模式下的最优SAO代价,与直接采用左边或上面CTU的SAO参数作比较,找出最优的SAO代价,
   并将最优SAO模式下的各种参数保存在 saoParam->ctuParam[plane][add]中 */
void SAO::rdoSaoUnitCu(SAOParam* saoParam, int rowBaseAddr, int idxX, int addr)
{
    Slice* slice = m_frame->m_encData->m_slice;
    const CUData* cu = m_frame->m_encData->getPicCTU(addr);
    int qp = cu->m_qp[0];
    int64_t lambda[2] = { 0 };
    int qpCb = qp;

    // 色度量化因子qpCb
    if (m_param->internalCsp == X265_CSP_I420)
        qpCb = x265_clip3(QP_MIN, QP_MAX_MAX, (int)g_chromaScale[qp + slice->m_pps->chromaQpOffset[0]]);
    else
        qpCb = X265_MIN(qp + slice->m_pps->chromaQpOffset[0], QP_MAX_SPEC);

    // lambda[0]用于亮度SAO参数计算,lambda[1]用于色度SAO参数计算
    lambda[0] = (int64_t)floor(256.0 * x265_lambda2_tab[qp]);
    lambda[1] = (int64_t)floor(256.0 * x265_lambda2_tab[qpCb]); 

    // 左边和上面的CU是否存在
    const bool allowMerge[2] = {(idxX != 0), (rowBaseAddr != 0)}; 

    // 左边和上面的CU的编号
    const int addrMerge[2] = {(idxX ? addr - 1 : -1), (rowBaseAddr ? addr - m_numCuInWidth : -1)};

    // 是否存在色度平面 
    bool chroma = m_param->internalCsp != X265_CSP_I400 && 
                  m_frame->m_fencPic->m_picCsp != X265_CSP_I400;

    // 我们只考虑I420格式,因此存在色度平面,因此此处planes取值3
    int planes = chroma ? 3 : 1;    

    // 选择SAO方法处理去方块边界像素,如果开启则处理所有边界像素,
    // 关闭则不处理右边和下面边界的像素;缺省是关闭。
    if (m_param->bSaoNonDeblocked)
    {
        memcpy(m_count, m_countPreDblk[addr], sizeof(m_count));
        memcpy(m_offsetOrg, m_offsetOrgPreDblk[addr], sizeof(m_offsetOrg));
    }
    else
    {   // 初始化各模式各类型点的个数和失真值为0
        memset(m_count, 0, sizeof(m_count));
        memset(m_offsetOrg, 0, sizeof(m_offsetOrg));
    }

    for (int i = 0; i < planes; i++)
        saoParam->ctuParam[i][addr].reset();

    // 统计当前CTU的亮度块在BO和EO各模式下的像素归类,
    // 包括重构像素与原始像素差值之和,以及对classIdx的计数
    if (saoParam->bSaoFlag[0])
        calcSaoStatsCTU(addr, 0);

    // 统计当前CTU的色度块在BO和EO各模式下的像素归类
    if (saoParam->bSaoFlag[1])
    {
        calcSaoStatsCTU(addr, 1);
        calcSaoStatsCTU(addr, 2);
    }

    // 利用上一步的统计信息计算BO和EO初始补偿值
    saoStatsInitialOffset(planes);

    // SAO distortion calculation
    m_entropyCoder.load(m_rdContexts.cur);
    m_entropyCoder.resetBits();
    if (allowMerge[0])
        m_entropyCoder.codeSaoMerge(0);
    if (allowMerge[1])
        m_entropyCoder.codeSaoMerge(0);
    m_entropyCoder.store(m_rdContexts.temp);

    // Estimate distortion and cost of new SAO params
    int64_t bestCost = 0;
    int64_t rateDist = 0;

    // Estimate distortion and cost of new SAO params
    // 亮度和色度最优SAO模式的选择,得到最优率失真代价
    saoLumaComponentParamDist(saoParam, addr, rateDist, lambda, bestCost);
    if (chroma)
        saoChromaComponentParamDist(saoParam, addr, rateDist, lambda, bestCost);

    if (saoParam->bSaoFlag[0] || saoParam->bSaoFlag[1])
    {
        // Cost of merge left or Up, mergeIdx为0表示左边,为1表示上面
        // 计算直接采用左边和上面CTU的SAO参数的代价
        for (int mergeIdx = 0; mergeIdx < 2; ++mergeIdx)
        {
            // 如果左边或上面的CTU不存在,则跳过下面的计算,进入下一轮循环
            if (!allowMerge[mergeIdx])
                continue;

            int64_t mergeDist = 0; 
            for (int plane = 0; plane < planes; plane++)
            {
                // 初始失真值为0,获取左边或上面CTU的SAO参数
                int64_t estDist = 0;
                SaoCtuParam* mergeSrcParam = &(saoParam->ctuParam[plane][addrMerge[mergeIdx]]);
                int typeIdx = mergeSrcParam->typeIdx;
                if (typeIdx >= 0)
                {
                    // 如果是边带模式,获取第一个条带的编号;否则取值1
                    int bandPos = (typeIdx == SAO_BO) ? mergeSrcParam->bandPos : 1;
                    for (int classIdx = 0; classIdx < SAO_NUM_OFFSET; classIdx++)
                    {
                        // 根据4种类型的补偿值来计算失真差值
                        int mergeOffset = mergeSrcParam->offset[classIdx];
                        estDist += estSaoDist(m_count[plane][typeIdx][classIdx + bandPos], mergeOffset, 
                                                m_offsetOrg[plane][typeIdx][classIdx + bandPos]);
                    }
                }
                mergeDist += (estDist << 8) / lambda[!!plane];
            }

            m_entropyCoder.load(m_rdContexts.cur);
            m_entropyCoder.resetBits();
            if (allowMerge[0])
                m_entropyCoder.codeSaoMerge(1 - mergeIdx);
            if (allowMerge[1] && (mergeIdx == 1))
                m_entropyCoder.codeSaoMerge(1);

            uint32_t estRate = m_entropyCoder.getNumberOfWrittenBits();
            int64_t mergeCost = mergeDist + estRate;
            if (mergeCost < bestCost)
            {
                // merge的代价比SAO各模式代价更小,就采用merge模式
                SaoMergeMode mergeMode = mergeIdx ? SAO_MERGE_UP : SAO_MERGE_LEFT;
                bestCost = mergeCost;
                m_entropyCoder.store(m_rdContexts.temp);
                for (int plane = 0; plane < planes; plane++)
                {
                    // 更新SAO参数为merge模式下的参数
                    if (saoParam->bSaoFlag[plane > 0])
                    {
                        SaoCtuParam* dstCtuParam   = &saoParam->ctuParam[plane][addr];
                        SaoCtuParam* mergeSrcParam = &(saoParam->ctuParam[plane][addrMerge[mergeIdx]]);
                        dstCtuParam->mergeMode = mergeMode;
                        dstCtuParam->typeIdx   = mergeSrcParam->typeIdx;
                        dstCtuParam->bandPos   = mergeSrcParam->bandPos;

                        for (int i = 0; i < SAO_NUM_OFFSET; i++)
                            dstCtuParam->offset[i] = mergeSrcParam->offset[i];
                    }
                }
            }   // if (mergeCost < bestCost) 结束
        }   // mergeIdx循环结束

        if (saoParam->ctuParam[0][addr].typeIdx < 0)
            m_numNoSao[0]++;
        if (chroma && saoParam->ctuParam[1][addr].typeIdx < 0)
            m_numNoSao[1]++;
        m_entropyCoder.load(m_rdContexts.temp);
        m_entropyCoder.store(m_rdContexts.cur);
    }
}

/* 利用先前已经得到的统计信息(即m_count和m_offsetOrg)计算初始补偿值(即m_offset) */
void SAO::saoStatsInitialOffset(int planes)
{
    memset(m_offset, 0, sizeof(m_offset));

    // EO
    for (int plane = 0; plane < planes; plane++)
    {
        // typeIdx, 边界补偿的四种模式,即 SAO_EO_X
        for (int typeIdx = 0; typeIdx < MAX_NUM_SAO_TYPE - 1; typeIdx++)
        {
            // 任意一种模式下,边界点有四个种类
            for (int classIdx = 1; classIdx < SAO_NUM_OFFSET + 1; classIdx++)
            {
                int32_t&  count     = m_count[plane][typeIdx][classIdx];
                int32_t& offsetOrg = m_offsetOrg[plane][typeIdx][classIdx];
                int32_t& offsetOut = m_offset[plane][typeIdx][classIdx];

                if (count)
                {
                    // 计算平均失真(offsetOrg/count并四舍五入),将其限制在[-7,7]之内
                    offsetOut = roundIBDI(offsetOrg, count << SAO_BIT_INC);
                    offsetOut = x265_clip3(-OFFSET_THRESH + 1, OFFSET_THRESH - 1, offsetOut);

                    // 种类1、种类2的补偿值必须大于等于0;
                    // 种类3、种类4的补偿值必须小于等于0.
                    if (classIdx < 3) 
                        offsetOut = X265_MAX(offsetOut, 0);
                    else
                        offsetOut = X265_MIN(offsetOut, 0);
                }
            }
        }
    }

    // BO,为每个条带计算初始补偿值
    for (int plane = 0; plane < planes; plane++)
    {
        // 深度8位,256级分为32个边带,即[8k, 8k + 7]为第k个边带
        for (int classIdx = 0; classIdx < MAX_NUM_SAO_CLASS; classIdx++)
        {
            int32_t&  count     = m_count[plane][SAO_BO][classIdx];
            int32_t& offsetOrg = m_offsetOrg[plane][SAO_BO][classIdx];
            int32_t& offsetOut = m_offset[plane][SAO_BO][classIdx];

            if (count)
            {
                // 计算平均失真,并将其限制在[-7,7]之内
                offsetOut = roundIBDI(offsetOrg, count << SAO_BIT_INC);
                offsetOut = x265_clip3(-OFFSET_THRESH + 1, OFFSET_THRESH - 1, offsetOut);
            }
        }
    }
}

/* 计算率失真代价值,公式为:(失真 + lambda * 编码比特数)*/
inline int64_t SAO::calcSaoRdoCost(int64_t distortion, uint32_t bits, int64_t lambda)
{
    // lambda = 256.0 * x265_lambda2_tab[],所以需要右移8位,即除以256
    // 数组x265_lambda2_tab 定义在 common/constants.cpp 中
    return distortion + ((bits * lambda + 128) >> 8);
}

/**
 * @brief 找到最优率失真代价及对应的补偿值和失真值.
 * @param typeIdx : SAO模式,即 SAO_EO_X 和 SAO_BO
 * @param lambda  : 拉格朗日乘子,取值依赖QP,即 256.0 * x265_lambda2_tab[qp]
 * @param count   : typeIdx模式下,某classIdx的点的数目
 * @param offsetOrg : 原始像素与重构像素(SAO补偿前)之间的差值之和
 * @param offset[输出] : 最优率失真代价对应的补偿值
 * @param distClasses[输出] : 最优率失真代价对应的失真
 * @param costClasses[输出] : 最优率失真代价
 */
void SAO::estIterOffset(int typeIdx, int64_t lambda, int32_t count, int32_t offsetOrg, 
                        int32_t& offset, int32_t& distClasses, int64_t& costClasses)
{
    int bestOffset = 0;
    distClasses    = 0;

    // Assuming sending quantized value 0 results in zero offset 
    // and sending the value zero needs 1 bit.
    // entropy coder can be used to measure the exact rate here.
    int64_t bestCost = calcSaoRdoCost(0, 1, lambda);
    while (offset != 0)
    {
        // Calculate the bits required for signalling the offset
        uint32_t rate = (typeIdx == SAO_BO) ? (abs(offset) + 2) : (abs(offset) + 1);
        if (abs(offset) == OFFSET_THRESH - 1)
            rate--;

        // Do the dequntization before distorion calculation
        // 计算D_post和 D_pre的差值,即SAO补偿前后失真的差值
        int64_t dist = estSaoDist(count, offset << SAO_BIT_INC, offsetOrg);

        // 计算率失真代价
        int64_t cost  = calcSaoRdoCost(dist, rate, lambda);
        if (cost < bestCost)
        {
            bestCost = cost;
            bestOffset = offset;
            distClasses = (int)dist;
        }
        offset = (offset > 0) ? (offset - 1) : (offset + 1);
    }

    costClasses = bestCost;
    offset = bestOffset;
}

/* 寻找亮度最优SAO模式,得到最优率失真代价 */
void SAO::saoLumaComponentParamDist(SAOParam* saoParam, int32_t addr, int64_t& rateDist, 
                                    int64_t* lambda, int64_t &bestCost)
{
    int64_t bestDist = 0;
    int bestTypeIdx = -1;
    SaoCtuParam* lclCtuParam = &saoParam->ctuParam[0][addr];

    int32_t distClasses[MAX_NUM_SAO_CLASS];
    int64_t costClasses[MAX_NUM_SAO_CLASS];

    // RDO SAO_NA
    m_entropyCoder.load(m_rdContexts.temp);
    m_entropyCoder.resetBits();
    m_entropyCoder.codeSaoType(0);

    // 计算初始的率失真代价值
    int64_t costPartBest = calcSaoRdoCost(0, m_entropyCoder.getNumberOfWrittenBits(), lambda[0]);

    // EO distortion calculation
    // 外循环是EO的模式,即4种方向,内循环是点的种类
    for (int typeIdx = 0; typeIdx < MAX_NUM_SAO_TYPE - 1; typeIdx++)
    {
        int64_t estDist = 0;        // 用于保存某EO模式下各种类失真总和
        for (int classIdx = 1; classIdx < SAO_NUM_OFFSET + 1; classIdx++)
        {
            int32_t&  count     = m_count[0][typeIdx][classIdx];
            int32_t& offsetOrg = m_offsetOrg[0][typeIdx][classIdx];
            int32_t& offsetOut = m_offset[0][typeIdx][classIdx];

            // 计算率失真代价值最小的 offset
            estIterOffset(typeIdx, lambda[0], count, offsetOrg, offsetOut, 
                            distClasses[classIdx], costClasses[classIdx]);

            // Calculate distortion
            estDist += distClasses[classIdx];
        }

        m_entropyCoder.load(m_rdContexts.temp);
        m_entropyCoder.resetBits();
        m_entropyCoder.codeSaoOffsetEO(m_offset[0][typeIdx] + 1, typeIdx, 0);

        // 计算某EO模式下的率失真代价,
        // 如果比前面计算的更小,则更新最优率失真代和相应的EO模式值
        int64_t cost = calcSaoRdoCost(estDist, m_entropyCoder.getNumberOfWrittenBits(), lambda[0]);
        if (cost < costPartBest)
        {
            costPartBest = cost;
            bestDist = estDist;
            bestTypeIdx = typeIdx;
        }
    }

    // 找到了最优的EO模式,则将最优模式值和补偿值保存起来
    if (bestTypeIdx != -1)
    {
        lclCtuParam->mergeMode = SAO_MERGE_NONE;
        lclCtuParam->typeIdx = bestTypeIdx;
        lclCtuParam->bandPos = 0;
        for (int classIdx = 0; classIdx < SAO_NUM_OFFSET; classIdx++)
            lclCtuParam->offset[classIdx] = m_offset[0][bestTypeIdx][classIdx + 1];
    }

    // BO RDO,为每个条带计算最优率失真代价及对应的补偿值
    // costClasses 保存了每个条带的最优率失真代价
    int64_t estDist = 0;
    for (int classIdx = 0; classIdx < MAX_NUM_SAO_CLASS; classIdx++)
    {
        int32_t&  count    = m_count[0][SAO_BO][classIdx];
        int32_t& offsetOrg = m_offsetOrg[0][SAO_BO][classIdx];
        int32_t& offsetOut = m_offset[0][SAO_BO][classIdx];

        estIterOffset(SAO_BO, lambda[0], count, offsetOrg, offsetOut, 
                        distClasses[classIdx], costClasses[classIdx]);
    }

    // Estimate Best Position
    int64_t bestRDCostBO = MAX_INT64;
    int32_t bestClassBO  = 0;

    // 统计任意连续4个条带的最优率失真代价之和,找出值最小的连续4个条带
    for (int i = 0; i < MAX_NUM_SAO_CLASS - SAO_NUM_OFFSET + 1; i++)
    {
        int64_t currentRDCost = 0;
        for (int j = i; j < i + SAO_NUM_OFFSET; j++)
            currentRDCost += costClasses[j];

        if (currentRDCost < bestRDCostBO)
        {
            bestRDCostBO = currentRDCost;
            bestClassBO  = i;                   // 连续4个条带的起始条带编号
        }
    }

    // 计算最优的连续4个条带的失真之和
    estDist = 0;
    for (int classIdx = bestClassBO; classIdx < bestClassBO + SAO_NUM_OFFSET; classIdx++)
        estDist += distClasses[classIdx];

    m_entropyCoder.load(m_rdContexts.temp);
    m_entropyCoder.resetBits();
    m_entropyCoder.codeSaoOffsetBO(m_offset[0][SAO_BO] + bestClassBO, bestClassBO, 0);

    // 计算BO模式下的率失真代价
    int64_t cost = calcSaoRdoCost(estDist, m_entropyCoder.getNumberOfWrittenBits(), lambda[0]);

    // 如果BO模式下的率失真代价比上面EO模式下的率失真代价更小,则更新相应的SAO参数
    if (cost < costPartBest)
    {
        costPartBest = cost;
        bestDist = estDist;

        lclCtuParam->mergeMode = SAO_MERGE_NONE;
        lclCtuParam->typeIdx = SAO_BO;
        lclCtuParam->bandPos = bestClassBO;
        for (int classIdx = 0; classIdx < SAO_NUM_OFFSET; classIdx++)
            lclCtuParam->offset[classIdx] = m_offset[0][SAO_BO][classIdx + bestClassBO];
    }

    rateDist = (bestDist << 8) / lambda[0];
    m_entropyCoder.load(m_rdContexts.temp);
    m_entropyCoder.codeSaoOffset(*lclCtuParam, 0);
    m_entropyCoder.store(m_rdContexts.temp);
}

/* 寻找色度最优SAO模式,得到最优率失真代价 */
void SAO::saoChromaComponentParamDist(SAOParam* saoParam, int32_t addr, 
                int64_t& rateDist, int64_t* lambda, int64_t &bestCost);

/* 统计某个CU内条带点数目及失真之和,count和stats分别是条带点计数和失真之和 */
void saoCuStatsBO_c(const int16_t *diff, const pixel *rec, intptr_t stride, 
                    int endX, int endY, int32_t *stats, int32_t *count)
{
    const int boShift = X265_DEPTH - SAO_BO_BITS;

    for (int y = 0; y < endY; y++)
    {
        for (int x = 0; x < endX; x++)
        {
            int classIdx = rec[x] >> boShift;   // 条带编号
            stats[classIdx] += diff[x];         // 某条带失真累计
            count[classIdx]++;                  // 某条带点数目累计
        }

        diff += MAX_CU_SIZE;        // 下一行失真数据地址
        rec += stride;              // 当前CU的下一行重构图像地址
    }
}

/* 统计CU内的点在EO_0模式(水平方向)下的各种类点的数目及失真之和 */
void saoCuStatsE0_c(const int16_t *diff, const pixel *rec, intptr_t stride, 
                    int endX, int endY, int32_t *stats, int32_t *count)
{
    int32_t tmp_stats[SAO::NUM_EDGETYPE];
    int32_t tmp_count[SAO::NUM_EDGETYPE];

    memset(tmp_stats, 0, sizeof(tmp_stats));
    memset(tmp_count, 0, sizeof(tmp_count));

    for (int y = 0; y < endY; y++)
    {
        int signLeft = signOf(rec[0] - rec[-1]);        // 当前边界点的左符号
        for (int x = 0; x < endX; x++)
        {
            int signRight = signOf2(rec[x], rec[x + 1]);      // 当前边界点的右符号
            uint32_t edgeType = signRight + signLeft + 2;     // 边界点类型
            signLeft = -signRight;               // 当前点的右符号 = - 右边点的左符号

            // edgeType与真实的点种类转换关系就是数组 s_eoTable[]
            X265_CHECK(edgeType <= 4, "edgeType check failure\n");
            tmp_stats[edgeType] += diff[x];     // 该类型点的失真累计
            tmp_count[edgeType]++;              // 该类型点的数目累计
        }

        diff += MAX_CU_SIZE;
        rec += stride;
    }

    // 返回各类型点的失真之和和数目
    for (int x = 0; x < SAO::NUM_EDGETYPE; x++)
    {
        stats[SAO::s_eoTable[x]] += tmp_stats[x];
        count[SAO::s_eoTable[x]] += tmp_count[x];
    }
}

/* 统计CU内的点在EO_1模式(垂直方向)下的各种类点的数目及失真之和 */
void saoCuStatsE1_c(const int16_t *diff, const pixel *rec, intptr_t stride, 
    int8_t *upBuff1, int endX, int endY, int32_t *stats, int32_t *count);

/* 统计CU内的点在EO_2模式(135度方向)下的各种类点的数目及失真之和 */
void saoCuStatsE2_c(const int16_t *diff, const pixel *rec, intptr_t stride, int8_t 
    *upBuff1, int8_t *upBufft, int endX, int endY, int32_t *stats, int32_t *count);

/* 统计CU内的点在EO_3模式(45度方向)下的各种类点的数目及失真之和 */
void saoCuStatsE3_c(const int16_t *diff, const pixel *rec, intptr_t stride, 
    int8_t *upBuff1, int endX, int endY, int32_t *stats, int32_t *count);

猜你喜欢

转载自blog.csdn.net/nk_wavelet/article/details/52554537