x265码率控制-VBV更新过程

2 篇文章 0 订阅

最近在改AVS3的码率控制模块,因此先去了解了x265的码率控制模型。
首先,了解x265的码率控制原理可以先看看https://blog.csdn.net/liulinyu1234/article/details/80857652,本人也是在该博客的原理的介绍下了解x265码率控制模型。
这里主要讲一下自己对x265的VBV更新的理解。
涉及到VBV参数的类主要在class RateControl,参数主有
在这里插入图片描述
在x265编码过程中,有VBV这样一个机制,相当于一个容器,它有一个最大容量以及一个初始值,即BufferSize和initvbvBuffer,可以理解为,你编码一帧,需要从容器里取你编码当前帧的bit,然后再往里面加平均每帧的bit消耗。当你的编码帧bit消耗太多时,即你从容器里拿的速度远大于往里面加的速度,那么里边初始的Buffer很快会被消耗完,出现下溢现象,就会导致用户观看时的卡顿;相反,拿的速度远小于加的速度,那么VBV就会上溢,不过一般上溢的影响不大,个人理解为上溢的话是给了你那么多的bit但是你没有充分利用,视频质量不是最佳的。上溢一般没大多关系,主要注意不能出现下溢,因为主要的卡顿会直接影响用户体验。
x265的码率控制主要在rateControlStart函数和rateControlEnd函数。前一个函数为编码前的参数计算过程,包括QP的计算,主要函数调用为rateControlStart->rateEstimateQscale->getQScale、tuneAbrQScaleFromFeedback、clipQscale。VBV状态对Qscale的影响主要体现在clipQscale函数中。
该函数会从当前帧开始,预测后面几帧的Bit,然后根据VBV的状态,调整当前帧的Qscale,使VBV状态在0.5-0.8之间。该函数对Qscale的计算影响比较大,个人感觉是码率控制过程防止VBV上下溢出的重要函数。贴出clipQscale函数以及自己的注释代码:

