硬件編碼相關知識(H264,H265)

硬件編碼相關知識(H264,H265)

閱讀人羣:研究硬件編碼器應用於iOS開發中,從0研究關於硬件編解碼,碼流中解析數據結構

內容概述:關於H264,H265的背景,數據結構,在iOS開發中編解碼的應用


簡書地址 : 硬件編碼相關知識

博客地址 : 硬件編碼相關知識

掘金地址 : 硬件編碼相關知識


一. 背景及概述

1. 在升級 iOS 11 以後,iPhone 7 及更新的設備內的照片存儲將再也不用 JPEG 了,而採用了一種新的圖片格式 HEIF(發音同 heef),在 iOS 中對應的文件後綴爲 .heic ,其編碼用的是 HEVC(這個發不了音,哈哈哈)格式,又稱 H.265 (這個就很熟悉了 H.264 的下一代),同時視頻也用 HEVC 做爲編碼器,對應的文件後綴仍是 .mov 。
2. 這裏要注意他們倆的關係, HEIF 是圖片格式,而 HEVC 是編碼格式(相似 H.264,VP8),HEIF 是圖片容器(相似於視頻的 mkv,mp4 後綴),而用 HEVC 進行編碼的 HEIF 圖片就是後綴爲 .heic 的圖片,也是蘋果主要使用的格式。
3. HEIF 全稱 High Efficiency Image Format (HEIF)。是由 Moving Picture Experts Group 制定的,存儲圖片和圖片序列的格式。下圖是形容HEIF的一句英文詩,JPEG很大,可是HEIF很小。

HEIF

4. 優勢
  • 壓縮比高,在相同圖片質量狀況下,比JPEG高兩倍
  • 能增長如圖片的深度信息,透明通道等輔助圖片。
  • 支持存放多張圖片,相似相冊和集合。(實現多重曝光的效果)
  • 支持多張圖片實現GIF和livePhoto的動畫效果。
  • 無相似JPEG的最大像素限制
  • 支持透明像素
  • 分塊加載機制
  • 支持縮略圖
5. 文件組成
  • 在視頻文件中,容器和編碼是獨立開的,好比mp4,mkv等格式是容器而H.264,vp8等是編碼
  • 可是圖像文件中,像JPEG就是混合在一塊兒的,因此天然不太好用。HEIF就把容器和編碼分開了,有用來存放單個或多個圖像的容器。
6. 兼容

通常狀況下,用戶是對這個格式無感知的,由於只有在新款支持硬解碼的 iOS 手機內部是以 heif & hevc 格式來存儲照片和視頻的,而在用戶經過 Airdrop或者數據線傳送到電腦上的時候,對不兼容的設備會自動轉換到 JPEG 的格式。因此也不會影響你使用微信,微博等軟件。ios

二. 視頻編解碼

1.軟編與硬編概念
  • 軟編碼:使用CPU進行編碼。
  • 硬編碼:不使用CPU進行編碼,使用顯卡GPU,專用的DSP、FPGA、ASIC芯片等硬件進行編碼。
  • 比較
    • 軟編碼:實現直接、簡單,參數調整方便,升級易,但CPU負載重,性能較硬編碼低,低碼率下質量一般比硬編碼要好一點。
    • 性能高,低碼率下一般質量低於軟編碼器,但部分產品在GPU硬件平臺移植了優秀的軟編碼算法(如X264)的,質量基本等同於軟編碼。
    • 蘋果在iOS 8.0系統以前,沒有開放系統的硬件編碼解碼功能,不過Mac OS系統一直有,被稱爲Video ToolBox的框架來處理硬件的編碼和解碼,終於在iOS 8.0後,蘋果將該框架引入iOS系統。
2. h.264編碼原理

