九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

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

需求

在移動端做音視頻開發(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í).


閱讀前提:

  • 音視頻基礎(chǔ)知識
  • iOS中VideoToolbox框架

1. 概覽

1.1. 為什么要編碼

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

  1. 數(shù)據(jù)量/每秒=1280*720*33*3/1024/1024=87MB
  2. 復(fù)制代碼

因此,像這樣體積重大的視頻是無法在網(wǎng)絡(luò)中直接傳輸?shù)?而視頻編碼技術(shù)也就因運(yùn)而生.關(guān)于視頻編碼原理的技術(shù)可以參考本人其他文章,這里不做過多描述.

1.2. 編碼技術(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)它.

1.3. 編碼分類

  • 軟件編碼(簡稱軟編):使用CPU進(jìn)行編碼。
  • 硬件編碼(簡稱硬編):不使用CPU進(jìn)行編碼,使用顯卡GPU,專用的DSP、FPGA、ASIC芯片等硬件進(jì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)。

1.4. 編碼原理

對視頻執(zhí)行編碼操作后,原始視頻數(shù)據(jù)會被壓縮成三種不同類型的視頻幀: I幀,P幀,B幀.

  • I幀:關(guān)鍵幀.完整編碼的幀.可以理解成是一張完整畫面,不依賴其他幀
  • P幀:參考前面的I幀或P幀,即通過前面的I幀與自己記錄的不同的部分可以形成完整的畫面.因此,單獨的P幀無法形成畫面.
  • B幀:參考前面的I幀或P幀以及后面的P幀

補(bǔ)充: I幀的壓縮率是7(跟JPG差不多),P幀是20,B幀可以達(dá)到50. 但是iOS中一般不開啟B幀,因為B幀的存在會導(dǎo)致時間戳同步較為復(fù)雜.

兩種核心算法

  • 幀內(nèi)壓縮

當(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é)省了空間

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

相鄰幾幀的數(shù)據(jù)有很大的相關(guān)性,或者說前后兩幀信息變化很小的特點。也即連續(xù)的視頻其相鄰幀之間具有冗余信息,根據(jù)這一特性,壓縮相鄰幀之間的冗余量就可以進(jìn)一步提高壓縮量,減小壓縮比。幀間壓縮也稱為時間壓縮(Temporal compression),它通過比較時間軸上不同幀之間的數(shù)據(jù)進(jìn)行壓縮。幀間壓縮一般是無損的。幀差值(Frame differencing)算法是一種典型的時間壓縮法,它通過比較本幀與相鄰幀之間的差異,僅記錄本幀與其相鄰幀的差值,這樣可以大大減少數(shù)據(jù)量。

如下圖:可以看到前后兩幀的差異其實是很小的,這時候用幀間壓縮就很有意義。

有損壓縮與無損壓縮

  • 有損壓縮: 解壓縮后的數(shù)據(jù)與壓縮前的數(shù)據(jù)不一致.在壓縮的過程中要丟失一些人眼和人耳所不敏感的圖像或音頻信息,而且丟失的信息不可恢復(fù)
  • 無損壓縮: 壓縮前和解壓縮后的數(shù)據(jù)完全一致.優(yōu)化數(shù)據(jù)的排列等.

DTS和PTS

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

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

2. 編碼數(shù)據(jù)碼流結(jié)構(gòu)

在我們的印象中,一張圖片就是一張圖像,視頻就是很多張圖片的集合.。但是因為我們要做音視頻編程,就需要更加深入理解視頻的本質(zhì).

2.1 刷新圖像概念.

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

  • 逐行掃描:每次掃描得到的信號就是一副圖像,也就是一幀. 逐行掃描適合于運(yùn)動圖像
  • 隔行掃描:掃描下來的一幀圖像就被分為了兩個部分,這每一部分就稱為「場」,根據(jù)次序分為:「頂場」和「底場」.適合于非運(yùn)動圖像

2.2. 重要參數(shù)

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

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信息

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

包含一個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ù)集PPS(Picture Parameter Set)

包含一幅圖像所用的公共參數(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)用。

2.3. 原始碼流

  • IDR

一個序列的第一個圖像叫做 IDR 圖像(立即刷新圖像),IDR 圖像都是 I 幀圖像。引入 IDR 圖像是為了解碼的重同步,當(dāng)解碼器解碼到 IDR 圖像時,立即將參考幀隊列清空,將已解碼的數(shù)據(jù)全部輸出或拋棄,重新查找參數(shù)集,開始一個新的序列。這樣,如果前一個序列出現(xiàn)重大錯誤,在這里可以獲得重新同步的機(jī)會。IDR圖像之后的圖像永遠(yuǎn)不會使用IDR之前的圖像的數(shù)據(jù)來解碼。

  • 結(jié)構(gòu)

由一個接一個的 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é)對齊。

