去抖音面試被問到硬編碼與軟編碼區別,如何選取硬編與軟編?

原文連接:https://zhuanlan.zhihu.com/p/82130600java

Android的視頻相關的開發,大概一直是整個Android生態,以及Android API中,最爲分裂以及兼容性問題最爲突出的一部分。攝像頭,以及視頻編碼相關的API,Google一直對這方面的控制力很是差,致使不一樣廠商對這兩個API的實現有很多差別,並且從API的設計來看,一直以來優化也至關有限,甚至有人認爲這是「Android上最難用的API之一」web

以微信爲例,咱們錄製一個540p的mp4文件,對於Android來講,大致上是遵循這麼一個流程:數組

 

 

大致上就是從攝像頭輸出的YUV幀通過預處理以後,送入編碼器,得到編碼好的h264視頻流。微信

上面只是針對視頻流的編碼,另外還須要對音頻流單獨錄製,最後再將視頻流和音頻流進行合成出最終視頻。多線程

這篇文章主要將會對視頻流的編碼中兩個常見問題進行分析:app

1.視頻編碼器的選擇(硬編 or 軟編)?
2.如何對攝像頭輸出的YUV幀進行快速預處理(鏡像,縮放,旋轉)?異步

視頻編碼器的選擇

對於錄製視頻的需求,很多app都須要對每一幀數據進行單獨處理,所以不多會直接用到MediaRecorder來直接錄取視頻,通常來講,會有這麼兩個選擇:
1.MediaCodec
2.FFMpeg+x264/openh264ide

咱們來逐個解析一下優化

一.MediaCodec(硬編)

MediaCodec是API 16以後Google推出的用於音視頻編解碼的一套偏底層的API,能夠直接利用硬件加速進行視頻的編解碼。調用的時候須要先初始化MediaCodec做爲視頻的編碼器,而後只須要不停傳入原始的YUV數據進入編碼器就能夠直接輸出編碼好的h264流,整個API設計模型來看,就是同時包含了輸入端和輸出端的兩條隊列:編碼

 

 

所以,做爲編碼器,輸入端隊列存放的就是原始YUV數據,輸出端隊列輸出的就是編碼好的h264流,做爲解碼器則對應相反。在調用的時候,MediaCodec提供了同步和異步兩種調用方式,可是異步使用Callback的方式是在API 21以後才加入的,以同步調用爲例,通常來講調用方式大概是這樣(摘自官方例子):

MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format, …);
 MediaFormat outputFormat = codec.getOutputFormat(); // option B
 codec.start();
 for (;;) {
   int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
   if (inputBufferId >= 0) {
     ByteBuffer inputBuffer = codec.getInputBuffer(…);
     // fill inputBuffer with valid data
     …
     codec.queueInputBuffer(inputBufferId, …);
   }
   int outputBufferId = codec.dequeueOutputBuffer(…);
   if (outputBufferId >= 0) {
     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
     // bufferFormat is identical to outputFormat
     // outputBuffer is ready to be processed or rendered.
     …
     codec.releaseOutputBuffer(outputBufferId, …);
   } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
     // Subsequent data will conform to new format.
     // Can ignore if using getOutputFormat(outputBufferId)
     outputFormat = codec.getOutputFormat(); // option B
   }
 }
 codec.stop();
 codec.release();

  

簡單解釋一下,經過getInputBuffers獲取輸入隊列,而後調用dequeueInputBuffer獲取輸入隊列空閒數組下標,注意dequeueOutputBuffer會有幾個特殊的返回值表示當前編解碼狀態的變化,而後再經過queueInputBuffer把原始YUV數據送入編碼器,而在輸出隊列端一樣經過getOutputBuffersdequeueOutputBuffer獲取輸出的h264流,處理完輸出數據以後,須要經過releaseOutputBuffer把輸出buffer還給系統,從新放到輸出隊列中。

從上面例子來看的確是很是原始的API,因爲MediaCodec底層是直接調用了手機平臺硬件的編解碼能力,因此速度很是快,可是由於Google對整個Android硬件生態的掌控力很是弱,因此這個API有不少問題:

1.顏色格式問題

MediaCodec在初始化的時候,在configure的時候,須要傳入一個MediaFormat對象,看成爲編碼器使用的時候,咱們通常須要在MediaFormat中指定視頻的寬高,幀率,碼率,I幀間隔等基本信息,除此以外,還有一個重要的信息就是,指定編碼器接受的YUV幀的顏色格式。這個是由於因爲YUV根據其採樣比例,UV份量的排列順序有不少種不一樣的顏色格式,而對於Android的攝像頭在onPreviewFrame輸出的YUV幀格式,若是沒有配置任何參數的狀況下,基本上都是NV21格式,但Google對MediaCodec的API在設計和規範的時候,顯得很不厚道,過於貼近Android的HAL層了,致使了NV21格式並非全部機器的MediaCodec都支持這種格式做爲編碼器的輸入格式! 所以,在初始化MediaCodec的時候,咱們須要經過codecInfo.getCapabilitiesForType來查詢機器上的MediaCodec實現具體支持哪些YUV格式做爲輸入格式,通常來講,起碼在4.4+的系統上,這兩種格式在大部分機器都有支持:

MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar

  

兩種格式分別是YUV420P和NV21,若是機器上只支持YUV420P格式的狀況下,則須要先將攝像頭輸出的NV21格式先轉換成YUV420P,才能送入編碼器進行編碼,不然最終出來的視頻就會花屏,或者顏色出現錯亂

這個算是一個不大不小的坑,基本上用上了MediaCodec進行視頻編碼都會趕上這個問題

2.編碼器支持特性至關有限

若是使用MediaCodec來編碼H264視頻流,對於H264格式來講,會有一些針對壓縮率以及碼率相關的視頻質量設置,典型的諸如Profile(baseline, main, high),Profile Level, Bitrate mode(CBR, CQ, VBR),合理配置這些參數可讓咱們在同等的碼率下,得到更高的壓縮率,從而提高視頻的質量,Android也提供了對應的API進行設置,能夠設置到MediaFormat中這些設置項:

MediaFormat.KEY_BITRATE_MODE
MediaFormat.KEY_PROFILE
MediaFormat.KEY_LEVEL

  

但問題是,對於Profile,Level, Bitrate mode這些設置,在大部分手機上都是不支持的,即便是設置了最終也不會生效,例如設置了Profile爲high,最後出來的視頻依然還會是Baseline,Shit....
這個問題,在7.0如下的機器幾乎是必現的,其中一個可能的緣由是,Android在源碼層級hardcode了profile的的設置:

// XXX
if (h264type.eProfile != OMX_VIDEO_AVCProfileBaseline) {
    ALOGW("Use baseline profile instead of %d for AVC recording",
            h264type.eProfile);
    h264type.eProfile = OMX_VIDEO_AVCProfileBaseline;
}

  

Android直到7.0以後才取消了這段地方的Hardcode

if (h264type.eProfile == OMX_VIDEO_AVCProfileBaseline) {
    ....
} else if (h264type.eProfile == OMX_VIDEO_AVCProfileMain ||
            h264type.eProfile == OMX_VIDEO_AVCProfileHigh) {
    .....
}

  

這個問題能夠說間接致使了MediaCodec編碼出來的視頻質量偏低,同等碼率下,難以得到跟軟編碼甚至iOS那樣的視頻質量。

3.16位對齊要求

前面說到,MediaCodec這個API在設計的時候,過於貼近HAL層,這在不少Soc的實現上,是直接把傳入MediaCodec的buffer,在不通過任何前置處理的狀況下就直接送入了Soc中。而在編碼h264視頻流的時候,因爲h264的編碼塊大小通常是16x16,因而乎在一開始設置視頻的寬高的時候,若是設置了一個沒有對齊16的大小,例如960x540,在某些cpu上,最終編碼出來的視頻就會直接花屏!

很明顯這仍是由於廠商在實現這個API的時候,對傳入的數據缺乏校驗以及前置處理致使的,目前來看,華爲,三星的Soc出現這個問題會比較頻繁,其餘廠商的一些早期Soc也有這種問題,通常來講解決方法仍是在設置視頻寬高的時候,統一設置成對齊16位以後的大小就行了。

二.FFMpeg+x264/openh264(軟編)

除了使用MediaCodec進行編碼以外,另一種比較流行的方案就是使用ffmpeg+x264/openh264進行軟編碼,ffmpeg是用於一些視頻幀的預處理。這裏主要是使用x264/openh264做爲視頻的編碼器。

x264基本上被認爲是當今市面上最快的商用視頻編碼器,並且基本上全部h264的特性都支持,經過合理配置各類參數仍是可以獲得較好的壓縮率和編碼速度的,限於篇幅,這裏再也不闡述h264的參數配置

openh264則是由思科開源的另一個h264編碼器,項目在2013年開源,對比起x264來講略顯年輕,不過因爲思科支付滿了h264的年度專利費,因此對於外部用戶來講,至關於能夠直接無償使用了,另外,firefox直接內置了openh264,做爲其在webRTC中的視頻的編解碼器使用。

但對比起x264,openh264在h264高級特性的支持比較差:

  • Profile只支持到baseline, level 5.2
  • 多線程編碼只支持slice based,不支持frame based的多線程編碼

從編碼效率上來看,openh264的速度也並不會比x264快,不過其最大的好處,仍是可以直接無償使用吧。

軟硬編對比

從上面的分析來看,

1.硬編的好處主要在於速度快,並且系統自帶不須要引入外部的庫,可是特性支持有限,並且硬編的壓縮率通常偏低,2.而對於軟編碼來講,雖然速度較慢,可是壓縮率比較高,並且支持的H264特性也會比硬編碼多不少,相對來講比較可控。就可用性而言,3.在4.4+的系統上,MediaCodec的可用性是可以基本保證的,可是不一樣等級的機器的編碼器能力會有很多差異,建議能夠根據機器的配置,選擇不一樣的編碼器配置。視頻流合流而後包裝到mp4文件,這部分咱們能夠經過

相關文章
相關標籤/搜索