在移動端做音視頻開發(fā)不同于基本的UI業(yè)務(wù)邏輯工作,音視頻開發(fā)需要你懂得音視頻中一些基本概念,針對編解碼而言,我們必須提前懂得編解碼器的一些特性,碼流的結(jié)構(gòu),碼流中一些重要信息如sps,pps,vps,start code以及基本的工作原理,而大多同學(xué)都只是一知半解,所以導(dǎo)致代碼中的部分內(nèi)容雖可以簡單理解卻不知其意,所以,在這里總結(jié)出了當(dāng)前主流的H.264,H.265編碼相關(guān)的原理,以供學(xué)習(xí).
眾所周知,視頻數(shù)據(jù)原始體積是巨大的,以720P 30fps的視頻為例,一個像素大約3個字節(jié),如下所得,每秒鐘產(chǎn)生87MB,這樣計算可得一分鐘就將產(chǎn)生5.22GB.
- 數(shù)據(jù)量/每秒=1280*720*33*3/1024/1024=87MB
- 復(fù)制代碼
因此,像這樣體積重大的視頻是無法在網(wǎng)絡(luò)中直接傳輸?shù)?而視頻編碼技術(shù)也就因運(yùn)而生.關(guān)于視頻編碼原理的技術(shù)可以參考本人其他文章,這里不做過多描述.
經(jīng)過很多年的開發(fā)迭代,已經(jīng)有很多大牛實現(xiàn)了視頻編碼技術(shù),其中最主流的有H.264編碼,以及新一代的H.265編碼,谷歌也開發(fā)了VP8,VP9編碼技術(shù).對移動端而言,蘋果內(nèi)部已經(jīng)實現(xiàn)了如H.264,H.265編碼,我們需要使用蘋果提供的VideoToolbox框架來實現(xiàn)它.
優(yōu)缺點
軟編:實現(xiàn)直接、簡單,參數(shù)調(diào)整方便,升級易,但CPU負(fù)載重,性能較硬編碼低,低碼率下質(zhì)量通常比硬編碼要好一點。
硬編:性能高,低碼率下通常質(zhì)量低于硬編碼器,但部分產(chǎn)品在GPU硬件平臺移植了優(yōu)秀的軟編碼算法(如X264)的,質(zhì)量基本等同于軟編碼。
iOS系統(tǒng)中的硬編碼 蘋果在iOS 8.0系統(tǒng)之前,沒有開放系統(tǒng)的硬件編碼解碼功能,不過Mac OS系統(tǒng)一直有,被稱為Video ToolBox的框架來處理硬件的編碼和解碼,終于在iOS 8.0后,蘋果將該框架引入iOS系統(tǒng)。
對視頻執(zhí)行編碼操作后,原始視頻數(shù)據(jù)會被壓縮成三種不同類型的視頻幀: I幀,P幀,B幀.
補(bǔ)充: I幀的壓縮率是7(跟JPG差不多),P幀是20,B幀可以達(dá)到50. 但是iOS中一般不開啟B幀,因為B幀的存在會導(dǎo)致時間戳同步較為復(fù)雜.
兩種核心算法
當(dāng)壓縮一幀圖像時,僅考慮本幀的數(shù)據(jù)而不考慮相鄰幀之間的冗余信息,這實際上與靜態(tài)圖像壓縮類似。幀內(nèi)一般采用有損壓縮算法,由于幀內(nèi)壓縮是編碼一個完整的圖像,所以可以獨立的解碼、顯示。幀內(nèi)壓縮一般達(dá)不到很高的壓縮,跟編碼jpeg差不多。
如下圖:我們可以通過第 1、2、3、4、5 塊的編碼來推測和計算第 6 塊的編碼,因此就不需要對第 6 塊進(jìn)行編碼了,從而壓縮了第 6 塊,節(jié)省了空間
相鄰幾幀的數(shù)據(jù)有很大的相關(guān)性,或者說前后兩幀信息變化很小的特點。也即連續(xù)的視頻其相鄰幀之間具有冗余信息,根據(jù)這一特性,壓縮相鄰幀之間的冗余量就可以進(jìn)一步提高壓縮量,減小壓縮比。幀間壓縮也稱為時間壓縮(Temporal compression),它通過比較時間軸上不同幀之間的數(shù)據(jù)進(jìn)行壓縮。幀間壓縮一般是無損的。幀差值(Frame differencing)算法是一種典型的時間壓縮法,它通過比較本幀與相鄰幀之間的差異,僅記錄本幀與其相鄰幀的差值,這樣可以大大減少數(shù)據(jù)量。
如下圖:可以看到前后兩幀的差異其實是很小的,這時候用幀間壓縮就很有意義。
有損壓縮與無損壓縮
DTS和PTS
如上圖:I幀的解碼不依賴于任何的其它的幀.而P幀的解碼則依賴于其前面的I幀或者P幀.B幀的解碼則依賴于其前的最近的一個I幀或者P幀 及其后的最近的一個P幀.
在我們的印象中,一張圖片就是一張圖像,視頻就是很多張圖片的集合.。但是因為我們要做音視頻編程,就需要更加深入理解視頻的本質(zhì).
在編碼的碼流中圖像是個集合的概念,幀、頂場、底場都可以稱為圖像,一幀通常就是一幅完整的圖像.
VPS主要用于傳輸視頻分級信息,有利于兼容標(biāo)準(zhǔn)在可分級視頻編碼或多視點視頻的擴(kuò)展。
(1)用于解釋編碼過的視頻序列的整體結(jié)構(gòu),包括時域子層依賴關(guān)系等。HEVC中加入該結(jié)構(gòu)的主要目的是兼容標(biāo)準(zhǔn)在系統(tǒng)的多子層方面的擴(kuò)展,處理比如未來的可分級或者多視點視頻使用原先的解碼器進(jìn)行解碼但是其所需的信息可能會被解碼器忽略的問題。
(2)對于給定視頻序列的某一個子層,無論其SPS相不相同,都共享一個VPS。其主要包含的信息有:多個子層或操作點共享的語法元素;檔次和級別等會話關(guān)鍵信息;其他不屬于SPS的操作點特定信息。
(3)編碼生成的碼流中,第一個NAL單元攜帶的就是VPS信息
包含一個CVS中所有編碼圖像的共享編碼參數(shù)。
(1)一段HEVC碼流可能包含一個或者多個編碼視頻序列,每個視頻序列由一個隨機(jī)接入點開始,即IDR/BLA/CRA。序列參數(shù)集SPS包含該視頻序列中所有slice需要的信息。
(2)SPS的內(nèi)容大致可以分為幾個部分:1、自引ID;2、解碼相關(guān)信息,如檔次級別、分辨率、子層數(shù)等;3、某檔次中的功能開關(guān)標(biāo)識及該功能的參數(shù);4、對結(jié)構(gòu)和變換系數(shù)編碼靈活性的限制信息;5、時域可分級信息;6、VUI。
包含一幅圖像所用的公共參數(shù),即一幅圖像中所有片段SS(Slice Segment)引用同一個PPS。
(1)PPS包含每一幀可能不同的設(shè)置信息,其內(nèi)容同H.264中的大致類似,主要包括:1、自引信息;2、初始圖像控制信息,如初始QP等;3、分塊信息。
(2)在解碼開始的時候,所有的PPS全部是非活動狀態(tài),而且在解碼的任意時刻,最多只能有一個PPS處于激活狀態(tài)。當(dāng)某部分碼流引用了某個PPS的時候,這個PPS便被激活,稱為活動PPS,一直到另一個PPS被激活。
參數(shù)集包含了相應(yīng)的編碼圖像的信息。SPS包含的是針對一連續(xù)編碼視頻序列的參數(shù)(標(biāo)識符seq_parameter_set_id、幀數(shù)及POC的約束、參考幀數(shù)目、解碼圖像尺寸和幀場編碼模式選擇標(biāo)識等等)。PPS對應(yīng)的是一個序列中某一幅圖像或者某幾幅圖像 ,其參數(shù)如標(biāo)識符pic_parameter_set_id、可選的seq_parameter_set_id、熵編碼模式選擇標(biāo)識、片組數(shù)目、初始量化參數(shù)和去方塊濾波系數(shù)調(diào)整標(biāo)識等等。
通常,SPS 和PPS 在片的頭信息和數(shù)據(jù)解碼前傳送至解碼器。每個片的頭信息對應(yīng)一個 pic_parameter_set_id,PPS被其激活后一直有效到下一個PPS被激活;類似的,每個PPS對應(yīng)一個 seq_parameter_set_id,SPS被其激活以后將一直有效到下一個SPS被激活。 參數(shù)集機(jī)制將一些重要的、改變少的序列參數(shù)和圖像參數(shù)與編碼片分離,并在編碼片之前傳送 至解碼端,或者通過其他機(jī)制傳輸。
擴(kuò)展知識點:檔次(Profile)、層(Tier)和級別(Level)
檔次: 主要規(guī)定編碼器可采用哪些編碼工具或算法。
級別: 指根據(jù)解碼端的負(fù)載和存儲空間情況對關(guān)鍵參數(shù)(最大采樣率、最大圖像尺寸、分辨率、最小壓縮比、最大比特率、解碼緩沖區(qū)DPB大小等)加以限制。
考慮到應(yīng)用可根據(jù)最大的碼率和CPB大小來區(qū)分,因此有些級別定義了兩個層Tier:主層和高層,主層用于大多數(shù)應(yīng)用,而高層用于那些最嚴(yán)苛的應(yīng)用。
一個序列的第一個圖像叫做 IDR 圖像(立即刷新圖像),IDR 圖像都是 I 幀圖像。引入 IDR 圖像是為了解碼的重同步,當(dāng)解碼器解碼到 IDR 圖像時,立即將參考幀隊列清空,將已解碼的數(shù)據(jù)全部輸出或拋棄,重新查找參數(shù)集,開始一個新的序列。這樣,如果前一個序列出現(xiàn)重大錯誤,在這里可以獲得重新同步的機(jī)會。IDR圖像之后的圖像永遠(yuǎn)不會使用IDR之前的圖像的數(shù)據(jù)來解碼。
由一個接一個的 NALU 組成的,而它的功能分為兩層,VCL(視頻編碼層)和 NAL(網(wǎng)絡(luò)提取層).
下圖以h264的碼流結(jié)構(gòu)為例,如果是h265則在sps前還有vps.
NALU (Nal Unit) = NALU頭 RBSP 在 VCL
數(shù)據(jù)傳輸或存儲之前,這些編碼的 VCL 數(shù)據(jù),先被映射或封裝進(jìn) NAL 單元(以下簡稱 NALU,Nal Unit) 中。每個 NALU 包括一個原始字節(jié)序列負(fù)荷(RBSP, Raw Byte Sequence Payload)、一組 對應(yīng)于視頻編碼的 NALU 頭部信息。RBSP 的基本結(jié)構(gòu)是:在原始編碼數(shù)據(jù)的后面填加了結(jié)尾 比特。一個 bit“1”若干比特“0”,以便字節(jié)對齊。
一個原始的H.264 NALU 單元常由 [StartCode] [NALU Header] [NALU Payload] 三部分組成
StartCode : Start Code 用于標(biāo)示這是一個NALU 單元的開始,必須是”00 00 00 01” 或”00 00 01”
NALU Header 下表為 NAL Header Type
例如,下面幅圖分別代表IDR與非IDR幀具體的碼流信息:
在一個NALU中,第一個字節(jié)(即NALU header)用以表示其包含數(shù)據(jù)的類型及其他信息。我們假定一個頭信息字節(jié)為0x67作為例子:
十六進(jìn)制 | 二進(jìn)制 |
---|---|
0x67 | 0 11 00111 |
如表所示,頭字節(jié)可以被解析成3個部分,其中:
1>. forbidden_zero_bit = 0:占1個bit,禁止位,用以檢查傳輸過程中是否發(fā)生錯誤,0表示正常,1表示違反語法;
2>. nal_ref_idc = 3:占2個bit,用來表示當(dāng)前NAL單元的優(yōu)先級。非0值表示參考字段/幀/圖片數(shù)據(jù),其他不那么重要的數(shù)據(jù)則為0。對于非0值,值越大表示NALU重要性越高
3>. nal_unit_type = 7:最后5位用以指定NALU類型,NALU類型定義如上表
從表中我們可以獲知,NALU類型1-5為視頻幀,其余則為非視頻幀。在解碼過程中,我們只需要取出NALU頭字節(jié)的后5位,即將NALU頭字節(jié)和0x1F進(jìn)行與計算即可得知NALU類型,即:
- NALU類型 = NALU頭字節(jié) & 0x1F
- 復(fù)制代碼
注意: 可以將start code理解為不同nalu的分隔符,header是某種類型的key,payload是該key的value.
H.264標(biāo)準(zhǔn)中指定了視頻如何編碼成獨立的包,但如何存儲和傳輸這些包卻未作規(guī)范,雖然標(biāo)準(zhǔn)中包含了一個Annex附件,里面描述了一種可能的格式Annex B,但這并不是一個必須要求的格式。 為了針對不同的存儲傳輸需求,出現(xiàn)了兩種打包方法。一種即Annex B格式,另一種稱為AVCC格式。
從上文可知,一個NALU中的數(shù)據(jù)并未包含他的大?。ㄩL度)信息,因此我們并不能簡單的將一個個NALU連接起來生成一個流,因為數(shù)據(jù)流的接收端并不知道一個NALU從哪里結(jié)束,另一個NALU從哪里開始。 Annex B格式用起始碼(Start Code)來解決這個問題,它在每個NALU的開始處添加三字節(jié)或四字節(jié)的起始碼0x000001或0x00000001。通過定位起始碼,解碼器就可以很容易的識別NALU的邊界。 當(dāng)然,用起始碼定位NALU邊界存在一個問題,即NALU中可能存在與起始碼相同的數(shù)據(jù)。為了防止這個問題,在構(gòu)建NALU時,需要將數(shù)據(jù)中的0x000000,0x000001,0x000002,0x000003中插入防競爭字節(jié)(Emulation Prevention Bytes)0x03,使其變?yōu)椋?/p>
0x000000 = 0x0000 03 00 0x000001 = 0x0000 03 01 0x000002 = 0x0000 03 02 0x000003 = 0x0000 03 03 解碼器在檢測到0x000003時,將0x03拋棄,恢復(fù)原始數(shù)據(jù)。
由于Annex B格式每個NALU都包含起始碼,所以解碼器可以從視頻流隨機(jī)點開始進(jìn)行解碼,常用于實時的流格式。在這種格式中通常會周期性的重復(fù)SPS和PPS,并且經(jīng)常時在每一個關(guān)鍵幀之前。
AVCC格式不使用起始碼作為NALU的分界,這種格式在每個NALU前都加上一個指定NALU長度的大端格式表示的前綴。這個前綴可以是1、2或4個字節(jié),所以在解析AVCC格式的時候需要將指定的前綴字節(jié)數(shù)的值保存在一個頭部對象中,這個都通常稱為extradata或者sequence header。同時,SPS和PPS數(shù)據(jù)也需要保存在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字節(jié)的后2位表示的就是NAL size的字節(jié)數(shù)。需要注意的是,這個NALULengthSizeMinusOne是NALU前綴長度減一,即,假設(shè)前綴長度為4,那么這個值應(yīng)該為3。 這里還需要注意的一點是,雖然AVCC格式不使用起始碼,但防競爭字節(jié)還是有的。
AVCC格式的一個優(yōu)點在于解碼器配置參數(shù)在一開始就配置好了,系統(tǒng)可以很容易的識別NALU的邊界,不需要額外的起始碼,減少了資源的浪費,同時可以在播放時調(diào)到視頻的中間位置。這種格式通常被用于可以被隨機(jī)訪問的多媒體數(shù)據(jù),如存儲在硬盤的文件。
HEVC全稱High Efficiency Video Coding(高效率視頻編碼,又稱H.265),是比H.264更優(yōu)秀的一種視頻壓縮標(biāo)準(zhǔn)。HEVC在低碼率視頻壓縮上,提升視頻質(zhì)量、減少容量即節(jié)省帶寬方面都有突出表現(xiàn)。 H.265標(biāo)準(zhǔn)圍繞H.264編碼標(biāo)準(zhǔn),保留原有的某些技術(shù),同時對一些技術(shù)進(jìn)行改進(jìn),編碼結(jié)構(gòu)大致上和H.264的架構(gòu)類似。這里著重講一下兩者編碼格式的區(qū)別。 同H.264一樣,H.265也是以NALU的形式組織起來。而在NALU header上,H.264的HALU header是一個字節(jié),而H.265則是兩個字節(jié)。我們同樣假定一個頭信息為0x4001作為例子:
十六進(jìn)制 | 二進(jìn)制 |
---|---|
0x4001 | 0 100000 000000 001 |
如表所示,頭信息可以被解析成4個部分,其中:
對比H.264的頭信息,H.265移除了nal_ref_idc,此信息被合并到了nal_unit_type中,H.265NALU類型規(guī)定如下:
nal_unit_type | NALU類型 | 備注 |
---|---|---|
0 | NAL_UNIT_CODE_SLICE_TRAIL_N | 非關(guā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 | 關(guān)鍵幀 |
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 | 未規(guī)定 |
64 | NAL_UNIT_INVALID |
具體type含義可以參考這篇文檔type類型 H.265的NALU類型是在信息頭的第一個字節(jié)的第2到7位,所以判斷H.265NALU類型的方法是將NALU第一個字節(jié)與0x7E進(jìn)行與操作并右移一位,即:
- NALU類型 = (NALU頭第一字節(jié) & 0x7E) >> 1
- 復(fù)制代碼
與H.264類似,H.265碼流也有兩種封裝格式,一種是用起始碼作為分界的Annex B格式,另一種則是在NALU頭添加NALU長度前綴的格式,稱為HVCC。在HVCC中,同樣需要一個extradata來保存視頻流的編解碼參數(shù),其格式定義如下:
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后半段是一段格式重復(fù)的數(shù)組數(shù)據(jù),里面需要包含的除了與H.264相同的SPS、PPS外,還需多添加一個VPS。
VPS(Video Parament Set,視頻參數(shù)集),在H.265中類型為32。VPS用于解釋編碼過的視頻的整體結(jié)構(gòu),包括時域子層依賴關(guān)系等,主要目的在于兼容H.265標(biāo)準(zhǔn)在系統(tǒng)的多子層方面的擴(kuò)展。
iOS中視頻處理框架分層
對于 AVKit、AVFoundation 和 VideoToolbox 來說,他們的功能和可定制性越來越強(qiáng),但相應(yīng)的使用難度也越大,因此你應(yīng)該根據(jù)實際需求合理的選擇使用哪個層級的接口。事實上,即使你使用 AVFoundation 或 AVKit 依然可以獲得硬件加速的效果,你失去的只是直接訪問硬編解碼器的權(quán)限。對于 VideoToolbox 來說,你可以通過直接訪問硬編解碼器,將 H.264 文件或傳輸流轉(zhuǎn)換為 iOS 上的 CMSampleBuffer 并解碼成 CVPixelBuffer,或?qū)閴嚎s的 CVPixelBuffer 編碼成 CMSampleBuffer:
調(diào)用 AVCaptureSession 拍攝輸出的每一幀圖像都會被包裝成 CMSampleBuffer 對象,通過這個 CMSampleBuffer 對象你就可以獲取到未壓縮的 CVPixelBuffer 對象;如果讀取 H.264 文件你也可以獲取數(shù)據(jù)生成壓縮的 CMBlockBuffer 對象并創(chuàng)建一個 CMSampleBuffer 對象給 VideoToolbox 來解碼。
也就是說,CMSampleBuffer 既可以作為 CVPixelBuffer 對象的容器,也可以作為 CMBlockBuffer 對象的容器,CVPixelBuffer 可以說是未壓縮的圖像數(shù)據(jù)容器,而 CMBlockBuffer 則是壓縮圖像數(shù)據(jù)容器。
NALU. 對于一個 H.264 裸流或者文件來說,它是由一個一個的 NALU(Network Abstraction Layer Unit) 單元組成,每個 NALU 既可以表示圖像數(shù)據(jù),也可以表示處理圖像所需要的參數(shù)數(shù)據(jù)。它主要有兩種格式:Annex B 和 AVCC。也被稱為 Elementary Stream 和 MPEG-4 格式,Annex B 格式以 0x000001 或 0x00000001 開頭,AVCC 格式以所在的 NALU 的長度開頭。
在iOS中使用vtCompressionSession編碼后的數(shù)據(jù)不包含start code.需要我們自行添加,為什么要添加start code? 如上文所說,為了區(qū)分每個NALU及以后提取vps,sps,pps等關(guān)鍵信息.
如下圖,我們在編碼回調(diào)中可以拿到編碼后的CMSampleBuffer
數(shù)據(jù).如果是h265,CMVideoFormatDesc
中還有vps.而這段數(shù)據(jù)在推流前必須尋找到關(guān)鍵數(shù)據(jù)vps,sps,pps以及添加start code.因為在CMBlockBufferRef
中可能還含有一個或多個NALU,所以我們需要通過遍歷它的內(nèi)存地址找到在每個NALU的分割點將數(shù)據(jù)替換為start code.具體代碼操作參考實戰(zhàn)篇.
接下來,我們就需要處理這一幀視頻的圖像數(shù)據(jù)了。通過 CMSampleBufferGetDataBuffer 和 CMBlockBufferGetDataPointer 我們可以獲取視頻數(shù)據(jù)的內(nèi)存地址。VTCompressionSession 編碼出來的視頻幀為 AVCC 格式,因此我們可以讀取頭部 4 個字節(jié)數(shù)據(jù)來獲取當(dāng)前 NALU 的長度。這里有一個需要注意的是,AVCC 格式使用大端字節(jié)序,它可能跟當(dāng)前使用的系統(tǒng)字節(jié)序不一樣,事實上,iOS 系統(tǒng)使用小端字節(jié)序,因此我們需要先將這個長度數(shù)據(jù)轉(zhuǎn)換為 iOS 系統(tǒng)使用的小端字節(jié)序:
NALUnitLength = CFSwapInt32BigToHost(NALUnitLength)
硬編碼基本上是硬解碼的一個逆過程。解析出參數(shù)集SPS和PPS,加上開始碼后組裝成NALU。提取出視頻數(shù)據(jù),將長度碼轉(zhuǎn)換成開始碼,組長成NALU。將NALU發(fā)送出去。
如上圖,我們知道,CMSampleBuffer = CMTime FormatDesc CMBlockBuffer . 需要從H264的碼流里面提取出以上的三個信息。最后組合成CMSampleBuffer,提供給硬解碼接口來進(jìn)行解碼工作。
在H.264, H.265的語法中,有一個最基礎(chǔ)的層,叫做Network Abstraction Layer, 簡稱為NAL。編碼數(shù)據(jù)正是由一系列的NAL單元(NAL Unit, 簡稱NAUL)組成的。
編碼碼流由NALU單元組成,一個NALU可能包含有:
流數(shù)據(jù)中,屬性集合可能是這樣的:(如果是H.265碼流,SPS前面還有VPS)
經(jīng)過處理之后,在Format Description中則是:
要從基礎(chǔ)的流數(shù)據(jù)將SPS和PPS轉(zhuǎn)化為Format Desc中的話,需要調(diào)用CMVideoFormatDescriptionCreateFromH264ParameterSets,CMVideoFormatDescriptionGetHEVCParameterSetAtIndex
方法
對于流數(shù)據(jù)來說,一個NAUL的Header中,可能是0x00 00 01或者是0x00 00 00 01作為開頭(兩者都有可能,下面以0x00 00 01作為例子)。0x00 00 01因此被稱為開始碼(Start code).
總結(jié)以上知識,我們知道編碼碼流由NALU單元組成,NALU單元包含視頻圖像數(shù)據(jù)和編碼器的參數(shù)信息。其中視頻圖像數(shù)據(jù)就是CMBlockBuffer,而編碼器參數(shù)信息則可以組合成FormatDesc。具體來說參數(shù)信息包含VPS,SPS(Sequence Parameter Set)和PPS(Picture Parameter Set).如下圖顯示了一個H.264碼流結(jié)構(gòu):
提取sps和pps生成FormatDesc
提取視頻圖像數(shù)據(jù)生成CMBlockBuffer
根據(jù)需要,生成CMTime信息。(實際測試時,加入time信息后,有不穩(wěn)定的圖像,不加入time信息反而沒有,需要進(jìn)一步研究,這里建議不加入time信息)
根據(jù)上述得到CMVideoFormatDescriptionRef、CMBlockBufferRef和可選的時間信息,使用CMSampleBufferCreate接口得到CMSampleBuffer數(shù)據(jù)這個待解碼的原始的數(shù)據(jù)。如下圖所示的H264數(shù)據(jù)轉(zhuǎn)換示意圖。
顯示的方式有兩種:
聯(lián)系客服