H264是新一代的編碼標準,以高壓縮高質量和支持多種網絡的流媒體傳輸著稱,在編碼方面,我理解的他的理論依據是:參照一段時間內圖像的統計結果代表,在相鄰幾幅圖像畫面中,通常有差異的像素只有10%之內的點,亮度差值變化不超過2%,而色度差值的變化只有1%之內。因此對於一段變化不大圖像畫面,咱們能夠先編碼出一個完整的圖像幀A,隨後的B幀就不編碼所有圖像,只寫入與A幀的差異,這樣B幀的大小就只有完整幀的1/10或更小!B幀以後的C幀若是變化不大,咱們能夠繼續以參考B的方式編碼C幀,這樣循環下去。這段圖像咱們稱爲一個序列(序列就是有相同特色的一段數據),當某個圖像與以前的圖像變化很大,沒法參考前面的幀來生成,那咱們就結束上一個序列,開始下一段序列,也就是對這個圖像生成一個完整幀A1,隨後的圖像就參考A1生成,只寫入與A1的差異內容。git

在H264協議裏定義了三種幀,完整編碼的幀叫I幀,參考以前的I幀生成的只包含差別部分編碼的幀叫P幀,還有一種參考先後的幀編碼的幀叫B幀。github

H264採用的核心算法是幀內壓縮和幀間壓縮,幀內壓縮是生成I幀的算法,幀間壓縮是生成B幀和P幀的算法。算法

3. 對序列的說明

在H264中圖像以序列爲單位進行組織,一個序列是一段圖像編碼後的數據流,以I幀開始,到下一個I幀結束。緩存

一個序列的第一個圖像叫作 IDR 圖像(當即刷新圖像),IDR 圖像都是 I 幀圖像。H.264 引入 IDR 圖像是爲了解碼的重同步,當解碼器解碼到 IDR 圖像時,當即將參考幀隊列清空,將已解碼的數據所有輸出或拋棄,從新查找參數集,開始一個新的序列。這樣,若是前一個序列出現重大錯誤,在這裏能夠得到從新同步的機會。IDR圖像以後的圖像永遠不會使用IDR以前的圖像的數據來解碼。微信

一個序列就是一段內容差別不太大的圖像編碼後生成的一串數據流。當運動變化比較少時,一個序列能夠很長,由於運動變化少就表明圖像畫面的內容變更很小,因此就能夠編一個I幀,而後一直P幀、B幀了。當運動變化多時,可能一個序列就比較短了,好比就包含一個I幀和三、4個P幀。網絡

4. 對三種幀的介紹
  • I幀session

    • 幀內編碼幀 ,I幀表示關鍵幀,你能夠理解爲這一幀畫面的完整保留;解碼時只須要本幀數據就能夠完成(由於包含完整畫面)。
    • 特色
      • 它是一個全幀壓縮編碼幀。它將全幀圖像信息進行JPEG壓縮編碼及傳輸
      • 解碼時僅用I幀的數據就可重構完整圖像
      • I幀描述了圖像背景和運動主體的詳情
      • I幀不須要參考其餘畫面而生成
      • I幀是P幀和B幀的參考幀(其質量直接影響到同組中之後各幀的質量)
      • I幀是幀組GOP的基礎幀(第一幀),在一組中只有一個I幀
      • I幀不須要考慮運動矢量
      • I幀所佔數據的信息量比較大
  • P幀數據結構

    • 前向預測編碼幀。P幀表示的是這一幀跟以前的一個關鍵幀(或P幀)的差異,解碼時須要用以前緩存的畫面疊加上本幀定義的差異,生成最終畫面。(也就是差異幀,P幀沒有完整畫面數據,只有與前一幀的畫面差異的數據),經過充分將低於圖像序列中前面已編碼幀的時間冗餘信息來壓縮傳輸數據量的編碼圖像,也叫預測幀
    • P幀的預測與重構:P幀是以I幀爲參考幀,在I幀中找出P幀「某點」的預測值和運動矢量,取預測差值和運動矢量一塊兒傳送。在接收端根據運動矢量從I幀中找出P幀「某點」的預測值並與差值相加以獲得P幀「某點」樣值,從而可獲得完整的P幀。
    • 特色:
      • P幀是I幀後面相隔1~2幀的編碼幀
      • P幀採用運動補償的方法傳送它與前面的I或P幀的差值及運動矢量(預測偏差)
      • 解碼時必須將I幀中的預測值與預測偏差求和後才能重構完整的P幀圖像
      • P幀屬於前向預測的幀間編碼。它只參考前面最靠近它的I幀或P幀
      • P幀能夠是其後面P幀的參考幀,也能夠是其先後的B幀的參考幀
      • 因爲P幀是參考幀,它可能形成解碼錯誤的擴散
      • 因爲是差值傳送,P幀的壓縮比較高
  • B幀框架

    • 雙向預測內插編碼幀。B幀是雙向差異幀,也就是B幀記錄的是本幀與先後幀的差異(具體比較複雜,有4種狀況,但我這樣說簡單些),換言之,要解碼B幀,不只要取得以前的緩存畫面,還要解碼以後的畫面,經過先後畫面的與本幀數據的疊加取得最終的畫面。B幀壓縮率高,可是解碼時CPU會比較累。
    • B幀的預測與重構:B幀之前面的I或P幀和後面的P幀爲參考幀,「找出」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幀,這樣在相同碼率下,能夠提供更好的畫質。

