H264 編碼詳解(收集轉載)
(1) x264_param_default( x264_param_t *param )
作用: 對編碼器進行參數設定
cqm:量化表相關信息
csp:
量化表相關信息里的memset( param->cqm_4iy, 16, 16 );
memset( param->cqm_4ic, 16, 16 );
memset( param->cqm_4py, 16, 16 );
memset( param->cqm_4pc, 16, 16 );
memset( param->cqm_8iy, 16, 64 );
memset( param->cqm_8py, 16, 64 );
(2)static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt ) 初始化
1. getopt_long(nargc, nargv, options, long_options, idx) 得到入口地址的向量與方式的選擇
2. getopt_internal(nargc, nargv, options) 解析入口地址向量
(3) static int Encode( x264_param_t *param, cli_opt_t *opt )
h->param=param
vui信息主要包括幀率、圖像尺寸等信息
x264_sps_init( h->sps, 0, &h->param );
x264_pps_init( h->pps, 0, &h->param, h->sps);
初始化並開辟幀空間
對前一宏塊的信息保存,因為是初始化,所以作為第一個宏塊的參考,后面會有x264_macroblock_cache_load( h, i_mb_x, i_mb_y );它是將要編碼的宏塊的周圍的宏塊的值讀進來, 要想得到當前塊的預測值,要先知道上面,左面的預測值
初始化cpu對各種分塊的參數設定
1. x264_t *x264_encoder_open ( x264_param_t *param ) 這個函數是對不正確的參數進行修改,並對各結構體參數和cabac編碼,預測等需要的參數進行初始化
2、p_read_frame( &pic, opt->hin, i_frame + opt->i_seek, param->i_width, param->i_height )
讀取一幀,並把這幀設為prev
3. i_file += Encode_frame( h, opt->hout, &pic );進入核碼層
核心編碼層的總流程圖:(x264.c)
1. x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out )對幀進行編碼
2. i_size = x264_nal_encode( data, &i_data, 1, &nal[i] )
網絡打包編碼
3. i_file += p_write_nalu( hout, data, i_size )
把網絡包寫入到輸出文件中去
4. 返回,對下一幀進行編碼
下面一頁是詳細的流程圖:
一.幀內詳細流程圖:
1. x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out )對幀進行編碼
1.
x264_frame_t*fenc=x264_frame_get( h->frames.unused );
x264_frame_copy_picture( h, fenc, pic_in );
fenc->i_frame = h->frames.i_input++;
x264_frame_put( h->frames.next, fenc );
x264_frame_init_lowres( h->param.cpu, fenc );//里面包含低象素的擴展,很多for循環,應該是抽頭計算和半精度象素的擴展,要認真看
2. 264_slicetype_decide( h );對slice類型的判定,里面也要看一下
3. while( IS_X264_TYPE_B( h->frames.next[bframes]->i_type ) )
bframes++;
x264_frame_put(h->frames.current,x264_frame_get( &h->frames.next[bframes] ) );這主要是因為B幀必須等后面的非B幀編碼結束后才能編碼,所以把暫時不編的一系列B幀存入隊列中,一直到非B幀才取出進行編碼,之后再進行前面的B幀編碼
do_encode:
4. 建立list0 & list1.我感覺
x264_reference_build_list( h, h->fdec->i_poc, i_slice_type );
比特率控制初始化
x264_ratecontrol_start(h, i_slice_type, h->fenc->i_qpplus1 );
5. 創建slice的頭部數據
x264_slice_init( h, i_nal_type, i_slice_type, i_global_qp );
6 i_frame_size = x264_slices_write( h );這是編碼的關鍵了
1. x264_slice_header_write(&h->out.bs,&h->sh,h->i_nal_ref_idc );
2. 一些初始化工作
3. for(mb_xy=h->sh.i_first_mb, i_skip = 0; mb_xy < h->sh.i_last_mb; mb_xy++ )對一個slice中每個宏塊進行循環遍歷編碼,其中const int i_mb_y = mb_xy / h->sps->i_mb_width;和const int i_mb_x = mb_xy % h->sps->i_mb_width;是對宏塊位置在slice中的x,y坐標的定位,這個for語句幾乎覆蓋了整個x264_slices_write()函數
4. x264_macroblock_cache_load( h, i_mb_x, i_mb_y ); 它是將要編碼的宏塊的周圍的宏塊的值讀進來, 要想得到當前塊的預測值,要先知道上面,左面的預測值!
5. *****x264_macroblock_analyse( h );重點。通過一系列的SAD算出最優化方案,例如把I幀16×16的宏塊分成16個4×4分別計算SAD和與原16×16SAD比較我感覺,在下面一層再詳細分析。
a. x264_mb_analyse_intra( h, &analysis, COST_MAX );我感覺是在一個16×16的SAD,4個8×8的SAD和,16個4×4SAD和中選出最優方式進行,可能我的理解不對,里面的x264_mb_encode_i4x4( h, idx, a->i_qp );i8×8幾個函數的跟蹤有問題,跟得我都找不到,要仔細看(現在又能跟到了)
這邊好像如果是直流分量在這里就進行量化ZIGZAG掃描了,不用等到x264_macroblock_encode( h )再完成了
b. x264_analyse_update_cache( h, &analysis ); 有對色度塊的模式選擇的計算,好像也有更新信息以為下次的預測作為參考
6. x264_macroblock_encode( h );
a. 判斷宏塊的類型
b. 根據判斷的類型進行DCT,量化,ZIGZAG,並記錄當前的模式為下次編碼宏塊(亞宏塊)做參考
ZIGZAG的實現不明白(原來ZIGZAG有宏定義,在上面,現在明白了),反量化和IDCT的過程跟不進去,應該是匯編了!函數如下:( I 4×4 中 x264_mb_encode_i4x4( h, i, i_qp );)
x264_mb_dequant_4x4( dct4x4, h->dequant4_mf[CQM_4IY], i_qscale );
h->dctf.add4x4_idct( p_dst, i_stride, dct4x4 );
還有,這個函數跟蹤不進去,應該是重構圖像的反變換吧
h->dctf.add4x4_idct( p_dst, i_stride, dct4x4 );
h->mb.cache.intra4x4_pred_mode[x264_scan8[i]]=x264_mb_pred_mode4x4_fix(i_mode);這個值到底是怎么根據前面的模式改變的,可能是上面兩個函數沒能更進去所以模糊
c. 對色度塊進行編碼,QP限制在0-51之間,選定預測模式(DC的話值全為128)
x264_mb_encode_8x8_chroma( h, !IS_INTRA( h->mb.i_type ), i_qp );里面對兩個色度信號分別編碼,與亮度信號類似
d. 求亮度和色度的cbp,完全不明白是怎么求的,需要解決!現在有點明白,每個比特代表子塊是不是全為0,但還沒有全部明白,色度塊cbp中0x02表示有AC,DC 0x01表示只有DC,
e.利用CBP判斷要不要SKIP.,里面還關系到向量預測,明天好好看一下。 其中
h->mb.qp[h->mb.i_mb_xy] = h->mb.i_last_qp;這個為讀下一個 qp的保存,不然解碼端是讀不出下一個 qp的,
關於CBP的理解還存在問題,他的8位比特各個代表的意思還不是十分明確,反正是對DC,AC的編碼的選擇。185頁有介紹(新一代視頻壓縮標准畢厚傑)
7. 選用CABAC還是CAVLC
CABAC的原理實現沒仔細看
8. x264_macroblock_cache_save( h );保存以為下次的預測作為參考
9. 一些收尾工作,為下次宏塊作准備(看的比較粗)
一視頻編碼介紹(H264)
1.1 視頻壓縮編碼的目標
1)保證壓縮比例
2)保證恢復的質量
3)易實現,低成本,可靠性
1.2 壓縮的出發點(可行性)
1)時間相關性
在一組視頻序列中,相鄰相鄰兩幀只有極少的不同之處,這便是時間相關性。
2)空間相關性
在同一幀中,相鄰象素之間有很大的相關性,兩象素越近,側相關性越強。
根據采用的信源的模型分類:
1)基於波形的編碼
如果采用“一幅圖像由許多象素構成”的信源模型,這種信源模型的參數就是象素的亮度和色度的幅度值,對這些參數進行編碼的技術即為基於波形編碼。
2)基於內容的編碼
如果采用一個分量有幾個物體構成的信源模型,這種信源模型的參數事各個物體的形狀,紋理,運動,對這些參數進行編碼的技術就是基於內容的編碼。
h264應用可分為3個級別:
1)基本檔次:(簡單版本, 應用面廣 , 支持幀內和幀間編碼,基於可變程度的熵編碼.)
應用領域:視頻會話,會議電視, 無線通信等實時通信.
2)主要檔次:(采用了多項提高圖像質量和增加壓縮比的技術措施, 支持隔行視頻, 支持基於上下文的自適應的算術編碼.)
應用領域: 數字廣播與數字視頻存儲
3)擴展檔次: 應用領域: 可用於各種網絡的視頻流傳輸,視頻點播
二視頻編碼的原理
2.1 一個圖像或者一個視頻序列進行壓縮,產生碼流。
對圖像的處理即是:幀內預測編碼
其預測值P,是由已編碼的圖像做參考,經運動補償得到的。預測圖像P和當前幀Fn相減,得到兩圖像的殘差值Dn,Dn在經過轉換T,量化Q,去處空間冗余,得到系數X,將X重排(使數據更加緊湊),熵編碼(加入運動矢量。。。一些圖像相關得信息),得到nal數據。
對視頻序列的處理:幀間預測編碼
預測值P,是由當前片中,己編碼的宏塊預測得到的(亮度4×4或者16×16預測,色度8×8預測)。當前待處理的塊,減去預測值P,得殘差值Dn,Dn在經過轉換T,量化Q,得到系數X,將X重排(使數據更加緊湊),熵編碼,得到nal數據
2.2 場、幀、圖像
場:隔行掃描的圖像,偶數行成為頂場行。奇數行成為底場行。
所有頂場行稱為頂場。所有底場行稱為底場.
幀:逐行掃描的圖像
圖像:場和幀都可認為是圖像.
2.3宏塊、片
宏塊(MB):一個宏塊由一個16×16亮度塊、一個8×8Cb和一個8×8Cr組成
片(slice):一個圖像可以划分成一個或多個片,一個片由一個或多個宏塊組成。
三H264結構和應用
H.264從框架結構上將NAL與VCL分離,主要有兩個目的:
其一,可以定義VCL視頻壓縮處理與NAL網絡傳輸機制的接口,這樣允許視頻編碼層VCL的設計可以在不同的處理器平台進行移植,而與NAL層的數據封裝格式無關;
其二,VCL和NAL都被設計成工作於不同的傳輸環境,異構的網絡環境並不需要對VCL比特流進行重構和重編碼。
3.1 H264的編碼格式
h264的功能分為兩層,視頻編碼層(VCL)和網絡提取層(NAL)
VCL功能是進行視頻編解碼,包括運動補償預測,變換編碼和熵編碼等功能;
NAL用於采用適當的格式對VCL視頻數據進行封裝打包
1)VCL數據即被壓縮編碼后的視頻數據序列。
在VCL數據要封裝到NAL單元中之后,才可以用來傳輸或存儲。
2)NAL單元格式
NAL單元由1字節的頭,3個定長的字段和一個字節數不定的編碼段組成。
頭標的語法:NALU類型(5bit)、重要性指示位(2bit)、禁止位(1bit)。
NALU類型:1~12由H.264使用,24~31由H.264以外的應用使用。
重要性指示:標志該NAL單元用於重建時的重要性,值越大,越重要。
禁止位:網絡發現NAL單元有比特錯誤時可設置該比特為1,以便接收方丟掉該單元
| Nal頭 |
Rbsp |
Nal頭 |
Rbsp |
Nal頭 |
Rbsp |
(1)NAL Units:視頻數據封裝在整數字節的NALU中,它的第一個字節標志該單元中數據的類型。H.264定義了兩種封裝格式。基於包交換的網絡(如H.323系統)可以使用RTP封裝格式封裝NALU。而另外一些系統可能要求將NALU作為順序比特流傳送,為此H.264定義了一種比特流格式的傳輸機制,使用start_code_prefix將NALU封裝起來,從而確定NAL邊界。
(2)參數集:以往視頻編解碼標准中GOB\GOP\圖像等頭信息是至關重要的,包含這些信息的包的丟失常導致與這些信息相關的圖像不能解碼。為此H.264將這些很少變化並且對大量VCL NALU起作用的信息放在參數集中傳送。參數集分為兩種,即序列參數集和圖像參數集。為適應多種網絡環境,參數集可以帶內傳送,也可以采用帶外方式傳送。
序列的參數集(SPS):包括了一個圖像序列的所有信息,
圖像的參數集(PPS):包括了一個圖像所有片的信息。
3.2 H264的網絡傳輸
H.264能夠在基於RTP/UDP/IP、H.323/M、MPEG-2傳輸和H.320協議的網絡中使用
H.264的RTP封裝參考RFC 3550,載荷類型(PT)域未作規定
3.3數據的划分
通常情況下,一個宏塊的數據是存放在一起而組成片的,數據划分使得一個片中的宏塊數據重新組合,把宏塊語義相關的數據組成一個划分,由划分來組裝片。在H.264中有三種不同的數據划分。
(1)頭信息划分:包含片中宏塊的類型,量化參數和運動矢量,是片中最重要的信息。
(2)幀內信息划分:包含幀內CBPs和幀內系數,幀內信息可以阻止錯誤的蔓延。
(3)幀間信息划分:包含幀間CBPs和幀間系數,通常比前兩個划分要大得多。
幀內信息划分結合頭信息解出幀內宏塊,幀間信息划分結合頭信息解出幀間宏塊。幀間信息划分的重要性最低,對重同步沒有貢獻。當使用數據划分時,片中的數據根據其類型被保存到不同的緩存,同時片的大小也要調整,使得片中最大的划分小於MTU尺寸。
解碼端若獲得所有的划分,就可以完整重構片;解碼端若發現幀內信息或幀間信息划分丟失,可用的頭信息仍然有很好的錯誤恢復性能。這是因為宏塊類型和宏塊的運動矢量含有宏塊的基本特征。
3.4靈活的宏塊次序(FMO)
通過設置宏塊次序映射表(MBAmap)來任意地指配宏塊到不同的片組,FMO模式打亂了原宏塊順序,降低了編碼效率,增加了時延,但增強了抗誤碼性能。FMO模式划分圖像的模式各種各樣,重要的有棋盤模式、矩形模式等。當然FMO模式也可以使一幀中的宏塊順序分割,使得分割后的片的大小小於無線網絡的MTU尺寸。經過FMO模式分割后的圖像數據分開進行傳輸,以棋盤模式為例,當一個片組的數據丟失時可用另一個片組的數據(包含丟失宏塊的相鄰宏塊信息)進行錯誤掩蓋。實驗數據顯示,當丟失率為(視頻會議應用時)10%時,經錯誤掩蓋后的圖像仍然有很高的質量。
四 H264的網絡傳輸
NAL支持眾多基於包的有線/無線通信網絡,諸如H.320、MPEG-2和RTP/IP等。但目前,絕大部分的視頻應用所采用的網絡協議層次是RTP/UDP/IP,因此在下面的描述中主要基於這個傳輸框架。下面首先分析NAL層的基本處理單元NALU以及它的網絡封裝、分割和合並的方法。
4.1. NAL單元
每個NAL單元是一個一定語法元素的可變長字節字符串,包括包含一個字節的頭信息(用來表示數據類型),以及若干整數字節的負荷數據。一個NAL單元可以攜帶一個編碼片、A/B/C型數據分割或一個序列或圖像參數集。
NAL單元按RTP序列號按序傳送。其中,T為負荷數據類型,占5bit;R為重要性指示位,占2個bit;最后的F為禁止位,占1bit。具體如下:
(1)NALU類型位
可以表示NALU的32種不同類型特征,類型1~12是H.264定義的,類型24~31是用於H.264以外的,RTP負荷規范使用這其中的一些值來定義包聚合和分裂,其他值為H.264保留。
(2)重要性指示位
用於在重構過程中標記一個NAL單元的重要性,值越大,越重要。值為0表示這個NAL單元沒有用於預測,因此可被解碼器拋棄而不會有錯誤擴散;值高於0表示此NAL單元要用於無漂移重構,且值越高,對此NAL單元丟失的影響越大。
(3)禁止位
編碼中默認值為0,當網絡識別此單元中存在比特錯誤時,可將其設為1,以便接收方丟掉該單元,主要用於適應不同種類的網絡環境(比如有線無線相結合的環境)。例如對於從無線到有線的網關,一邊是無線的非IP環境,一邊是有線網絡的無比特錯誤的環境。假設一個NAL單元到達無線那邊時,校驗和檢測失敗,網關可以選擇從NAL流中去掉這個NAL單元,也可以把已知被破壞的NAL單元前傳給接收端。在這種情況下,智能的解碼器將嘗試重構這個NAL單元(已知它可能包含比特錯誤)。而非智能的解碼器將簡單地拋棄這個NAL單元。NAL單元結構規定了用於面向分組或用於流的傳輸子系統的通用格式。在H.320和MPEG-2系統中,NAL單元的流應該在NAL單元邊界內,每個NAL單元前加一個3字節的起始前綴碼。在分組傳輸系統中,NAL單元由系統的傳輸規程確定幀界,因此不需要上述的起始前綴碼。一組NAL單元被稱為一個接入單元,定界后加上定時信息(SEI),形成基本編碼圖像。該基本編碼圖像(PCP)由一組已編碼的NAL單元組成,其后是冗余編碼圖像(RCP),它是PCP同一視頻圖像的冗余表示,用於解碼中PCP丟失情況下恢復信息。如果該編碼視頻圖像是編碼視頻序列的最后一幅圖像,應出現序列NAL單元的end,表示該序列結束。一個圖像序列只有一個序列參數組,並被獨立解碼。如果該編碼圖像是整個NAL單元流的最后一幅圖像,則應出現流的end。
H.264采用上述嚴格的接入單元,不僅使H.264可自適應於多種網絡,而且進一步提高其抗誤碼能力。序列號的設置可發現丟的是哪一個VCL單元,冗余編碼圖像使得即使基本編碼圖像丟失,仍可得到較“粗糙”的圖像。
4.2. H.264中的RTP
上面闡述了NAL單元的結構和實現,這里要詳細討論RTP的載荷規范和抗誤碼性能。RTP可通過發送冗余信息來減少接收端的丟包率,會增加時延,與冗余片不同的是它增加的冗余信息是個別重點信息的備份,適合於非平等保護機制。相應的多媒體傳輸規范有:
(1)分組復制多次重發,發送端對最重要的比特信息分組進行復制重發,使得保證接收端能至少正確接收到一次,同時接收端要丟棄已經正確接收的分組的多余備份。
(2)基於分組的前向糾錯,對被保護的分組進行異或運算,將運算結果作為冗余信息發送到接收方。由於時延,不用於對話型應用,可用於流媒體。
(3)音頻冗余編碼,可保護包括視頻在內的任何數據流。每個分組由頭標、載荷以及前一分組的載荷組成,H.264中可與數據分割一起使用。
RTP的封裝規范總結如下:
(1)額外開銷要少,使MTU尺寸在100~64千字節范圍都可以;
(2)易於區分分組的重要性,而不必對分組內的數據解碼;
(3)載荷規范應當保證不用解碼就可識別由於其他比特丟失而造成的分組不可解碼;
(4)支持將NALU分割成多個RTP分組;
(5)支持將多個NALU匯集在一個RTP分組中。
H.264采用了簡單打包的方案,即一個RTP分組里放入一個NALU,將NALU(包括同時作為載荷頭標的NALU頭)放入RTP的載荷中,設置RTP頭標值。理想情況下,VCL不會產生超過MTU尺寸的NAL單元,來避免IP層的分拆。在接收端,通過RTP序列信息識別復制包並丟棄,取出有效RTP包里的NAL單元。基本檔次和擴展檔次允許片的無序解碼,這樣在抖動緩存中就不必對包重新排序。在使用主檔次時(不允許片的亂序),要通過RTP序列信息來對包重新排序,解碼順序號(DON)的概念現正在IETF的討論中。
存在如下情況,例如當使用內容預編碼時,編碼器不了解底層網絡的MTU大小,將產生許多大於MTU尺寸的NALU。這就需要涉及NALU的分割和合並。
(1)NALU的分割
雖然IP層的分割可以使數據塊小於64千字節,但無法在應用層實現保護,從而降低了非平等保護方案的效果。由於UDP數據包小於64千字節,而且一個片的長度對某些應用場合來說太小,所以應用層打包是RTP打包方案的一部分。目前的拆分方案正在IETF的討論之中,大致具有以下特點:①NALU的分塊以按RTP次序號升序傳輸;②能夠標記第一個和最后一個NALU分塊;③可以檢測丟失的分塊。
(2)NALU的合並
一些NALU如SEI、參數集等非常小,將它們合並在一起有利於減少頭標開銷。現有的兩種集合分組:①單一時間集合分組(STAP),按時間戳進行組合,一般用於低時延環境;②多時間集合分組(MTAP),不同時間戳也可以組合,一般用於高時延環境,比如流應用
