码率控制的主要目的是控制每一帧图像编码输出的比特数,并在总比特数一定的约束条件下使图像失真最小。当然,由于视频图像质量及其编码复杂性,码率控制的目标并不是单一的。常见的控制目标包括:提高输出码率控制精度使其最大程度的接近目标码率;提高编码后输出比特流的峰值信噪比;减少码率波动;提高编码速度等。码率控制是一个多约束条件、多目标的优化问题。

码率控制涉及视频质量和信道带宽的折衷。减少码率会牺牲质量,质量提高就会增加码率。常用的码率调节手段包括:

  1. 调节编码帧率。当码率高于信道时,通过丢帧来降低码率;当码率低于信道时,可提高帧率以提高视觉效果;
  2. 调节图像大小。一般情况下,图像大小设定后不可不改变。
  3. 调节量化参数。编码残差系数的量化参数会直接影响到码率。量化参数大,则编码比特数降低;量化参数小,编码比特数高。
  4. 编码过程相关参数选取。如在编码过程中,如何确定并选取Skip宏块、模式选择算法以及计算量等,这些参数都会影响编码码率。

1. 典型码率控制过程

  1. 在每个短的时间间隔内确定一个目标码率。这个目标码率主要由信道带宽决定,可分为常信道和可变信道。在一个CBR系统中这个目标码率是个常数。在一个VBR系统中,目标码率要满足信道的平均带宽和峰值带宽的要求。比如网络信道,由于信道不稳定,目标码率需要实时更新。
  2. 确定帧/宏块的码率预算,这个预算通常和目标码率和编码数据缓存区占用程度相关。按照目标码率和剩下可用带宽,给当前要编码的帧和宏块分配码率。当缓存块溢出或不够存储当前帧数据时,会选择丢弃当前帧。
  3. 定量化参数,使码率和预算码率匹配。

2. H264的码率控制模型

H264参考软件JM实现的码率控制算法基于JVT-G102,是一个多层次的CBR码率控制算法。码率控制由三个层次组成:GOP层码率控制、图像层码率控制以及宏块层码率控制。

JM编码流程如下图所示,绿色框为码率控制模块。
GOP层码率控制主要在rc_init_GOP函数中实现;
对于GOP中每一帧图像调用rc_init_frame实现图像层码率控制;
图像中的每一个宏块编码前调用rc_handle_mb实现宏块层码率控制,更新RQ模型系数,并得到编码宏块QP

2.1 GOP层码率控制

GOP层码率控制主要目的是:1.确定当前GOP中尚未编码图像的目标比特数;2.GOP中第一个IDR图像的量化参数。JM对应实现在rc_init_GOP函数中

2.1.1 目标比特数

目标比特数的计算满足以下原则:

  1. 控制在本GOP编码结束时清空虚拟缓存区;
  2. 根据输出码率变化及时调整GOP的目标比特数

当编码到第i个GOP的第j帧图像时,当前GOP剩余图像的目标比特数按照如下公式更新:

其中,f为帧率,Ni为当前GOP的帧数,Vi(j)为虚拟缓存区占用bit数。Ri(j)为当前可用的信道带宽(bit/s),也就是设置的输出码率,在网络传输时,该值需要根据当前网络状况动态调节。bi(j-1)为前一图像编码后的实际比特数。在CBR时,Ri(j)=Ri(j-1),此使显然:
Bi(j)=Bi(j-1)-bi(j-1)
每编码完一帧图像后,需要按照下式更新虚拟缓存区大小:

2.1.2 量化参数

对于视频序列第一个GOP,其IDR图像的量化参数根据设置的输出码率确定。根据每个像素分配的bpp(bits per pixel)将初始QP值分为4个等级。

if (bpp<= L1)qp = 35;
else if(bpp<=L2)qp = 25;zh
else if(bpp<=L3)qp = 20;
elseqp = 10;

