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

需求

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


閱讀前提:

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

1. 概覽

1.1. 為什么要編碼

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

數(shù)據(jù)量/每秒=1280*720*33*3/1024/1024=87MB

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

1.2. 編碼技術(shù)

經(jīng)過(guò)很多年的開(kāi)發(fā)迭代,已經(jīng)有很多大牛實(shí)現(xiàn)了視頻編碼技術(shù),其中最主流的有H.264編碼,以及新一代的H.265編碼,谷歌也開(kāi)發(fā)了VP8,VP9編碼技術(shù).對(duì)移動(dòng)端而言,蘋果內(nèi)部已經(jīng)實(shí)現(xiàn)了如H.264,H.265編碼,我們需要使用蘋果提供的VideoToolbox框架來(lái)實(shí)現(xiàn)它.

1.3. 編碼分類

  • 軟件編碼(簡(jiǎn)稱軟編):使用CPU進(jìn)行編碼。
  • 硬件編碼(簡(jiǎn)稱硬編):不使用CPU進(jìn)行編碼,使用顯卡GPU,專用的DSP碟嘴、FPGA丹拯、ASIC芯片等硬件進(jìn)行編碼。

優(yōu)缺點(diǎn)

  • 軟編:實(shí)現(xiàn)直接匾灶、簡(jiǎn)單,參數(shù)調(diào)整方便,升級(jí)易忽你,但CPU負(fù)載重,性能較硬編碼低臂容,低碼率下質(zhì)量通常比硬編碼要好一點(diǎn)科雳。

  • 硬編:性能高,低碼率下通常質(zhì)量低于硬編碼器脓杉,但部分產(chǎn)品在GPU硬件平臺(tái)移植了優(yōu)秀的軟編碼算法(如X264)的糟秘,質(zhì)量基本等同于軟編碼。

iOS系統(tǒng)中的硬編碼
蘋果在iOS 8.0系統(tǒng)之前球散,沒(méi)有開(kāi)放系統(tǒng)的硬件編碼解碼功能尿赚,不過(guò)Mac OS系統(tǒng)一直有,被稱為Video ToolBox的框架來(lái)處理硬件的編碼和解碼蕉堰,終于在iOS 8.0后凌净,蘋果將該框架引入iOS系統(tǒng)。

1.4. 編碼原理

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

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

補(bǔ)充: I幀的壓縮率是7(跟JPG差不多)屋讶,P幀是20冰寻,B幀可以達(dá)到50. 但是iOS中一般不開(kāi)啟B幀,因?yàn)锽幀的存在會(huì)導(dǎo)致時(shí)間戳同步較為復(fù)雜.

兩種核心算法

  • 幀內(nèi)壓縮

當(dāng)壓縮一幀圖像時(shí),僅考慮本幀的數(shù)據(jù)而不考慮相鄰幀之間的冗余信息皿渗,這實(shí)際上與靜態(tài)圖像壓縮類似斩芭。幀內(nèi)一般采用有損壓縮算法轻腺,由于幀內(nèi)壓縮是編碼一個(gè)完整的圖像,所以可以獨(dú)立的解碼划乖、顯示贬养。幀內(nèi)壓縮一般達(dá)不到很高的壓縮,跟編碼jpeg差不多迁筛。

如下圖:我們可以通過(guò)第 1煤蚌、2、3细卧、4尉桩、5 塊的編碼來(lái)推測(cè)和計(jì)算第 6 塊的編碼,因此就不需要對(duì)第 6 塊進(jìn)行編碼了贪庙,從而壓縮了第 6 塊蜘犁,節(jié)省了空間

幀內(nèi)預(yù)測(cè).png
  • 幀間壓縮: P幀與B幀的壓縮算法

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

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

幀間壓縮11.jpg

有損壓縮與無(wú)損壓縮

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

DTS和PTS

  • DTS:主要用于視頻的解碼,在解碼階段使用.
  • PTS:主要用于視頻的同步和輸出.在渲染的時(shí)候使用.在沒(méi)有B frame的情況下.DTS和PTS的輸出順序是一樣的忍疾。
