天天看點

微信團隊分享:微信Android版小視訊編碼填過的那些坑1、前言 2、視訊編碼器的選擇3、MediaCodec4、FFMpeg+x264/openh2645、軟硬編對比6、YUV幀的預處理7、參考資料附錄1:音視訊基礎資料彙總附錄2:有關微信、QQ的文章彙總

1、前言

Android端的視訊相關的開發,大概一直是整個Android生态,以及Android API中,最為分裂以及相容性問題最為突出的一部分。攝像頭,以及視訊編碼相關的API,Google一直對這方面的控制力非常差,導緻不同廠商對這兩個API的實作有不少差異,而且從API的設計來看,一直以來優化也相當有限,甚至有人認為這是“Android上最難用的API之一”

以微信的小視訊為例,我們錄制一個540p的mp4檔案,對于Android來說,大體上是遵循這麼一個流程:

大體上就是從攝像頭輸出的YUV幀經過預處理之後,送入編碼器,獲得編碼好的h264視訊流。

上面隻是針對視訊流的編碼,另外還需要對音頻流單獨錄制,最後再将視訊流和音頻流進行合成出最終視訊。

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

1)視訊編碼器的選擇:硬編、軟編;

2)如何對攝像頭輸出的YUV幀進行快速預處理:鏡像、縮放、旋轉。

(本文同步釋出于:

http://www.52im.net/thread-1173-1-1.html

2、視訊編碼器的選擇

對于錄制視訊的需求,不少app都需要對每一幀資料進行單獨處理,是以很少會直接用到MediaRecorder來直接錄取視訊。

一般來說,會有這麼兩個選擇:

1)MediaCodec;

2)FFMpeg+x264/openh264。

我們來逐個解析一下。

3、MediaCodec

3.1 基本介紹

MediaCodec是API 16之後Google推出的用于音視訊編解碼的一套偏底層的API,可以直接利用硬體加速進行視訊的編解碼。調用的時候需要先初始化MediaCodec作為視訊的編碼器,然後隻需要不停傳入原始的YUV資料進入編碼器就可以直接輸出編碼好的h264流。

整個API設計模型來看,就是同時包含了輸入端和輸出端的兩條隊列:

是以,作為編碼器,輸入端隊列存放的就是原始YUV資料,輸出端隊列輸出的就是編碼好的h264流,作為解碼器則對應相反。在調用的時候,MediaCodec提供了同步和異步兩種調用方式,但是異步使用Callback的方式是在API 21之後才加入的。

以同步調用為例,一般來說調用方式大概是這樣(摘自官方例子):

簡單解釋一下,通過getInputBuffers擷取輸入隊列,然後調用dequeueInputBuffer擷取輸入隊列空閑數組下标,注意dequeueOutputBuffer會有幾個特殊的傳回值表示目前編解碼狀态的變化,然後再通過queueInputBuffer把原始YUV資料送入編碼器,而在輸出隊列端同樣通過getOutputBuffers和dequeueOutputBuffer擷取輸出的h264流,處理完輸出資料之後,需要通過releaseOutputBuffer把輸出buffer還給系統,重新放到輸出隊列中。

關于MediaCodec更複雜的使用例子,可以參照下CTS測試裡面的使用方式:

EncodeDecodeTest.java

從上面例子來看的确是非常原始的API,由于MediaCodec底層是直接調用了手機平台硬體的編解碼能力,是以速度非常快,但是因為Google對整個Android硬體生态的掌控力非常弱,是以這個API有很多問題。

3.2 顔色格式問題

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+的系統上,這兩種格式在大部分機器都有支援:

兩種格式分别是YUV420P和NV21,如果機器上隻支援YUV420P格式的情況下,則需要先将攝像頭輸出的NV21格式先轉換成YUV420P,才能送入編碼器進行編碼,否則最終出來的視訊就會花屏,或者顔色出現錯亂。

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

3.3 編碼器支援特性相當有限