double RateControl::clipQscale(Frame* curFrame, RateControlEntry* rce, double q)
{
    // B-frames are not directly subject to VBV,
    // since they are controlled by referenced P-frames' QPs.
    double lmin = m_lmin[rce->sliceType];
    double lmax = m_lmax[rce->sliceType];
    double q0 = q;
    if (m_isVbv && m_currentSatd > 0 && curFrame)
    {
        if (m_param->lookaheadDepth || m_param->rc.cuTree ||
            (m_param->scenecutThreshold || m_param->bHistBasedSceneCut) ||
            (m_param->bFrameAdaptive && m_param->bframes))
        {
           /* Lookahead VBV: If lookahead is done, raise the quantizer as necessary
            * such that no frames in the lookahead overflow and such that the buffer
            * is in a reasonable state by the end of the lookahead. */
            int loopTerminate = 0;
            /* Avoid an infinite loop. loopTerminate==3 即loopTerminate|1又|2*/
            for (int iterations = 0; iterations < 1000 && loopTerminate != 3; iterations++)		//从当前开始,向后看i帧,计算所需的bit,估计vbv情况
            {
                double frameQ[3];
                double curBits;
                curBits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd);			//预测当前帧bit
                double bufferFillCur = m_bufferFill - curBits;									//bufferfill状态
                double targetFill;
                double totalDuration = m_frameDuration;
                frameQ[P_SLICE] = m_sliceType == I_SLICE ? q * m_param->rc.ipFactor : (m_sliceType == B_SLICE ? q / m_param->rc.pbFactor : q);
                frameQ[B_SLICE] = frameQ[P_SLICE] * m_param->rc.pbFactor;
                frameQ[I_SLICE] = frameQ[P_SLICE] / m_param->rc.ipFactor;
                /* Loop over the planned future frames. */
                bool iter = true;
                for (int j = 0; bufferFillCur >= 0 && iter ; j++)			//根据satd预测后面帧的bit
                {
                    int type = curFrame->m_lowres.plannedType[j];
                    if (type == X265_TYPE_AUTO || totalDuration >= 1.0)
                        break;
                    totalDuration += m_frameDuration;
                    double wantedFrameSize = m_vbvMaxRate * m_frameDuration;
                    if (bufferFillCur + wantedFrameSize <= m_bufferSize)
                        bufferFillCur += wantedFrameSize;					//理想的buffer状态
                    int64_t satd = curFrame->m_lowres.plannedSatd[j] >> (X265_DEPTH - 8);
                    type = IS_X265_TYPE_I(type) ? I_SLICE : IS_X265_TYPE_B(type) ? B_SLICE : P_SLICE;
                    int predType = getPredictorType(curFrame->m_lowres.plannedType[j], type);
                    curBits = predictSize(&m_pred[predType], frameQ[type], (double)satd);
                    bufferFillCur -= curBits;								//实际的buffer状态
                    if (!m_param->bResetZoneConfig && ((uint64_t)j == (m_param->reconfigWindowSize - 1)))
                        iter = false;
                }
                if (rce->vbvEndAdj)			//不进入IF条件
                {
                    bool loopBreak = false;
                    double bufferDiff = m_param->vbvBufferEnd - (m_bufferFill / m_bufferSize);
                    rce->targetFill = m_bufferFill + m_bufferSize * (bufferDiff / (m_param->totalFrames - rce->encodeOrder));
                    if (bufferFillCur < rce->targetFill)
                    {
                        q *= 1.01;
                        loopTerminate |= 1;
                        loopBreak = true;
                    }
                    if (bufferFillCur > m_param->vbvBufferEnd * m_bufferSize)
                    {
                        q /= 1.01;
                        loopTerminate |= 2;
                        loopBreak = true;
                    }
                    if (!loopBreak)
                        break;
                }
                else       //令bufferFillCur在0.5至0.8之间
                {
                    /* Try to get the buffer at least 50% filled, but don't set an impossible goal. */
                    double finalDur = 1;
                    if (m_param->rc.bStrictCbr)
                    {
                        finalDur = x265_clip3(0.4, 1.0, totalDuration);
                    }
                    targetFill = X265_MIN(m_bufferFill + totalDuration * m_vbvMaxRate * 0.5, m_bufferSize * (1 - 0.5 * finalDur));
                    if (bufferFillCur < targetFill)
                    {
                        q *= 1.01;
                        loopTerminate |= 1;
                        continue;
                    }
                    /* Try to get the buffer not more than 80% filled, but don't set an impossible goal. */
                    targetFill = x265_clip3(m_bufferSize * (1 - 0.2 * finalDur), m_bufferSize, m_bufferFill - totalDuration * m_vbvMaxRate * 0.5);
                    if (m_isCbr && bufferFillCur > targetFill && !m_isSceneTransition)
                    {
                        q /= 1.01;
                        loopTerminate |= 2;
                        continue;
                    }
                    break;
                }
            }
            q = X265_MAX(q0 / 2, q);
        }
        else                             
        {
            /* Fallback to old purely-reactive algorithm: no lookahead. */
            if ((m_sliceType == P_SLICE || m_sliceType == B_SLICE ||
                    (m_sliceType == I_SLICE && m_lastNonBPictType == I_SLICE)) &&
                m_bufferFill / m_bufferSize < 0.5)
            {
                q /= x265_clip3(0.5, 1.0, 2.0 * m_bufferFill / m_bufferSize);
            }
            // Now a hard threshold to make sure the frame fits in VBV.
            // This one is mostly for I-frames.
            double bits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd);

            // For small VBVs, allow the frame to use up the entire VBV.
            double maxFillFactor;
            maxFillFactor = m_bufferSize >= 5 * m_bufferRate ? 2 : 1;
            // For single-frame VBVs, request that the frame use up the entire VBV.
            double minFillFactor = m_singleFrameVbv ? 1 : 2;

            for (int iterations = 0; iterations < 10; iterations++)
            {
                double qf = 1.0;
                if (bits > m_bufferFill / maxFillFactor)
                    qf = x265_clip3(0.2, 1.0, m_bufferFill / (maxFillFactor * bits));
                q /= qf;
                bits *= qf;
                if (bits < m_bufferRate / minFillFactor)
                    q *= bits * minFillFactor / m_bufferRate;
                bits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd);
            }

            q = X265_MAX(q0, q);
        }

        /* Apply MinCR restrictions */
        double pbits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd);
        if (pbits > rce->frameSizeMaximum)
            q *= pbits / rce->frameSizeMaximum;
        /* To detect frames that are more complex in SATD costs compared to prev window, yet 
         * lookahead vbv reduces its qscale by half its value. Be on safer side and avoid drastic 
         * qscale reductions for frames high in complexity */
        bool mispredCheck = rce->movingAvgSum && m_currentSatd >= rce->movingAvgSum && q <= q0 / 2;
        if (!m_isCbr || (m_isAbr && mispredCheck))
            q = X265_MAX(q0, q);

        if (m_rateFactorMaxIncrement)
        {
            double qpNoVbv = x265_qScale2qp(q0);
            double qmax = X265_MIN(lmax,x265_qp2qScale(qpNoVbv + m_rateFactorMaxIncrement));
            return x265_clip3(lmin, qmax, q);
        }
    }
    if (m_2pass)
    {
        double min = log(lmin);
        double max = log(lmax);
        q = (log(q) - min) / (max - min) - 0.5;
        q = 1.0 / (1.0 + exp(-4 * q));
        q = q*(max - min) + min;
        return exp(q);
    }
    return x265_clip3(lmin, lmax, q);
}