1.dtspts

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

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

在我們的印象中,一張圖片就是一張圖像谨朝,視頻就是很多張圖片的集合.卤妒。但是因?yàn)槲覀円鲆粢曨l編程,就需要更加深入理解視頻的本質(zhì).

2.1 刷新圖像概念.

在編碼的碼流中圖像是個(gè)集合的概念,幀、頂場(chǎng)字币、底場(chǎng)都可以稱為圖像,一幀通常就是一幅完整的圖像.

  • 逐行掃描:每次掃描得到的信號(hào)就是一副圖像则披,也就是一幀. 逐行掃描適合于運(yùn)動(dòng)圖像
  • 隔行掃描:掃描下來(lái)的一幀圖像就被分為了兩個(gè)部分,這每一部分就稱為「場(chǎng)」纬朝,根據(jù)次序分為:「頂場(chǎng)」和「底場(chǎng)」.適合于非運(yùn)動(dòng)圖像
逐行掃描與隔行掃描.png
幀與場(chǎng).png

2.2. 重要參數(shù)

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

VPS主要用于傳輸視頻分級(jí)信息收叶,有利于兼容標(biāo)準(zhǔn)在可分級(jí)視頻編碼或多視點(diǎn)視頻的擴(kuò)展。

(1)用于解釋編碼過(guò)的視頻序列的整體結(jié)構(gòu)共苛,包括時(shí)域子層依賴關(guān)系等判没。HEVC中加入該結(jié)構(gòu)的主要目的是兼容標(biāo)準(zhǔn)在系統(tǒng)的多子層方面的擴(kuò)展蜓萄,處理比如未來(lái)的可分級(jí)或者多視點(diǎn)視頻使用原先的解碼器進(jìn)行解碼但是其所需的信息可能會(huì)被解碼器忽略的問(wèn)題。

(2)對(duì)于給定視頻序列的某一個(gè)子層澄峰,無(wú)論其SPS相不相同嫉沽,都共享一個(gè)VPS。其主要包含的信息有:多個(gè)子層或操作點(diǎn)共享的語(yǔ)法元素俏竞;檔次和級(jí)別等會(huì)話關(guān)鍵信息绸硕;其他不屬于SPS的操作點(diǎn)特定信息。

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

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

包含一個(gè)CVS中所有編碼圖像的共享編碼參數(shù)玻佩。

(1)一段HEVC碼流可能包含一個(gè)或者多個(gè)編碼視頻序列,每個(gè)視頻序列由一個(gè)隨機(jī)接入點(diǎn)開(kāi)始席楚,即IDR/BLA/CRA咬崔。序列參數(shù)集SPS包含該視頻序列中所有slice需要的信息。

(2)SPS的內(nèi)容大致可以分為幾個(gè)部分:1烦秩、自引ID垮斯;2、解碼相關(guān)信息只祠,如檔次級(jí)別兜蠕、分辨率、子層數(shù)等抛寝;3熊杨、某檔次中的功能開(kāi)關(guān)標(biāo)識(shí)及該功能的參數(shù);4墩剖、對(duì)結(jié)構(gòu)和變換系數(shù)編碼靈活性的限制信息猴凹;5夷狰、時(shí)域可分級(jí)信息岭皂;6、VUI沼头。

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

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

(1)PPS包含每一幀可能不同的設(shè)置信息进倍,其內(nèi)容同H.264中的大致類似土至,主要包括:1、自引信息猾昆;2陶因、初始圖像控制信息,如初始QP等垂蜗;3楷扬、分塊信息解幽。

(2)在解碼開(kāi)始的時(shí)候,所有的PPS全部是非活動(dòng)狀態(tài)烘苹,而且在解碼的任意時(shí)刻躲株,最多只能有一個(gè)PPS處于激活狀態(tài)。當(dāng)某部分碼流引用了某個(gè)PPS的時(shí)候镣衡,這個(gè)PPS便被激活霜定,稱為活動(dòng)PPS,一直到另一個(gè)PPS被激活廊鸥。

