(推薦閱讀)H264, H265硬件編解碼基礎及碼流分析

需求

在移動端作音視頻開發不一樣於基本的UI業務邏輯工做,音視頻開發須要你懂得音視頻中一些基本概念,針對編解碼而言,咱們必須提早懂得編解碼器的一些特性,碼流的結構,碼流中一些重要信息如sps,pps,vps,start code以及基本的工做原理,而大多同窗都只是只知其一;不知其二,因此致使代碼中的部份內容雖能夠簡單理解殊不知其意,因此,在這裏總結出了當前主流的H.264,H.265編碼相關的原理,以供學習.html


閱讀前提:

  • 音視頻基礎知識
  • iOS中VideoToolbox框架

1. 概覽

1.1. 爲何要編碼

衆所周知,視頻數據原始體積是巨大的,以720P 30fps的視頻爲例,一個像素大約3個字節,以下所得,每秒鐘產生87MB,這樣計算可得一分鐘就將產生5.22GB.git

數據量/每秒=1280*720*33*3/1024/1024=87MB
複製代碼

所以,像這樣體積重大的視頻是沒法在網絡中直接傳輸的.而視頻編碼技術也就因運而生.關於視頻編碼原理的技術能夠參考本人其餘文章,這裏不作過多描述.github

1.2. 編碼技術

通過不少年的開發迭代,已經有不少大牛實現了視頻編碼技術,其中最主流的有H.264編碼,以及新一代的H.265編碼,谷歌也開發了VP8,VP9編碼技術.對移動端而言,蘋果內部已經實現瞭如H.264,H.265編碼,咱們須要使用蘋果提供的VideoToolbox框架來實現它.算法

1.3. 編碼分類

  • 軟件編碼(簡稱軟編):使用CPU進行編碼。
  • 硬件編碼(簡稱硬編):不使用CPU進行編碼,使用顯卡GPU,專用的DSP、FPGA、ASIC芯片等硬件進行編碼。

優缺點編程

  • 軟編:實現直接、簡單,參數調整方便,升級易,但CPU負載重,性能較硬編碼低,低碼率下質量一般比硬編碼要好一點。數組

  • 硬編:性能高,低碼率下一般質量低於硬編碼器,但部分產品在GPU硬件平臺移植了優秀的軟編碼算法(如X264)的,質量基本等同於軟編碼。bash

iOS系統中的硬編碼 蘋果在iOS 8.0系統以前,沒有開放系統的硬件編碼解碼功能,不過Mac OS系統一直有,被稱爲Video ToolBox的框架來處理硬件的編碼和解碼,終於在iOS 8.0後,蘋果將該框架引入iOS系統。網絡

1.4. 編碼原理

對視頻執行編碼操做後,原始視頻數據會被壓縮成三種不一樣類型的視頻幀: I幀,P幀,B幀.數據結構

  • I幀:關鍵幀.完整編碼的幀.能夠理解成是一張完整畫面,不依賴其餘幀
  • P幀:參考前面的I幀或P幀,即經過前面的I幀與本身記錄的不一樣的部分能夠造成完整的畫面.所以,單獨的P幀沒法造成畫面.
  • B幀:參考前面的I幀或P幀以及後面的P幀

補充: I幀的壓縮率是7(跟JPG差很少),P幀是20,B幀能夠達到50. 可是iOS中通常不開啓B幀,由於B幀的存在會致使時間戳同步較爲複雜.架構

兩種核心算法

  • 幀內壓縮

當壓縮一幀圖像時,僅考慮本幀的數據而不考慮相鄰幀之間的冗餘信息,這實際上與靜態圖像壓縮相似。幀內通常採用有損壓縮算法,因爲幀內壓縮是編碼一個完整的圖像,因此能夠獨立的解碼、顯示。幀內壓縮通常達不到很高的壓縮,跟編碼jpeg差很少。

以下圖:咱們能夠經過第 一、二、三、四、5 塊的編碼來推測和計算第 6 塊的編碼,所以就不須要對第 6 塊進行編碼了,從而壓縮了第 6 塊,節省了空間

幀內預測.png

  • 幀間壓縮: P幀與B幀的壓縮算法

