一,、簡述
流媒體編解碼流程大致如圖1所示: 【流媒體編解碼流程 圖1】
視頻數(shù)據(jù)編解碼層格式包含有:H264,,H265,MPEG4等,。
本文我們主要對H264編碼原理進(jìn)行整理,,并對NALU做簡要介紹。
二、H264編解碼
2.1,、H264簡介
參考來源:H264百度百科 H.264從1999年開始到2003年形成草案,,最后在2007年定稿有待核實。在ITU的標(biāo)準(zhǔn)里稱為H.264,,在MPEG的標(biāo)準(zhǔn)里是MPEG-4的一個組成部分(MPEG-4 Part 10),,又叫Advanced Video Codec,因此H.264也常常稱為MPEG-4或直接叫AVC,。
比如下面使用 MediaInfo工具查看flv音視頻文件的信息,,可以看到video格式為AVC,其實也就是H264格式,。 【flv音視頻文件基本信息 圖2】
2.2,、H264編解碼原理
參考來源:H264 編解碼協(xié)議詳解,深入淺出理解視頻編碼H264結(jié)構(gòu),,h264編解碼結(jié)構(gòu)框圖 1,、H264概述
問題:為什么要對音視頻文件進(jìn)行H264編解碼?
因為,,在音視頻傳輸過程中,視頻文件的傳輸是個極大的問題,;一段分辨率為1920 * 1080,,每個像素點(diǎn)為RGB占用3個字節(jié),幀率是25的視頻,,對于傳輸帶寬的要求是:1920 * 1080 * 3 * 25/1024/1024=148.315MB/s,,換成bps則意味著視頻每秒帶寬為1186.523Mbps,這樣的速率對于網(wǎng)絡(luò)存儲是不可接受的,。因此視頻壓縮和編碼技術(shù)應(yīng)運(yùn)而生,。
對于視頻文件來說,視頻由單張圖片幀所組成,,比如每秒25幀,,但是圖片幀的像素塊之間存在相似性,因此視頻幀圖像可以進(jìn)行圖像壓縮,;H264采用了16 * 16的分塊大小對,,視頻幀圖像進(jìn)行相似比較和壓縮編碼。如下圖所示: 【圖像切分 圖3】 壓縮編碼可以分為內(nèi)部壓縮和外部壓縮,。
1)內(nèi)部壓縮 內(nèi)部壓縮指的是一幀圖片的內(nèi)部壓縮,。當(dāng)H264對圖片進(jìn)行 16 * 16 分塊后,會對每個小塊內(nèi)的圖像進(jìn)行分析,,如果2個小塊圖像比較相近,,那么住需要存儲一張即可,無需存儲重復(fù)圖塊,。這樣可以有效壓縮圖片的存儲大小,。
比如下面一張圖片,,劃分的A、B小塊圖像分析后是基本一樣的,,那么只需要存儲A即可,,B不需要進(jìn)行存儲。 【內(nèi)部壓縮 圖4】
2)外部壓縮 外部壓縮指的是圖片間的圖像壓縮,。在每幀圖片劃分成16 * 16 小塊的圖像進(jìn)行分析基礎(chǔ)上,,比圖片間的數(shù)據(jù),如果兩張圖片比較相近,,對相同的圖像模塊只需存儲一份,,對不同的部分再做存儲。避免了重復(fù)數(shù)據(jù)的存儲,,極大改善了圖片壓縮空間,。
比如下面兩張圖片 ,除了E小塊不同之外,,其他都一樣,,那么存儲圖1數(shù)據(jù)后,圖2片只需要存儲與圖片1不同的數(shù)據(jù)即可,。 【外部壓縮 圖5】
2,、H264中的 I幀、P幀和B幀 H264 使用幀內(nèi)壓縮和幀間壓縮的方式提高編碼壓縮率,;H264采用了獨(dú)特的 I幀,,P幀和B幀策略來實現(xiàn),連續(xù)幀之間的壓縮,。 【H264 IBP幀排序 圖6】 1)I 幀 (幀內(nèi)編碼幀 intra picture) I 幀通常是每個 GOP(MPEG 所使用的一種視頻壓縮技術(shù))的第一個幀,,經(jīng)過適度地壓縮,做為隨機(jī)訪問的參考點(diǎn),,可以當(dāng)成圖象,。I幀表示關(guān)鍵幀,解碼時只需要本幀數(shù)據(jù)就可以完成,。I幀可以看成是一個圖像經(jīng)過壓縮后的產(chǎn)物,。自身可以通過視頻解壓算法解壓成一張單獨(dú)的完整的圖片。
I幀特點(diǎn):
- 是一個全幀壓縮編碼幀,。它將全幀圖像信息進(jìn)行JPEG壓縮編碼及傳輸,。
- 解碼時僅用 I幀的數(shù)據(jù)就可重構(gòu)完整圖像。
- I幀描述了圖像背景和運(yùn)動主體的詳情,。
- I幀不需要參考其他畫面而生成,。
- I幀是P幀和B幀的參考幀(I幀質(zhì)量直接影響到同組以后各幀的質(zhì)量)。
- I幀是幀組GOP的基礎(chǔ)幀(如果為IDR則為第一幀),在一組中只有一個IDR幀,,一個或多個I幀(包括IDR幀),。
- I幀不需要考慮運(yùn)動矢量。
- I幀所占數(shù)據(jù)的信息量比較大,。
2)P幀 (前向預(yù)測編碼幀 predictive-frame) 通過充分將低于圖像序列中前面已編碼幀的時間冗余信息來壓縮傳輸數(shù)據(jù)量的編碼圖像,,也叫預(yù)測幀。 P幀表示這一幀跟之前的一個關(guān)鍵幀(或P幀)的差別,,解碼時需要用之前緩存的畫面疊加上本幀定義的差別,,生成最終畫面。 需要參考其前面的一個I frame 或者P frame來生成一張完整的圖片,。
P幀的預(yù)測和重構(gòu): P幀是以 I幀為參考幀,,在I幀中找出P幀“某點(diǎn)”的預(yù)測值和運(yùn)動矢量,取預(yù)測差值和運(yùn)動矢量一起傳送,。在接收端根據(jù)運(yùn)動矢量從I幀中找出P幀“某點(diǎn)”的預(yù)測值并與差值相加得到P幀“某點(diǎn)”樣值,,從而得到完整的P幀。
P幀特點(diǎn):
- P幀是I幀后面相隔1~2幀的編碼幀,。
- P幀采用運(yùn)動補(bǔ)償?shù)姆椒▊魉退c前面的I幀或P幀的差值及運(yùn)動矢量(預(yù)測誤差),。
- 解碼時必須將 I幀的預(yù)測值與預(yù)測誤差求和后才能重構(gòu)完整的P幀圖像。
- P幀屬于向前預(yù)測的幀間編碼,。它只參考前面最靠近它的I幀或P幀,。
- P幀可以是其后面P幀的參考幀,也可以是其前后的B幀的參考幀,。
- 由于P幀是參考幀,它可能造成解碼錯誤的擴(kuò)散,。
- 由于是差值傳送,,P幀的壓縮比較高。
3)B 幀 (雙向預(yù)測幀 bi-directional interpolated prediction frame) 既考慮與源圖像序列前面已編碼幀,,也顧及源圖像序列后面已編碼幀之間的時間冗余信息來壓縮傳輸數(shù)據(jù)量的編碼圖像,也叫雙向預(yù)測幀,。B幀要參考其前一個I或者P幀及其后面的一個P幀來生成一張完整的圖片。
B幀是雙向差別幀,,B幀記錄的是本幀與前后幀的差別,。要解碼B幀,不僅要取得之前的緩存畫面,,還要解碼之后的畫面,,通過前后畫面與本幀的疊加取得最終畫面。
B幀的預(yù)測和重構(gòu): B幀以前面的 I幀或P幀為參考幀,,找出B幀“某點(diǎn)”的預(yù)測值和兩個運(yùn)動矢量,,并取預(yù)測差值和運(yùn)動矢量傳送。接收端根據(jù)運(yùn)行矢量在兩個參考幀中找出預(yù)測值并與差值求和,得到B幀“某點(diǎn)”樣值,,從而可得到完整的B幀,。
B幀特點(diǎn):
- B幀是由前面的I幀或P幀和后面的P幀來進(jìn)行預(yù)測的。
- B幀傳送的是它與前面的I幀或P幀之間的預(yù)測誤差及運(yùn)動矢量,。
- B幀是雙向預(yù)測編碼幀,。
- B幀壓縮比最高,因為它只反映參考幀間運(yùn)動主體的變化情況,,預(yù)測比較準(zhǔn)確,。
- B幀不是參考幀,不會造成解碼錯誤導(dǎo)致的擴(kuò)散,。
壓縮率比較:B幀 > P 幀 > I 幀
3,、H264編碼結(jié)構(gòu)解析 H264除了實現(xiàn)對視頻的壓縮處理外,為了方便網(wǎng)絡(luò)傳輸,,還提供了對應(yīng)的視頻編碼和分片策略,。類似網(wǎng)絡(luò)數(shù)據(jù)幀封裝成IP幀,在H264中將其稱為組(GOP,,group of picture),、片(slice)、宏塊(Macroblock),,它們一起組成了H264的碼流分層結(jié)構(gòu),。H264將其組織成為序列(GOP)、圖片(pictrue),、片(Slice),、宏塊(Macroblock)、子塊(subblock)五個層次,。 【H264結(jié)構(gòu)組織 圖7】
H264將視頻分為連續(xù)的幀進(jìn)行傳輸,,在連續(xù)的幀之間使用 I幀、P幀和B幀,。同時對于幀內(nèi)而言,,將圖像分塊為片、宏塊和字塊進(jìn)行分片傳輸,;通過這個過程實現(xiàn)對視頻文件的壓縮包裝,。
IDR(Instantaneous Decoding Refresh,即時解碼刷新) 一個序列的第一個圖像叫做IDR圖像(立即刷新圖像),,IDR圖像都是 I 幀圖像,。(I幀圖像不一定是IDR圖像) I 幀和IDR幀都使用幀內(nèi)預(yù)測。I 幀不用參考任何幀,,但是之后的P幀和B幀是有可能參考這個I幀之前的幀,。但 IDR不允許這樣做,。
比如原始圖像幀序為: IDR1 B2 B3 P4 B5 B6 P7 B8 B9 I10 解碼順序:
- IDR1 P4 B2 B3 P7 B5 B6 I10 B8 B9 P13 B11 B12 P16 B14 B15 這里的B8可以跨過I10去參考P7
- IDR1 P4 B2 B3 P7 B5 B6 IDR8 P11 B9 B10 P14 B11 B12 這里的B9就只能參照IDR8和P11,不可以參考IDR8前面的幀
IDR幀的核心作用是為了解碼的重同步,,當(dāng)解碼器解碼到 IDR 圖像時,,立即將參考幀列清空,將已解碼的數(shù)據(jù)全部輸出或拋棄 ,,重新查找參數(shù)集,,開始一個新的序列。這樣做的好處是,,如果前一個序列出現(xiàn)重大失誤,,在這里可以獲得重新同步的機(jī)會。IDR圖下之后的圖像永遠(yuǎn)不會使用 IDR幀之前的圖像的數(shù)據(jù)來解碼,。
下圖為一個 H264 碼流的示例(從碼流幀分析可以看出來B幀不能被當(dāng)作參考幀) 【H264 碼流的示例 圖8】 GOP (圖像組)主要用作形容一個IDR幀 到下一個IDR幀之間的間隔了多少個幀,。
比如說GOP為120,如果是720 p60的話,,就是2s一次 I幀,。
在碼率不變的前提下,GOP值越大,,P,、B幀的數(shù)量越多,平均每個I,、P,、B幀占用的字節(jié)數(shù)越多,也更容易獲取較好的圖像質(zhì)量,。
Reference(參考周期)指兩個P幀之間的距離,。一個I幀所占用的字節(jié)數(shù)大于一個P幀,一個P幀所占用的字節(jié)數(shù)大于一個B幀,。Reference越大,,B幀的數(shù)量越多,同理也更容易獲得較好的圖像質(zhì)量,。
不過通過提高GOP值來提高圖像質(zhì)量是有限度的,因為:
- 在遇到場景切換時,,H264編碼器會自動強(qiáng)制插入一個 I幀,,此時實際的GOP值被縮短了。
- 在一個GOP中,,P,、B幀是由 I幀預(yù)測得到,當(dāng) I幀的圖像質(zhì)量比較差時,,會影響到一個GOP中后續(xù)P,、B幀的圖像質(zhì)量,,直到下一個GOP開始才得以回復(fù),因此GOP值不宜設(shè)置過大,。
- 由于P,、B幀的復(fù)雜度大于I幀,所以過多的P,、B幀會影響編碼效率,,使編碼效率降低。
- 過長的GOP還會影響Seek操作的響應(yīng)速度,,由于P,、B幀是由前面的I或P幀預(yù)測得到的,所以Seek操作需要直接定位,,解碼某一個P或B幀時,,需要先解碼得到本GOP內(nèi)的I幀及之前的N個預(yù)測幀才可以,GOP值越大,,需要解碼的預(yù)測幀就越多,,seek響應(yīng)的時間也越長。
做直播時,,一般不用B幀,,因為B幀需要占用較大的緩存,并且容易出現(xiàn)延遲,。因為B幀要參考其前一個I或者P幀及其后面的一個P幀來生成一張完整的圖片,,因此在編碼的時候B幀要等到P幀才能發(fā)送出去。
比如:收到的I,、B,、P幀的序列為(后面數(shù)值表示收到時間,單位 ms):I0 B40 B80 B120 P160,,P幀是160ms的時候才收到,, 這樣B40幀從收到到發(fā)出就會延遲 160-40=120ms。
三,、NALU介紹
【NALU示意 圖8】
- SPS:序列參數(shù)集,,SPS中保存了一組編碼視頻序列(Coded video sequence)的全局參數(shù)。
- PPS:圖像參數(shù)集,,對應(yīng)的是一個序列中某一幅圖像或者某一幅圖像的參數(shù),。
- I幀:幀內(nèi)編碼幀,可獨(dú)立解碼生成完整的圖片,。
- P幀: 前向預(yù)測編碼幀,,需要參考其前面的一個I 或者B 來生成一張完整的圖片。
- B幀: 雙向預(yù)測內(nèi)插編碼幀,,則要參考其前面?zhèn)€I或者P幀及其后面的一個P幀來生成一張完整的圖片,。
注意: 1)從上圖我們可以知道,,一張圖片可以有多個NALU。 2)對解碼器來說,,需要先收到SPS和PPS進(jìn)行初始化,,否則解碼器無法解出正常的幀數(shù)據(jù)。 3)發(fā)I幀之前,,至少要發(fā)送一次SPS和PPS,,因此如果在實際應(yīng)用中遇到H264無法解碼的時候,檢查SPS和PPS是否有接收到并正常初始化,。
NALU結(jié)構(gòu) H264原始碼流(裸流)是由一個接一個NALU組成,,功能分為兩層:VCL(視頻編碼層)和NAL(網(wǎng)絡(luò)提取層):
- VCL:包括核心壓縮引擎和塊,宏塊和片的語法級別定義,,設(shè)計目標(biāo)是盡可能地獨(dú)立于網(wǎng)絡(luò)進(jìn)行高效的編碼,。
- NAL:負(fù)責(zé)將VCL產(chǎn)生的比特字符串適配到各種各樣的網(wǎng)絡(luò)和多元環(huán)境,覆蓋所有片級以上的語法級別,。
在VCL進(jìn)行數(shù)據(jù)傳輸或存儲之前,,這些編碼的VCL數(shù)據(jù),被映射或封裝進(jìn)NAL單元,。 NALU=一組對應(yīng)視頻編碼的NALU頭部信息+一個原始字節(jié)序列負(fù)荷(RBSP,,RawByte Sequence Payload)
【NALU結(jié)構(gòu)單元的主體結(jié)構(gòu) 圖9】 一個原始的H264 NALU單元通常由 [StartCode] [NALU Header] [NALU Payload] 三部分組成,其中Start Code用于表示這是一個NALU單元的開始,,必須是“00 00 00 01” 或 “00 00 01”,,除此之外基本相當(dāng)于一個NAL header+RBSP。
對于FFmpeg解復(fù)用,,MP4,,flv等文件讀取出來的packet不帶Start code,TS文件讀取出來的packet帶StartCode,,因此在對MP4,、flv文件解碼封裝的的時候,需要添加上Startcode,,否則會出現(xiàn)生成文件損壞導(dǎo)致無法播放問題,。
NALU解析 每個NAL單元是一個一定語法元素的可變長字節(jié)字符串,包括包含一個字節(jié)的頭信息(用來表示數(shù)據(jù)類型),,以及若干整數(shù)字節(jié)的負(fù)荷數(shù)據(jù),。
NALU頭信息(占一個字節(jié)大小): 【NALU頭信息 圖10】 字節(jié)位參數(shù)說明:
-
T:負(fù)荷數(shù)據(jù)類型,,占 5bit,。 nal_unit_type:這個NALU單元的類型,,1~12由H.264使用,,24~31由H.264以外的應(yīng)用使用,。 -
R:指示位,占2bit,。 nal_ref_idc.:取00~11,,似乎指示這個NALU的重要性,如00的NALU解碼器可以丟棄它而不影響圖像的回放,,0~3,,取值越大,表示當(dāng)前NAL越重要,,需要優(yōu)先受到保護(hù),。如果當(dāng)前NAL是屬于參考幀的片,或是序列參數(shù)集,,或是圖像參數(shù)集這些重要的單位時,,本句法元素必需大于0。 -
F:禁止位,,占1bit,。 forbidden_zero_bit: 在 H.264 規(guī)范中規(guī)定了這?位必須為 0。
H.264標(biāo)準(zhǔn)指出,,當(dāng)數(shù)據(jù)流是儲存在介質(zhì)上時,,在每個NALU 前添加起始碼:0x000001 或 0x00000001,用來指示一個NALU 的起始和終止位置:
- 在碼流中檢測起始碼,,作為一個NALU的起始標(biāo)識,,當(dāng)檢測到下一個起始碼時,當(dāng)前NALU結(jié)束,。
- 3字節(jié)的0x000001只有一種場合下使用,,就是一個完整的幀被編為多個slice(片)的時候,包含這些slice的NALU 使用3字節(jié)起始碼,。其余場合都是4字節(jié)0x00000001的,。
例子: 0x00 00 00 01 67 … 0x00 00 00 01 68 … 0x00 00 00 01 65 …
0x67:二進(jìn)制 0110 0111 ,nal_unit_type:0 0111=7(十進(jìn)制)
【nal_unit_type數(shù)值對應(yīng)表1】
四,、H264 annexb模式
H264有兩種封裝模式:annexb模式和mp4模式,。
- annexb模式,屬于傳統(tǒng)模式,,有startcode,;SPS和PPS是在ES中。
- mp4模式:mp4 mkv都是mp4模式,,沒有startcode,,SPS和PPS以及其它信息被封裝在container中,每一個frame前面4個字節(jié)是這個frame的長度,。
很多解碼器只支持annexb這種模式,,因此需要將mp4做轉(zhuǎn)換:在ffmpeg中用h264_mp4toannexb_filter可以做轉(zhuǎn)換,。 轉(zhuǎn)換源碼:
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]->codecpar);
av_bsf_init(bsf_ctx);
|