參數(shù)集包含了相應(yīng)的編碼圖像的信息望浩。SPS包含的是針對(duì)一連續(xù)編碼視頻序列的參數(shù)(標(biāo)識(shí)符seq_parameter_set_id、幀數(shù)及POC的約束惰说、參考幀數(shù)目曾雕、解碼圖像尺寸和幀場(chǎng)編碼模式選擇標(biāo)識(shí)等等)助被。PPS對(duì)應(yīng)的是一個(gè)序列中某一幅圖像或者某幾幅圖像 剖张,其參數(shù)如標(biāo)識(shí)符pic_parameter_set_id、可選的seq_parameter_set_id揩环、熵編碼模式選擇標(biāo)識(shí)搔弄、片組數(shù)目、初始量化參數(shù)和去方塊濾波系數(shù)調(diào)整標(biāo)識(shí)等等丰滑。

通常顾犹,SPS 和PPS 在片的頭信息和數(shù)據(jù)解碼前傳送至解碼器。每個(gè)片的頭信息對(duì)應(yīng)一個(gè)
pic_parameter_set_id褒墨,PPS被其激活后一直有效到下一個(gè)PPS被激活炫刷;類似的,每個(gè)PPS對(duì)應(yīng)一個(gè)
seq_parameter_set_id郁妈,SPS被其激活以后將一直有效到下一個(gè)SPS被激活浑玛。
參數(shù)集機(jī)制將一些重要的、改變少的序列參數(shù)和圖像參數(shù)與編碼片分離噩咪,并在編碼片之前傳送
至解碼端顾彰,或者通過(guò)其他機(jī)制傳輸。

擴(kuò)展知識(shí)點(diǎn):檔次(Profile)胃碾、層(Tier)和級(jí)別(Level)

  • 檔次: 主要規(guī)定編碼器可采用哪些編碼工具或算法涨享。

  • 級(jí)別: 指根據(jù)解碼端的負(fù)載和存儲(chǔ)空間情況對(duì)關(guān)鍵參數(shù)(最大采樣率、最大圖像尺寸仆百、分辨率厕隧、最小壓縮比、最大比特率、解碼緩沖區(qū)DPB大小等)加以限制吁讨。

考慮到應(yīng)用可根據(jù)最大的碼率和CPB大小來(lái)區(qū)分帖族,因此有些級(jí)別定義了兩個(gè)層Tier:主層和高層,主層用于大多數(shù)應(yīng)用挡爵,而高層用于那些最嚴(yán)苛的應(yīng)用竖般。

2.3. 原始碼流

  • IDR

一個(gè)序列的第一個(gè)圖像叫做 IDR 圖像(立即刷新圖像),IDR 圖像都是 I 幀圖像茶鹃。引入 IDR 圖像是為了解碼的重同步涣雕,當(dāng)解碼器解碼到 IDR 圖像時(shí),立即將參考幀隊(duì)列清空闭翩,將已解碼的數(shù)據(jù)全部輸出或拋棄挣郭,重新查找參數(shù)集,開(kāi)始一個(gè)新的序列疗韵。這樣兑障,如果前一個(gè)序列出現(xiàn)重大錯(cuò)誤,在這里可以獲得重新同步的機(jī)會(huì)蕉汪。IDR圖像之后的圖像永遠(yuǎn)不會(huì)使用IDR之前的圖像的數(shù)據(jù)來(lái)解碼流译。

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

由一個(gè)接一個(gè)的 NALU 組成的,而它的功能分為兩層者疤,VCL(視頻編碼層)和 NAL(網(wǎng)絡(luò)提取層).

下圖以h264的碼流結(jié)構(gòu)為例,如果是h265則在sps前還有vps.

H264碼流.png
  • 組成

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