5.對壓縮算法得說明

h264的壓縮方法:

  • 分組:把幾幀圖像分爲一組(GOP,也就是一個序列),爲防止運動變化,幀數不宜取多。

  • 定義幀:將每組內各幀圖像定義爲三種類型,即I幀、B幀和P幀;

  • 預測幀:以I幀作爲基礎幀,以I幀預測P幀,再由I幀和P幀預測B幀;

  • 數據傳輸:最後將I幀數據與預測的差值信息進行存儲和傳輸。

  • 幀內(Intraframe)壓縮也稱爲空間壓縮(Spatial compression)。

    • 當壓縮一幀圖像時,僅考慮本幀的數據而不考慮相鄰幀之間的冗餘信息,這實際上與靜態圖像壓縮相似。幀內通常採用有損壓縮算法,因爲幀內壓縮是編碼一個完整的圖像,因此能夠獨立的解碼、顯示。幀內壓縮通常達不到很高的壓縮,跟編碼jpeg差很少。
  • 幀間(Interframe)壓縮

    • 相鄰幾幀的數據有很大的相關性,或者說先後兩幀信息變化很小的特色。也即連續的視頻其相鄰幀之間具備冗餘信息,根據這一特性,壓縮相鄰幀之間的冗餘量就能夠進一步提升壓縮量,減少壓縮比。幀間壓縮也稱爲時間壓縮(Temporal compression),它經過比較時間軸上不一樣幀之間的數據進行壓縮。幀間壓縮通常是無損的。幀差值(Frame differencing)算法是一種典型的時間壓縮法,它經過比較本幀與相鄰幀之間的差別,僅記錄本幀與其相鄰幀的差值,這樣能夠大大減小數據量。
  • 有損(Lossy )壓縮和無損(Lossy less)壓縮。

    • 無損壓縮也即壓縮前和解壓縮後的數據徹底一致。多數的無損壓縮都採用RLE行程編碼算法。
    • 有損壓縮意味着解壓縮後的數據與壓縮前的數據不一致。在壓縮的過程當中要丟失一些人眼和人耳所不敏感的圖像或音頻信息,並且丟失的信息不可恢復。幾乎全部高壓縮的算法都採用有損壓縮,這樣才能達到低數據率的目標。丟失的數據率與壓縮比有關,壓縮比越小,丟失的數據越多,解壓縮後的效果通常越差。此外,某些有損壓縮算法採用屢次重複壓縮的方式,這樣還會引發額外的數據丟失。
6. DTS和PTS的不一樣

DTS主要用於視頻的解碼,在解碼階段使用.PTS主要用於視頻的同步和輸出.在display的時候使用.在沒有B frame的狀況下.DTS和PTS的輸出順序是同樣的。

EX:下面給出一個GOP爲15的例子,其解碼的參照frame及其解碼的順序都在裏面:

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

三. IOS系統 H.264視頻硬件編解碼說明