如果使用MediaCodec來編碼H264視訊流,對于H264格式來說,會有一些針對壓縮率以及碼率相關的視訊品質設定,典型的諸如Profile(baseline, main, high),Profile Level, Bitrate mode(CBR, CQ, VBR),合理配置這些參數可以讓我們在同等的碼率下,獲得更高的壓縮率,進而提升視訊的品質。

Android也提供了對應的API進行設定,可以設定到MediaFormat中這些設定項:

但問題是,對于Profile,Level, Bitrate mode這些設定,在大部分手機上都是不支援的,即使是設定了最終也不會生效,例如設定了Profile為high,最後出來的視訊依然還會是Baseline....

這個問題,在7.0以下的機器幾乎是必現的,其中一個可能的原因是,Android在源碼層級hardcode了profile的的設定:

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

這個問題可以說間接導緻了MediaCodec編碼出來的視訊品質偏低,同等碼率下,難以獲得跟軟編碼甚至iOS那樣的視訊品質。

3.4 16位對齊要求

前面說到,MediaCodec這個API在設計的時候,過于貼近HAL層,這在很多Soc的實作上,是直接把傳入MediaCodec的buffer,在不經過任何前置處理的情況下就直接送入了Soc中。而在編碼h264視訊流的時候,由于h264的編碼塊大小一般是16x16,于是乎在一開始設定視訊的寬高的時候,如果設定了一個沒有對齊16的大小,例如960x540,在某些cpu上,最終編碼出來的視訊就會直接花屏!

很明顯這還是因為廠商在實作這個API的時候,對傳入的資料缺少校驗以及前置處理導緻的,目前來看,華為,三星的Soc出現這個問題會比較頻繁,其他廠商的一些早期Soc也有這種問題,一般來說解決方法還是在設定視訊寬高的時候,統一設定成對齊16位之後的大小就好了。

4、FFMpeg+x264/openh264

除了使用MediaCodec進行編碼之外,另外一種比較流行的方案就是使用ffmpeg+x264/openh264進行軟編碼,ffmpeg是用于一些視訊幀的預處理。這裡主要是使用x264/openh264作為視訊的編碼器。

x264基本上被認為是當今市面上最快的商用視訊編碼器,而且基本上所有h264的特性都支援,通過合理配置各種參數還是能夠得到較好的壓縮率和編碼速度的。

限于篇幅,這裡不再闡述h264的參數配置,有興趣可以看下這兩篇文章對x264編碼參數的調優: https://www.nmm-hd.org/d/index.php?title=X264%E4%BD%BF%E7%94%A8%E4%BB%8B%E7%BB%8D&variant=zh-cn http://www.cnblogs.com/wainiwann/p/5647521.html

openh264(

https://github.com/cisco/openh264

)則是由思科開源的另外一個h264編碼器,項目在2013年開源,對比起x264來說略顯年輕,不過由于思科支付滿了h264的年度專利費,是以對于外部使用者來說,相當于可以直接免費使用了,另外,firefox直接内置了openh264,作為其在webRTC中的視訊的編解碼器使用。

但對比起x264,openh264在h264進階特性的支援比較差:

1)Profile隻支援到baseline, level 5.2;

2)多線程編碼隻支援slice based,不支援frame based的多線程編碼;

3)從編碼效率上來看,openh264的速度也并不會比x264快,不過其最大的好處,還是能夠直接免費使用吧。

5、軟硬編對比

從上面的分析來看,硬編的好處主要在于速度快,而且系統自帶不需要引入外部的庫,但是特性支援有限,而且硬編的壓縮率一般偏低,而對于軟編碼來說,雖然速度較慢,但是壓縮率比較高,而且支援的H264特性也會比寫死多很多,相對來說比較可控。就可用性而言,在4.4+的系統上,MediaCodec的可用性是能夠基本保證的,但是不同等級的機器的編碼器能力會有不少差别,建議可以根據機器的配置,選擇不同的編碼器配置。