數(shù)據(jù)傳輸或存儲(chǔ)之前福澡,這些編碼的 VCL 數(shù)據(jù),先被映射或封裝進(jìn) NAL 單元(以下簡(jiǎn)稱 NALU驹马,Nal Unit) 中革砸。每個(gè) NALU 包括一個(gè)原始字節(jié)序列負(fù)荷(RBSP, Raw Byte Sequence Payload)、一組 對(duì)應(yīng)于視頻編碼的 NALU 頭部信息糯累。RBSP 的基本結(jié)構(gòu)是:在原始編碼數(shù)據(jù)的后面填加了結(jié)尾 比特算利。一個(gè) bit“1”若干比特“0”,以便字節(jié)對(duì)齊泳姐。

2.3.1. H.264碼流

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

NALU組成.jpeg
  • StartCode : Start Code 用于標(biāo)示這是一個(gè)NALU 單元的開(kāi)始效拭,必須是”00 00 00 01” 或”00 00 01”

  • NALU Header
    下表為 NAL Header Type

NAL Header Type.png

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


2.IDR

在一個(gè)NALU中,第一個(gè)字節(jié)(即NALU header)用以表示其包含數(shù)據(jù)的類型及其他信息仗岸。我們假定一個(gè)頭信息字節(jié)為0x67作為例子:

十六進(jìn)制 二進(jìn)制
0x67 0 11 00111

如表所示允耿,頭字節(jié)可以被解析成3個(gè)部分借笙,其中:

1>. forbidden_zero_bit = 0:占1個(gè)bit扒怖,禁止位,用以檢查傳輸過(guò)程中是否發(fā)生錯(cuò)誤业稼,0表示正常盗痒,1表示違反語(yǔ)法;

2>. nal_ref_idc = 3:占2個(gè)bit,用來(lái)表示當(dāng)前NAL單元的優(yōu)先級(jí)俯邓。非0值表示參考字段/幀/圖片數(shù)據(jù)骡楼,其他不那么重要的數(shù)據(jù)則為0。對(duì)于非0值稽鞭,值越大表示NALU重要性越高

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

從表中我們可以獲知,NALU類型1-5為視頻幀,其余則為非視頻幀朦蕴。在解碼過(guò)程中篮条,我們只需要取出NALU頭字節(jié)的后5位,即將NALU頭字節(jié)和0x1F進(jìn)行與計(jì)算即可得知NALU類型吩抓,即:

NALU類型 = NALU頭字節(jié) & 0x1F

注意: 可以將start code理解為不同nalu的分隔符,header是某種類型的key,payload是該key的value.

碼流格式

H.264標(biāo)準(zhǔn)中指定了視頻如何編碼成獨(dú)立的包涉茧,但如何存儲(chǔ)和傳輸這些包卻未作規(guī)范,雖然標(biāo)準(zhǔn)中包含了一個(gè)Annex附件疹娶,里面描述了一種可能的格式Annex B伴栓,但這并不是一個(gè)必須要求的格式。
為了針對(duì)不同的存儲(chǔ)傳輸需求雨饺,出現(xiàn)了兩種打包方法钳垮。一種即Annex B格式,另一種稱為AVCC格式额港。

  • Annex B

從上文可知扔枫,一個(gè)NALU中的數(shù)據(jù)并未包含他的大小(長(zhǎng)度)信息锹安,因此我們并不能簡(jiǎn)單的將一個(gè)個(gè)NALU連接起來(lái)生成一個(gè)流短荐,因?yàn)閿?shù)據(jù)流的接收端并不知道一個(gè)NALU從哪里結(jié)束,另一個(gè)NALU從哪里開(kāi)始叹哭。
Annex B格式用起始碼(Start Code)來(lái)解決這個(gè)問(wèn)題忍宋,它在每個(gè)NALU的開(kāi)始處添加三字節(jié)或四字節(jié)的起始碼0x000001或0x00000001。通過(guò)定位起始碼风罩,解碼器就可以很容易的識(shí)別NALU的邊界糠排。
當(dāng)然,用起始碼定位NALU邊界存在一個(gè)問(wèn)題超升,即NALU中可能存在與起始碼相同的數(shù)據(jù)入宦。為了防止這個(gè)問(wèn)題,在構(gòu)建NALU時(shí)室琢,需要將數(shù)據(jù)中的0x000000,0x000001,0x000002,0x000003中插入防競(jìng)爭(zhēng)字節(jié)(Emulation Prevention Bytes)0x03乾闰,使其變?yōu)椋?/p>