1.對VideoToolbox的介紹

在iOS中,與視頻相關的接口有5個,從頂層開始分別是 AVKit - AVFoundation - VideoToolbox - Core Media - Core Video

其中VideoToolbox能夠將視頻解壓到CVPixelBuffer,也能夠壓縮到CMSampleBuffer。

若是須要使用硬編碼的話,在5個接口中,就須要用到AVKit,AVFoundation和VideoToolbox。在這裏我就只介紹VideoToolbox。

2.VideoToolbox中的對象
  • CVPixelBuffer : 編碼前和解碼後的圖像數據結構(未壓縮光柵圖像緩存區-Uncompressed Raster Image Buffer)

CVPixelBuffer

  • CVPixelBufferPool : 顧名思義,存放CVPixelBuffer

CVPixelBufferPool

  • 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);

CMTime

  • CMBlockBuffer : 編碼後,結果圖像的數據結構
  • CMVideoFormatDescription : 圖像存如圖所示,編解碼先後的視頻圖像均封裝在CMSampleBuffer中,若是是編碼後的圖像,以CMBlockBuffe方式存儲;解碼後的圖像,以CVPixelBuffer存儲。CMSampleBuffer裏面還有另外的時間信息CMTime和視頻描述信息CMVideoFormatDesc。儲方式,編解碼器等格式描述
  • CMSampleBuffer : 存放編解碼先後的視頻圖像的容器數據結構
  • 如圖所示,編解碼先後的視頻圖像均封裝在CMSampleBuffer中,若是是編碼後的圖像,以CMBlockBuffe方式存儲;解碼後的圖像,以CVPixelBuffer存儲。CMSampleBuffer裏面還有另外的時間信息CMTime和視頻描述信息CMVideoFormatDesc。
    CMSampleBuffer
3. 硬解碼

經過如圖所示的一個典型應用,來講明如何使用硬件解碼接口。該應用場景是從網絡處傳來H264編碼後的視頻碼流,最後顯示在手機屏幕上。

NAL單元
要完成以上功能須要通過如下幾個步驟:

1> 將 H.264碼流轉換爲 CMSampleBuffer

咱們知道,CMSampleBuffer = CMTime + FormatDesc + CMBlockBuffer . 須要從H264的碼流裏面提取出以上的三個信息。最後組合成CMSampleBuffer,提供給硬解碼接口來進行解碼工做。

在H.264的語法中,有一個最基礎的層,叫作Network Abstraction Layer, 簡稱爲NAL。H.264流數據正是由一系列的NAL單元(NAL Unit, 簡稱NAUL)組成的。

NALU

H264的碼流由NALU單元組成,一個NALU可能包含有:

  • 視頻幀,視頻幀也就是視頻片斷,具體有 P幀, I幀,B幀

H264的碼流

  • H.264屬性合集-FormatDesc(包含 SPS和PPS)

流數據中,屬性集合多是這樣的:

屬性集合

通過處理以後,在Format Description中則是:

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).

NALU header

總結以上知識,咱們知道H264的碼流由NALU單元組成,NALU單元包含視頻圖像數據和H264的參數信息。其中視頻圖像數據就是CMBlockBuffer,而H264的參數信息則能夠組合成FormatDesc。具體來講參數信息包含SPS(Sequence Parameter Set)和PPS(Picture Parameter Set).以下圖顯示了一個H.264碼流結構:

H.264碼流

  • 提取sps和pps生成FormatDesc

    • 每一個NALU的開始碼是0x00 00 01,按照開始碼定位NALU
    • 經過類型信息找到sps和pps並提取,開始碼後第一個byte的後5位,7表明sps,8表明pps
    • 使用CMVideoFormatDescriptionCreateFromH264ParameterSets函數來構建CMVideoFormatDescriptionRef
  • 提取視頻圖像數據生成CMBlockBuffer

    • 經過開始碼,定位到NALU
    • 肯定類型爲數據後,將開始碼替換成NALU的長度信息(4 Bytes)
    • 使用CMBlockBufferCreateWithMemoryBlock接口構造CMBlockBufferRef
  • 根據須要,生成CMTime信息。(實際測試時,加入time信息後,有不穩定的圖像,不加入time信息反而沒有,須要進一步研究,這裏建議不加入time信息)