2.3.1. H.264碼流

一個原始的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)制
0x670 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類型,即:

  1. NALU類型 = NALU頭字節(jié) & 0x1F
  2. 復(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格式。

  • Annex B

從上文可知,一個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

AVCC格式不使用起始碼作為NALU的分界,這種格式在每個NALU前都加上一個指定NALU長度的大端格式表示的前綴。這個前綴可以是1、2或4個字節(jié),所以在解析AVCC格式的時候需要將指定的前綴字節(jié)數(shù)的值保存在一個頭部對象中,這個都通常稱為extradata或者sequence header。同時,SPS和PPS數(shù)據(jù)也需要保存在extradata中。 H.264 extradata語法如下:

bitsline by byteremark
8versionalways
8avc profilesps[0][1]
8avc compatibilitysps[0][2]
8avc levelsps[0][3]
6reservedall bits on
2NALULengthSizeMinusOne
3reservedall bits on
5number of SPS NALUs usually1
16SPS size
Nvariable SPS NALU data
8number of PPS NALUs usually1
16PPS size
Nvariable 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ù),如存儲在硬盤的文件。

2.3.2. H.265碼流

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)制
0x40010 100000 000000 001

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

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

對比H.264的頭信息,H.265移除了nal_ref_idc,此信息被合并到了nal_unit_type中,H.265NALU類型規(guī)定如下:

nal_unit_typeNALU類型備注
0NAL_UNIT_CODE_SLICE_TRAIL_N非關(guān)鍵幀
1NAL_UNIT_CODED_SLICE_TRAIL_R
2NAL_UNIT_CODED_SLICE_TSA_N
3NAL_UINT_CODED_SLICE_TSA_R
4NAL_UINT_CODED_SLICE_STSA_N
5NAL_UINT_CODED_SLICE_STSA_R
6NAL_UNIT_CODED_SLICE_RADL_N
7NAL_UNIT_CODED_SLICE_RADL_R
8NAL_UNIT_CODED_SLICE_RASL_N
9NAL_UNIT_CODE_SLICE_RASL_R
10 ~ 15NAL_UNIT_RESERVED_X保留
16NAL_UNIT_CODED_SLICE_BLA_W_LP關(guān)鍵幀
17NAL_UNIT_CODE_SLICE_BLA_W_RADL
18NAL_UNIT_CODE_SLICE_BLA_N_LP
19NAL_UNIT_CODE_SLICE_IDR_W_RADL
20NAL_UNIT_CODE_SLICE_IDR_N_LP
21NAL_UNIT_CODE_SLICE_CRA
22 ~ 31NAL_UNIT_RESERVED_X保留
32NAL_UNIT_VPSVPS(Video Paramater Set)
33NAL_UNIT_SPSSPS
34NAL_UNIT_PPSPPS
35NAL_UNIT_ACCESS_UNIT_DELIMITER
36NAL_UNIT_EOS
37NAL_UNIT_EOB
38NAL_UNIT_FILLER_DATA
39NAL_UNIT_SEIPrefix SEI
40NAL_UNIT_SEI_SUFFIXSuffix SEI
41 ~ 47NAL_UNIT_RESERVED_X保留
48 ~ 63NAL_UNIT_UNSPECIFIED_X未規(guī)定
64NAL_UNIT_INVALID

具體type含義可以參考這篇文檔type類型 H.265的NALU類型是在信息頭的第一個字節(jié)的第2到7位,所以判斷H.265NALU類型的方法是將NALU第一個字節(jié)與0x7E進(jìn)行與操作并右移一位,即:

  1. NALU類型 = (NALU頭第一字節(jié) & 0x7E) >> 1
  2. 復(fù)制代碼

與H.264類似,H.265碼流也有兩種封裝格式,一種是用起始碼作為分界的Annex B格式,另一種則是在NALU頭添加NALU長度前綴的格式,稱為HVCC。在HVCC中,同樣需要一個extradata來保存視頻流的編解碼參數(shù),其格式定義如下:

bitsline by byteremark
8configurationVersionalways 0x01
2general_profile_space
1general_tier_flag
5general_profile_idc
32general_profile_compatibility_flags
48general_constraint_indicator_flags
8general_level_idc
4reserved'1111’b
12min_spatial_segmentation_idc
6reserved'111111’b
2parallelismType
6reserved'111111’b
2chromaFormat
5reserved'11111’b
3bitDepthLumaMinus8
5reserved'11111’b
3bitDepthChromaMinus8
16avgFrameRate
2constantFrameRate
3numTemporalLayers
1tmporalIdNested
2lengthSizeMinusOne
8numOfArrays

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ò)展。

3. iOS中的視頻編解碼

iOS中視頻處理框架分層

3.1. 框架的選擇

對于 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:

3.2. 視頻數(shù)據(jù)的容器