0x000000 = 0x0000 03 00
0x000001 = 0x0000 03 01
0x000002 = 0x0000 03 02
0x000003 = 0x0000 03 03
解碼器在檢測(cè)到0x000003時(shí),將0x03拋棄盈滴,恢復(fù)原始數(shù)據(jù)涯肩。

由于Annex B格式每個(gè)NALU都包含起始碼,所以解碼器可以從視頻流隨機(jī)點(diǎn)開(kāi)始進(jìn)行解碼,常用于實(shí)時(shí)的流格式病苗。在這種格式中通常會(huì)周期性的重復(fù)SPS和PPS疗垛,并且經(jīng)常時(shí)在每一個(gè)關(guān)鍵幀之前。

  • AVCC

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

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字節(jié)的后2位表示的就是NAL size的字節(jié)數(shù)。需要注意的是荠割,這個(gè)NALULengthSizeMinusOne是NALU前綴長(zhǎng)度減一妹卿,即,假設(shè)前綴長(zhǎng)度為4蔑鹦,那么這個(gè)值應(yīng)該為3夺克。
這里還需要注意的一點(diǎn)是,雖然AVCC格式不使用起始碼嚎朽,但防競(jìng)爭(zhēng)字節(jié)還是有的铺纽。

AVCC格式的一個(gè)優(yōu)點(diǎn)在于解碼器配置參數(shù)在一開(kāi)始就配置好了,系統(tǒng)可以很容易的識(shí)別NALU的邊界哟忍,不需要額外的起始碼狡门,減少了資源的浪費(fèi),同時(shí)可以在播放時(shí)調(diào)到視頻的中間位置锅很。這種格式通常被用于可以被隨機(jī)訪問(wèn)的多媒體數(shù)據(jù)其馏,如存儲(chǔ)在硬盤的文件。

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í)對(duì)一些技術(shù)進(jìn)行改進(jìn)撬码,編碼結(jié)構(gòu)大致上和H.264的架構(gòu)類似。這里著重講一下兩者編碼格式的區(qū)別缘揪。
同H.264一樣耍群,H.265也是以NALU的形式組織起來(lái)义桂。而在NALU header上找筝,H.264的HALU header是一個(gè)字節(jié)蹈垢,而H.265則是兩個(gè)字節(jié)。我們同樣假定一個(gè)頭信息為0x4001作為例子:

十六進(jìn)制 二進(jìn)制
0x4001 0 100000 000000 001

如表所示袖裕,頭信息可以被解析成4個(gè)部分曹抬,其中:

  • forbidden_zero_bit = 0:占1個(gè)bit,與H.264相同急鳄,禁止位谤民,用以檢查傳輸過(guò)程中是否發(fā)生錯(cuò)誤,0表示正常疾宏,1表示違反語(yǔ)法张足;
  • nal_unit_type = 32:占6個(gè)bit,用來(lái)用以指定NALU類型
  • nuh_reserved_zero_6bits = 0:占6位坎藐,預(yù)留位为牍,要求為0,用于未來(lái)擴(kuò)展或3D視頻編碼
  • nuh_temporal_id_plus1 = 1:占3個(gè)bit岩馍,表示NAL所在的時(shí)間層ID

對(duì)比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類型是在信息頭的第一個(gè)字節(jié)的第2到7位疫铜,所以判斷H.265NALU類型的方法是將NALU第一個(gè)字節(jié)與0x7E進(jìn)行與操作并右移一位,即:

NALU類型 = (NALU頭第一字節(jié) & 0x7E) >> 1