根據上述獲得CMVideoFormatDescriptionRef、CMBlockBufferRef和可選的時間信息,使用CMSampleBufferCreate接口獲得CMSampleBuffer數據這個待解碼的原始的數據。以下圖所示的H264數據轉換示意圖。

CMSampleBufferCreate

2> 將 CMSampleBuffer顯示出來

顯示的方式有兩種:

  • 將CMSampleBuffers提供給系統的AVSampleBufferDisplayLayer 直接顯示
    • 使用方式和其它CALayer相似。該層內置了硬件解碼功能,將原始的CMSampleBuffer解碼後的圖像直接顯示在屏幕上面,很是的簡單方便。
  • 利用OPenGL本身渲染 經過VTDecompression接口來,將CMSampleBuffer解碼成圖像,將圖像經過UIImageView或者OpenGL上顯示。
    • 初始化VTDecompressionSession,設置解碼器的相關信息。初始化信息須要CMSampleBuffer裏面的FormatDescription,以及設置解碼後圖像的存儲方式。demo裏面設置的CGBitmap模式,使用RGB方式存放。編碼後的圖像通過解碼後,會調用一個回調函數,將解碼後的圖像交個這個回調函數來進一步處理。咱們就在這個回調裏面,將解碼後的圖像發給control來顯示,初始化的時候要將回調指針做爲參數傳給create接口函數。最後使用create接口對session來進行初始化。
    • a中所述的回調函數能夠完成CGBitmap圖像轉換成UIImage圖像的處理,將圖像經過隊列發送到Control來進行顯示處理。
    • 調用VTDecompresSessionDecodeFrame接口進行解碼操做。解碼後的圖像會交由以上兩步驟設置的回調函數,來進一步的處理。
4.硬編碼

硬編碼的使用也經過一個典型的應用場景來描述。首先,經過攝像頭來採集圖像,而後將採集到的圖像,經過硬編碼的方式進行編碼,最後編碼後的數據將其組合成H264的碼流經過網絡傳播。

  • 攝像頭採集數據

    攝像頭採集,iOS系統提供了AVCaptureSession來採集攝像頭的圖像數據。設定好session的採集解析度。再設定好input和output便可。output設定的時候,須要設置delegate和輸出隊列。在delegate方法,處理採集好的圖像。

    圖像輸出的格式,是未編碼的CMSampleBuffer形式。

  • 使用VTCompressionSession進行硬編碼

    • 初始化VTCompressionSession

    VTCompressionSession初始化的時候,通常須要給出width寬,height長,編碼器類型kCMVideoCodecType_H264等。而後經過調用VTSessionSetProperty接口設置幀率等屬性,demo裏面提供了一些設置參考,測試的時候發現幾乎沒有什麼影響,可能須要進一步調試。最後須要設定一個回調函數,這個回調是視頻圖像編碼成功後調用。所有準備好後,使用VTCompressionSessionCreate建立session

    • 提取攝像頭採集的原始圖像數據給VTCompressionSession來硬編碼

    攝像頭採集後的圖像是未編碼的CMSampleBuffer形式,利用給定的接口函數CMSampleBufferGetImageBuffer從中提取出CVPixelBufferRef,使用硬編碼接口VTCompressionSessionEncodeFrame來對該幀進行硬編碼,編碼成功後,會自動調用session初始化時設置的回調函數。

    • 利用回調函數,將因編碼成功的CMSampleBuffer轉換成H264碼流,經過網絡傳播

基本上是硬解碼的一個逆過程。解析出參數集SPS和PPS,加上開始碼後組裝成NALU。提取出視頻數據,將長度碼轉換成開始碼,組長成NALU。將NALU發送出去。

參考:H264介紹HEIF & HEVC 你知道多少?

相關文章
相關標籤/搜索