H264簡介
H.264從1999年開始,到2003年形成草案,最后在2007年定稿有待核實。在ITU的標准⾥稱為H.264,在MPEG的標准⾥是MPEG-4的⼀個組成部分–MPEG-4 Part 10,⼜叫AdvancedVideo Codec,因此常常稱為MPEG-4 AVC或直接叫AVC。
H264編碼原理
在⾳視頻傳輸過程中,視頻⽂件的傳輸是⼀個極⼤的問題;⼀段分辨率為1920*1080,每個像素點為RGB占⽤3個字節,幀率是25的視頻,對於傳輸帶寬的要求是:1920*1080*3*25/1024/1024=148.315MB/s,換成bps則意味着視頻每秒帶寬為1186.523Mbps,這樣的速率對於⽹絡存儲是不可接受的。因此視頻壓縮和編碼技術應運⽽⽣。
對於視頻⽂件來說,視頻由單張圖⽚幀所組成,⽐如每秒25幀,但是圖⽚幀的像素塊之間存在相似性,因此視頻幀圖像可以進⾏圖像壓縮;H264采⽤了16*16的分塊⼤⼩對,視頻幀圖像進⾏相似⽐較和壓縮編碼。如下圖所示:
H264中的I幀、P幀和B幀
H26使⽤幀內壓縮和幀間壓縮的⽅式提⾼編碼壓縮率;H264采⽤了獨特的I幀、P幀和B幀策略來實現,連續幀之間的壓縮;
如上圖所示:
幀的分類 | 中文 | 意義 |
I幀 | 幀內編碼幀 intra picture |
I 幀通常是每個 GOP(MPEG 所使⽤的⼀種視頻壓縮技術)的第⼀個幀,經過適度地壓縮,做為隨機訪問的參考點,可 以當成圖象。I幀可以看成是⼀個圖像經過壓縮后的產物。⾃身可以通過視頻解壓算法解壓成⼀張單獨的完整的圖⽚。 |
P幀 | 前向預測編碼幀 predictive-frame |
通過充分將低於圖像序列中前⾯已編碼幀的時間冗余信息來壓縮傳輸數據量的編碼圖像,也叫預測幀。 需要參考其前⾯的⼀個I frame 或者P frame來⽣成⼀張完整的圖⽚。 |
B幀 | 雙向預測幀 bi-directional interpolated prediction frame |
既考慮與源圖像序列前⾯已編碼幀,也顧及源圖像序列后⾯已編碼幀之間的時間冗余信息來壓縮傳輸數據量的編碼圖像, 也叫雙向預測幀。 則要參考其前⼀個I或者P幀及其后⾯的⼀個P幀來⽣成⼀張完整的圖⽚。 |
壓縮率 B > P > I
H264編碼結構解析
H264除了實現了對視頻的壓縮處理之外,為了⽅便⽹絡傳輸,提供了對應的視頻編碼和分⽚策略;類似於⽹絡數據封裝成IP幀,在H264中將其稱為組(GOP, group of pictures)、⽚(slice)、宏塊(Macroblock)這些⼀起組成了H264的碼流分層結構;H264將其組織成為序列(GOP)、圖⽚(pictrue)、⽚(Slice)、宏塊(Macroblock)、⼦塊(subblock)五個層次。
GOP (圖像組)主要⽤作形容⼀個IDR幀 到下⼀個IDR幀之間的間隔了多少個幀。
H264將視頻分為連續的幀進⾏傳輸,在連續的幀之間使⽤I幀、P幀和B幀。同時對於幀內⽽⾔,將圖像分塊為⽚、宏塊和字塊進⾏分⽚傳輸;通過這個過程實現對視頻⽂件的壓縮包裝。
IDR(Instantaneous Decoding Refresh,即時解碼刷新)
⼀個序列的第⼀個圖像叫做 IDR 圖像(⽴即刷新圖像),IDR 圖像都是 I 幀圖像。I和IDR幀都使⽤幀內預測。I幀不⽤參考任何幀,但是之后的P幀和B幀是有可能參考這個I幀之前的幀的。IDR就不允許這樣。⽐如(解碼的順序):
- IDR1 P4 B2 B3 P7 B5 B6 I10 B8 B9 P13 B11 B12 P16 B14 B15 這⾥的B8可以跨過I10去參考P7
原始圖像: IDR1 B2 B3 P4 B5 B6 P7 B8 B9 I10
- IDR1 P4 B2 B3 P7 B5 B6 IDR8 P11 B9 B10 P14 B11 B12 這⾥的B9就只能參照IDR8和P11,不可以參考IDR8前⾯的幀
其核⼼作⽤是,是為了解碼的重同步,當解碼器解碼到 IDR 圖像時,⽴即將參考幀隊列清空,將已解碼的數據全部輸出或拋棄,重新查找參數集,開始⼀個新的序列。這樣,如果前⼀個序列出現重⼤錯誤,在這⾥可以獲得重新同步的機會。IDR圖像之后的圖像永遠不會使⽤IDR之前的圖像的數據來解碼。
下⾯是⼀個H264碼流的舉例(從碼流的幀分析可以看出來B幀不能被當做參考幀)
I0 B40 B80 B120 P160
I0 B160
GOP group of pictures
GOP 指的就是兩個I幀之間的間隔. ⽐較說GOP為120,如果是720 p60 的話,那就是2s⼀次I幀.在視頻編碼序列中,主要有三種編碼幀:I幀、P幀、B幀,如下所示:
- I幀即Intra-coded picture(幀內編碼圖像幀),不參考其他圖像幀,只利⽤本幀的信息進⾏編碼。
- P幀即Predictive-codedPicture(預測編碼圖像幀),利⽤之前的I幀或P幀,采⽤運動預測的⽅式進⾏幀間預測編碼。
- B幀即Bidirectionallypredicted picture(雙向預測編碼圖像幀),提供最⾼的壓縮⽐,它既需要之前的圖像幀(I幀或P幀),也需要后來的圖像幀(P幀),采⽤運動預測的⽅式進⾏幀間雙向預測編碼。
在視頻編碼序列中,GOP即Group of picture(圖像組),指兩個I幀之間的距離,Reference(參考周期)指兩個P幀之間的距離。⼀個I幀所占⽤的字節數⼤於⼀個P幀,⼀個P幀所占⽤的字節數⼤於⼀個B幀。
所以在碼率不變的前提下,GOP值越⼤,P、B幀的數量會越多,平均每個I、P、B幀所占⽤的字節數就越多,也就更容易獲取較好的圖像質量;Reference越⼤,B幀的數量越多,同理也更容易獲得較好的圖像質量。
需要說明的是,通過提⾼GOP值來提⾼圖像質量是有限度的,在遇到場景切換的情況時,H.264編碼器會⾃動強制插⼊⼀個I幀,此時實際的GOP值被縮短了。另⼀⽅⾯,在⼀個GOP中,P、B幀是由I幀預測得到的,當I幀的圖像質量⽐較差時,會影響到⼀個GOP中后續P、B幀的圖像質量,直到下⼀個GOP開始才有可能得以恢復,所以GOP值也不宜設置過⼤。同時,由於P、B幀的復雜度⼤於I幀,所以過多的P、B幀會影響編碼效率,使編碼效率降低。另外,過⻓的GOP還會影響Seek操作的響應速度,由於P、B幀是由前⾯的I或P幀預測得到的,所以Seek操作需要直接定位,解碼某⼀個P或B幀時,需要先解碼得到本GOP內的I幀及之前的N個預測幀才可以,GOP值越⻓,需要解碼的預測幀就越多,seek響應的時間也越⻓。
NALU
SPS:序列參數集,SPS中保存了⼀組編碼視頻序列(Coded video sequence)的全局參數。
PPS:圖像參數集,對應的是⼀個序列中某⼀幅圖像或者某⼏幅圖像的參數。
I幀:幀內編碼幀,可獨⽴解碼⽣成完整的圖⽚。
P幀: 前向預測編碼幀,需要參考其前⾯的⼀個I 或者B 來⽣成⼀張完整的圖⽚。
B幀: 雙向預測內插編碼幀,則要參考其前⼀個I或者P幀及其后⾯的⼀個P幀來⽣成⼀張完整的圖⽚。
發I幀之前,⾄少要發⼀次SPS和PPS。
NALU結構
H.264原始碼流(裸流)是由⼀個接⼀個NALU組成,它的功能分為兩層,VCL(視頻編碼層)和NAL(⽹絡提取層):
- VCL:包括核⼼壓縮引擎和塊,宏塊和⽚的語法級別定義,設計⽬標是盡可能地獨⽴於⽹絡進⾏⾼效的編碼;
- NAL:負責將VCL產⽣的⽐特字符串適配到各種各樣的⽹絡和多元環境中,覆蓋了所有⽚級以上的語法級別
在VCL進⾏數據傳輸或存儲之前,這些編碼的VCL數據,被映射或封裝進NAL單元。(NALU)
⼀個NALU = ⼀組對應於視頻編碼的NALU頭部信息 + ⼀個原始字節序列負荷(RBSP,Raw Byte Sequence Payload).
NALU結構單元的主體結構如下所示;⼀個原始的H.264 NALU單元通常由[StartCode] [NALU Header] [NALU Payload]三部分組成,其中 Start Code ⽤於標示這是⼀個NALU 單元的開始,必須是"00 00 00 01" 或"00 00 01",除此之外基本相當於⼀個NAL header + RBSP;
(對於FFmpeg解復⽤后,MP4⽂件讀取出來的packet是不帶startcode,但TS⽂件讀取出來的packet帶了startcode)
解析NALU
每個NAL單元是⼀個⼀定語法元素的可變⻓字節字符串,包括包含⼀個字節的頭信息(⽤來表示數據類型),以及若⼲整數字節的負荷數據。
NALU頭信息(⼀個字節):
其中:
- T為負荷數據類型,占5bit
nal_unit_type:這個NALU單元的類型,1~12由H.264使⽤,24~31由H.264以外的應⽤使⽤
- R為重要性指示位,占2個bit
nal_ref_idc.:取00~11,似乎指示這個NALU的重要性,如00的NALU解碼器可以丟棄它⽽不影響圖像的回放,0~3,取值越⼤,表示當前NAL越重要,需要優先受到保護。如果當前NAL是屬於參考幀的⽚,或是序列參數集,或是圖像參數集這些重要的單位時,本句法元
素必需⼤於0。
- 最后的F為禁⽌位,占1bit
forbidden_zero_bit: 在 H.264 規范中規定了這⼀位必須為 0.
H.264標准指出,當數據流是儲存在介質上時,在每個NALU 前添加起始碼:0x000001 或0x00000001,⽤來指示⼀個NALU 的起始和終⽌位置:
- 在這樣的機制下,在碼流中檢測起始碼,作為⼀個NALU得起始標識,當檢測到下⼀個起始碼時,當前NALU結束。
- 3字節的0x000001只有⼀種場合下使⽤,就是⼀個完整的幀被編為多個slice(⽚)的時候,包含這些slice的NALU 使⽤3字節起始碼。其余場合都是4字節0x00000001的。
例⼦:
0x00 00 00 01 67 …
0x00 00 00 01 68 …
0x00 00 00 01 65 …
67:
⼆進制:0110 0111
00111 = 7(⼗進制)
nal_unit_type | NAL單元和RBSP語法結構的內容 |
0 | 未指定 |
1 | ⼀個⾮IDR圖像的編碼條帶slice_layer_without_partitioning_rbsp( ) |
2 | 編碼條帶數據分割塊A slice_data_partition_a_layer_rbsp( ) |
3 | 編碼條帶數據分割塊B slice_data_partition_b_layer_rbsp( ) |
4 | 編碼條帶數據分割塊C slice_data_partition_c_layer_rbsp( ) |
5 | IDR圖像的編碼條帶(⽚) slice_layer_without_partitioning_rbsp( ) |
6 | 輔助增強信息 (SEI) sei_rbsp( ) |
7 | 序列參數集 seq_parameter_set_rbsp( ) |
8 | 圖像參數集 pic_parameter_set_rbsp( ) |
9 | 訪問單元分隔符 access_unit_delimiter_rbsp( ) |
10 | 序列結尾 end_of_seq_rbsp( ) |
11 | 流結尾 end_of_stream_rbsp( ) |
12 | 填充數據 filler_data_rbsp( ) |
13 | 序列參數集擴展 seq_parameter_set_extension_rbsp( ) |
14...18 | 保留 |
19 | 未分割的輔助編碼圖像的編碼條帶 slice_layer_without_partitioning_rbsp( ) |
20...23 | 保留 |
24...31 | 未指定 |
H264 annexb模式
H264有兩種封裝
⼀種是annexb模式,傳統模式,有startcode,SPS和PPS是在ES中
⼀種是mp4模式,⼀般mp4 mkv都是mp4模式,沒有startcode,SPS和PPS以及其它信息被封裝在container中,每⼀個frame前⾯4個字節是這個frame的⻓度
很多解碼器只⽀持annexb這種模式,因此需要將mp4做轉換:在ffmpeg中⽤h264_mp4toannexb_filter可以做轉換
實現:
const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb"); AVBSFContext *bsf_ctx = NULL; // 2 初始化過濾器上下⽂ av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext; // 3 添加解碼器屬性 avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->cod ecpar); av_bsf_init(bsf_ctx);