6、YUV幀的預處理

根據最開始給出的流程,在送入編碼器之前,我們需要先對攝像頭輸出的YUV幀進行一些前置處理。

6.1 縮放

如果設定了camera的預覽大小為1080p的情況下,在onPreviewFrame中輸出的YUV幀直接就是1920x1080的大小,如果需要編碼跟這個大小不一樣的視訊,我們就需要在錄制的過程中,實時的對YUV幀進行縮放。

以微信為例,攝像頭預覽1080p的資料,需要編碼960x540大小的視訊。

最為常見的做法是使用ffmpeg這種的sws_scale函數進行直接縮放,效果/性能比較好的一般是選擇SWS_FAST_BILINEAR算法:

在nexus 6p上,直接使用ffmpeg來進行縮放的時間基本上都需要40ms+,對于我們需要錄制30fps的來說,每幀處理時間最多就30ms左右,如果光是縮放就消耗了如此多的時間,基本上錄制出來的視訊隻能在15fps上下了。

很明顯,直接使用ffmpeg進行縮放是在是太慢了,不得不說swsscale簡直就是ffmpeg裡面的渣渣。

在對比了幾種業界常用的算之後,我們最後考慮實作使用這種快速縮放的算法: 我們選擇一種叫做的局部均值算法,前後兩行四個臨近點算出最終圖檔的四個像素點,對于源圖檔的每行像素,我們可以使用Neon直接實作,以縮放Y分量為例:

上面使用的Neon指令每次隻能讀取和存儲8或者16位的資料,對于多出來的資料,隻需要用同樣的算法改成用C語言實作即可。

在使用上述的算法優化之後,進行每幀縮放,在Nexus 6p上,隻需要不到5ms就能完成了,而對于縮放品質來說,ffmpeg的SWS_FAST_BILINEAR算法和上述算法縮放出來的圖檔進行對比,峰值信噪比(psnr)在大部分場景下大概在38-40左右,品質也足夠好了。

6.2 旋轉

在android機器上,由于攝像頭安裝角度不同,onPreviewFrame出來的YUV幀一般都是旋轉了90或者270度,如果最終視訊是要豎拍的,那一般來說需要把YUV幀進行旋轉。

對于旋轉的算法,如果是純C實作的代碼,一般來說是個O(n^2 ) 複雜度的算法,如果是旋轉960x540的yuv幀資料,在nexus 6p上,每幀旋轉也需要30ms+,這顯然也是不能接受的。

在這裡我們換個思路,能不能不對YUV幀進行旋轉?

事實上在mp4檔案格式的頭部,我們可以指定一個旋轉矩陣,具體來說是在moov.trak.tkhd box裡面指定,視訊播放器在播放視訊的時候,會在讀取這裡矩陣資訊,進而決定視訊本身的旋轉角度,位移,縮放等,具體可以參考下蘋果的文檔:

https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737

通過ffmpeg,我們可以很輕松的給合成之後的mp4檔案打上這個旋轉角度:

于是可以在錄制的時候省下一大筆旋轉的開銷了,excited!

6.3 鏡像

在使用前置攝像頭拍攝的時候,如果不對YUV幀進行處理,那麼直接拍出來的視訊是會鏡像翻轉的,這裡原理就跟照鏡子一樣,從前置攝像頭方向拿出來的YUV幀剛好是反的,但有些時候拍出來的鏡像視訊可能不合我們的需求,是以這個時候我們就需要對YUV幀進行鏡像翻轉。

但由于攝像頭安裝角度一般是90或者270度,是以實際上原生的YUV幀是水準翻轉過來的,是以做鏡像翻轉的時候,隻需要剛好以中間為中軸,分别上下交換每行資料即可,注意Y跟UV要分開處理。

這種算法用Neon實作相當簡單:

同樣,剩餘的資料用純C代碼實作就好了, 在nexus6p上,這種鏡像翻轉一幀1080x1920 YUV資料大概隻要不到5ms。在編碼好h264視訊流之後,最終處理就是把音頻流跟視訊流合流然後包裝到mp4檔案,這部分我們可以通過系統的MediaMuxer, mp4v2, 或者ffmpeg來實作,這部分比較簡單,在這裡就不再闡述了。

7、參考資料

1)雷霄骅(leixiaohua1020)的專欄: http://blog.csdn.net/leixiaohua1020

大名鼎鼎雷神的部落格,裡面有非常多關于音視訊編碼/ffmpeg相關的學習資料,入門必備。也祝願他能夠在天堂安息吧。

2)Android MediaCodec stuff: http://bigflake.com/mediacodec/

包含了一些MediaCodec使用的示例代碼,初次使用可以參考下這裡。

3)Coding for NEON: https://community.arm.com/processors/b/blog/posts/coding-for-neon---part-1-load-and-stores

一個系列教程,講述了一些常用Neon指令使用方法。上面在介紹縮放的時候使用到了Neon,事實上大部分音視訊處理過程都會使用到,以YUV幀處理為例,縮放,旋轉,鏡像翻轉都可以使用neon來做優化。

4)libyuv: https://chromium.googlesource.com/libyuv/libyuv/

Google開源的一個YUV處理庫,上面隻針對1080p->540p視訊幀縮放的算法,而對于通用的壓縮處理,可以直接使用這裡的實作,對比起ffmpeg的速度快上不少。

