H264编码-码率控制原理以及JM代码分析
碼率控制的主要目的是控制每一幀圖像編碼輸出的比特數(shù),并在總比特數(shù)一定的約束條件下使圖像失真最小。當然,由于視頻圖像質量及其編碼復雜性,碼率控制的目標并不是單一的。常見的控制目標包括:提高輸出碼率控制精度使其最大程度的接近目標碼率;提高編碼后輸出比特流的峰值信噪比;減少碼率波動;提高編碼速度等。碼率控制是一個多約束條件、多目標的優(yōu)化問題。
碼率控制涉及視頻質量和信道帶寬的折衷。減少碼率會犧牲質量,質量提高就會增加碼率。常用的碼率調節(jié)手段包括:
1. 典型碼率控制過程
2. H264的碼率控制模型
H264參考軟件JM實現(xiàn)的碼率控制算法基于JVT-G102,是一個多層次的CBR碼率控制算法。碼率控制由三個層次組成:GOP層碼率控制、圖像層碼率控制以及宏塊層碼率控制。
JM編碼流程如下圖所示,綠色框為碼率控制模塊。
GOP層碼率控制主要在rc_init_GOP函數(shù)中實現(xiàn);
對于GOP中每一幀圖像調用rc_init_frame實現(xiàn)圖像層碼率控制;
圖像中的每一個宏塊編碼前調用rc_handle_mb實現(xiàn)宏塊層碼率控制,更新RQ模型系數(shù),并得到編碼宏塊QP
2.1 GOP層碼率控制
GOP層碼率控制主要目的是:1.確定當前GOP中尚未編碼圖像的目標比特數(shù);2.GOP中第一個IDR圖像的量化參數(shù)。JM對應實現(xiàn)在rc_init_GOP函數(shù)中
2.1.1 目標比特數(shù)
目標比特數(shù)的計算滿足以下原則:
當編碼到第i個GOP的第j幀圖像時,當前GOP剩余圖像的目標比特數(shù)按照如下公式更新:
其中,f為幀率,Ni為當前GOP的幀數(shù),Vi(j)為虛擬緩存區(qū)占用bit數(shù)。Ri(j)為當前可用的信道帶寬(bit/s),也就是設置的輸出碼率,在網(wǎng)絡傳輸時,該值需要根據(jù)當前網(wǎng)絡狀況動態(tài)調節(jié)。bi(j-1)為前一圖像編碼后的實際比特數(shù)。在CBR時,Ri(j)=Ri(j-1),此使顯然:
Bi(j)=Bi(j-1)-bi(j-1)
每編碼完一幀圖像后,需要按照下式更新虛擬緩存區(qū)大小:
2.1.2 量化參數(shù)
對于視頻序列第一個GOP,其IDR圖像的量化參數(shù)根據(jù)設置的輸出碼率確定。根據(jù)每個像素分配的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圖像,則根據(jù)前一個GOP的統(tǒng)計數(shù)據(jù)計算確定其量化參數(shù)。公式如下:
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幀幀數(shù),SumPQP(i?1)表示這些P幀量化參數(shù)的和。其中, N_p(i-1)表示第i-1個GOP中P幀幀數(shù), SumPQP(i-1)表示這些P幀量化參數(shù)的和。其中,Np?(i?1)表示第i?1個GOP中P幀幀數(shù),SumPQP(i?1)表示這些P幀量化參數(shù)的和。
2.2 圖像層碼率控制
圖像層碼率控制可分為兩個部分:編碼前控制和編碼后控制。其中編碼前控制的目的是確定該GOP中每一幀圖像的量化參數(shù)。編碼后控制的目的是根據(jù)當前圖像的編碼結果,更新RDO模型參數(shù)。
2.2.1 編碼前控制
編碼前控制對于參考圖像(也就是P slice)和非參考圖像(B slice)由不同的處理方式,每個GOP中第一個I slice在GOP層碼率控制中已經(jīng)確定。
JM中對應代碼在rc_quadratic.c中的updateQPRC0函數(shù)(根據(jù)參數(shù)RCUpdateMode選擇不同函數(shù)調用,RCUpdateMode默認方式為0)。
- 對于非參考圖像(B slice)的QP根據(jù)相鄰的兩幀參考圖像使用簡單的插值方式處理。計算過程如下:
假設j和j+L+1是相鄰的兩幀參考圖像,QP(j)和QP(j+L+1)分別對應他們的量化參數(shù),根據(jù)L是否大于1,分兩種情況計算兩者間非參考幀的量化參數(shù)。
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,編碼前控制由兩個步驟組成:確定每一個參考圖像的目標比特數(shù);確定參考圖像的量化參數(shù)并進行RDO。JM中對應代碼在rc_quadratic.c中的rc_init_pict函數(shù)。
1)確定參考圖像的目標比特數(shù)主要考慮以下幾個因素:不同類型圖像分配比特數(shù)的比例有差異;編碼圖像的復雜度;GOP中不同位置的圖像對清空緩存區(qū)的貢獻不同。
首先,計算圖像復雜度,圖像復雜度與編碼比特數(shù)和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)。這個變量用于修正待編碼圖像對清空緩存區(qū)的權重,基本原理時清空緩存區(qū)的工作應該更多由非參考幀來承擔。當編碼完IDR圖像后,目標緩存級別設置為當前緩存區(qū)的占用程度:
p_quad->TargetBufferLevel = (double) p_gen->CurrentBufferFullness;
以后,每編碼完一個參考圖像,當前圖像目標緩存按照下式進行更新:
考慮目標緩存級別、幀率、可用帶寬以及緩存區(qū)的實際占用情況,可以得到一個計算參考幀比特數(shù)的公式:
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剩余比特數(shù)考慮,可以得到另一個計算目標比特數(shù)的公式:
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);綜合考慮這兩種計算方法,得到如下計算目標比特數(shù)的公式:
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
接下來需要確定參考圖像的量化參數(shù)并進行RDO。主要實現(xiàn)參考JM代碼updateQPRC0。
首先,根據(jù)線性模型,根據(jù)前一個已編碼P幀的MAD預測當前幀MAD;
其中兩個線性模型參數(shù)MADPictureC1、MADPictureC2在編碼當前圖像后,需要用線性回歸法對它們進行更新。
然后,根據(jù)二次RQ模型確定圖像的量化參數(shù),計算過程參考JM代碼updateModelQPFrame函數(shù)實現(xiàn)。
通過對一元二次方程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. 編碼后處理
在圖像級編碼前控制中,我們確定了每幀圖像的量化參數(shù)。在該層次的編碼后控制階段主要完成以下兩件事情:根據(jù)當前幀(或宏塊)的編碼情況更新MAD預測模型(線性模型)和二次RQ模型;更新緩存區(qū)狀態(tài),處理緩存區(qū)溢出。
1.更新碼率控制模型:在編碼完當前幀/宏塊后,編碼器需要更新RQ和MAD預測模型。這里使用線性回歸算法來更新模型的參數(shù)。更新過程由下面三步組成(這部分代碼實現(xiàn)參考JM的updateRCModel函數(shù)):
1)選擇數(shù)據(jù)集:模型的準確度依賴于做線性回歸的數(shù)據(jù)集大小及其質量。一般來說,選擇的數(shù)據(jù)量越多,越能反映出平均情況,但難以反映出實時情況。例如在場景切換時,需要根據(jù)最近的情況及時更新模型。
使用滑動窗機制來確定數(shù)據(jù)集大小。其基本原則是在平穩(wěn)情況下,選擇較大窗口。提高模型準確度,在場景切換時,選擇較小窗口,以及時反映新場景對模型的影響。
JM代碼中使用圖像的MAD來表示圖像發(fā)生場景切換的強度。通過當前圖像的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)計算模型參數(shù):根據(jù)滑動窗內(nèi)數(shù)據(jù)集(QStep、MAD、編碼bit數(shù)),使用線性回歸法更新RQ模型參數(shù)。計算過程在JM的RCModelEstimator函數(shù).
具體推導過程參考RQ模型參數(shù)推導過程
3)移除滑動窗內(nèi)誤差太大的數(shù)據(jù)樣本,并重新計算RQ模型參數(shù)。使用上一步驟計算得到的RQ模型參數(shù)計算每個樣本的均方差,均方差超過閾值K的樣本則認為該數(shù)據(jù)是錯誤數(shù)據(jù),應該從數(shù)據(jù)集剔除。然后再根據(jù)步驟2重新計算一次RQ模型參數(shù)。
//計算誤差是看參數(shù)估計后模型計算結果表現(xiàn)怎么樣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; //默認第一幀圖像無條件保留,防止數(shù)據(jù)集為空2.3 基本編碼單元層碼率控制
基本編碼單元(basic unit)是碼率控制的基本單元,由連續(xù)編碼的一組n個宏塊構成。如果n和圖像的宏塊個數(shù)相等,則變成圖像層碼率控制;如果n等于1,則相當于宏塊層的碼率控制。
如果當前圖像為IDR幀或非參考幀,則該幀所有編碼單元使用相同的量化參數(shù)QP。
基本編碼單元層碼率控制的目標是為每一幀圖像中的每個基本單元選擇合適的量化參數(shù),使得圖像編碼比特數(shù)盡可能接近目標值。算法分為5步:
總結
以上是生活随笔為你收集整理的H264编码-码率控制原理以及JM代码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 女孩的问题,男孩的回答
- 下一篇: LM317稳压芯片在工程中的应用