其中,Bpp由下式确定:
Bpp=R1(1)/(fxN)
对于QCIF视频(176x144)推荐L1 = 0.1,L2 = 0.3,L3 = 0.6;对于CIF视频(352x288)推荐L1 = 0.2,L2 = 0.6,L3 = 1.2;其他情况L1 = 0.6,L2 = 1.4,L3 = 2.4;
对于视频序列其他GOP的IDR图像,则根据前一个GOP的统计数据计算确定其量化参数。公式如下:
QPi(1)=max(QPi−1(1),min(QPi−1(1)+2,SumPQP(i−1)Np(i−1)−min(2,Ni−115)))QP_i(1)=max(QP_{i-1}(1),min(QP_{i-1}(1)+2, \frac {SumPQP(i-1)} {N_p(i-1)} - min(2,\frac {N_{i-1} } {15} ) )) QPi​(1)=max(QPi−1​(1),min(QPi−1​(1)+2,Np​(i−1)SumPQP(i−1)​−min(2,15Ni−1​​)))

其中,Np(i−1)表示第i−1个GOP中P帧帧数,SumPQP(i−1)表示这些P帧量化参数的和。其中, N_p(i-1)表示第i-1个GOP中P帧帧数, SumPQP(i-1)表示这些P帧量化参数的和。其中,Np​(i−1)表示第i−1个GOP中P帧帧数,SumPQP(i−1)表示这些P帧量化参数的和。

2.2 图像层码率控制

图像层码率控制可分为两个部分:编码前控制和编码后控制。其中编码前控制的目的是确定该GOP中每一帧图像的量化参数。编码后控制的目的是根据当前图像的编码结果,更新RDO模型参数。

2.2.1 编码前控制

编码前控制对于参考图像(也就是P slice)和非参考图像(B slice)由不同的处理方式,每个GOP中第一个I slice在GOP层码率控制中已经确定。

JM中对应代码在rc_quadratic.c中的updateQPRC0函数(根据参数RCUpdateMode选择不同函数调用,RCUpdateMode默认方式为0)。

  • 对于非参考图像(B slice)的QP根据相邻的两帧参考图像使用简单的插值方式处理。计算过程如下:
    假设j和j+L+1是相邻的两帧参考图像,QP(j)和QP(j+L+1)分别对应他们的量化参数,根据L是否大于1,分两种情况计算两者间非参考帧的量化参数。

1)L=1时,两帧参考帧之间只有一个非参考帧。此使QP(j+1)按下式计算:


2)L>1时,两帧参考帧之间只有多个非参考帧。此使QP(j+k)按下式计算:

其中,k=1,2,…,L。a由QP(j+L+1)和QP(j)的差值确定:

  • 对于参考图像(P slice)的QP,编码前控制由两个步骤组成:确定每一个参考图像的目标比特数;确定参考图像的量化参数并进行RDO。JM中对应代码在rc_quadratic.c中的rc_init_pict函数。

1)确定参考图像的目标比特数主要考虑以下几个因素:不同类型图像分配比特数的比例有差异;编码图像的复杂度;GOP中不同位置的图像对清空缓存区的贡献不同。

首先,计算图像复杂度,图像复杂度与编码比特数和QP值正相关:
AveWp(k)=Wp(k)/8+7∗AveWp(k−1)/8AveWp(k) = Wp(k)/8+7 * AveWp(k-1)/8AveWp(k)=Wp(k)/8+7∗AveWp(k−1)/8
AveWb(l)=Wb(l)/8+7∗AveWb(l−1)/8AveWb(l) = Wb(l)/8+7 * AveWb(l-1)/8AveWb(l)=Wb(l)/8+7∗AveWb(l−1)/8
Wp(k)=b(k)∗QP(k)Wp(k) = b(k) * QP(k)Wp(k)=b(k)∗QP(k)
Wb(l)=b(l)∗QP(l)/1.3636Wb(l) = b(l) * QP(l)/1.3636Wb(l)=b(l)∗QP(l)/1.3636

其中,AveWp(k)为P slice的平均复杂度,AveWb(k)为B slice的平均复杂度。

其次,需要确定当前图像的目标缓存级别(target buffer level)Si(j)。这个变量用于修正待编码图像对清空缓存区的权重,基本原理时清空缓存区的工作应该更多由非参考帧来承担。当编码完IDR图像后,目标缓存级别设置为当前缓存区的占用程度:
p_quad->TargetBufferLevel = (double) p_gen->CurrentBufferFullness;
以后,每编码完一个参考图像,当前图像目标缓存按照下式进行更新:

if(p_quad->NumberofPPicture==1) // 当前GOP第一个P帧
{// P167公式Si(k+1)p_quad->TargetBufferLevel = (double) p_gen->CurrentBufferFullness; // Si(2)=Vi(2)p_quad->DeltaP = (p_gen->CurrentBufferFullness - p_quad->GOPTargetBufferLevel) / (p_quad->TotalPFrame-1);p_quad->TargetBufferLevel -= p_quad->DeltaP;
}
else if(p_quad->NumberofPPicture>1)p_quad->TargetBufferLevel -= p_quad->DeltaP;

考虑目标缓存级别、帧率、可用带宽以及缓存区的实际占用情况,可以得到一个计算参考帧比特数的公式:

tmp_T  = imax(0, (int) floor(p_quad->bit_rate / p_quad->frame_rate - p_quad->GAMMAP * (p_gen->CurrentBufferFullness-p_quad->TargetBufferLevel) + 0.5));

其中,如果P slice间没有B slice时,常量GAMMAP=0.5;其他情况下GAMMAP=0.25.

同样如果从GOP剩余比特数考虑,可以得到另一个计算目标比特数的公式:

p_quad->Target = (int) floor( p_quad->Wp * p_gen->RemainingBits / (p_quad->Np * p_quad->Wp + p_quad->Nb * p_quad->Wb) + 0.5);

综合考虑这两种计算方法,得到如下计算目标比特数的公式:

p_quad->Target = (int) floor(p_quad->BETAP * (p_quad->Target - tmp_T) + tmp_T + 0.5);

其中,如果P slice间没有B slice时,常量BETAP=0.5;其他情况下BETAP=0.9

接下来需要确定参考图像的量化参数并进行RDO。主要实现参考JM代码updateQPRC0。
首先,根据线性模型,根据前一个已编码P帧的MAD预测当前帧MAD;

p_quad->CurrentFrameMAD = p_quad->MADPictureC1*p_quad->PreviousPictureMAD + p_quad->MADPictureC2;

其中两个线性模型参数MADPictureC1、MADPictureC2在编码当前图像后,需要用线性回归法对它们进行更新。

然后,根据二次RQ模型确定图像的量化参数,计算过程参考JM代码updateModelQPFrame函数实现。

通过对一元二次方程m_Bits = m_X1 * MAD / QStep + m_X2 * MAD / (QStep * QStep)求解得到量化步长QStep。

void updateModelQPFrame( RCQuadratic *p_quad, int m_Bits )
{double dtmp, m_Qstep;// ax*x+bx+c=0 的判别式 b*b-4acdtmp = p_quad->CurrentFrameMAD * p_quad->m_X1 * p_quad->CurrentFrameMAD * p_quad->m_X1+ 4 * p_quad->m_X2 * p_quad->CurrentFrameMAD * m_Bits; // 退化为一阶方程if ((p_quad->m_X2 == 0.0) || (dtmp < 0) || ((sqrt (dtmp) - p_quad->m_X1 * p_quad->CurrentFrameMAD) <= 0.0)) // fall back 1st order modem_Qstep = (float) (p_quad->m_X1 * p_quad->CurrentFrameMAD / (double) m_Bits);else // 2nd order modem_Qstep = (float) ((2 * p_quad->m_X2 * p_quad->CurrentFrameMAD) / (sqrt (dtmp) - p_quad->m_X1 * p_quad->CurrentFrameMAD));p_quad->m_Qc = Qstep2QP(m_Qstep, p_quad->bitdepth_qp_scale);
}

2.2.2. 编码后处理

在图像级编码前控制中,我们确定了每帧图像的量化参数。在该层次的编码后控制阶段主要完成以下两件事情:根据当前帧(或宏块)的编码情况更新MAD预测模型(线性模型)和二次RQ模型;更新缓存区状态,处理缓存区溢出。

1.更新码率控制模型:在编码完当前帧/宏块后,编码器需要更新RQ和MAD预测模型。这里使用线性回归算法来更新模型的参数。更新过程由下面三步组成(这部分代码实现参考JM的updateRCModel函数):