相鄰幾幀的數據有很大的相關性,或者說先後兩幀信息變化很小的特色。也即連續的視頻其相鄰幀之間具備冗餘信息,根據這一特性,壓縮相鄰幀之間的冗餘量就能夠進一步提升壓縮量,減少壓縮比。幀間壓縮也稱爲時間壓縮(Temporal compression),它經過比較時間軸上不一樣幀之間的數據進行壓縮。幀間壓縮通常是無損的。幀差值(Frame differencing)算法是一種典型的時間壓縮法,它經過比較本幀與相鄰幀之間的差別,僅記錄本幀與其相鄰幀的差值,這樣能夠大大減小數據量。

以下圖:能夠看到先後兩幀的差別實際上是很小的,這時候用幀間壓縮就頗有意義。

幀間壓縮11.jpg

有損壓縮與無損壓縮

  • 有損壓縮: 解壓縮後的數據與壓縮前的數據不一致.在壓縮的過程當中要丟失一些人眼和人耳所不敏感的圖像或音頻信息,並且丟失的信息不可恢復
  • 無損壓縮: 壓縮前和解壓縮後的數據徹底一致.優化數據的排列等.

DTS和PTS

  • DTS:主要用於視頻的解碼,在解碼階段使用.
  • PTS:主要用於視頻的同步和輸出.在渲染的時候使用.在沒有B frame的狀況下.DTS和PTS的輸出順序是同樣的。

1.dtspts

如上圖:I幀的解碼不依賴於任何的其它的幀.而P幀的解碼則依賴於其前面的I幀或者P幀.B幀的解碼則依賴於其前的最近的一個I幀或者P幀 及其後的最近的一個P幀.

2. 編碼數據碼流結構

在咱們的印象中,一張圖片就是一張圖像,視頻就是不少張圖片的集合.。可是由於咱們要作音視頻編程,就須要更加深刻理解視頻的本質.

2.1 刷新圖像概念.

在編碼的碼流中圖像是個集合的概念,幀、頂場、底場均可以稱爲圖像,一幀一般就是一幅完整的圖像.

  • 逐行掃描:每次掃描獲得的信號就是一副圖像,也就是一幀. 逐行掃描適合於運動圖像
  • 隔行掃描:掃描下來的一幀圖像就被分爲了兩個部分,這每一部分就稱爲「場」,根據次序分爲:「頂場」和「底場」.適合於非運動圖像

逐行掃描與隔行掃描.png

幀與場.png

2.2. 重要參數

  • 視頻參數集VPS(Video Parameter Set)

VPS主要用於傳輸視頻分級信息,有利於兼容標準在可分級視頻編碼或多視點視頻的擴展。

(1)用於解釋編碼過的視頻序列的總體結構,包括時域子層依賴關係等。HEVC中加入該結構的主要目的是兼容標準在系統的多子層方面的擴展,處理好比將來的可分級或者多視點視頻使用原先的解碼器進行解碼可是其所需的信息可能會被解碼器忽略的問題。

(2)對於給定視頻序列的某一個子層,不管其SPS相不相同,都共享一個VPS。其主要包含的信息有:多個子層或操做點共享的語法元素;檔次和級別等會話關鍵信息;其餘不屬於SPS的操做點特定信息。

(3)編碼生成的碼流中,第一個NAL單元攜帶的就是VPS信息

  • 序列參數集SPS(Sequence Parameter Set)

包含一個CVS中全部編碼圖像的共享編碼參數。

(1)一段HEVC碼流可能包含一個或者多個編碼視頻序列,每一個視頻序列由一個隨機接入點開始,即IDR/BLA/CRA。序列參數集SPS包含該視頻序列中全部slice須要的信息。

(2)SPS的內容大體能夠分爲幾個部分:一、自引ID;二、解碼相關信息,如檔次級別、分辨率、子層數等;三、某檔次中的功能開關標識及該功能的參數;四、對結構和變換系數編碼靈活性的限制信息;五、時域可分級信息;六、VUI。

  • 圖像參數集PPS(Picture Parameter Set)

包含一幅圖像所用的公共參數,即一幅圖像中全部片斷SS(Slice Segment)引用同一個PPS。

(1)PPS包含每一幀可能不一樣的設置信息,其內容同H.264中的大體相似,主要包括:一、自引信息;二、初始圖像控制信息,如初始QP等;三、分塊信息。

(2)在解碼開始的時候,全部的PPS所有是非活動狀態,並且在解碼的任意時刻,最多隻能有一個PPS處於激活狀態。當某部分碼流引用了某個PPS的時候,這個PPS便被激活,稱爲活動PPS,一直到另外一個PPS被激活。

