上幾篇文章介紹了音頻的採集以及編碼,如今咱們開始學習視頻相關的知識,一樣先從概念開始。本篇文章的主要內容有:html
關於視頻硬編碼和軟編碼實現可參考--視頻H264硬編碼和軟編碼&編譯ffmpeg庫及環境搭建(附完整demo)ios
幀率(Frame rate)是用於測量顯示幀數的量度。所謂的測量單位爲每秒顯示幀數(Frames per Second,簡稱:FPS)或「赫茲」(Hz)算法
因爲人類眼睛的特殊生理結構,若是所看畫面之幀率高於24的時候,就會認爲是連貫的,此現象稱之爲視覺暫留。這也就是爲何電影膠片是一格一格拍攝出來,而後快速播放的。 而對遊戲,通常來講,第一人稱射擊遊戲比較注重FPS的高低,若是FPS<30的話,遊戲會顯得不連貫。因此有一句有趣的話:「FPS(指FPS遊戲)重在FPS(指幀率)。緩存
每秒的幀數(fps)或者說幀率表示圖形處理器處理場時每秒鐘可以更新的次數。高的幀率能夠獲得更流暢、更逼真的動畫。通常來講30fps就是能夠接受的,可是將性能提高至60fps則能夠明顯提高交互感和逼真感,可是通常來講超過75fps通常就不容易察覺到有明顯的流暢度提高了。若是幀率超過屏幕刷新率只會浪費圖形處理的能力,由於監視器不能以這麼快的速度更新,這樣超過刷新率的幀率就浪費掉了。bash
咱們能夠根據幀率得出連續兩幀的時間間隔。好比幀率爲30FPS,那麼相鄰兩幀的時間間隔爲 33ms網絡
顯示分辨率(屏幕分辨率)是屏幕圖像的精密度,是指顯示器所能顯示的像素有多少。因爲屏幕上的點、線和麪都是由像素組成的,顯示器可顯示的像素越多,畫面就越精細,一樣的屏幕區域內能顯示的信息也越多,因此分辨率是個很是重要的性能指標之一。能夠把整個圖像想象成是一個大型的棋盤,而分辨率的表示方式就是全部經線和緯線交叉點的數目。顯示分辨率必定的狀況下,顯示屏越小圖像越清晰,反之,顯示屏大小固定時,顯示分辨率越高圖像越清晰。session
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主要用於視頻的同步和輸出,在 display 的時候使用。在沒有B幀的狀況下,DTS和PTS的輸出順序是同樣的。less
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 8.0系統以前,沒有開放系統的硬件編碼解碼功能,不過Mac OS系統一直有,被稱爲Video ToolBox的框架來處理硬件的編碼和解碼,終於在iOS 8.0後,蘋果將該框架引入iOS系統。
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幀和三、4個P幀。
爲了更好地理解I幀的概念,我羅列了兩種解釋:
幀內編碼幀 ,I幀表示關鍵幀,你能夠理解爲這一幀畫面的完整保留;解碼時只須要本幀數據就能夠完成(由於包含完整畫面)。
幀內編碼幀 又稱intra picture,I 幀一般是每一個 GOP(MPEG 所使用的一種視頻壓縮技術)的第一個幀,通過適度地壓縮,作爲隨機訪問的參考點,能夠當成圖象。I幀能夠當作是一個圖像通過壓縮後的產物。
I幀的特色:
爲了更好地理解P幀的概念,我也羅列了兩種解釋:
**P幀的預測與重構:**P幀是以I幀爲參考幀,在I幀中找出P幀「某點」的預測值和運動矢量,取預測差值和運動矢量一塊兒傳送。在接收端根據運動矢量從I幀中找出P幀「某點」的預測值並與差值相加以獲得P幀「某點」樣值,從而可獲得完整的P幀。
P幀特色:
爲了更好地理解P幀的概念,我依然羅列了兩種解釋:
**B幀的預測與重構:**B幀之前面的I或P幀和後面的P幀爲參考幀,「找出」B幀「某點」的預測值和兩個運動矢量,並取預測差值和運動矢量傳送。接收端根據運動矢量在兩個參考幀中「找出(算出)」預測值並與差值求和,獲得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行程編碼算法。有損壓縮意味着解壓縮後的數據與壓縮前的數據不一致。在壓縮的過程當中要丟失一些人眼和人耳所不敏感的圖像或音頻信息,並且丟失的信息不可恢復。幾乎全部高壓縮的算法都採用有損壓縮,這樣才能達到低數據率的目標。丟失的數據率與壓縮比有關,壓縮比越小,丟失的數據越多,解壓縮後的效果通常越差。此外,某些有損壓縮算法採用屢次重複壓縮的方式,這樣還會引發額外的數據丟失。
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.
咱們知道,在IOS8系統之後,蘋果將Mac OS中用於硬件編解碼 VideoToolbox
框架引入IOS系統。
按照蘋果WWDC2014 513《direct access to media encoding and decoding》的描述,蘋果以前提供的AVFoundation框架也使用硬件對視頻進行硬編碼和解碼,可是編碼後直接寫入文件,解碼後直接顯示。Video Toolbox框架能夠獲得編碼後的幀結構,也能夠獲得解碼後的原始圖像,所以具備更大的靈活性作一些視頻圖像處理。
在iOS中,與視頻相關的接口有5個,從頂層開始分別是 AVKit - AVFoundation - VideoToolbox - Core Media - Core Video
其中VideoToolbox能夠將視頻解壓到CVPixelBuffer,也能夠壓縮到CMSampleBuffer。
若是須要使用硬編碼的話,在5個接口中,就須要用到AVKit,AVFoundation和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編碼後的視頻碼流,最後顯示在手機屏幕上。
要完成以上功能須要通過如下幾個步驟:
咱們知道,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
2)提取視頻圖像數據生成CMBlockBuffer
3)根據須要,生成CMTime信息。(實際測試時,加入time信息後,有不穩定的圖像,不加入time信息反而沒有,須要進一步研究,這裏建議不加入time信息)
根據上述獲得CMVideoFormatDescriptionRef、CMBlockBufferRef和可選的時間信息,使用CMSampleBufferCreate接口獲得CMSampleBuffer數據這個待解碼的原始的數據。以下圖所示的H264數據轉換示意圖。
顯示的方式有兩種:
1)經過系統提供的AVSampleBufferDisplayLayer來解碼並顯示
使用方式和其它CALayer相似。該層內置了硬件解碼功能,將原始的CMSampleBuffer解碼後的圖像直接顯示在屏幕上面,很是的簡單方便。
2)利用OPenGL本身渲染
經過VTDecompression接口來,將CMSampleBuffer解碼成圖像,將圖像經過UIImageView或者OpenGL上顯示。
硬編碼的使用也經過一個典型的應用場景來描述。首先,經過攝像頭來採集圖像,而後將採集到的圖像,經過硬編碼的方式進行編碼,最後編碼後的數據將其組合成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硬編碼和軟編碼的實現。