1)选择数据集:模型的准确度依赖于做线性回归的数据集大小及其质量。一般来说,选择的数据量越多,越能反映出平均情况,但难以反映出实时情况。例如在场景切换时,需要根据最近的情况及时更新模型。

使用滑动窗机制来确定数据集大小。其基本原则是在平稳情况下,选择较大窗口。提高模型准确度,在场景切换时,选择较小窗口,以及时反映新场景对模型的影响。

JM代码中使用图像的MAD来表示图像发生场景切换的强度。通过当前图像的MAD和前一帧图像的MAD的比值来调整滑动窗大小,如下:

n_windowSize = (p_quad->CurrentFrameMAD>p_quad->PreviousFrameMAD)? (int)(p_quad->PreviousFrameMAD/p_quad->CurrentFrameMAD * (RC_MODEL_HISTORY-1) ): (int)(p_quad->CurrentFrameMAD/p_quad->PreviousFrameMAD *(RC_MODEL_HISTORY-1));n_windowSize=iClip3(1, m_Nc, n_windowSize); //确定窗口上界n_windowSize=imin(n_windowSize,p_quad->m_windowSize+1); // 防止窗口阶跃过大n_windowSize=imin(n_windowSize,(RC_MODEL_HISTORY-1));

2)计算模型参数:根据滑动窗内数据集(QStep、MAD、编码bit数),使用线性回归法更新RQ模型参数。计算过程在JM的RCModelEstimator函数.
具体推导过程参考RQ模型参数推导过程

3)移除滑动窗内误差太大的数据样本,并重新计算RQ模型参数。使用上一步骤计算得到的RQ模型参数计算每个样本的均方差,均方差超过阈值K的样本则认为该数据是错误数据,应该从数据集剔除。然后再根据步骤2重新计算一次RQ模型参数。

//计算误差是看参数估计后模型计算结果表现怎么样for (i = 0; i < (int) n_windowSize; i++){error[i] = p_quad->m_X1 / p_quad->m_rgQp[i] + p_quad->m_X2 / (p_quad->m_rgQp[i] * p_quad->m_rgQp[i]) - p_quad->m_rgRp[i];std += error[i] * error[i];}threshold = (n_windowSize == 2) ? 0 : sqrt (std / n_windowSize); //均方误差for (i = 0; i < (int) n_windowSize; i++){//* 对预测误差较大的帧,标记为限制访问if (fabs(error[i]) > threshold)m_rgRejected[i] = TRUE;}// always include the last data pointm_rgRejected[0] = FALSE; //默认第一帧图像无条件保留,防止数据集为空

2.3 基本编码单元层码率控制

基本编码单元(basic unit)是码率控制的基本单元,由连续编码的一组n个宏块构成。如果n和图像的宏块个数相等,则变成图像层码率控制;如果n等于1,则相当于宏块层的码率控制。
如果当前图像为IDR帧或非参考帧,则该帧所有编码单元使用相同的量化参数QP。
基本编码单元层码率控制的目标是为每一帧图像中的每个基本单元选择合适的量化参数,使得图像编码比特数尽可能接近目标值。算法分为5步:

  1. 预测当前编码单元的MAD(updateRCModel函数)
  2. 计算当前编码单元的目标比特数,预估编码单元头部信息所需比特数,并计算编码残差系数可用比特数(updateRCModel函数);
  3. 预测当前编码单元的量化参数(updateQPRC0函数),计算过程与图像层码率控制一样
  4. 对当前基本单元的每个宏块进行RDO
  5. 更新当前图像剩余比特数,并根据编码实际结果更新RQ模型以及预测模型系数