參數集包含了相應的編碼圖像的信息。SPS包含的是針對一連續編碼視頻序列的參數(標識符seq_parameter_set_id、幀數及POC的約束、參考幀數目、解碼圖像尺寸和幀場編碼模式選擇標識等等)。PPS對應的是一個序列中某一幅圖像或者某幾幅圖像 ,其參數如標識符pic_parameter_set_id、可選的seq_parameter_set_id、熵編碼模式選擇標識、片組數目、初始量化參數和去方塊濾波係數調整標識等等。

一般,SPS 和PPS 在片的頭信息和數據解碼前傳送至解碼器。每一個片的頭信息對應一個 pic_parameter_set_id,PPS被其激活後一直有效到下一個PPS被激活;相似的,每一個PPS對應一個 seq_parameter_set_id,SPS被其激活之後將一直有效到下一個SPS被激活。 參數集機制將一些重要的、改變少的序列參數和圖像參數與編碼片分離,並在編碼片以前傳送 至解碼端,或者經過其餘機制傳輸。

擴展知識點:檔次(Profile)、層(Tier)和級別(Level)

  • 檔次: 主要規定編碼器可採用哪些編碼工具或算法。

  • 級別: 指根據解碼端的負載和存儲空間狀況對關鍵參數(最大采樣率、最大圖像尺寸、分辨率、最小壓縮比、最大比特率、解碼緩衝區DPB大小等)加以限制。

考慮到應用可根據最大的碼率和CPB大小來區分,所以有些級別定義了兩個層Tier:主層和高層,主層用於大多數應用,而高層用於那些最嚴苛的應用。

2.3. 原始碼流

  • IDR

一個序列的第一個圖像叫作 IDR 圖像(當即刷新圖像),IDR 圖像都是 I 幀圖像。引入 IDR 圖像是爲了解碼的重同步,當解碼器解碼到 IDR 圖像時,當即將參考幀隊列清空,將已解碼的數據所有輸出或拋棄,從新查找參數集,開始一個新的序列。這樣,若是前一個序列出現重大錯誤,在這裏能夠得到從新同步的機會。IDR圖像以後的圖像永遠不會使用IDR以前的圖像的數據來解碼。

  • 結構

由一個接一個的 NALU 組成的,而它的功能分爲兩層,VCL(視頻編碼層)和 NAL(網絡提取層).

下圖以h264的碼流結構爲例,若是是h265則在sps前還有vps.

H264碼流.png

  • 組成

NALU (Nal Unit) = NALU頭 + RBSP 在 VCL

數據傳輸或存儲以前,這些編碼的 VCL 數據,先被映射或封裝進 NAL 單元(如下簡稱 NALU,Nal Unit) 中。每一個 NALU 包括一個原始字節序列負荷(RBSP, Raw Byte Sequence Payload)、一組 對應於視頻編碼的 NALU 頭部信息。RBSP 的基本結構是:在原始編碼數據的後面填加告終尾 比特。一個 bit「1」若干比特「0」,以便字節對齊。

2.3.1. H.264碼流

一個原始的H.264 NALU 單元常由 [StartCode] [NALU Header] [NALU Payload] 三部分組成

NALU組成.jpeg

  • StartCode : Start Code 用於標示這是一個NALU 單元的開始,必須是」00 00 00 01」 或」00 00 01」

  • NALU Header 下表爲 NAL Header Type

NAL Header Type.png

例如,下面幅圖分別表明IDR與非IDR幀具體的碼流信息:

2.IDR

在一個NALU中,第一個字節(即NALU header)用以表示其包含數據的類型及其餘信息。咱們假定一個頭信息字節爲0x67做爲例子:

十六進制 二進制
0x67 0 11 00111

如表所示,頭字節能夠被解析成3個部分,其中:

1>. forbidden_zero_bit = 0:佔1個bit,禁止位,用以檢查傳輸過程當中是否發生錯誤,0表示正常,1表示違反語法;

2>. nal_ref_idc = 3:佔2個bit,用來表示當前NAL單元的優先級。非0值表示參考字段/幀/圖片數據,其餘不那麼重要的數據則爲0。對於非0值,值越大表示NALU重要性越高

3>. nal_unit_type = 7:最後5位用以指定NALU類型,NALU類型定義如上表

