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

需求

在移動端做音視頻開發不同于基本的UI業務邏輯工作,音視頻開發需要你懂得音視頻中一些基本概念,針對編解碼而言,我們必須提前懂得編解碼器的一些特性,碼流的結構,碼流中一些重要信息如sps,pps,vps,start code以及基本的工作原理,而大多同學都只是一知半解,所以導致代碼中的部分內容雖可以簡單理解卻不知其意,所以,在這里總結出了當前主流的H.264,H.265編碼相關的原理,以供學習.


閱讀前提:

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

1. 概覽

1.1. 為什么要編碼

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

數據量/每秒=1280*720*33*3/1024/1024=87MB

因此,像這樣體積重大的視頻是無法在網絡中直接傳輸的.而視頻編碼技術也就因運而生.關于視頻編碼原理的技術可以參考本人其他文章,這里不做過多描述.

1.2. 編碼技術

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

1.3. 編碼分類

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

優缺點

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

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

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差不多。

如下圖:我們可以通過第 1、2、3、4、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的內容大致可以分為幾個部分:1、自引ID;2、解碼相關信息,如檔次級別、分辨率、子層數等;3、某檔次中的功能開關標識及該功能的參數;4、對結構和變換系數編碼靈活性的限制信息;5、時域可分級信息;6、VUI。

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

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

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

(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長度的大端格式表示的前綴。這個前綴可以是1、2或4個字節,所以在解析AVCC格式的時候需要將指定的前綴字節數的值保存在一個頭部對象中,這個都通常稱為extradata或者sequence header。同時,SPS和PPS數據也需要保存在extradata中。
H.264 extradata語法如下:

bits line by byte remark
8 version always 0x01
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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容

  • 一、軟編與硬編概念 1、軟編碼:使用CPU進行編碼。 實現直接、簡單,參數調整方便,升級易,但CPU負載重,性能較...
    Evans_Xiao閱讀 4,714評論 0 6
  • ### YUV顏色空間 視頻是由一幀一幀的數據連接而成,而一幀視頻數據其實就是一張圖片。 yuv是一種圖片儲存格式...
    天使君閱讀 3,356評論 0 4
  • 在目前,無論在各個行只要和視頻相關的,我們都可以看見H264相關的身影,H264作為目前使用最廣泛的視頻壓縮標準,...
    DramaScript閱讀 21,668評論 7 56
  • 視頻格式封裝——H264 轉載自 http://blog.csdn.net/yangzhongxuan/artic...
    microchip閱讀 2,387評論 0 1
  • 只要感覺自己做的對,就給自己獎勵
    張嚴閱讀 223評論 0 0