調(diào)用 AVCaptureSession 拍攝輸出的每一幀圖像都會被包裝成 CMSampleBuffer 對象,通過這個 CMSampleBuffer 對象你就可以獲取到未壓縮的 CVPixelBuffer 對象;如果讀取 H.264 文件你也可以獲取數(shù)據(jù)生成壓縮的 CMBlockBuffer 對象并創(chuàng)建一個 CMSampleBuffer 對象給 VideoToolbox 來解碼。

也就是說,CMSampleBuffer 既可以作為 CVPixelBuffer 對象的容器,也可以作為 CMBlockBuffer 對象的容器,CVPixelBuffer 可以說是未壓縮的圖像數(shù)據(jù)容器,而 CMBlockBuffer 則是壓縮圖像數(shù)據(jù)容器。

3.3. 編碼數(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 的長度開頭。

3.4. VideoToolBox中常用數(shù)據(jù)結(jié)構(gòu)

  • CMSampleBuffer: 存放編解碼前后的視頻圖像的容器數(shù)據(jù)結(jié)構(gòu)
  • CVPixelBuffer: 編碼前和解碼后的圖像數(shù)據(jù)結(jié)構(gòu)
  • CMBlockBuffer: 編碼后圖像的數(shù)據(jù)結(jié)構(gòu)
  • CMVideoFormatDescription: 圖像存儲方式,編解碼器等格式描述
  • pixelBufferAttributes: : CFDictionary對象,可能包含了視頻的寬高,像素格式類型(32RGBA, YCbCr420),是否可以用于OpenGL ES等相關(guān)信息
  • CMTime: 時間戳相關(guān)。時間以 64-big/32-bit形式出現(xiàn)。 分子是64-bit的時間值,分母是32-bit的時標(biāo)(time scale)

3.5. 為編碼的數(shù)據(jù)添加start code.

在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ā)送出去。

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

如上圖,我們知道,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可能包含有:

  • 視頻幀,視頻幀也就是視頻片段,具體有 P幀, I幀,B幀
  • 編碼屬性合集-FormatDesc(包含VPS,SPS和PPS)

流數(shù)據(jù)中,屬性集合可能是這樣的:(如果是H.265碼流,SPS前面還有VPS)

經(jīng)過處理之后,在Format Description中則是:

要從基礎(chǔ)的流數(shù)據(jù)將SPS和PPS轉(zhuǎn)化為Format Desc中的話,需要調(diào)用CMVideoFormatDescriptionCreateFromH264ParameterSets,CMVideoFormatDescriptionGetHEVCParameterSetAtIndex方法

  • NALU header

對于流數(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

    • 每個NALU的開始碼是0x00 00 01,按照開始碼定位NALU
    • 通過類型信息找到sps和pps并提取,開始碼后第一個byte的后5位,7代表sps,8代表pps
    • 使用CMVideoFormatDescriptionCreateFromH264ParameterSets函數(shù)來構(gòu)建CMVideoFormatDescriptionRef
  • 提取視頻圖像數(shù)據(jù)生成CMBlockBuffer

    • 通過開始碼,定位到NALU
    • 確定類型為數(shù)據(jù)后,將開始碼替換成NALU的長度信息(4 Bytes)
    • 使用CMBlockBufferCreateWithMemoryBlock接口構(gòu)造CMBlockBufferRef
  • 根據(jù)需要,生成CMTime信息。(實際測試時,加入time信息后,有不穩(wěn)定的圖像,不加入time信息反而沒有,需要進(jìn)一步研究,這里建議不加入time信息)

根據(jù)上述得到CMVideoFormatDescriptionRef、CMBlockBufferRef和可選的時間信息,使用CMSampleBufferCreate接口得到CMSampleBuffer數(shù)據(jù)這個待解碼的原始的數(shù)據(jù)。如下圖所示的H264數(shù)據(jù)轉(zhuǎn)換示意圖。

3.7. 將 CMSampleBuffer渲染到界面

顯示的方式有兩種:

  • 將CMSampleBuffers提供給系統(tǒng)的AVSampleBufferDisplayLayer 直接顯示
    • 使用方式和其它CALayer類似。該層內(nèi)置了硬件解碼功能,將原始的CMSampleBuffer解碼后的圖像直接顯示在屏幕上面,非常的簡單方便。
  • 利用OPenGL自己渲染 通過VTDecompression接口來,將CMSampleBuffer解碼成圖像,將圖像通過UIImageView或者OpenGL上顯示。

參考文章

視頻碼流格式解析

HM源碼分析

VPS SPS PPS

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
iOS RTMP 視頻直播開發(fā)筆記(3)
H264幀格式解析
入門理解H264編碼
H264編碼原理及NALU介紹
H264(NAL簡介與I幀判斷)
H.264中NAL、Slice與frame意思及相互關(guān)系
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服