一碱璃、軟編與硬編概念
1、軟編碼:使用CPU進(jìn)行編碼饭入。
實(shí)現(xiàn)直接嵌器、簡單,參數(shù)調(diào)整方便谐丢,升級易爽航,但CPU負(fù)載重,性能較硬編碼低乾忱,低碼率下質(zhì)量通常比硬編碼要好一點(diǎn)讥珍。
2、硬編碼:不使用CPU進(jìn)行編碼窄瘟,使用顯卡GPU,專用的DSP串述、FPGA、ASIC芯片等硬件進(jìn)行編碼寞肖。
性能高纲酗,低碼率下通常質(zhì)量低于軟編碼器,但部分產(chǎn)品在GPU硬件平臺移植了優(yōu)秀的軟編碼算法(如X264)的新蟆,質(zhì)量基本等同于軟編碼觅赊。
蘋果在iOS 8.0系統(tǒng)之前,沒有開放系統(tǒng)的硬件編碼解碼功能琼稻,不過Mac OS系統(tǒng)一直有吮螺,被稱為Video ToolBox的框架來處理硬件的編碼和解碼,終于在iOS 8.0(即WWDC 2014 513)后帕翻,蘋果將該框架引入iOS系統(tǒng)鸠补。
二、H.264編碼原理
H.264是新一代的編碼標(biāo)準(zhǔn)嘀掸,以高壓縮高質(zhì)量和支持多種網(wǎng)絡(luò)的流媒體傳輸著稱紫岩,在編碼方面,我理解的理論依據(jù)是:參照一段時間內(nèi)圖像的統(tǒng)計(jì)結(jié)果表明睬塌,在相鄰幾幅圖像畫面中泉蝌,一般有差別的像素只有10%以內(nèi)的點(diǎn),亮度差值變化不超過2%,而色度差值的變化只有1%以內(nèi)揩晴。所以對于一段變化不大圖像畫面勋陪,我們可以先編碼出一個完整的圖像幀A,隨后的B幀就不編碼全部圖像硫兰,只寫入與A幀的差別诅愚,這樣B幀的大小就只有完整幀的1/10或更小劫映!B幀之后的C幀如果變化不大违孝,我們可以繼續(xù)以參考B的方式編碼C幀涤妒,這樣循環(huán)下去贵扰。這段圖像我們稱為一個序列(序列就是有相同特點(diǎn)的一段數(shù)據(jù))弛随,當(dāng)某個圖像與之前的圖像變化很大抄谐,無法參考前面的幀來生成,那我們就結(jié)束上一個序列筹燕,開始下一段序列轧飞,也就是對這個圖像生成一個完整幀A1,隨后的圖像就參考A1生成撒踪,只寫入與A1的差別內(nèi)容过咬。
需要注意的是:
在H264協(xié)議里定義了三種幀,完整編碼的幀叫I幀制妄,參考之前的I幀生成的只包含差異部分編碼的幀叫P幀掸绞,還有一種參考前后的幀編碼的幀叫B幀。
H264采用的核心算法是幀內(nèi)壓縮和幀間壓縮耕捞,幀內(nèi)壓縮是生成I幀的算法衔掸,幀間壓縮是生成B幀和P幀的算法。
三敞映、序列的說明
在H264中圖像以序列為單位進(jìn)行組織,一個序列是一段圖像編碼后的數(shù)據(jù)流磷斧,以I幀開始振愿,到下一個I幀結(jié)束。
一個序列的第一個圖像叫做 IDR 圖像(立即刷新圖像)弛饭,IDR 圖像都是 I 幀圖像冕末。H.264 引入 IDR 圖像是為了解碼的重同步,當(dāng)解碼器解碼到 IDR 圖像時侣颂,立即將參考幀隊(duì)列清空档桃,將已解碼的數(shù)據(jù)全部輸出或拋棄,重新查找參數(shù)集横蜒,開始一個新的序列胳蛮。這樣,如果前一個序列出現(xiàn)重大錯誤丛晌,在這里可以獲得重新同步的機(jī)會。IDR圖像之后的圖像永遠(yuǎn)不會使用IDR之前的圖像的數(shù)據(jù)來解碼斗幼。
一個序列就是一段內(nèi)容差異不太大的圖像編碼后生成的一串?dāng)?shù)據(jù)流澎蛛。當(dāng)運(yùn)動變化比較少時,一個序列可以很長蜕窿,因?yàn)檫\(yùn)動變化少就代表圖像畫面的內(nèi)容變動很小谋逻,所以就可以編一個I幀呆馁,然后一直P幀、B幀了毁兆。當(dāng)運(yùn)動變化多時浙滤,可能一個序列就比較短了,比如就包含一個I幀和3气堕、4個P幀纺腊。
四、對三種幀的簡單介紹
I茎芭、B揖膜、P各幀是根據(jù)壓縮算法的需要,是人為定義的,它們都是實(shí)實(shí)在在的物理幀梅桩。一般來說壹粟,I幀的壓縮率是7(跟JPG差不多),P幀是20宿百,B幀可以達(dá)到50趁仙。可見使用B幀能節(jié)省大量空間垦页,節(jié)省出來的空間可以用來保存多一些I幀雀费,這樣在相同碼率下,可以提供更好的畫質(zhì)外臂。
說明:
I幀:紅色坐儿;P幀:藍(lán)色;B幀:綠色宋光。
五貌矿、H264壓縮算法的說明
1、分組:把幾幀圖像分為一組(GOP罪佳,也就是一個序列),為防止運(yùn)動變化,幀數(shù)不宜取多逛漫。
2、定義幀:將每組內(nèi)各幀圖像定義為三種類型,即I幀赘艳、B幀和P幀;
3酌毡、預(yù)測幀:以I幀做為基礎(chǔ)幀,以I幀預(yù)測P幀,再由I幀和P幀預(yù)測B幀;
4、數(shù)據(jù)傳輸:最后將I幀數(shù)據(jù)與預(yù)測的差值信息進(jìn)行存儲和傳輸蕾管。
5枷踏、幀內(nèi)(Intraframe)壓縮也稱為空間壓縮(Spatial compression)。
當(dāng)壓縮一幀圖像時掰曾,僅考慮本幀的數(shù)據(jù)而不考慮相鄰幀之間的冗余信息旭蠕,這實(shí)際上與靜態(tài)圖像壓縮類似。幀內(nèi)一般采用有損壓縮算法,由于幀內(nèi)壓縮是編碼一個完整的圖像掏熬,所以可以獨(dú)立的解碼佑稠、顯示。幀內(nèi)壓縮一般達(dá)不到很高的壓縮旗芬,跟編碼jpeg差不多舌胶。
6、幀間(Interframe)壓縮疮丛。
相鄰幾幀的數(shù)據(jù)有很大的相關(guān)性幔嫂,或者說前后兩幀信息變化很小的特點(diǎn)。也即連續(xù)的視頻其相鄰幀之間具有冗余信息,根據(jù)這一特性这刷,壓縮相鄰幀之間的冗余量就可以進(jìn)一步提高壓縮量婉烟,減小壓縮比。幀間壓縮也稱為時間壓縮(Temporal compression)暇屋,它通過比較時間軸上不同幀之間的數(shù)據(jù)進(jìn)行壓縮似袁。幀間壓縮一般是無損的。幀差值(Frame differencing)算法是一種典型的時間壓縮法咐刨,它通過比較本幀與相鄰幀之間的差異昙衅,僅記錄本幀與其相鄰幀的差值,這樣可以大大減少數(shù)據(jù)量定鸟。
7而涉、有損(Lossy)壓縮和無損(Lossy less)壓縮。
無損壓縮也即壓縮前和解壓縮后的數(shù)據(jù)完全一致联予。多數(shù)的無損壓縮都采用RLE行程編碼算法啼县。
有損壓縮意味著解壓縮后的數(shù)據(jù)與壓縮前的數(shù)據(jù)不一致。在壓縮的過程中要丟失一些人眼和人耳所不敏感的圖像或音頻信息,而且丟失的信息不可恢復(fù)沸久。幾乎所有高壓縮的算法都采用有損壓縮,這樣才能達(dá)到低數(shù)據(jù)率的目標(biāo)季眷。丟失的數(shù)據(jù)率與壓縮比有關(guān),壓縮比越小,丟失的數(shù)據(jù)越多,解壓縮后的效果一般越差卷胯。此外,某些有損壓縮算法采用多次重復(fù)壓縮的方式,這樣還會引起額外的數(shù)據(jù)丟失子刮。
六、DTS與PTS的區(qū)別
DTS主要用于視頻的解碼,在解碼階段使用.
PTS主要用于視頻的同步和輸出.
在display的時候使用.在沒有B frame的情況下.DTS和PTS的輸出順序是一樣的窑睁。
下面給出一個GOP為15的例子,其解碼的參照frame及其解碼的順序都在里面:
如上圖:
I frame 的解碼不依賴于任何的其它的幀.而p frame的解碼則依賴于其前面的I frame或者P frame.B frame的解碼則依賴于其前的最近的一個I frame或者P frame 及其后的最近的一個P frame.
七挺峡、iOS系統(tǒng) H.264視頻硬件編解碼說明
1、VideoToolbox的介紹
在iOS中担钮,與視頻相關(guān)的Framework庫有5個橱赠,從頂層開始分別是 AVKit
-> AVFoundation
-> VideoToolbox
-> Core Media
-> Core Video
其中VideoToolbox可以將視頻解壓到CVPixelBuffer
,也可以壓縮到CMSampleBuffer
。
但是我們常用的是CMSampleBuffer
.
2箫津、VideoToolbox中的對象
1)CVPixelBuffer
編碼前和解碼后的圖像數(shù)據(jù)結(jié)構(gòu)(未壓縮光柵圖像緩存區(qū)-Uncompressed Raster Image Buffer)
2)CVPixelBufferPool
存放CVPixelBuffer
3)pixelBufferAttributes
CFDictionary對象病线,可能包含了視頻的寬高吓著,像素格式類型(32RGBA, YCbCr420)鲤嫡,是否可以用于OpenGL ES等相關(guān)信息
4)CMTime
時間戳相關(guān)送挑。時間以 64-big/32-bit形式出現(xiàn)。 分子是64-bit的時間值暖眼,分母是32-bit的時標(biāo)(time scale)
5)CMClock
時間戳相關(guān)惕耕。時間以 64-big/32-bit形式出現(xiàn)。 分子是64-bit的時間值诫肠,分母是32-bit的時標(biāo)(time scale)司澎。它封裝了時間源,其中CMClockGetHostTimeClock()封裝了mach_absolute_time()
6)CMTimebase
時間戳相關(guān)栋豫。時間以 64-big/32-bit形式出現(xiàn)挤安。CMClock上的控制視圖。提供了時間的映射:CMTimebaseSetTime(timebase, kCMTimeZero); 速率控制:
CMTimebaseSetRate(timebase, 1.0);
7)CMBlockBuffer
編碼后丧鸯,結(jié)果圖像的數(shù)據(jù)結(jié)構(gòu)
8)CMVideoFormatDescription
編解碼前后的視頻圖像均封裝在CMSampleBuffer中蛤铜,如果是編碼后的圖像,以CMBlockBuffe方式存儲丛肢;解碼后的圖像围肥,以CVPixelBuffer存儲。
9)CMSampleBuffer
存放編解碼前后的視頻圖像的容器數(shù)據(jù)結(jié)構(gòu)蜂怎。如圖所示穆刻,編解碼前后的視頻圖像均封裝在CMSampleBuffer中,如果是編碼后的圖像杠步,以CMBlockBuffer方式存儲氢伟;解碼后的圖像,以CVPixelBuffer存儲幽歼。CMSampleBuffer里面還有另外的時間信息CMTime和視頻描述信息CMVideoFormatDesc朵锣。
八、硬解碼
目標(biāo):如何將從網(wǎng)絡(luò)處傳來H.264編碼后的視頻碼流顯示在手機(jī)屏幕上试躏?
實(shí)現(xiàn)步驟如下:
1猪勇、將 H.264碼流轉(zhuǎn)換為 CMSampleBuffer
CMSampleBuffer = CMTime + FormatDesc + CMBlockBuffer
需要從H.264的碼流里面提取出以上的三個信息。最后組合成CMSampleBuffer颠蕴,提供給硬解碼接口來進(jìn)行解碼工作泣刹。
在H.264的語法中,有一個最基礎(chǔ)的層犀被,叫做Network Abstraction Layer, 簡稱為NAL椅您。H.264流數(shù)據(jù)正是由一系列的NAL單元(NAL Unit, 簡稱NAUL)組成的。
H264的碼流由NALU單元組成,一個NALU可能包含有:
-
視頻幀,視頻幀也就是視頻片段寡键,具體有 P幀, I幀掀泳,B幀
B-006.png
2)H.264屬性合集-FormatDesc(包含 SPS和PPS),即流數(shù)據(jù)中,屬性集合可能是這樣的:
經(jīng)過處理之后,在Format Description中則是:
需要注意的是:
要從基礎(chǔ)的流數(shù)據(jù)將SPS和PPS轉(zhuǎn)化為Format Desc中的話员舵,需要調(diào)用CMVideoFormatDescriptionCreateFromH264ParameterSets()
方法脑沿。
3)NALU header
對于流數(shù)據(jù)來說,一個NAUL的Header中马僻,可能是0x00 00 01或者是0x00 00 00 01作為開頭(兩者都有可能庄拇,下面以0x00 00 01作為例子)。0x00 00 01因此被稱為開始碼(Start code).
總結(jié)以上知識韭邓,我們知道H264的碼流由NALU單元組成措近,NALU單元包含視頻圖像數(shù)據(jù)和H264的參數(shù)信息。其中視頻圖像數(shù)據(jù)就是CMBlockBuffer女淑,而H264的參數(shù)信息則可以組合成FormatDesc瞭郑。具體來說參數(shù)信息包含SPS(Sequence Parameter Set)和PPS(Picture Parameter Set).如下圖顯示了一個H.264碼流結(jié)構(gòu):
(1)提取sps和pps生成FormatDesc
- 每個NALU的開始碼是0x00 00 01,按照開始碼定位NALU
- 通過類型信息找到sps和pps并提取鸭你,開始碼后第一個byte的后5位屈张,7代表sps,8代表pps
- 使用CMVideoFormatDescriptionCreateFromH264ParameterSets函數(shù)來構(gòu)建CMVideoFormatDescriptionRef
(2)提取視頻圖像數(shù)據(jù)生成CMBlockBuffer
- 通過開始碼苇本,定位到NALU
- 確定類型為數(shù)據(jù)后袜茧,將開始碼替換成NALU的長度信息(4 Bytes)
- 使用CMBlockBufferCreateWithMemoryBlock接口構(gòu)造CMBlockBufferRef
(3)根據(jù)需要,生成CMTime信息瓣窄。
(實(shí)際測試時笛厦,加入time信息后,有不穩(wěn)定的圖像俺夕,不加入time信息反而沒有裳凸,需要進(jìn)一步研究,這里建議不加入time信息)
根據(jù)上述得到CMVideoFormatDescriptionRef劝贸、CMBlockBufferRef和可選的時間信息姨谷,使用CMSampleBufferCreate接口得到CMSampleBuffer數(shù)據(jù)這個待解碼的原始的數(shù)據(jù)。如下圖所示的H264數(shù)據(jù)轉(zhuǎn)換示意圖映九。
2梦湘、將 CMSampleBuffer顯示出來
顯示的方式有兩種:
1)、將CMSampleBuffers提供給系統(tǒng)的AVSampleBufferDisplayLayer 直接顯示
使用方式和其它CALayer類似件甥。該層內(nèi)置了硬件解碼功能捌议,將原始的CMSampleBuffer解碼后的圖像直接顯示在屏幕上面,非常的簡單方便引有。
2)瓣颅、利用OPenGL渲染
通過VTDecompression接口來,將CMSampleBuffer解碼成圖像譬正,將圖像通過UIImageView或者OpenGL上顯示宫补。
初始化VTDecompressionSession檬姥,設(shè)置解碼器的相關(guān)信息。初始化信息需要CMSampleBuffer里面的FormatDescription粉怕,以及設(shè)置解碼后圖像的存儲方式健民。demo里面設(shè)置的CGBitmap模式,使用RGB方式存放斋荞。編碼后的圖像經(jīng)過解碼后荞雏,會調(diào)用一個回調(diào)函數(shù),將解碼后的圖像交個這個回調(diào)函數(shù)來進(jìn)一步處理平酿。我們就在這個回調(diào)里面,將解碼后的圖像發(fā)給control來顯示悦陋,初始化的時候要將回調(diào)指針作為參數(shù)傳給create接口函數(shù)蜈彼。最后使用create接口對session來進(jìn)行初始化。
上所述的回調(diào)函數(shù)可以完成CGBitmap圖像轉(zhuǎn)換成UIImage圖像的處理俺驶,將圖像通過隊(duì)列發(fā)送到Control來進(jìn)行顯示處理幸逆。
調(diào)用VTDecompresSessionDecodeFrame接口進(jìn)行解碼操作。解碼后的圖像會交由以上兩步驟設(shè)置的回調(diào)函數(shù)暮现,來進(jìn)一步的處理还绘。
九、硬解碼
硬編碼的使用也通過一個典型的應(yīng)用場景來描述栖袋。首先拍顷,通過攝像頭來采集圖像,然后將采集到的圖像塘幅,通過硬編碼的方式進(jìn)行編碼昔案,最后編碼后的數(shù)據(jù)將其組合成H264的碼流通過網(wǎng)絡(luò)傳播。
1电媳、攝像頭采集數(shù)據(jù)
攝像頭采集踏揣,iOS系統(tǒng)提供了AVCaptureSession來采集攝像頭的圖像數(shù)據(jù)。設(shè)定好session的采集解析度匾乓。再設(shè)定好input和output即可捞稿。output設(shè)定的時候,需要設(shè)置delegate和輸出隊(duì)列拼缝。在delegate方法娱局,處理采集好的圖像。
圖像輸出的格式珍促,是未編碼的CMSampleBuffer形式铃辖。
2、使用VTCompressionSession進(jìn)行硬編碼
1)初始化VTCompressionSession
VTCompressionSession初始化的時候猪叙,一般需要給出width寬娇斩,height長仁卷,編碼器類型kCMVideoCodecType_H264等。然后通過調(diào)用VTSessionSetProperty接口設(shè)置幀率等屬性犬第,demo里面提供了一些設(shè)置參考锦积,測試的時候發(fā)現(xiàn)幾乎沒有什么影響,可能需要進(jìn)一步調(diào)試歉嗓。最后需要設(shè)定一個回調(diào)函數(shù)丰介,這個回調(diào)是視頻圖像編碼成功后調(diào)用。全部準(zhǔn)備好后鉴分,使用VTCompressionSessionCreate創(chuàng)建session
2)提取攝像頭采集的原始圖像數(shù)據(jù)給VTCompressionSession來硬編碼
攝像頭采集后的圖像是未編碼的CMSampleBuffer形式哮幢,利用給定的接口函數(shù)CMSampleBufferGetImageBuffer從中提取出CVPixelBufferRef,使用硬編碼接口VTCompressionSessionEncodeFrame來對該幀進(jìn)行硬編碼志珍,編碼成功后橙垢,會自動調(diào)用session初始化時設(shè)置的回調(diào)函數(shù)。
3)利用回調(diào)函數(shù)伦糯,將因編碼成功的CMSampleBuffer轉(zhuǎn)換成H264碼流柜某,通過網(wǎng)絡(luò)傳播。
基本上是硬解碼的一個逆過程敛纲。解析出參數(shù)集SPS和PPS喂击,加上開始碼后組裝成NALU。提取出視頻數(shù)據(jù)淤翔,將長度碼轉(zhuǎn)換成開始碼翰绊,組長成NALU。將NALU發(fā)送出去办铡。
本文主要為轉(zhuǎn)載學(xué)習(xí)辞做,部分細(xì)節(jié)有刪改。
相關(guān)資料傳送: