x265 传递残差计算

一 传递残差的作用

     传递残差最终会直接增加到当前帧的Cost上,影响了最终的码控,本文分析传递残差计算过程

二 代码详细分析

     传递残差迭代过程

void Lookahead::estimateCUPropagate(Lowres **frames, double averageDuration, int p0, int p1, int b, int referenced)
{
/*
1 帧序列
2 平均duration
3 前向帧
4 后向帧
5 当前帧
6 是否被参考了
*/
    uint16_t *refCosts[2] = { frames[p0]->propagateCost, frames[p1]->propagateCost };
    int32_t distScaleFactor = (((b - p0) << 8) + ((p1 - p0) >> 1)) / (p1 - p0);
    int32_t bipredWeight = m_param->bEnableWeightedBiPred ? 64 - (distScaleFactor >> 2) : 32;
    int32_t bipredWeights[2] = { bipredWeight, 64 - bipredWeight }; //计算双向权重
    int listDist[2] = { b - p0, p1 - b };

    memset(m_scratch, 0, m_8x8Width * sizeof(int));

    uint16_t *propagateCost = frames[b]->propagateCost; //传递残差指针

    s265_emms();
    double fpsFactor = CLIP_DURATION((double)m_param->fpsDenom / m_param->fpsNum) / CLIP_DURATION(averageDuration);
    //duration factor,看下是否是不均匀的帧率,正常情况下是1

    /* For non-referred frames the source costs are always zero, so just memset one row and re-use it. */
    if (!referenced) //如果非参考
        memset(frames[b]->propagateCost, 0, m_8x8Width * sizeof(uint16_t)); //

    int32_t strideInCU = m_8x8Width; //以cu为单位的行宽
    for (uint16_t blocky = 0; blocky < m_8x8Height; blocky++) //
    {
        int cuIndex = blocky * strideInCU; //遍历每一行
        if (m_param->rc.qgSize == 8) //如果qgSize == 8
            primitives.propagateCost(m_scratch, propagateCost,
                       frames[b]->intraCost + cuIndex, frames[b]->lowresCosts[b - p0][p1 - b] + cuIndex,
                       frames[b]->invQscaleFactor8x8 + cuIndex, &fpsFactor, m_8x8Width);
                       //计算传递残差
        else
            primitives.propagateCost(m_scratch, propagateCost,
                       frames[b]->intraCost + cuIndex, frames[b]->lowresCosts[b - p0][p1 - b] + cuIndex,
                       frames[b]->invQscaleFactor + cuIndex, &fpsFactor, m_8x8Width);
                       //

        if (referenced) //如果是参考帧,
            propagateCost += m_8x8Width; //偏移一行,要不就是复用的

        for (uint16_t blockx = 0; blockx < m_8x8Width; blockx++, cuIndex++) //遍历每一行的每个块
        {
            int32_t propagate_amount = m_scratch[blockx];
            /* Don't propagate for an intra block. */
            if (propagate_amount > 0) //propagate_amount 传递次数
            {
                /* Access width-2 bitfield. */
                int32_t lists_used = frames[b]->lowresCosts[b - p0][p1 - b][cuIndex] >> LOWRES_COST_SHIFT; //
                //最高两位存放 前向和后向使用情况, 一共16位的lowresCosts, 后14真的存放的是cost,前2位存放的是方向信息
                 lowresCosts ,画面在1/4分辨率时候的Cost , 右移位 14,
                /* Follow the MVs to the previous frame(s). */
                for (uint16_t list = 0; list < 2; list++) //0, 1 前向/后向参考传递
                {
                    if ((lists_used >> list) & 1) //为1 表示有这个方向上的。 当list为0, 
                    {
#define CLIP_ADD(s, x) (s) = (uint16_t)S265_MIN((s) + (x), (1 << 16) - 1)
                        int32_t listamount = propagate_amount;
                        /* Apply bipred weighting. */
                        if (lists_used == 3) //双向的
                            listamount = (listamount * bipredWeights[list] + 32) >> 6; //双向的需要调整,根据双向权重值

                        MV *mvs = frames[b]->lowresMvs[list][listDist[list]]; //拿到一个方向上的mv值

                        /* Early termination for simple case of mv 0. */
                        if (!mvs[cuIndex].word) //如果mv是0, 说明是
                        {
                            CLIP_ADD(refCosts[list][cuIndex], listamount);//直接把当前块的cost增大一些,当然不能超过1 << 16
                            //因为这里没有运动向量的cost, 所以只需要加上传递残差
                            continue;
                        }

                        int32_t x = mvs[cuIndex].x;
                        int32_t y = mvs[cuIndex].y;
                        int32_t cux = (x >> 5) + blockx;
                        int32_t cuy = (y >> 5) + blocky;
                        int32_t idx0 = cux + cuy * strideInCU; //当前块位置
                        int32_t idx1 = idx0 + 1;//当前块前一个块
                        int32_t idx2 = idx0 + strideInCU;// 当前块下面一个块
                        int32_t idx3 = idx0 + strideInCU + 1; //当前块,前面下方一个块
                        /*
                           D(当前块)  idx1
                           idx2      idx3
                        */
                        x &= 31;
                        y &= 31;
                        int32_t idx0weight = (32 - y) * (32 - x);
                        int32_t idx1weight = (32 - y) * x;
                        int32_t idx2weight = y * (32 - x);
                        int32_t idx3weight = y * x;

                        /* We could just clip the MVs, but pixels that lie outside the frame probably shouldn't
                         * be counted. */
                        if (cux < m_8x8Width - 1 && cuy < m_8x8Height - 1 && cux >= 0 && cuy >= 0)
                        { //给这些块,都加上传递残差的影响 ,在边界范围内的
                            CLIP_ADD(refCosts[list][idx0], (listamount * idx0weight + 512) >> 10);
                            CLIP_ADD(refCosts[list][idx1], (listamount * idx1weight + 512) >> 10);
                            CLIP_ADD(refCosts[list][idx2], (listamount * idx2weight + 512) >> 10);
                            CLIP_ADD(refCosts[list][idx3], (listamount * idx3weight + 512) >> 10);
                        }
                        else /* Check offsets individually  出界的,需要逐个判断,要不然idx 下标访问越界*/
                        {
                            if (cux < m_8x8Width && cuy < m_8x8Height && cux >= 0 && cuy >= 0)
                                CLIP_ADD(refCosts[list][idx0], (listamount * idx0weight + 512) >> 10);
                            if (cux + 1 < m_8x8Width && cuy < m_8x8Height && cux + 1 >= 0 && cuy >= 0)
                                CLIP_ADD(refCosts[list][idx1], (listamount * idx1weight + 512) >> 10);
                            if (cux < m_8x8Width && cuy + 1 < m_8x8Height && cux >= 0 && cuy + 1 >= 0)
                                CLIP_ADD(refCosts[list][idx2], (listamount * idx2weight + 512) >> 10);
                            if (cux + 1 < m_8x8Width && cuy + 1 < m_8x8Height && cux + 1 >= 0 && cuy + 1 >= 0)
                                CLIP_ADD(refCosts[list][idx3], (listamount * idx3weight + 512) >> 10);
                        }
                    }
                }
            }
        }
    }

    if (m_param->rc.vbvBufferSize && m_param->lookaheadDepth && referenced)
        cuTreeFinish(frames[b], averageDuration, b == p1 ? b - p0 : 0);
}

三 传递残差具体计算

/* Estimate the total amount of influence on future quality that could be had if we
 * were to improve the reference samples used to inter predict any given CU. */
static void estimateCUPropagateCost(
int* dst, //最终存储传递残差Amount的地方
const uint16_t* propagateIn,//当前帧存储传递残差的地方,这个会不断迭代,因为一帧一帧参考关系的计算
const int32_t* intraCosts, 
const uint16_t* interCosts,
const int32_t* invQscales, 
const double* fpsFactor, 
int len)
{
    double fps = *fpsFactor / 256;  // range[0.01, 1.00]
    for (int i = 0; i < len; i++) //一行的每个cu块
    {
        int intraCost = intraCosts[i]; //当前块的帧内Costs
        int interCost = S265_MIN(intraCosts[i], interCosts[i] & LOWRES_COST_MASK);//00ffffff 低14bit存放的才是cost
        //上面选择最小的作为Cost
        double propagateIntra = intraCost * invQscales[i]; // Q16 x Q8.8 = Q24.8 帧内cost * 一定的系数
        double propagateAmount = (double)propagateIn[i]/*原有的传递残差*/ + propagateIntra * fps; // Q16.0 + Q24.8 x Q0.x = Q25.0 帧内Cost * fps,计算1/duration
        double propagateNum = (double)(intraCost - interCost); // Q32 - Q32 = Q33.0 帧间Cost  - 帧内Cost


        double propagateDenom = (double)intraCost;             // Q32
        dst[i] = (int)(propagateAmount * propagateNum / propagateDenom + 0.5);//最终的传递残差值
    }
    //}
}

以上就是cuTree影响帧Cost的全过程

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值