從表中咱們能夠獲知,NALU類型1-5爲視頻幀,其他則爲非視頻幀。在解碼過程當中,咱們只須要取出NALU頭字節的後5位,即將NALU頭字節和0x1F進行與計算便可得知NALU類型,即:

NALU類型 = NALU頭字節 & 0x1F
複製代碼

注意: 能夠將start code理解爲不一樣nalu的分隔符,header是某種類型的key,payload是該key的value.

碼流格式

H.264標準中指定了視頻如何編碼成獨立的包,但如何存儲和傳輸這些包卻未做規範,雖然標準中包含了一個Annex附件,裏面描述了一種可能的格式Annex B,但這並非一個必需要求的格式。 爲了針對不一樣的存儲傳輸需求,出現了兩種打包方法。一種即Annex B格式,另外一種稱爲AVCC格式。

  • Annex B

從上文可知,一個NALU中的數據並未包含他的大小(長度)信息,所以咱們並不能簡單的將一個個NALU鏈接起來生成一個流,由於數據流的接收端並不知道一個NALU從哪裏結束,另外一個NALU從哪裏開始。 Annex B格式用起始碼(Start Code)來解決這個問題,它在每一個NALU的開始處添加三字節或四字節的起始碼0x000001或0x00000001。經過定位起始碼,解碼器就能夠很容易的識別NALU的邊界。 固然,用起始碼定位NALU邊界存在一個問題,即NALU中可能存在與起始碼相同的數據。爲了防止這個問題,在構建NALU時,須要將數據中的0x000000,0x000001,0x000002,0x000003中插入防競爭字節(Emulation Prevention Bytes)0x03,使其變爲:

0x000000 = 0x0000 03 00 0x000001 = 0x0000 03 01 0x000002 = 0x0000 03 02 0x000003 = 0x0000 03 03 解碼器在檢測到0x000003時,將0x03拋棄,恢復原始數據。

因爲Annex B格式每一個NALU都包含起始碼,因此解碼器能夠從視頻流隨機點開始進行解碼,經常使用於實時的流格式。在這種格式中一般會週期性的重複SPS和PPS,而且常常時在每個關鍵幀以前。

  • AVCC

AVCC格式不使用起始碼做爲NALU的分界,這種格式在每一個NALU前都加上一個指定NALU長度的大端格式表示的前綴。這個前綴能夠是一、2或4個字節,因此在解析AVCC格式的時候須要將指定的前綴字節數的值保存在一個頭部對象中,這個都一般稱爲extradata或者sequence header。同時,SPS和PPS數據也須要保存在extradata中。 H.264 extradata語法以下:

bits line by byte remark
8 version always
8 avc profile sps[0][1]
8 avc compatibility sps[0][2]
8 avc level sps[0][3]
6 reserved all bits on
2 NALULengthSizeMinusOne
3 reserved all bits on
5 number of SPS NALUs usually 1
16 SPS size
N variable SPS NALU data
8 number of PPS NALUs usually 1
16 PPS size
N variable PPS NALU data

其中第5字節的後2位表示的就是NAL size的字節數。須要注意的是,這個NALULengthSizeMinusOne是NALU前綴長度減一,即,假設前綴長度爲4,那麼這個值應該爲3。 這裏還須要注意的一點是,雖然AVCC格式不使用起始碼,但防競爭字節仍是有的。

AVCC格式的一個優勢在於解碼器配置參數在一開始就配置好了,系統能夠很容易的識別NALU的邊界,不須要額外的起始碼,減小了資源的浪費,同時能夠在播放時調到視頻的中間位置。這種格式一般被用於能夠被隨機訪問的多媒體數據,如存儲在硬盤的文件。

2.3.2. H.265碼流

HEVC全稱High Efficiency Video Coding(高效率視頻編碼,又稱H.265),是比H.264更優秀的一種視頻壓縮標準。HEVC在低碼率視頻壓縮上,提高視頻質量、減小容量即節省帶寬方面都有突出表現。 H.265標準圍繞H.264編碼標準,保留原有的某些技術,同時對一些技術進行改進,編碼結構大體上和H.264的架構相似。這裏着重講一下二者編碼格式的區別。 同H.264同樣,H.265也是以NALU的形式組織起來。而在NALU header上,H.264的HALU header是一個字節,而H.265則是兩個字節。咱們一樣假定一個頭信息爲0x4001做爲例子:

十六進制 二進制
0x4001 0 100000 000000 001

如表所示,頭信息能夠被解析成4個部分,其中:

  • forbidden_zero_bit = 0:佔1個bit,與H.264相同,禁止位,用以檢查傳輸過程當中是否發生錯誤,0表示正常,1表示違反語法;
  • nal_unit_type = 32:佔6個bit,用來用以指定NALU類型
  • nuh_reserved_zero_6bits = 0:佔6位,預留位,要求爲0,用於將來擴展或3D視頻編碼
  • nuh_temporal_id_plus1 = 1:佔3個bit,表示NAL所在的時間層ID

對比H.264的頭信息,H.265移除了nal_ref_idc,此信息被合併到了nal_unit_type中,H.265NALU類型規定以下:

nal_unit_type NALU類型 備註
0 NAL_UNIT_CODE_SLICE_TRAIL_N 非關鍵幀
1 NAL_UNIT_CODED_SLICE_TRAIL_R
2 NAL_UNIT_CODED_SLICE_TSA_N
3 NAL_UINT_CODED_SLICE_TSA_R
4 NAL_UINT_CODED_SLICE_STSA_N
5 NAL_UINT_CODED_SLICE_STSA_R
6 NAL_UNIT_CODED_SLICE_RADL_N
7 NAL_UNIT_CODED_SLICE_RADL_R
8 NAL_UNIT_CODED_SLICE_RASL_N
9 NAL_UNIT_CODE_SLICE_RASL_R
10 ~ 15 NAL_UNIT_RESERVED_X 保留
16 NAL_UNIT_CODED_SLICE_BLA_W_LP 關鍵幀
17 NAL_UNIT_CODE_SLICE_BLA_W_RADL
18 NAL_UNIT_CODE_SLICE_BLA_N_LP
19 NAL_UNIT_CODE_SLICE_IDR_W_RADL
20 NAL_UNIT_CODE_SLICE_IDR_N_LP
21 NAL_UNIT_CODE_SLICE_CRA
22 ~ 31 NAL_UNIT_RESERVED_X 保留
32 NAL_UNIT_VPS VPS(Video Paramater Set)
33 NAL_UNIT_SPS SPS
34 NAL_UNIT_PPS PPS
35 NAL_UNIT_ACCESS_UNIT_DELIMITER
36 NAL_UNIT_EOS
37 NAL_UNIT_EOB
38 NAL_UNIT_FILLER_DATA
39 NAL_UNIT_SEI Prefix SEI
40 NAL_UNIT_SEI_SUFFIX Suffix SEI
41 ~ 47 NAL_UNIT_RESERVED_X 保留
48 ~ 63 NAL_UNIT_UNSPECIFIED_X 未規定
64 NAL_UNIT_INVALID

具體type含義能夠參考這篇文檔type類型 H.265的NALU類型是在信息頭的第一個字節的第2到7位,因此判斷H.265NALU類型的方法是將NALU第一個字節與0x7E進行與操做並右移一位,即:

NALU類型 = (NALU頭第一字節 & 0x7E) >> 1
複製代碼

與H.264相似,H.265碼流也有兩種封裝格式,一種是用起始碼做爲分界的Annex B格式,另外一種則是在NALU頭添加NALU長度前綴的格式,稱爲HVCC。在HVCC中,一樣須要一個extradata來保存視頻流的編解碼參數,其格式定義以下:

bits line by byte remark
8 configurationVersion always 0x01
2 general_profile_space
1 general_tier_flag
5 general_profile_idc
32 general_profile_compatibility_flags
48 general_constraint_indicator_flags
8 general_level_idc
4 reserved ‘1111’b
12 min_spatial_segmentation_idc
6 reserved ‘111111’b
2 parallelismType
6 reserved ‘111111’b
2 chromaFormat
5 reserved ‘11111’b
3 bitDepthLumaMinus8
5 reserved ‘11111’b
3 bitDepthChromaMinus8
16 avgFrameRate
2 constantFrameRate
3 numTemporalLayers
1 tmporalIdNested
2 lengthSizeMinusOne
8 numOfArrays

Repeated of Array(VPS/SPS/PPS) 1| array_completeness 1| reserved| ‘0’b 6| NAL_unit_type 16| numNalus 16| nalUnitLength N| NALU data