與H.264類似双谆,H.265碼流也有兩種封裝格式壳咕,一種是用起始碼作為分界的Annex B格式,另一種則是在NALU頭添加NALU長(zhǎng)度前綴的格式顽馋,稱為HVCC囱井。在HVCC中,同樣需要一個(gè)extradata來(lái)保存視頻流的編解碼參數(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外住练,還需多添加一個(gè)VPS。

VPS(Video Parament Set,視頻參數(shù)集),在H.265中類型為32愁拭。VPS用于解釋編碼過(guò)的視頻的整體結(jié)構(gòu)讲逛,包括時(shí)域子層依賴關(guān)系等,主要目的在于兼容H.265標(biāo)準(zhǔn)在系統(tǒng)的多子層方面的擴(kuò)展岭埠。

3. iOS中的視頻編解碼

iOS中視頻處理框架分層

3.

3.1. 框架的選擇

對(duì)于 AVKit盏混、AVFoundation 和 VideoToolbox 來(lái)說(shuō)蔚鸥,他們的功能和可定制性越來(lái)越強(qiáng),但相應(yīng)的使用難度也越大许赃,因此你應(yīng)該根據(jù)實(shí)際需求合理的選擇使用哪個(gè)層級(jí)的接口止喷。事實(shí)上,即使你使用 AVFoundation 或 AVKit 依然可以獲得硬件加速的效果混聊,你失去的只是直接訪問(wèn)硬編解碼器的權(quán)限弹谁。對(duì)于 VideoToolbox 來(lái)說(shuō),你可以通過(guò)直接訪問(wèn)硬編解碼器句喜,將 H.264 文件或傳輸流轉(zhuǎn)換為 iOS 上的 CMSampleBuffer 并解碼成 CVPixelBuffer预愤,或?qū)閴嚎s的 CVPixelBuffer 編碼成 CMSampleBuffer:

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

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

也就是說(shuō),CMSampleBuffer 既可以作為 CVPixelBuffer 對(duì)象的容器展懈,也可以作為 CMBlockBuffer 對(duì)象的容器销睁,CVPixelBuffer 可以說(shuō)是未壓縮的圖像數(shù)據(jù)容器,而 CMBlockBuffer 則是壓縮圖像數(shù)據(jù)容器标沪。

4.data

3.3. 編碼數(shù)據(jù)裸流

NALU. 對(duì)于一個(gè) H.264 裸流或者文件來(lái)說(shuō)榄攀,它是由一個(gè)一個(gè)的 NALU(Network Abstraction Layer Unit) 單元組成,每個(gè) NALU 既可以表示圖像數(shù)據(jù)金句,也可以表示處理圖像所需要的參數(shù)數(shù)據(jù)檩赢。它主要有兩種格式:Annex B 和 AVCC。也被稱為 Elementary Stream 和 MPEG-4 格式违寞,Annex B 格式以 0x000001 或 0x00000001 開(kāi)頭贞瞒,AVCC 格式以所在的 NALU 的長(zhǎng)度開(kāi)頭。

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: 圖像存儲(chǔ)方式趁曼,編解碼器等格式描述
  • pixelBufferAttributes: : CFDictionary對(duì)象军浆,可能包含了視頻的寬高,像素格式類型(32RGBA, YCbCr420)挡闰,是否可以用于OpenGL ES等相關(guān)信息
  • CMTime: 時(shí)間戳相關(guān)乒融。時(shí)間以 64-big/32-bit形式出現(xiàn)。 分子是64-bit的時(shí)間值摄悯,分母是32-bit的時(shí)標(biāo)(time scale)

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

在iOS中使用vtCompressionSession編碼后的數(shù)據(jù)不包含start code.需要我們自行添加,為什么要添加start code? 如上文所說(shuō),為了區(qū)分每個(gè)NALU及以后提取vps,sps,pps等關(guān)鍵信息.