在实际的一帧编码完之后,rateControlEnd上场了,根据帧的实际编码bit的消耗,跟新VBV状态以及预测参数。主要函数调用过程为rateControlEnd->updateVbv->updatePredictor.其中,VBV状态的跟新在updateVbv函数里,即m_bufferFillFinal -= bits 减去实际编码的bit; m_bufferFillFinal += m_bufferRate 加上每帧平均bit;即之前说的一取一加操作。在这边会对VBV是否下溢进行一个判断:

 if (m_bufferFillFinal < 0)
        x265_log(m_param, X265_LOG_WARNING, "poc:%d, VBV underflow (%.0f bits)\n", rce->poc, m_bufferFillFinal);

说明下溢是需要重点防止的。另外提一下,updatePredictor函数会更新bit的预测参数。在rateControlStart函数中会调用predictSize对不同帧类型,根据其SATD、Qscale预测其需要消耗的bit,该函数在clipQscale被多次用到,具有重要作用。updatePredictor函数的作用就是根据预测以及编码实际bit,对相应帧类型(B/P/I/BREF)跟新预测参数,使下一次相同类型的帧的bit预测更加准确。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编译好的x265,带y4m文件 Syntax: x265 [options] infile [-o] outfile infile can be YUV or Y4M outfile is raw HEVC bitstream Executable Options: -h/--help Show this help text and exit -V/--version Show version info and exit Output Options: -o/--output Bitstream output file name --log-level Logging level: none error warning info debug full. Default info --no-progress Disable CLI progress reports --[no-]cu-stats Enable logging stats about distribution of cu across all modes. Default disabled Input Options: --input Raw YUV or Y4M input file name. `-` for stdin --fps Source frame rate (float or num/denom), auto-detected if Y4M --input-res WxH Source picture size [w x h], auto-detected if Y4M -f/--frames Maximum number of frames to encode. Default all --seek First frame to encode Quality reporting metrics: --[no-]ssim Enable reporting SSIM metric scores. Default disabled --[no-]psnr Enable reporting PSNR metric scores. Default disabled Profile, Level, Tier: --profile Enforce an encode profile: main, main10, mainstillpicture --level-idc Force a minumum required decoder level (as '5.0' or '50') --[no-]high-tier If a decoder level is specified, this modifier selects High tier of that level Threading, performance: --threads Number of threads for thread pool (0: detect CPU core count, default) -F/--frame-threads Number of concurrently encoded frames. 0: auto-determined by core count --[no-]wpp Enable Wavefront Parallel Processing. Default enabled --[no-]pmode Parallel mode analysis. Default disabled --[no-]pme Parallel motion estimation. De

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值