從上表能夠看到,在H.265的extradata後半段是一段格式重複的數組數據,裏面須要包含的除了與H.264相同的SPS、PPS外,還需多添加一個VPS。

VPS(Video Parament Set,視頻參數集),在H.265中類型爲32。VPS用於解釋編碼過的視頻的總體結構,包括時域子層依賴關係等,主要目的在於兼容H.265標準在系統的多子層方面的擴展。

3. iOS中的視頻編解碼

iOS中視頻處理框架分層

3.

3.1. 框架的選擇

對於 AVKit、AVFoundation 和 VideoToolbox 來講,他們的功能和可定製性愈來愈強,但相應的使用難度也越大,所以你應該根據實際需求合理的選擇使用哪一個層級的接口。事實上,即便你使用 AVFoundation 或 AVKit 依然能夠得到硬件加速的效果,你失去的只是直接訪問硬編解碼器的權限。對於 VideoToolbox 來講,你能夠經過直接訪問硬編解碼器,將 H.264 文件或傳輸流轉換爲 iOS 上的 CMSampleBuffer 並解碼成 CVPixelBuffer,或將未壓縮的 CVPixelBuffer 編碼成 CMSampleBuffer:

3.2. 視頻數據的容器

調用 AVCaptureSession 拍攝輸出的每一幀圖像都會被包裝成 CMSampleBuffer 對象,經過這個 CMSampleBuffer 對象你就能夠獲取到未壓縮的 CVPixelBuffer 對象;若是讀取 H.264 文件你也能夠獲取數據生成壓縮的 CMBlockBuffer 對象並建立一個 CMSampleBuffer 對象給 VideoToolbox 來解碼。

也就是說,CMSampleBuffer 既能夠做爲 CVPixelBuffer 對象的容器,也能夠做爲 CMBlockBuffer 對象的容器,CVPixelBuffer 能夠說是未壓縮的圖像數據容器,而 CMBlockBuffer 則是壓縮圖像數據容器。

4.data

3.3. 編碼數據裸流

NALU. 對於一個 H.264 裸流或者文件來講,它是由一個一個的 NALU(Network Abstraction Layer Unit) 單元組成,每一個 NALU 既能夠表示圖像數據,也能夠表示處理圖像所須要的參數數據。它主要有兩種格式:Annex B 和 AVCC。也被稱爲 Elementary Stream 和 MPEG-4 格式,Annex B 格式以 0x000001 或 0x00000001 開頭,AVCC 格式以所在的 NALU 的長度開頭。

3.4. VideoToolBox中經常使用數據結構

  • CMSampleBuffer: 存放編解碼先後的視頻圖像的容器數據結構
  • CVPixelBuffer: 編碼前和解碼後的圖像數據結構
  • CMBlockBuffer: 編碼後圖像的數據結構
  • CMVideoFormatDescription: 圖像存儲方式,編解碼器等格式描述
  • pixelBufferAttributes: : CFDictionary對象,可能包含了視頻的寬高,像素格式類型(32RGBA, YCbCr420),是否能夠用於OpenGL ES等相關信息
  • CMTime: 時間戳相關。時間以 64-big/32-bit形式出現。 分子是64-bit的時間值,分母是32-bit的時標(time scale)

3.5. 爲編碼的數據添加start code.

在iOS中使用vtCompressionSession編碼後的數據不包含start code.須要咱們自行添加,爲何要添加start code? 如上文所說,爲了區分每一個NALU及之後提取vps,sps,pps等關鍵信息.

以下圖,咱們在編碼回調中能夠拿到編碼後的CMSampleBuffer數據.若是是h265,CMVideoFormatDesc中還有vps.而這段數據在推流前必須尋找到關鍵數據vps,sps,pps以及添加start code.由於在CMBlockBufferRef中可能還含有一個或多個NALU,因此咱們須要經過遍歷它的內存地址找到在每一個NALU的分割點將數據替換爲start code.具體代碼操做參考實戰篇.

CMSampleBufferCreate

接下來,咱們就須要處理這一幀視頻的圖像數據了。經過 CMSampleBufferGetDataBuffer 和 CMBlockBufferGetDataPointer 咱們能夠獲取視頻數據的內存地址。VTCompressionSession 編碼出來的視頻幀爲 AVCC 格式,所以咱們能夠讀取頭部 4 個字節數據來獲取當前 NALU 的長度。這裏有一個須要注意的是,AVCC 格式使用大端字節序,它可能跟當前使用的系統字節序不同,事實上,iOS 系統使用小端字節序,所以咱們須要先將這個長度數據轉換爲 iOS 系統使用的小端字節序:

NALUnitLength = CFSwapInt32BigToHost(NALUnitLength)

硬編碼基本上是硬解碼的一個逆過程。解析出參數集SPS和PPS,加上開始碼後組裝成NALU。提取出視頻數據,將長度碼轉換成開始碼,組長成NALU。將NALU發送出去。

3.6. 將H.264裸流解碼爲CMSampleBuffer

如上圖,咱們知道,CMSampleBuffer = CMTime + FormatDesc + CMBlockBuffer . 須要從H264的碼流裏面提取出以上的三個信息。最後組合成CMSampleBuffer,提供給硬解碼接口來進行解碼工做。

在H.264, H.265的語法中,有一個最基礎的層,叫作Network Abstraction Layer, 簡稱爲NAL。編碼數據正是由一系列的NAL單元(NAL Unit, 簡稱NAUL)組成的。

NALU

編碼碼流由NALU單元組成,一個NALU可能包含有:

  • 視頻幀,視頻幀也就是視頻片斷,具體有 P幀, I幀,B幀

H264的碼流

  • 編碼屬性合集-FormatDesc(包含VPS,SPS和PPS)

流數據中,屬性集合多是這樣的:(若是是H.265碼流,SPS前面還有VPS)

屬性集合

通過處理以後,在Format Description中則是:

Format Description

要從基礎的流數據將SPS和PPS轉化爲Format Desc中的話,須要調用CMVideoFormatDescriptionCreateFromH264ParameterSets,CMVideoFormatDescriptionGetHEVCParameterSetAtIndex方法

  • NALU header

對於流數據來講,一個NAUL的Header中,多是0x00 00 01或者是0x00 00 00 01做爲開頭(二者都有可能,下面以0x00 00 01做爲例子)。0x00 00 01所以被稱爲開始碼(Start code).

NALU header

總結以上知識,咱們知道編碼碼流由NALU單元組成,NALU單元包含視頻圖像數據和編碼器的參數信息。其中視頻圖像數據就是CMBlockBuffer,而編碼器參數信息則能夠組合成FormatDesc。具體來講參數信息包含VPS,SPS(Sequence Parameter Set)和PPS(Picture Parameter Set).以下圖顯示了一個H.264碼流結構:

H.264碼流

  • 提取sps和pps生成FormatDesc

    • 每一個NALU的開始碼是0x00 00 01,按照開始碼定位NALU
    • 經過類型信息找到sps和pps並提取,開始碼後第一個byte的後5位,7表明sps,8表明pps
    • 使用CMVideoFormatDescriptionCreateFromH264ParameterSets函數來構建CMVideoFormatDescriptionRef
  • 提取視頻圖像數據生成CMBlockBuffer

    • 經過開始碼,定位到NALU
    • 肯定類型爲數據後,將開始碼替換成NALU的長度信息(4 Bytes)
    • 使用CMBlockBufferCreateWithMemoryBlock接口構造CMBlockBufferRef
  • 根據須要,生成CMTime信息。(實際測試時,加入time信息後,有不穩定的圖像,不加入time信息反而沒有,須要進一步研究,這裏建議不加入time信息)

根據上述獲得CMVideoFormatDescriptionRef、CMBlockBufferRef和可選的時間信息,使用CMSampleBufferCreate接口獲得CMSampleBuffer數據這個待解碼的原始的數據。以下圖所示的H264數據轉換示意圖。

CMSampleBufferCreate

3.7. 將 CMSampleBuffer渲染到界面

顯示的方式有兩種:

  • 將CMSampleBuffers提供給系統的AVSampleBufferDisplayLayer 直接顯示
    • 使用方式和其它CALayer相似。該層內置了硬件解碼功能,將原始的CMSampleBuffer解碼後的圖像直接顯示在屏幕上面,很是的簡單方便。
  • 利用OPenGL本身渲染 經過VTDecompression接口來,將CMSampleBuffer解碼成圖像,將圖像經過UIImageView或者OpenGL上顯示。

參考文章

視頻碼流格式解析

HM源碼分析

VPS SPS PPS

相關文章
相關標籤/搜索