如下圖,我們?cè)诰幋a回調(diào)中可以拿到編碼后的CMSampleBuffer數(shù)據(jù).如果是h265,CMVideoFormatDesc中還有vps.而這段數(shù)據(jù)在推流前必須尋找到關(guān)鍵數(shù)據(jù)vps,sps,pps以及添加start code.因?yàn)樵?code>CMBlockBufferRef中可能還含有一個(gè)或多個(gè)NALU,所以我們需要通過(guò)遍歷它的內(nèi)存地址找到在每個(gè)NALU的分割點(diǎn)將數(shù)據(jù)替換為start code.具體代碼操作參考實(shí)戰(zhàn)篇.

CMSampleBufferCreate

接下來(lái)赞季,我們就需要處理這一幀視頻的圖像數(shù)據(jù)了。通過(guò) CMSampleBufferGetDataBuffer 和 CMBlockBufferGetDataPointer 我們可以獲取視頻數(shù)據(jù)的內(nèi)存地址奢驯。VTCompressionSession 編碼出來(lái)的視頻幀為 AVCC 格式申钩,因此我們可以讀取頭部 4 個(gè)字節(jié)數(shù)據(jù)來(lái)獲取當(dāng)前 NALU 的長(zhǎng)度。這里有一個(gè)需要注意的是瘪阁,AVCC 格式使用大端字節(jié)序撒遣,它可能跟當(dāng)前使用的系統(tǒng)字節(jié)序不一樣邮偎,事實(shí)上,iOS 系統(tǒng)使用小端字節(jié)序义黎,因此我們需要先將這個(gè)長(zhǎng)度數(shù)據(jù)轉(zhuǎn)換為 iOS 系統(tǒng)使用的小端字節(jié)序:

NALUnitLength = CFSwapInt32BigToHost(NALUnitLength)

硬編碼基本上是硬解碼的一個(gè)逆過(guò)程禾进。解析出參數(shù)集SPS和PPS,加上開(kāi)始碼后組裝成NALU轩缤。提取出視頻數(shù)據(jù)命迈,將長(zhǎng)度碼轉(zhuǎn)換成開(kāi)始碼贩绕,組長(zhǎng)成NALU火的。將NALU發(fā)送出去。

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

如上圖,我們知道淑倾,CMSampleBuffer = CMTime + FormatDesc + CMBlockBuffer . 需要從H264的碼流里面提取出以上的三個(gè)信息馏鹤。最后組合成CMSampleBuffer,提供給硬解碼接口來(lái)進(jìn)行解碼工作娇哆。

在H.264, H.265的語(yǔ)法中湃累,有一個(gè)最基礎(chǔ)的層,叫做Network Abstraction Layer, 簡(jiǎn)稱為NAL碍讨。編碼數(shù)據(jù)正是由一系列的NAL單元(NAL Unit, 簡(jiǎn)稱NAUL)組成的治力。

NALU

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

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

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

屬性集合

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

Format Description

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

  • NALU header

對(duì)于流數(shù)據(jù)來(lái)說(shuō)马澈,一個(gè)NAUL的Header中,可能是0x00 00 01或者是0x00 00 00 01作為開(kāi)頭(兩者都有可能弄息,下面以0x00 00 01作為例子)痊班。0x00 00 01因此被稱為開(kāi)始碼(Start code).

NALU header

總結(jié)以上知識(shí),我們知道編碼碼流由NALU單元組成摹量,NALU單元包含視頻圖像數(shù)據(jù)和編碼器的參數(shù)信息涤伐。其中視頻圖像數(shù)據(jù)就是CMBlockBuffer,而編碼器參數(shù)信息則可以組合成FormatDesc缨称。具體來(lái)說(shuō)參數(shù)信息包含VPS,SPS(Sequence Parameter Set)和PPS(Picture Parameter Set).如下圖顯示了一個(gè)H.264碼流結(jié)構(gòu):

