生活随笔
收集整理的這篇文章主要介紹了
H.264基础知识及视频码流解析
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
H.264基礎知識及視頻碼流解析
目錄
H.264概述 H264相關概念 H264壓縮方式 H264分層結(jié)構(gòu) H264碼流結(jié)構(gòu) H264的NAL單元 H.264視頻碼流解析及代碼實現(xiàn)
1. H.264概述
編碼是為了將數(shù)據(jù)進行壓縮,這樣在傳輸?shù)倪^程中就不會使資源被浪費。 用一個簡單的例子來說明編碼的必要性:當你此刻顯示器正在播放一個視頻,分辨率是1280*720,幀率是25,那么一秒所產(chǎn)生正常的數(shù)據(jù)大小為:1280*720(位像素)*25(張) / 8(1字節(jié)8位)(結(jié)果:B) / 1024(結(jié)果:KB) / 1024 (結(jié)果:MB) = 2.75MB。顯然一秒這么大的數(shù)據(jù)你是無法接受的,需要將數(shù)據(jù)進行壓縮。 H264在視頻采集到輸出中屬于編解碼層次的數(shù)據(jù),如下圖所示,是在采集數(shù)據(jù)后做編碼壓縮時通過編碼標準編碼后所呈現(xiàn)的數(shù)據(jù)。 對于視頻?件來說,視頻由單張圖?幀所組成,?如每秒25幀,但是圖?幀的像素塊之間存在相似性,因此視頻幀圖像可以進?圖像壓縮;H264采?了16*16的分塊??對,視頻幀圖像進?相似?較和壓縮編碼。如下圖所示:
2. H264相關概念
1. 序列
H264編碼標準中所遵循的理論依據(jù)個人理解成:參照一段時間內(nèi)相鄰的圖像中,像素、亮度與色溫的差別很小。所以當面對一段時間內(nèi)圖像我們沒必要去對每一幅圖像進行完整一幀的編碼,而是可以選取這段時間的第一幀圖像作為完整編碼,而下一幅圖像可以記錄與第一幀完整編碼圖像像素、亮度與色溫等的差別即可,以此類推循環(huán)下去。 什么叫序列呢?上述的這段時間內(nèi)圖像變化不大的圖像集我們就可以稱之為一個序列。序列可以理解為有相同特點的一段數(shù)據(jù)。但是如果某個圖像與之前的圖像變換很大,很難參考之前的幀來生成新的幀,那么就結(jié)束刪一個序列,開始下一段序列。重復上一序列的做法,生成新的一段序列。
2. 幀類型
H264結(jié)構(gòu)中,一個視頻圖像編碼后的數(shù)據(jù)叫做一幀,一幀由一個片(slice)或多個片組成,一個片由一個或多個宏塊(MB)組成,一個宏塊由16x16的yuv數(shù)據(jù)組成。宏塊作為H264編碼的基本單位。 H26使?幀內(nèi)壓縮和幀間壓縮的?式提?編碼壓縮率; H264采?了獨特的I幀、P幀和B幀策略來實現(xiàn),連續(xù)幀之間的壓縮;
1. I幀
I幀:幀內(nèi)編碼幀 ,I幀表示關鍵幀,你可以理解為這一幀畫面的完整保留;解碼時只需要本幀數(shù)據(jù)就可以完成(因為包含完整畫面) I幀特點: 它是一個全幀壓縮編碼幀。它將全幀圖像信息進行JPEG壓縮編碼及傳輸; 解碼時僅用I幀的數(shù)據(jù)就可重構(gòu)完整圖像; I幀描述了圖像背景和運動主體的詳情; I幀不需要參考其他畫面而生成; I幀是P幀和B幀的參考幀(其質(zhì)量直接影響到同組中以后各幀的質(zhì)量); I幀是幀組GOP的基礎幀(第一幀),在一組中只有一個I幀; I幀不需要考慮運動矢量; I幀所占數(shù)據(jù)的信息量比較大。
2. P幀
P幀:前向預測編碼幀。P幀表示的是這一幀跟之前的一個關鍵幀(或P幀)的差別,解碼時需要用之前緩存的畫面疊加上本幀定義的差別,生成最終畫面。(也就是差別幀,P幀沒有完整畫面數(shù)據(jù),只有與前一幀的畫面差別的數(shù)據(jù)) P幀的預測與重構(gòu):P幀是以I幀為參考幀,在I幀中找出P幀“某點”的預測值和運動矢量,取預測差值和運動矢量一起傳送。在接收端根據(jù)運動矢量從I幀中找出P幀“某點”的預測值并與差值相加以得到P幀“某點”樣值,從而可得到完整的P幀。 P幀特點: P幀是I幀后面相隔1~2幀的編碼幀; P幀采用運動補償?shù)姆椒▊魉退c前面的I或P幀的差值及運動矢量(預測誤差); 解碼時必須將I幀中的預測值與預測誤差求和后才能重構(gòu)完整的P幀圖像; P幀屬于前向預測的幀間編碼。它只參考前面最靠近它的I幀或P幀; P幀可以是其后面P幀的參考幀,也可以是其前后的B幀的參考幀; 由于P幀是參考幀,它可能造成解碼錯誤的擴散; 由于是差值傳送,P幀的壓縮比較高。
3. B幀
B幀:雙向預測內(nèi)插編碼幀。B幀是雙向差別幀,也就是B幀記錄的是本幀與前后幀的差別(具體比較復雜,有4種情況),換言之,要解碼B幀,不僅要取得之前的緩存畫面,還要解碼之后的畫面,通過前后畫面的與本幀數(shù)據(jù)的疊加取得最終的畫面。B幀壓縮率高,但是解碼時CPU會比較累。 B幀以前面的I或P幀和后面的P幀為參考幀,“找出”B幀“某點”的預測值和兩個運動矢量,并取預測差值和運動矢量傳送。接收端根據(jù)運動矢量在兩個參考幀中“找出(算出)”預測值并與差值求和,得到B幀“某點”樣值,從而可得到完整的B幀。 B幀特點 B幀是由前面的I或P幀和后面的P幀來進行預測的; B幀傳送的是它與前面的I或P幀和后面的P幀之間的預測誤差及運動矢量; B幀是雙向預測編碼幀; B幀壓縮比最高,因為它只反映兩個參考幀間運動主體的變化情況,預測比較準確; B幀不是參考幀,不會造成解碼錯誤的擴散。
3. GOP(畫面組)
GOP即Group of picture(圖像組),指兩個I幀之間的距離(下圖所說的視頻序列就是GOP),Reference(參考周期)指兩個P幀之間的距離,可以理解為跟序列差不多意思,就是一段時間內(nèi)變化不大的圖像集,比較說GOP為120,如果是720 p60 的話,那就是2s一次I幀。一個I幀所占用的字節(jié)數(shù)大于一個P幀,一個P幀所占用的字節(jié)數(shù)大于一個B幀。所以在碼率不變的前提下,GOP值越大,P、B幀的數(shù)量會越多,平均每個I、P、B幀所占用的字節(jié)數(shù)就越多,也就更容易獲取較好的圖像質(zhì)量;Reference越大,B幀的數(shù)量越多,同理也更容易獲得較好的圖像質(zhì)量。 GOP結(jié)構(gòu)一般有兩個數(shù)字,如M=3,N=12。M指定I幀和P幀之間的距離,N指定兩個I幀之間的距離。上面的M=3,N=12,GOP結(jié)構(gòu)為:IBBPBBPBBPBBI。在一個GOP內(nèi)I frame解碼不依賴任何的其它幀,p frame解碼則依賴前面的I frame或P frame,B frame解碼依賴前最近的一個I frame或P frame 及其后最近的一個P frame。
4. IDR幀(關鍵幀)
IDR(Instantaneous Decoding Refresh)即時解碼刷新。 在編碼解碼中為了方便,將GOP中首個I幀要和其他I幀區(qū)別開,把第一個I幀叫IDR,這樣方便控制編碼和解碼流程,所以IDR幀一定是I幀,但I幀不一定是IDR幀;IDR幀的作用是立刻刷新,使錯誤不致傳播,從IDR幀開始算新的序列開始編碼。I幀有被跨幀參考的可能,IDR不會。 I幀不用參考任何幀,但是之后的P幀和B幀是有可能參考這個I幀之前的幀的。IDR就不允許這樣,例如: 其核?作?是,是為了解碼的重同步,當解碼器解碼到 IDR 圖像時,?即將參考幀隊列清空,將已解碼的數(shù)據(jù)全部輸出或拋棄,重新查找參數(shù)集,開始?個新的序列。這樣,如果前?個序列出現(xiàn)重?錯誤,在這?可以獲得重新同步的機會。IDR圖像之后的圖像永遠不會使?IDR之前的圖像的數(shù)據(jù)來解碼。
3. H264壓縮方式
H264采用的核心算法是幀內(nèi)壓縮和幀間壓縮,幀內(nèi)壓縮是生成I幀的算法,幀間壓縮是生成B幀和P幀的算法。 幀內(nèi)(Intraframe)壓縮也稱為空間壓縮(Spatialcompression) 。當壓縮一幀圖像時,僅考慮本幀的數(shù)據(jù)而不考慮相鄰幀之間的冗余信息,這實際上與靜態(tài)圖像壓縮類似。幀內(nèi)一般采用有損壓縮算法,由于幀內(nèi)壓縮是編碼一個完整的圖像,所以可以獨立的解碼、顯示。幀內(nèi)壓縮一般達不到很高的壓縮,跟編碼jpeg差不多。幀間(Interframe)壓縮 的原理是:相鄰幾幀的數(shù)據(jù)有很大的相關性,或者說前后兩幀信息變化很小的特點。也即連續(xù)的視頻其相鄰幀之間具有冗余信息,根據(jù)這一特性,壓縮相鄰幀之間的冗余量就可以進一步提高壓縮量,減小壓縮比。幀間壓縮也稱為時間壓縮(Temporalcompression),它通過比較時間軸上不同幀之間的數(shù)據(jù)進行壓縮。幀間壓縮一般是無損的。幀差值(Framedifferencing)算法是一種典型的時間壓縮法,它通過比較本幀與相鄰幀之間的差異,僅記錄本幀與其相鄰幀的差值,這樣可以大大減少數(shù)據(jù)量。
1. 壓縮方式說明
分組,也就是將一系列變換不大的圖像歸為一個組,也就是一個序列,也可以叫GOP(畫面組); 定義幀,將每組的圖像幀歸分為I幀、P幀和B幀三種類型; 預測幀, 以I幀做為基礎幀,以I幀預測P幀,再由I幀和P幀預測B幀; 數(shù)據(jù)傳輸, 最后將I幀數(shù)據(jù)與預測的差值信息進行存儲和傳輸。
4. H264分層結(jié)構(gòu)
H264的主要目標是為了有高的視頻壓縮比和良好的網(wǎng)絡親和性,為了達成這兩個目標,H264的解決方案是將系統(tǒng)框架分為兩個層面,分別是視頻編碼層面(VCL:Video Coding Layer)和網(wǎng)絡抽象層面(NAL:Network Coding Layer),H.264原始碼流(裸流)是由?個接?個NALU組成,如下圖: VLC層是對核心算法引擎、塊、宏塊及片的語法級別的定義,負責有效表示視頻數(shù)據(jù)的內(nèi)容,最終輸出編碼完的數(shù)據(jù)SODB; NAL層定義了片級以上的語法級別(如序列參數(shù)集和圖像參數(shù)集,針對網(wǎng)絡傳輸,后面會描述到),負責以網(wǎng)絡所要求的恰當方式去格式化數(shù)據(jù)并提供頭信息,以保證數(shù)據(jù)適合各種信道和存儲介質(zhì)上的傳輸 。NAL層將SODB打包成RBSP然后加上NAL頭組成一個NALU單元,具體NAL單元的組成也會在后面詳細描述。 在VCL進?數(shù)據(jù)傳輸或存儲之前,這些編碼的VCL數(shù)據(jù),被映射或封裝進NAL單元。(NALU) ?個NALU = ?組對應于視頻編碼的NALU頭部信息 + ?個原始字節(jié)序列負荷(RBSP,Raw Byte Sequence Payload). NALU結(jié)構(gòu)單元的主體結(jié)構(gòu)如下所示;?個原始的H.264 NALU單元通常由[StartCode] [NALU Header] [NALU Payload]三部分組成,其中 Start Code ?于標示這是?個NALU 單元的開始,必須是"00 00 00 01" 或"00 00 01",除此之外基本相當于?個NAL header + RBSP; SODB與RBSP的關聯(lián),具體結(jié)構(gòu)如圖3所示: 1. SODB(String Of Data Bits): 數(shù)據(jù)比特串,是編碼后的原始數(shù)據(jù); 2. RBSP(Raw Byte Sequence Payload): 原始字節(jié)序列載荷,即在SODB的后面添加了trailing bits,即一個bit 1和若干個bit 0,以便字節(jié)對齊; RBSP的形成過程 如果SODB的內(nèi)容是空的,那么RBSP的內(nèi)容也是空的。否則,RBSP的第一個字節(jié)取自SODB的第1到第8個比特,RBSP字節(jié)內(nèi)部按照從左到右從高到低的順序排列。以此類推,RBSP中的每個字節(jié)都直接取自SODP的相應比特。RBSP的最后一個字節(jié)包含SODB的最后幾個比特,以及trailing bits。其中,trailing bits的第一個比特為1,其余的比特為0,保證字節(jié)對齊。所以RBSP就等于,SODB在它的最后一個字節(jié)的最后一個比特后,緊跟值為1的1個比特,然后增加若干比特的0,以補齊這個字節(jié)。
5. H264碼流結(jié)構(gòu)
具體講述NAL單元前,十分有必要先了解一下H264的碼流結(jié)構(gòu)。在經(jīng)過編碼后的H264的碼流如下圖所示,從圖中我們需要得到一個概念,H264碼流是由一個個的NAL單元組成,其中SPS、PPS、IDR和SLICE是NAL單元某一類型的數(shù)據(jù)。
6. H264的NAL單元
1. H264的NAL結(jié)構(gòu)
在實際的網(wǎng)絡數(shù)據(jù)傳輸過程中H264的數(shù)據(jù)結(jié)構(gòu)是以NALU(NAL單元)進行傳輸?shù)?#xff0c;傳輸數(shù)據(jù)結(jié)構(gòu)組成為[NALU Header]+[RBSP],如下圖所示: 從之前的分析我們可以知道,VCL層編碼后的視頻幀數(shù)據(jù),幀有可能是I/B/P幀,這些幀也可能是屬于不同的序列之中;同一序列也還有相應的序列參數(shù)集與圖片參數(shù)集;綜上所述,想要完成準確無誤視頻的解碼,除了需要VCL層編碼出來的視頻幀數(shù)據(jù),同時還需要傳輸序列參數(shù)集和圖像參數(shù)集等等,所以RBSP不單純只保存I/B/P幀的數(shù)據(jù)編碼信息,還有其他信息也可能出現(xiàn)在里面。 上面知道NAL單元是作為實際視頻數(shù)據(jù)傳輸?shù)幕締卧?#xff0c;NALU頭是用來標識后面RBSP是什么類型的數(shù)據(jù),同時記錄RBSP數(shù)據(jù)是否會被其他幀參考以及網(wǎng)絡傳輸是否有錯誤,所以針對NAL頭和RBSP的作用以及結(jié)構(gòu)與所承載的數(shù)據(jù)需要做個簡單的了解。
2. NAL頭
1. NAL頭的組成
NAL單元的頭部是由forbidden_bit(1bit),nal_reference_bit(2bits)(優(yōu)先級),nal_unit_type(5bits)(類型)三個部分組成的,組成下圖所示:
F(forbiden):禁止位,占用NAL頭的第一個位,當禁止位值為1時表示語法錯誤; NRI:取00~11,似乎指示這個NALU的重要性,如00的NALU解碼器可以丟棄它?不影響圖像的回放,0~3,取值越?,表示當前NAL越重要,需要優(yōu)先受到保護。如果當前NAL是屬于參考幀的?,或是序列參數(shù)集,或是圖像參數(shù)集這些重要的單位時,本句法元素必需?于0。 Type:NAL單元數(shù)據(jù)類型,也就是標識該NAL單元的數(shù)據(jù)類型是哪種,占用NAL頭的第四到第八個位;
2. NAL單元數(shù)據(jù)類型
NAL類型主要就是下面圖中這些類型每個類型都有特殊的作用; 例子:0x00 00 00 01 67 ,67:?進制:0110 0111,00111 = 7(?進制),即為序列參數(shù)集
4. 在具體介紹NAL數(shù)據(jù)類型前,有必要知道NAL分為VCL的NAL單元和非VCL的NAL單元 。 5. nal_unit_type為1, 2, 3, 4, 5及12的NAL單元稱為VCL的NAL單元,其他類型的NAL單元為非VCL的NAL單元。 6. 另外一個需要了解的概念就是參數(shù)集(Parameter sets) ,參數(shù)集是攜帶解碼參數(shù)的NAL單元,參數(shù)集對于正確解碼是非常重要的,在一個有損耗的傳輸場景中,傳輸過程中比特列或包可能丟失或損壞,在這種網(wǎng)絡環(huán)境下,參數(shù)集可以通過高質(zhì)量的服務來發(fā)送,比如向前糾錯機制或優(yōu)先級機制。Parameter sets與其之外的句法元素之間的關系如下圖所示: 7. 每種類型都有代表一種數(shù)據(jù)類型,比較重要的以下幾種做個簡單的介紹:
1. 非VCL的NAL數(shù)據(jù)類型:
SPS(Sequence Parameter Set:序列參數(shù)集)包含一些通用的參數(shù),比如Profile和Level,比如視頻幀的尺寸,參考幀的最大數(shù)量等,這些參數(shù)對整個Video Sequence或者Programme都是通用的。 PPS(Picture Parameter Set:圖像參數(shù)集)包含一些通用的參數(shù),比如熵編碼類型,有效的參考圖像的數(shù)目和初始化參數(shù)等,這些參數(shù)可以應用到一個Video Sequence或者一部分編碼幀。 SEI(補充增強信息):這部分參數(shù)可作為H264的比特流數(shù)據(jù)而被傳輸,每一個SEI信息被封裝成一個NAL單元。SEI對于解碼器來說可能是有用的,但是對于基本的解碼過程來說,并不是必須的。 一個Parameter Set在開始的時候是不活躍的,直到被激活。一個PPS被預先傳到解碼器,當在一個Slice Header中涉及到的時候,就會被激活,而且直到一個不同的PPS被激活。對于SPS,當一個PPS涉及到它的時候,就會被激活。對于一個以IDR Access Unit開始的Coded Video Sequence,在整個過程中,一個SPS會一直處于活躍狀態(tài)。因此,一個SPS可以有效的被IDR Slice激活。
2. VCL的NAL數(shù)據(jù)類型
頭信息塊,包括宏塊類型,量化參數(shù),運動矢量。這些信息是最重要的,因為離開他們,被的數(shù)據(jù)塊種的碼元都無法使用。該數(shù)據(jù)分塊稱為A類數(shù)據(jù)分塊。 幀內(nèi)編碼信息數(shù)據(jù)塊,稱為B類數(shù)據(jù)分塊。它包含幀內(nèi)編碼宏塊類型,幀內(nèi)編碼系數(shù)。對應的slice來說,B類數(shù)據(jù)分塊的可用性依賴于A類數(shù)據(jù)分塊。和幀間編碼信息數(shù)據(jù)塊不同的是,幀內(nèi)編碼信息能防止進一步的偏差,因此比幀間編碼信息更重要。 幀間編碼信息數(shù)據(jù)塊,稱為C類數(shù)據(jù)分塊。它包含幀間編碼宏塊類型,幀間編碼系數(shù)。它通常是slice種最大的一部分。幀間編碼信息數(shù)據(jù)塊是不重要的一部分。它所包含的信息并不提供編解碼器之間的同步。C類數(shù)據(jù)分塊的可用性也依賴于A類數(shù)據(jù)分塊,但與B類數(shù)據(jù)分塊無關。 以上三種數(shù)據(jù)塊每種分割被單獨的存放在一個NAL單元中,因此可以被單獨傳輸。
3. H264的NAL單元與片,宏之間的聯(lián)系
為什么數(shù)據(jù)NAL單元中有這么多數(shù)據(jù)類型,這個SLICE又是什么東西,為什么不直接是編碼后出來的原始字節(jié)序列載荷,所以我覺得在這里再講述幀所細分的一些片和宏的概念應該是比較合適的,也是能夠參照上下文更能理解這些概念的位置,又能給這些困惑做一個合理一點的解釋,所以在此做一個描述: 1幀(一幅圖像) = 1~N個片(slice) //也可以說1到多個片為一個片組 1個片 = 1~N個宏塊(Marcroblock) 1個宏塊 = 16*16的YUV數(shù)據(jù)(原始視頻采集數(shù)據(jù)) 從數(shù)據(jù)層次角度來說,一幅原始的圖片可以算作廣義上的一幀,幀包含片組和片,片組由片來組成,片由宏塊來組成,每個宏塊可以是44、88、16*16像素規(guī)模的大小,它們之間的聯(lián)系如下圖所示。每個片都是一個獨立的編碼單位。 從容納數(shù)據(jù)角度來說,NAL單元除了容納Slice編碼的碼流外,還可以容納其他數(shù)據(jù),這也就是為什么有SPS、PPS等這些數(shù)據(jù)出現(xiàn)的原因,并且這些數(shù)據(jù)在傳輸H264碼流的過程中起到不可或缺的作用,具體作用上面也是有講到的。 那么也就可以對下面這些概念做一個大小的排序了:序列>圖像>片>宏>像素(當然還有片組、亞宏塊等等這些概念,初步了解就不了解這么深了,后面再慢慢研究) 同時有幾點需要說明一下,這樣能便于理解NAL單元: 如果不采用 FMO(靈活宏塊排序) 機制,則一幅圖像只有一個片組; 如果不使用多個片,則一個片組只有一個片; 如果不采用 DP(數(shù)據(jù)分割)機制,則一個片就是一個 NALU,一個 NALU 也就是一個片。 否則,一個片的組成需要由 三個 NALU 組成,也就是上面說到的A、B、C類數(shù)據(jù)塊。 這時候在看下面這幅碼流數(shù)據(jù)分層圖就比較能理解整體的碼流結(jié)構(gòu)組成了。 如我們所見,每個分片也包含著頭和數(shù)據(jù)兩部分,分片頭中包含著分片類型、分片中的宏塊類型、分片幀的數(shù)量以及對應的幀的設置和參數(shù)等信息,而分片數(shù)據(jù)中則是宏塊,這里就是我們要找的存儲像素數(shù)據(jù)的地方;宏塊是視頻信息的主要承載者,因為它包含著每一個像素的亮度和色度信息。視頻解碼最主要的工作則是提供高效的方式從碼流中獲得宏塊中的像素陣列。宏塊數(shù)據(jù)的組成如下圖所示: 從上圖中,可以看到,宏塊中包含了宏塊類型、預測類型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度數(shù)據(jù)集等等信息。 至此,我們對 H.264 的碼流數(shù)據(jù)結(jié)構(gòu)應該有了一個大致的了解。
需要注意的幾點:
H.264/AVC標準對送到解碼器的NAL單元順序是有嚴格要求的,如果NAL單元的順序是混亂的,必須將其重新依照規(guī)范組織后送入解碼器,否則解碼器不能夠正確解碼。 序列參數(shù)集NAL單元必須在傳送所有以此參數(shù)集為參考的其他NAL單元之前傳送,不過允許這些NAL單元中間出現(xiàn)重復的序列參數(shù)集NAL單元。所謂重復的詳細解釋為:序列參數(shù)集NAL單元都有其專門的標識,如果兩個序列參數(shù)集NAL單元的標識相同,就可以認為后一個只不過是前一個的拷貝,而非新的序列參數(shù)集。 圖像參數(shù)集NAL單元必須在所有以此參數(shù)集為參考的其他NAL單元之前傳送,不過允許這些NAL單元中間出現(xiàn)重復的圖像參數(shù)集NAL單元,這一點與上述的序列參數(shù)集NAL單元是相同。
7. H.264視頻碼流解析及代碼實現(xiàn)
視音頻數(shù)據(jù)處理入門:H.264視頻碼流解析 H.264原始碼流(又稱為“裸流”)是由一個一個的NALU組成的。他們的結(jié)構(gòu)如下圖所示。
其中每個NALU之間通過startcode(起始碼)進行分隔,起始碼分成兩種:0x000001(3Byte)或者0x00000001(4Byte)。如果NALU對應的Slice為一幀的開始就用0x00000001,否則就用0x000001。 H.264碼流解析的步驟就是首先從碼流中搜索0x000001和0x00000001,分離出NALU;然后再分析NALU的各個字段。本文的程序即實現(xiàn)了上述的兩個步驟。
1. H264 annexb模式
H264有兩種封裝 ?種是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)換 實現(xiàn)
const AVBitStreamFilter
* bsfilter
= av_bsf_get_by_name ( "h264_mp4toannexb" ) ; AVBSFContext
* bsf_ctx
= NULL
; av_bsf_alloc ( bsfilter
, & bsf_ctx
) ; avcodec_parameters_copy ( bsf_ctx
- > par_in
, ifmt_ctx
- > streams
[ videoindex
] - > codecpar
) ; av_bsf_init ( bsf_ctx
)
2. 解析代碼實現(xiàn)
#include
< stdio
. h
>
#include
< libavutil
/ log
. h
>
#include
< libavformat
/ avio
. h
>
#include
< libavformat
/ avformat
. h
> static char err_buf
[ 128 ] = { 0 } ; static char
* av_get_err ( int errnum
) { av_strerror ( errnum
, err_buf
, 128 ) ; return err_buf
;
}
int main ( int argc
, char
* * argv
) { AVFormatContext
* ifmt_ctx
= NULL
; int videoindex
= - 1 ; AVPacket
* pkt
= NULL
; int ret
= - 1 ; int file_end
= 0 ; FILE
* outfp
= fopen ( "/Users/lijinwang/Downloads/course/study/believe.h264" , "wb" ) ; ifmt_ctx
= avformat_alloc_context ( ) ; if ( ! ifmt_ctx
) { printf ( "[error] Could not allocate context.\n" ) ; return - 1 ; } ret
= avformat_open_input ( & ifmt_ctx
, "/Users/lijinwang/Downloads/course/study/believe.mp4" , NULL
, NULL
) ; if ( ret
!= 0 ) { printf ( "[error]avformat_open_input: %s\n" , av_get_err ( ret
) ) ; return - 1 ; } ret
= avformat_find_stream_info ( ifmt_ctx
, NULL
) ; if ( ret
< 0 ) { printf ( "[error]avformat_find_stream_info: %s\n" , av_get_err ( ret
) ) ; avformat_close_input ( & ifmt_ctx
) ; return - 1 ; } videoindex
= - 1 ; videoindex
= av_find_best_stream ( ifmt_ctx
, AVMEDIA_TYPE_VIDEO
, - 1 , - 1 , NULL
, 0 ) ; if ( videoindex
== - 1 ) { printf ( "Didn't find a video stream.\n" ) ; avformat_close_input ( & ifmt_ctx
) ; return - 1 ; } pkt
= av_packet_alloc ( ) ; av_init_packet ( pkt
) ; const AVBitStreamFilter
* bsfilter
= av_bsf_get_by_name ( "h264_mp4toannexb" ) ; AVBSFContext
* bsf_ctx
= NULL
; av_bsf_alloc ( bsfilter
, & bsf_ctx
) ; avcodec_parameters_copy ( bsf_ctx
- > par_in
, ifmt_ctx
- > streams
[ videoindex
] - > codecpar
) ; av_bsf_init ( bsf_ctx
) ; file_end
= 0 ; while
( 0 == file_end
) { if ( ( ret
= av_read_frame ( ifmt_ctx
, pkt
) ) < 0 ) { file_end
= 1 ; printf ( "read file end: ret:%d\n" , ret
) ; } if ( ret
== 0 && pkt
- > stream_index
== videoindex
) {
#
if 1 int input_size
= pkt
- > size
; int out_pkt_count
= 0 ; if ( av_bsf_send_packet ( bsf_ctx
, pkt
) != 0 ) { av_packet_unref ( pkt
) ; continue ; } av_packet_unref ( pkt
) ; while
( av_bsf_receive_packet ( bsf_ctx
, pkt
) == 0 ) { out_pkt_count
++ ; size_t size
= fwrite ( pkt
- > data
, 1 , pkt
- > size
, outfp
) ; if ( size
!= pkt
- > size
) { printf ( "fwrite failed-> write:%u, pkt_size:%u\n" , size
, pkt
- > size
) ; } av_packet_unref ( pkt
) ; } if ( out_pkt_count
>= 2 ) { printf ( "cur pkt(size:%d) only get 1 out pkt, it get %d pkts\n" , input_size
, out_pkt_count
) ; }
#
else size_t size
= fwrite ( pkt
- > data
, 1 , pkt
- > size
, outfp
) ; if ( size
!= pkt
- > size
) { printf ( "fwrite failed-> write:%u, pkt_size:%u\n" , size
, pkt
- > size
) ; } av_packet_unref ( pkt
) ;
#endif
} else { if ( ret
== 0 ) av_packet_unref ( pkt
) ; } } if ( outfp
) fclose ( outfp
) ; if ( bsf_ctx
) av_bsf_free ( & bsf_ctx
) ; if ( pkt
) av_packet_free ( & pkt
) ; if ( ifmt_ctx
) avformat_close_input ( & ifmt_ctx
) ; printf ( "finish\n" ) ; return 0 ;
}
參考博客:入門理解H264編碼 視音頻數(shù)據(jù)處理入門:H.264視頻碼流解析
總結(jié)
以上是生活随笔 為你收集整理的H.264基础知识及视频码流解析 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。