H264编码-码率控制原理以及JM代码分析相关推荐

  1. 团队项目开发“编码规范”之九:代码分析

    团队项目开发"编码规范"之九: 代码分析 发布日期:2011年3月17日星期三作者:EricHu                                           ...

  2. H264编码- 码率控制 RQ 模型参数推导过程以及JM代码分析

    摘要:本文主要介绍H264码率控制过程中,RQ模型参数更新推导过程,并结合JM19.0代码分析其功能实现. H264码率控制中比较重要的一个模型是RQ模型,不管是图像级码率控制还是基本单元码率控制都会 ...

  3. JPEG编码原理及文件格式及代码分析

    一 JPEG编码原理 首先我们先来看一下JPEG的编码原理图 如上图所示,下面进行逐步的分析: 1 RGB->YUV 首先为了降低互相的关联性,将RGB转换为YUV,这样就可以对亮度信号和色度信 ...

  4. H.264码率控制算法研究及JM相应代码分析(二)

    在前一篇文章的基础上,现在先看一下MPEG4 编码标准中应用的码率控制算法,总结起来,各大算法都是在解决两个问题:RD 率失真的优化以及避免缓冲区的上溢下溢. MPEG-4 VM8 码率控制算法 在这 ...

  5. MPEG音频编码 基本原理和C语言代码分析

    背景 MPEG(Moving Picture Experts Group)在汉语中译为活动图像专家组,特指活动影音压缩标准. MPEG 音频文件是MPEG1 标准中的声音部分,也叫MPEG 音频层,它 ...

  6. H264编码器5( x264源代码简单分析:x264_slice_write() 与H264 编码简介)

    x264源代码简单分析:x264_slice_write() 来自:https://blog.csdn.net/leixiaohua1020/article/details/45536607 H264 ...

  7. ffmpeg4.4项目学习--H264编码之码率控制模式及参数配置

    目录 一.引言 二.H264编码的四种模式 ------> 2.1.CBR ------> 2.2.VBR ------> 2.3.CVBR ------> 2.4.ABR - ...

  8. H264压缩比和编码码率

    在格式为YUV420的情况下,分辨率为1280x1024,帧率为60,每秒传输1280x1024x60X1.5x8 = 943,718,400 bit = 943.7184 Mbps,因此至少需要94 ...

  9. [JM] 如何结合标准看JM代码(JM86)

    http://bbs.chinavideo.org/viewthread.php?tid=3530 最近我也开始看 X264 的代码了,于是想到把我读代码的过程记录下来,因为总有很多新手问如何读代码, ...

  10. 4 海思Hi3518E实例代码分析

    海思媒体(mmp)处理平台架构 海思媒体处理平台的主要内部处理流程如图 1-2 所示,主要分为视频输入(VI).视频处理(VPSS).视频编码(VENC).视频解码(VDEC).视频输出(VO).视频 ...

最新文章

  1. 附录4:Matplotlib实例记录
  2. Microsoft Dynamics CRM4.0 Data Auditing and Restore (数据审核和恢复)
  3. 皮一皮:藏头诗有时候也不能太藏...
  4. Spring3 表达式语言(SpEL)介绍
  5. nodeJS之crypto加密
  6. 深度学习核心技术精讲100篇(七十五)-集成学习
  7. 人工智能状态图matlab,人工智能—TensorFlow(七):matplotlib图形可视化
  8. 程序员快来看!经典代码替你省去多少时间?
  9. 【Leetcode819】最常见的单词
  10. oracle 12c alert,Oracle 12c DG备库Alert报错ORA-01110
  11. 社区开源版本,基于Springboot精简了代码,改变为单体,方便大家一键启动
  12. 关于并发数与在线数的概念
  13. win10文件误删除怎么恢复,不能错过的恢复方法
  14. C#编程打字指法练习
  15. unity之粒子特效制作图片拼合文字效果
  16. excel表格中18位身份证号码如何转换成出生日期
  17. KindEditor图片上传路径URL的处理
  18. Kaggle竞赛——Titanic泰坦尼克之灾(0.76315==>0.79186)
  19. javaSE进阶学习笔记
  20. (转载)【笨木头Lua专栏】基础补充18:Lua的模块编写与module函数

热门文章

  1. 数据结构与算法面试题
  2. 拆解大数据总线平台DBus的系统架构
  3. html中hover的作用,hover在css中的用法
  4. 进阶级 - Git Hub 常用指南
  5. BZOJ2794 Cloakroom【有限制的背包问题】
  6. 谢孟媛初级文法28 课地方副词时间副词和程度副词
  7. C/C++ 内存泄漏检测工具汇总
  8. idea 2021 IDEA的Persistence 窗口 查看ERD关系图
  9. ViewBinding使用详解
  10. 苹果mac电脑的end 键和home键在哪