H.264碼流
  • 提取sps和pps生成FormatDesc

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

    • 通過(guò)開(kāi)始碼,定位到NALU
    • 確定類型為數(shù)據(jù)后骂删,將開(kāi)始碼替換成NALU的長(zhǎng)度信息(4 Bytes)
    • 使用CMBlockBufferCreateWithMemoryBlock接口構(gòu)造CMBlockBufferRef
  • 根據(jù)需要掌动,生成CMTime信息四啰。(實(shí)際測(cè)試時(shí),加入time信息后粗恢,有不穩(wěn)定的圖像柑晒,不加入time信息反而沒(méi)有,需要進(jìn)一步研究眷射,這里建議不加入time信息)

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

CMSampleBufferCreate
3.7. 將 CMSampleBuffer渲染到界面

顯示的方式有兩種:

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

參考文章

視頻碼流格式解析

HM源碼分析

VPS SPS PPS

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夏漱,一起剝皮案震驚了整個(gè)濱河市豪诲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挂绰,老刑警劉巖屎篱,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扮授,居然都是意外死亡芳室,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門刹勃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)堪侯,“玉大人,你說(shuō)我怎么就攤上這事荔仁∥榛拢” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵乏梁,是天一觀的道長(zhǎng)次洼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)遇骑,這世上最難降的妖魔是什么卖毁? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上亥啦,老公的妹妹穿的比我還像新娘炭剪。我一直安慰自己,他們只是感情好翔脱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布奴拦。 她就那樣靜靜地躺著,像睡著了一般届吁。 火紅的嫁衣襯著肌膚如雪错妖。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天疚沐,我揣著相機(jī)與錄音暂氯,去河邊找鬼。 笑死濒旦,一個(gè)胖子當(dāng)著我的面吹牛株旷,可吹牛的內(nèi)容都是我干的再登。 我是一名探鬼主播尔邓,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锉矢!你這毒婦竟也來(lái)了梯嗽?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沽损,失蹤者是張志新(化名)和其女友劉穎灯节,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體绵估,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡炎疆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了国裳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片形入。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缝左,靈堂內(nèi)的尸體忽然破棺而出亿遂,到底是詐尸還是另有隱情,我是刑警寧澤渺杉,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布蛇数,位于F島的核電站,受9級(jí)特大地震影響是越,放射性物質(zhì)發(fā)生泄漏耳舅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一倚评、第九天 我趴在偏房一處隱蔽的房頂上張望浦徊。 院中可真熱鬧绍赛,春花似錦、人聲如沸辑畦。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纯出。三九已至蚯妇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間暂筝,已是汗流浹背箩言。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留焕襟,地道東北人陨收。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鸵赖,于是被迫代替她去往敵國(guó)和親务漩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一它褪、軟編與硬編概念 1饵骨、軟編碼:使用CPU進(jìn)行編碼。 實(shí)現(xiàn)直接茫打、簡(jiǎn)單居触,參數(shù)調(diào)整方便,升級(jí)易老赤,但CPU負(fù)載重轮洋,性能較...
    Evans_Xiao閱讀 4,657評(píng)論 0 6
  • ### YUV顏色空間 視頻是由一幀一幀的數(shù)據(jù)連接而成,而一幀視頻數(shù)據(jù)其實(shí)就是一張圖片抬旺。 yuv是一種圖片儲(chǔ)存格式...
    天使君閱讀 3,249評(píng)論 0 4
  • 在目前弊予,無(wú)論在各個(gè)行只要和視頻相關(guān)的,我們都可以看見(jiàn)H264相關(guān)的身影嚷狞,H264作為目前使用最廣泛的視頻壓縮標(biāo)準(zhǔn)块促,...
    DramaScript閱讀 21,551評(píng)論 7 56
  • 視頻格式封裝——H264 轉(zhuǎn)載自 http://blog.csdn.net/yangzhongxuan/artic...
    microchip閱讀 2,362評(píng)論 0 1
  • 只要感覺(jué)自己做的對(duì),就給自己獎(jiǎng)勵(lì)
    張嚴(yán)閱讀 159評(píng)論 0 0