(原文連結:

https://www.qcloud.com/community/article/893795

附錄1:音視訊基礎資料彙總

即時通訊音視訊開發(一):視訊編解碼之理論概述

即時通訊音視訊開發(二):視訊編解碼之數字視訊介紹 即時通訊音視訊開發(三):視訊編解碼之編碼基礎 即時通訊音視訊開發(四):視訊編解碼之預測技術介紹 即時通訊音視訊開發(五):認識主流視訊編碼技術H.264 即時通訊音視訊開發(六):如何開始音頻編解碼技術的學習 即時通訊音視訊開發(七):音頻基礎及編碼原理入門 即時通訊音視訊開發(八):常見的實時語音通訊編碼标準 即時通訊音視訊開發(九):實時語音通訊的回音及回音消除�概述 即時通訊音視訊開發(十):實時語音通訊的回音消除�技術詳解 即時通訊音視訊開發(十一):實時語音通訊丢包補償技術詳解 即時通訊音視訊開發(十二):多人實時音視訊聊天架構探讨 即時通訊音視訊開發(十三):實時視訊編碼H.264的特點與優勢 即時通訊音視訊開發(十四):實時音視訊資料傳輸協定介紹 即時通訊音視訊開發(十五):聊聊P2P與實時音視訊的應用情況 即時通訊音視訊開發(十六):移動端實時音視訊開發的幾個建議 即時通訊音視訊開發(十七):視訊編碼H.264、VP8的前世今生

附錄2:有關微信、QQ的文章彙總

[1] 有關QQ、微信的技術文章:
微信團隊分享:微信Android版小視訊編碼填過的那些坑 微信手機端的本地資料全文檢索優化之路 企業微信用戶端中組織架構資料的同步更新方案優化實戰 微信團隊披露:微信界面卡死超級bug“15。。。。”的來龍去脈 QQ 18年:解密8億月活的QQ背景服務接口隔離技術 月活8.89億的超級IM微信是如何進行Android端相容測試的 以手機QQ為例探讨移動端IM中的“輕應用” 一篇文章get微信開源移動端資料庫元件WCDB的一切! 微信用戶端團隊負責人技術訪談:如何着手用戶端性能監控和優化 微信背景基于時間序的海量資料冷熱分級架構設計實踐 微信團隊原創分享:Android版微信的臃腫之困與子產品化實踐之路 微信背景團隊:微信背景異步消息隊列的優化更新實踐分享 微信團隊原創分享:微信用戶端SQLite資料庫損壞修複實踐 騰訊原創分享(一):如何大幅提升移動網絡下手機QQ的圖檔傳輸速度和成功率 騰訊原創分享(二):如何大幅壓縮移動網絡下APP的流量消耗(下篇) 騰訊原創分享(二):如何大幅壓縮移動網絡下APP的流量消耗(上篇) 微信Mars:微信内部正在使用的網絡層封裝庫,即将開源 如約而至:微信自用的移動端IM網絡層跨平台元件庫Mars已正式開源 開源libco庫:單機千萬連接配接、支撐微信8億使用者的背景架構基石 [源碼下載下傳] 微信新一代通信安全解決方案:基于TLS1.3的MMTLS詳解 微信團隊原創分享:Android版微信背景保活實戰分享(程序保活篇) 微信團隊原創分享:Android版微信背景保活實戰分享(網絡保活篇) Android版微信從300KB到30MB的技術演進(PPT講稿) [附件下載下傳] 微信團隊原創分享:Android版微信從300KB到30MB的技術演進 微信技術總監談架構:微信之道——大道至簡(演講全文) 微信技術總監談架構:微信之道——大道至簡(PPT講稿) [附件下載下傳] 如何解讀《微信技術總監談架構:微信之道——大道至簡》 微信海量使用者背後的背景系統存儲架構(視訊+PPT) [附件下載下傳] 微信異步化改造實踐:8億月活、單機千萬連接配接背後的背景解決方案 微信朋友圈海量技術之道PPT [附件下載下傳] 微信對網絡影響的技術試驗及分析(論文全文) 一份微信背景技術架構的總結性筆記 架構之道:3個程式員成就微信朋友圈日均10億釋出量[有視訊] 快速裂變:見證微信強大背景架構從0到1的演進曆程(一) 快速裂變:見證微信強大背景架構從0到1的演進曆程(二) 微信團隊原創分享:Android記憶體洩漏監控和優化技巧總結 全面總結iOS版微信更新iOS9遇到的各種“坑” 微信團隊原創資源混淆工具:讓你的APK立減1M 微信團隊原創Android資源混淆工具:AndResGuard [有源碼] Android版微信安裝包“減肥”實戰記錄 iOS版微信安裝包“減肥”實戰記錄 移動端IM實踐:iOS版微信界面卡頓監測方案 微信“紅包照片”背後的技術難題 移動端IM實踐:iOS版微信小視訊功能技術方案實錄 移動端IM實踐:Android版微信如何大幅提升互動性能(一) 移動端IM實踐:Android版微信如何大幅提升互動性能(二) 移動端IM實踐:實作Android版微信的智能心跳機制 移動端IM實踐:WhatsApp、Line、微信的心跳政策分析 移動端IM實踐:谷歌消息推送服務(GCM)研究(來自微信) 移動端IM實踐:iOS版微信的多裝置字型适配方案探讨 信鴿團隊原創:一起走過 iOS10 上消息推送(APNS)的坑 騰訊信鴿技術分享:百億級實時消息推送的實戰經驗 >> 更多同類文章 ……
[2] 有關QQ、微信的技術故事:
騰訊開發微信花了多少錢?技術難度真這麼大?難在哪? 技術往事:創業初期的騰訊——16年前的冬天,誰動了馬化騰的代碼 技術往事:史上最全QQ圖示變遷過程,追尋IM巨人的演進曆史 技術往事:“QQ群”和“微信紅包”是怎麼來的? 開發往事:深度講述2010到2015,微信一路風雨的背後 開發往事:微信千年不變的那張閃屏圖檔的由來 開發往事:記錄微信3.0版背後的故事(距微信1.0釋出9個月時) 一個微信實習生自述:我眼中的微信開發團隊 首次揭秘:QQ實時視訊聊天背後的神秘組織

繼續閱讀