视频的基本参数及H264编解码相关概念
概述
上幾篇文章介紹了音頻的采集以及編碼,現在我們開始學習視頻相關的知識,同樣先從概念開始。本篇文章的主要內容有:
- 視頻相關參數
- 幀率(fps)
- 分辨率
- DTS和PTS
- 碼率
- 音視頻同步
- 對視頻編解碼的概述
- H264編碼原理以及對I幀、B幀、P幀的介紹
- H264編碼原理
- 對序列的說明
- 簡單介紹壓縮算法
- 三種幀的不同
- DTS和PTS的不同
- IOS系統H.264視頻硬件編解碼說明
- 對VideoToolBox的介紹
- VideoToolBox中的對象
- 硬解碼流程
- 硬編碼流程
關于視頻硬編碼和軟編碼實現可參考--視頻H264硬編碼和軟編碼&編譯ffmpeg庫及環境搭建(附完整demo)
視頻相關參數
幀率
幀率(Frame rate)是用于測量顯示幀數的量度。所謂的測量單位為每秒顯示幀數(Frames per Second,簡稱:FPS)或“赫茲”(Hz)
由于人類眼睛的特殊生理結構,如果所看畫面之幀率高于24的時候,就會認為是連貫的,此現象稱之為視覺暫留。這也就是為什么電影膠片是一格一格拍攝出來,然后快速播放的。 而對游戲,一般來說,第一人稱射擊游戲比較注重FPS的高低,如果FPS<30的話,游戲會顯得不連貫。所以有一句有趣的話:“FPS(指FPS游戲)重在FPS(指幀率)。
每秒的幀數(fps)或者說幀率表示圖形處理器處理場時每秒鐘能夠更新的次數。高的幀率可以得到更流暢、更逼真的動畫。一般來說30fps就是可以接受的,但是將性能提升至60fps則可以明顯提升交互感和逼真感,但是一般來說超過75fps一般就不容易察覺到有明顯的流暢度提升了。如果幀率超過屏幕刷新率只會浪費圖形處理的能力,因為監視器不能以這么快的速度更新,這樣超過刷新率的幀率就浪費掉了。
我們可以根據幀率得出連續兩幀的時間間隔。比如幀率為30FPS,那么相鄰兩幀的時間間隔為 33ms
分辨率
顯示分辨率(屏幕分辨率)是屏幕圖像的精密度,是指顯示器所能顯示的像素有多少。由于屏幕上的點、線和面都是由像素組成的,顯示器可顯示的像素越多,畫面就越精細,同樣的屏幕區域內能顯示的信息也越多,所以分辨率是個非常重要的性能指標之一。可以把整個圖像想象成是一個大型的棋盤,而分辨率的表示方式就是所有經線和緯線交叉點的數目。顯示分辨率一定的情況下,顯示屏越小圖像越清晰,反之,顯示屏大小固定時,顯示分辨率越高圖像越清晰。
IOS中常見的分辨率有: 1080P(1920 x 1080) 、720P(1280 x 720) 、480P(640 x 480)、360P
ios設置分辨率的代碼:
+ (void)resetSessionPreset:(AVCaptureSession *)m_session andHeight:(int)g_height_size{[m_session beginConfiguration];switch (g_height_size) {case 1080:m_session.sessionPreset = [m_session canSetSessionPreset:AVCaptureSessionPreset1920x1080] ? AVCaptureSessionPreset1920x1080 : AVCaptureSessionPresetHigh;break;case 720:m_session.sessionPreset = [m_session canSetSessionPreset:AVCaptureSessionPreset1280x720] ? AVCaptureSessionPreset1280x720 : AVCaptureSessionPresetMedium;break;case 480:m_session.sessionPreset = [m_session canSetSessionPreset:AVCaptureSessionPreset640x480] ? AVCaptureSessionPreset640x480 : AVCaptureSessionPresetMedium;break;case 360:m_session.sessionPreset = AVCaptureSessionPresetMedium;break;default:break;}[m_session commitConfiguration];} 復制代碼DTS和PTS
概念
- DTS(Decoding Time Stamp) : 即解碼時間戳,是解碼器進行解碼時相對于SCR(系統參考時間)的時間戳。它主要標識讀入內存的bit流在什么時候開始送入解碼器中進行解碼。
- PTS(Presentation Time Stamp) : 即顯示時間戳,是顯示幀時相對于SCR的時間戳。它主要是度量解碼后的視頻什么時候被顯示出來。
DTS主要用于視頻的解碼,在解碼階段使用。PTS主要用于視頻的同步和輸出,在 display 的時候使用。在沒有B幀的情況下,DTS和PTS的輸出順序是一樣的。
以下內容摘自網絡
DTS 時間戳決定了解碼器在SCR時間等于DTS時間時進行解碼,PTS時間戳也是類似的。通常,DTS/PTS時間戳指示的是晚于音視頻包中的SCR的一個時 間。例如,如果一個視頻數據包的SCR是100ms(意味著此包是播放100ms以后從磁盤中讀取的),那么DTS/PTS值就差不多是200 /280ms,表明當SCR到200ms時這個視頻數據應該被解碼并在80ms以后被顯示出來(視頻數據在一個buffer中一直保存到開始解碼) 下 溢通常發生在設置的視頻數據流相關mux率太高。如果mux率是1000000bits/sec(意味著解碼器要以1000000bits/sec的速率 讀取文件),可是視頻速率是2000000bits/sec(意味著需要以2000000bits/sec的速率顯示視頻數據),從磁盤中讀取視頻數據時 速度不夠快以至于1秒鐘內不能夠讀取足夠的視頻數據這種情況下DTS/PTS時間戳就會指示視頻在從硬盤中讀出來之前進行解碼或顯示(DTS/PTS時間戳就要比包含它們的數據包中的SCR時間要早了)。
如今依靠解碼器,著基本已經不是什么問題了(盡管MPEG文件因為應該沒有下溢而并不完全符合MPEG標準)。一些解碼器(很多著名的基于PC的播放器)盡可能快的讀取文件以便顯示視頻,可以的話直接忽略SCR。
注意在你提供的列表中,平均的視頻流速率為~3Mbps(3000000bits/sec)但是它的峰值達到了14Mbps(相當大,DVD限制在 9.8Mbps內)。這意味著mux率需要調整足夠大以處理14Mbps的部分, bbMPEG計算出來的mux率有時候太低而導致下溢。 你計劃讓視頻流速率這么高么?這已經超過了DVD的說明了,而且很可能在大多數獨立播放其中都不能播放。如果你不是這么計劃,我會從1增加mquant的值并且在視頻設置中將最大碼流設置為9Mbps以保持一個小一點的碼流。
如果你確實想讓視頻碼率那么高,你需要增大mux率。從提供的列表可以得出bbMPEG使用14706800bits/sec或者1838350bytes /sec的mux率(總數據速率為:1838350bytes/sec(14706800bits/sec)行)。你在強制mux率字段設置的值應該是以 bytes/sec為單位并被50整除。所以我會從36767(1838350/50)開始,一直增加直到不會再出現下溢錯誤為止;
碼率(比特率)
關于碼率
由于保存完整的一幀一幀圖片的視頻原文件太大,必須要通過某種視頻壓縮算法將視頻中的圖片壓縮,以減小視頻文件大小,那么壓縮比越大,解壓縮還原后用來播放的視頻就會有越嚴重的失真,因為壓縮的同時不可避免的丟失了視頻中原來圖像的數據信息。在理解這個的前提下,我來舉個例子,一個分辨率為1080P的原視頻(未經壓縮)被壓縮成分別為4GB 和 1GB的兩個視頻文件。由于1GB的視頻的壓縮比更大,所以在觀看1GB視頻的明顯感覺到沒有4GB視頻清晰(雖然他們的分辨率都是1080P)。
碼率又稱比特率,是指在壓縮視頻的時候給這個視頻指定一個參數,用以告訴壓縮軟件期望的壓縮后視頻的大小。碼率的英文名為bps(bit per second),就是用平均每秒多少bit來衡量一個視頻大小。
計算視頻的碼率
我們可以根據一個視頻的長度和大小來推斷出該視頻的比特率是多少。下面是一個具體的例子。
一段1080P的視頻長度為100分鐘,大小為1GB,那么該視頻的比特率是多少?
100min = 100*60s = 6000s; 1G = 1024M = 1024*1024KB = 1024*1024*1024Bit = 1024*1024*1024*8bit = 8589934592bit; 比特率 = 8589934592/6000s = 1431655b/s = 1.4Mbit/s; 復制代碼那么這個視頻的碼率大概就是 1.4Mbit/s,這個比特率在在線視頻中已經是非常高的了,一般主流視頻平臺的最高碼率在1Mbit左右,比如直播網站斗魚的高清選項實際播放的視頻碼率是900Kbit/s(0.9Mbit)。
我們可以得出結論:對于時間長度相同的視頻,碼率越大,視頻的大小越大,視頻的畫質就越清晰(不考慮各種壓縮算法的優劣),這是最直觀的感覺。碼率對于視頻是非常重要的。
音視頻同步
上面說了視頻幀、DTS、PTS 相關的概念。我們都知道在一個媒體流中,除了視頻以外,通常還包括音頻。音頻的播放,也有 DTS、PTS 的概念,但是音頻沒有類似視頻中 B 幀,不需要雙向預測,所以音頻幀的 DTS、PTS 順序是一致的。
音頻視頻混合在一起播放,就呈現了我們常常看到的廣義的視頻。在音視頻一起播放的時候,我們通常需要面臨一個問題:怎么去同步它們,以免出現畫不對聲的情況。
要實現音視頻同步,通常需要選擇一個參考時鐘,參考時鐘上的時間是線性遞增的,編碼音視頻流時依據參考時鐘上的時間給每幀數據打上時間戳。在播放時,讀取數據幀上的時間戳,同時參考當前參考時鐘上的時間來安排播放。這里的說的時間戳就是我們前面說的 PTS。實踐中,我們可以選擇:同步視頻到音頻、同步音頻到視頻、同步音頻和視頻到外部時鐘。
對視頻編解碼的概述
軟編碼和硬編碼的概念
軟編碼:使用CPU進行編碼。
硬編碼:不使用CPU進行編碼,使用顯卡GPU,專用的DSP、FPGA、ASIC芯片等硬件進行編碼。
軟編碼和硬編碼的比較
軟編碼:實現直接、簡單,參數調整方便,升級易,但CPU負載重,性能較硬編碼低,低碼率下質量通常比硬編碼要好一點。
硬編碼:性能高,低碼率下通常質量低于硬編碼器,但部分產品在GPU硬件平臺移植了優秀的軟編碼算法(如X264)的,質量基本等同于軟編碼。
ios系統中的硬編碼
蘋果在iOS 8.0系統之前,沒有開放系統的硬件編碼解碼功能,不過Mac OS系統一直有,被稱為Video ToolBox的框架來處理硬件的編碼和解碼,終于在iOS 8.0后,蘋果將該框架引入iOS系統。
H264編碼原理以及I幀B幀P幀
H264編碼原理
H264是新一代的編碼標準,以高壓縮高質量和支持多種網絡的流媒體傳輸著稱,在編碼方面,我理解的他的理論依據是:參照一段時間內圖像的統計結果表明,在相鄰幾幅圖像畫面中,一般有差別的像素只有10%以內的點,亮度差值變化不超過2%,而色度差值的變化只有1%以內。所以對于一段變化不大圖像畫面,我們可以先編碼出一個完整的圖像幀A,隨后的B幀就不編碼全部圖像,只寫入與A幀的差別,這樣B幀的大小就只有完整幀的1/10或更小!B幀之后的C幀如果變化不大,我們可以繼續以參考B的方式編碼C幀,這樣循環下去。這段圖像我們稱為一個序列(序列就是有相同特點的一段數據),當某個圖像與之前的圖像變化很大,無法參考前面的幀來生成,那我們就結束上一個序列,開始下一段序列,也就是對這個圖像生成一個完整幀A1,隨后的圖像就參考A1生成,只寫入與A1的差別內容。
在H264協議里定義了三種幀,完整編碼的幀叫I幀,參考之前的I幀生成的只包含差異部分編碼的幀叫P幀,還有一種參考前后的幀編碼的幀叫B幀。
H264采用的核心算法是幀內壓縮和幀間壓縮,幀內壓縮是生成I幀的算法,幀間壓縮是生成B幀和P幀的算法。
對序列的說明
在H264中圖像以序列為單位進行組織,一個序列是一段圖像編碼后的數據流,以I幀開始,到下一個I幀結束。
一個序列的第一個圖像叫做 IDR 圖像(立即刷新圖像),IDR 圖像都是 I 幀圖像。H.264 引入 IDR 圖像是為了解碼的重同步,當解碼器解碼到 IDR 圖像時,立即將參考幀隊列清空,將已解碼的數據全部輸出或拋棄,重新查找參數集,開始一個新的序列。這樣,如果前一個序列出現重大錯誤,在這里可以獲得重新同步的機會。IDR圖像之后的圖像永遠不會使用IDR之前的圖像的數據來解碼。
一個序列就是一段內容差異不太大的圖像編碼后生成的一串數據流。當運動變化比較少時,一個序列可以很長,因為運動變化少就代表圖像畫面的內容變動很小,所以就可以編一個I幀,然后一直P幀、B幀了。當運動變化多時,可能一個序列就比較短了,比如就包含一個I幀和3、4個P幀。
對三種幀的介紹
I幀
為了更好地理解I幀的概念,我羅列了兩種解釋:
-
幀內編碼幀 ,I幀表示關鍵幀,你可以理解為這一幀畫面的完整保留;解碼時只需要本幀數據就可以完成(因為包含完整畫面)。
-
幀內編碼幀 又稱intra picture,I 幀通常是每個 GOP(MPEG 所使用的一種視頻壓縮技術)的第一個幀,經過適度地壓縮,做為隨機訪問的參考點,可以當成圖象。I幀可以看成是一個圖像經過壓縮后的產物。
I幀的特點:
- 它是一個全幀壓縮編碼幀。它將全幀圖像信息進行JPEG壓縮編碼及傳輸
- 解碼時僅用I幀的數據就可重構完整圖像
- I幀描述了圖像背景和運動主體的詳情
- I幀不需要參考其他畫面而生成
- I幀是P幀和B幀的參考幀(其質量直接影響到同組中以后各幀的質量)
- I幀是幀組GOP的基礎幀(第一幀),在一組中只有一個I幀
- I幀不需要考慮運動矢量
- I幀所占數據的信息量比較大
P幀
為了更好地理解P幀的概念,我也羅列了兩種解釋:
- 前向預測編碼幀。P幀表示的是這一幀跟之前的一個關鍵幀(或P幀)的差別,解碼時需要用之前緩存的畫面疊加上本幀定義的差別,生成最終畫面。(也就是差別幀,P幀沒有完整畫面數據,只有與前一幀的畫面差別的數據)
- 前向預測編碼幀 又稱predictive-frame,通過充分將低于圖像序列中前面已編碼幀的時間冗余信息來壓縮傳輸數據量的編碼圖像,也叫預測幀
**P幀的預測與重構:**P幀是以I幀為參考幀,在I幀中找出P幀“某點”的預測值和運動矢量,取預測差值和運動矢量一起傳送。在接收端根據運動矢量從I幀中找出P幀“某點”的預測值并與差值相加以得到P幀“某點”樣值,從而可得到完整的P幀。
P幀特點:
- P幀是I幀后面相隔1~2幀的編碼幀
- P幀采用運動補償的方法傳送它與前面的I或P幀的差值及運動矢量(預測誤差)
- 解碼時必須將I幀中的預測值與預測誤差求和后才能重構完整的P幀圖像
- P幀屬于前向預測的幀間編碼。它只參考前面最靠近它的I幀或P幀
- P幀可以是其后面P幀的參考幀,也可以是其前后的B幀的參考幀
- 由于P幀是參考幀,它可能造成解碼錯誤的擴散
- 由于是差值傳送,P幀的壓縮比較高
B幀
為了更好地理解P幀的概念,我依然羅列了兩種解釋:
- 雙向預測內插編碼幀。B幀是雙向差別幀,也就是B幀記錄的是本幀與前后幀的差別(具體比較復雜,有4種情況,但我這樣說簡單些),換言之,要解碼B幀,不僅要取得之前的緩存畫面,還要解碼之后的畫面,通過前后畫面的與本幀數據的疊加取得最終的畫面。B幀壓縮率高,但是解碼時CPU會比較累。
- 雙向預測內插編碼幀 又稱bi-directional interpolated prediction frame,既考慮與源圖像序列前面已編碼幀,也顧及源圖像序列后面已編碼幀之間的時間冗余信息來壓縮傳輸數據量的編碼圖像,也叫雙向預測幀;
**B幀的預測與重構:**B幀以前面的I或P幀和后面的P幀為參考幀,“找出”B幀“某點”的預測值和兩個運動矢量,并取預測差值和運動矢量傳送。接收端根據運動矢量在兩個參考幀中“找出(算出)”預測值并與差值求和,得到B幀“某點”樣值,從而可得到完整的B幀。
B幀的特點:
- B幀是由前面的I或P幀和后面的P幀來進行預測的
- B幀傳送的是它與前面的I或P幀和后面的P幀之間的預測誤差及運動矢量
- B幀是雙向預測編碼幀
- B幀壓縮比最高,因為它只反映丙參考幀間運動主體的變化情況,預測比較準確
- B幀不是參考幀,不會造成解碼錯誤的擴散
I、B、P各幀是根據壓縮算法的需要,是人為定義的,它們都是實實在在的物理幀。一般來說,I幀的壓縮率是7(跟JPG差不多),P幀是20,B幀可以達到50。可見使用B幀能節省大量空間,節省出來的空間可以用來保存多一些I幀,這樣在相同碼率下,可以提供更好的畫質。
對壓縮算法的說明
h264的壓縮方法:
幀內(Intraframe)壓縮也稱為空間壓縮(Spatial compression)。當壓縮一幀圖像時,僅考慮本幀的數據而不考慮相鄰幀之間的冗余信息,這實際上與靜態圖像壓縮類似。幀內一般采用有損壓縮算法,由于幀內壓縮是編碼一個完整的圖像,所以可以獨立的解碼、顯示。幀內壓縮一般達不到很高的壓縮,跟編碼jpeg差不多。
幀間(Interframe)壓縮的原理是:相鄰幾幀的數據有很大的相關性,或者說前后兩幀信息變化很小的特點。也即連續的視頻其相鄰幀之間具有冗余信息,根據這一特性,壓縮相鄰幀之間的冗余量就可以進一步提高壓縮量,減小壓縮比。幀間壓縮也稱為時間壓縮(Temporal compression),它通過比較時間軸上不同幀之間的數據進行壓縮。幀間壓縮一般是無損的。幀差值(Frame differencing)算法是一種典型的時間壓縮法,它通過比較本幀與相鄰幀之間的差異,僅記錄本幀與其相鄰幀的差值,這樣可以大大減少數據量。
順便說下有損(Lossy )壓縮和無損(Lossy less)壓縮。無損壓縮也即壓縮前和解壓縮后的數據完全一致。多數的無損壓縮都采用RLE行程編碼算法。有損壓縮意味著解壓縮后的數據與壓縮前的數據不一致。在壓縮的過程中要丟失一些人眼和人耳所不敏感的圖像或音頻信息,而且丟失的信息不可恢復。幾乎所有高壓縮的算法都采用有損壓縮,這樣才能達到低數據率的目標。丟失的數據率與壓縮比有關,壓縮比越小,丟失的數據越多,解壓縮后的效果一般越差。此外,某些有損壓縮算法采用多次重復壓縮的方式,這樣還會引起額外的數據丟失。
三種幀的不同
- I frame:自身可以通過視頻解壓算法解壓成一張單獨的完整的圖片。
- P frame:需要參考其前面的一個I frame 或者B frame來生成一張完整的圖片。
- B frame:則要參考其前一個I或者P幀及其后面的一個P幀來生成一張完整的圖片。
- 兩個I frame之間形成一個GOP,在x264中同時可以通過參數來設定bf的大小,即:I 和p或者兩個P之間B的數量。
- 通過上述基本可以說明如果有B frame 存在的情況下一個GOP的最后一個frame一定是P.
DTS和PTS的不同
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系統 H264視頻硬件編解碼說明
我們知道,在IOS8系統以后,蘋果將Mac OS中用于硬件編解碼 VideoToolbox 框架引入IOS系統。
按照蘋果WWDC2014 513《direct access to media encoding and decoding》的描述,蘋果之前提供的AVFoundation框架也使用硬件對視頻進行硬編碼和解碼,但是編碼后直接寫入文件,解碼后直接顯示。Video Toolbox框架可以得到編碼后的幀結構,也可以得到解碼后的原始圖像,因此具有更大的靈活性做一些視頻圖像處理。
對VideoToolbox的介紹
在iOS中,與視頻相關的接口有5個,從頂層開始分別是 AVKit - AVFoundation - VideoToolbox - Core Media - Core Video
其中VideoToolbox可以將視頻解壓到CVPixelBuffer,也可以壓縮到CMSampleBuffer。
如果需要使用硬編碼的話,在5個接口中,就需要用到AVKit,AVFoundation和VideoToolbox。在這里我就只介紹VideoToolbox。
VideoToolbox中的對象
-
CVPixelBuffer : 編碼前和解碼后的圖像數據結構(未壓縮光柵圖像緩存區-Uncompressed Raster Image Buffer)
-
CVPixelBufferPool : 顧名思義,存放CVPixelBuffer
-
pixelBufferAttributes : CFDictionary對象,可能包含了視頻的寬高,像素格式類型(32RGBA, YCbCr420),是否可以用于OpenGL ES等相關信息
-
CMTime : 時間戳相關。時間以 64-big/32-bit形式出現。 分子是64-bit的時間值,分母是32-bit的時標(time scale)
-
CMClock : 時間戳相關。時間以 64-big/32-bit形式出現。 分子是64-bit的時間值,分母是32-bit的時標(time scale)。它封裝了時間源,其中CMClockGetHostTimeClock()封裝了mach_absolute_time()
-
CMTimebase : 時間戳相關。時間以 64-big/32-bit形式出現。CMClock上的控制視圖。提供了時間的映射:CMTimebaseSetTime(timebase, kCMTimeZero); 速率控制: CMTimebaseSetRate(timebase, 1.0);
-
CMBlockBuffer : 編碼后,結果圖像的數據結構
-
CMVideoFormatDescription : 圖像存儲方式,編解碼器等格式描述
-
CMSampleBuffer : 存放編解碼前后的視頻圖像的容器數據結構
如圖所示,編解碼前后的視頻圖像均封裝在CMSampleBuffer中,如果是編碼后的圖像,以CMBlockBuffe方式存儲;解碼后的圖像,以CVPixelBuffer存儲。CMSampleBuffer里面還有另外的時間信息CMTime和視頻描述信息CMVideoFormatDesc。
硬解碼
通過如圖所示的一個典型應用,來說明如何使用硬件解碼接口。該應用場景是從網絡處傳來H264編碼后的視頻碼流,最后顯示在手機屏幕上。
要完成以上功能需要經過以下幾個步驟:
將H.264碼流轉化成CMSampleBuffer
我們知道,CMSampleBuffer = CMTime + FormatDesc + CMBlockBuffer . 需要從H264的碼流里面提取出以上的三個信息。最后組合成CMSampleBuffer,提供給硬解碼接口來進行解碼工作。
在H.264的語法中,有一個最基礎的層,叫做Network Abstraction Layer, 簡稱為NAL。H.264流數據正是由一系列的NAL單元(NAL Unit, 簡稱NAUL)組成的。
H264的碼流由NALU單元組成,一個NALU可能包含有:
-
視頻幀
視頻幀也就是視頻片段,具體有 P幀, I幀,B幀
-
H.264屬性合集-FormatDesc(包含 SPS和PPS)
流數據中,屬性集合可能是這樣的:
經過處理之后,在Format Description中則是:
要從基礎的流數據將SPS和PPS轉化為Format Desc中的話,需要調用CMVideoFormatDescriptionCreateFromH264ParameterSets()方法
NALU header
對于流數據來說,一個NAUL的Header中,可能是0x00 00 01或者是0x00 00 00 01作為開頭(兩者都有可能,下面以0x00 00 01作為例子)。0x00 00 01因此被稱為開始碼(Start code).
總結以上知識,我們知道H264的碼流由NALU單元組成,NALU單元包含視頻圖像數據和H264的參數信息。其中視頻圖像數據就是CMBlockBuffer,而H264的參數信息則可以組合成FormatDesc。具體來說參數信息包含SPS(Sequence Parameter Set)和PPS(Picture Parameter Set).如下圖顯示了一個H.264碼流結構:
1) 提取sps和pps生成FormatDesc
- 每個NALU的開始碼是0x00 00 01,按照開始碼定位NALU
- 通過類型信息找到sps和pps并提取,開始碼后第一個byte的后5位,7代表sps,8代表pps
- 使用CMVideoFormatDescriptionCreateFromH264ParameterSets函數來構建CMVideoFormatDescriptionRef
2)提取視頻圖像數據生成CMBlockBuffer
- 通過開始碼,定位到NALU
- 確定類型為數據后,將開始碼替換成NALU的長度信息(4 Bytes)
- 使用CMBlockBufferCreateWithMemoryBlock接口構造CMBlockBufferRef
3)根據需要,生成CMTime信息。(實際測試時,加入time信息后,有不穩定的圖像,不加入time信息反而沒有,需要進一步研究,這里建議不加入time信息)
根據上述得到CMVideoFormatDescriptionRef、CMBlockBufferRef和可選的時間信息,使用CMSampleBufferCreate接口得到CMSampleBuffer數據這個待解碼的原始的數據。如下圖所示的H264數據轉換示意圖。
將CMSampleBuffer顯示出來
顯示的方式有兩種:
- 將CMSampleBuffers提供給系統的AVSampleBufferDisplayLayer 直接顯示
- 利用OPenGL自己渲染
1)通過系統提供的AVSampleBufferDisplayLayer來解碼并顯示
使用方式和其它CALayer類似。該層內置了硬件解碼功能,將原始的CMSampleBuffer解碼后的圖像直接顯示在屏幕上面,非常的簡單方便。
2)利用OPenGL自己渲染
通過VTDecompression接口來,將CMSampleBuffer解碼成圖像,將圖像通過UIImageView或者OpenGL上顯示。
- 初始化VTDecompressionSession,設置解碼器的相關信息。初始化信息需要CMSampleBuffer里面的FormatDescription,以及設置解碼后圖像的存儲方式。demo里面設置的CGBitmap模式,使用RGB方式存放。編碼后的圖像經過解碼后,會調用一個回調函數,將解碼后的圖像交個這個回調函數來進一步處理。我們就在這個回調里面,將解碼后的圖像發給control來顯示,初始化的時候要將回調指針作為參數傳給create接口函數。最后使用create接口對session來進行初始化。
- a中所述的回調函數可以完成CGBitmap圖像轉換成UIImage圖像的處理,將圖像通過隊列發送到Control來進行顯示處理。
- 調用VTDecompresSessionDecodeFrame接口進行解碼操作。解碼后的圖像會交由以上兩步驟設置的回調函數,來進一步的處理。
硬編碼
硬編碼的使用也通過一個典型的應用場景來描述。首先,通過攝像頭來采集圖像,然后將采集到的圖像,通過硬編碼的方式進行編碼,最后編碼后的數據將其組合成H264的碼流通過網絡傳播。
下面是對具體步驟的說明:
攝像頭采集數據
攝像頭采集,iOS系統提供了AVCaptureSession來采集攝像頭的圖像數據。設定好session的采集解析度。再設定好input和output即可。output設定的時候,需要設置delegate和輸出隊列。在delegate方法,處理采集好的圖像。
圖像輸出的格式,是未編碼的CMSampleBuffer形式。
使用VTCompressionSession進行硬編碼
1)初始化VTCompressionSession
VTCompressionSession初始化的時候,一般需要給出width寬,height長,編碼器類型kCMVideoCodecType_H264等。然后通過調用VTSessionSetProperty接口設置幀率等屬性,demo里面提供了一些設置參考,測試的時候發現幾乎沒有什么影響,可能需要進一步調試。最后需要設定一個回調函數,這個回調是視頻圖像編碼成功后調用。全部準備好后,使用VTCompressionSessionCreate創建session
2)提取攝像頭采集的原始圖像數據給VTCompressionSession來硬編碼
攝像頭采集后的圖像是未編碼的CMSampleBuffer形式,利用給定的接口函數CMSampleBufferGetImageBuffer從中提取出CVPixelBufferRef,使用硬編碼接口VTCompressionSessionEncodeFrame來對該幀進行硬編碼,編碼成功后,會自動調用session初始化時設置的回調函數。
3)利用回調函數,將因編碼成功的CMSampleBuffer轉換成H264碼流,通過網絡傳播
基本上是硬解碼的一個逆過程。解析出參數集SPS和PPS,加上開始碼后組裝成NALU。提取出視頻數據,將長度碼轉換成開始碼,組長成NALU。將NALU發送出去。
下篇文章我們將介紹H264硬編碼和軟編碼的實現。
參考
- iOS8系統H264視頻硬件編解碼說明
- VideoToolbox解析
- 簡單談談硬編碼和軟編碼
- I,P,B幀和PTS,DTS的關系
轉載于:https://juejin.im/post/5cf07dfdf265da1b8466ca8c
總結
以上是生活随笔為你收集整理的视频的基本参数及H264编解码相关概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 负染电镜操作
- 下一篇: css font html里写,HTML