性能比肩美拍秒拍的Android視頻錄製編輯特效解決方案

               


圖片



前言

衆所周知,Android平臺開發分爲Java層和C++層,即Android SDK和Android NDK。常規產品功能只須要涉及到Java層便可,除非特殊須要是不須要引入NDK的。但若是是進行音視頻開發呢?java

Android系統Java層API對音視頻的支持在MediaCodec以前,還停留在很是抽象API的級別(即只提供簡單的參數和方法,能夠控制的行爲少,得不到中間數據,不能進行復雜功能的開發,更談不上擴展)。而在MediaCodec在推出以後,也未能完全解決問題,緣由有這些:一、MediaCodec出現的Android版本並不低,使用則沒法兼容低版本機器和系統版本;二、因爲Android的開源和定製特性,各大廠商實現的MediaCodec也不盡相同,也致使同一段代碼A機器跑着是這個樣,B機器跑着就是另外一個樣了。因此程序員童鞋們就把目光轉向了NDK,可是NDK裏面谷/歌並無提供什麼關於音視頻處理的API(好比解析生成文件,編解碼幀),因而童鞋們又想着使用開源的C/C++框架,首當其衝的固然是最出名的ffmpeg、x26四、mp3lame、faac這些了。問題又來了,ffmpeg最先對x86支持是最好的,arm平臺或者mips平臺支持就不這麼好了(筆者調研ffmpeg2.0之後狀況有所好轉)。那就只能使用軟解軟編,速度跟不上是個什麼體驗親們知道嗎?舉個栗子,假設要錄製640x480的視頻,音頻視頻所有使用軟編碼,x264若是純軟編碼加上手機CPU的處理性能50毫秒甚至100毫秒一幀都說不定,總之就是慢,還要算上音頻還要壓縮編碼。若是想錄制25幀率的視頻,一幀的編碼時間是不能超過40毫秒的,不然速度就跟不上了,算上其餘業務功能花的時間,這個時間起碼要降到30毫秒如下,而後再使用多線程異步編碼的方式優化一下應該勉強能達到邊獲取畫面邊生成視頻文件。正是由於有這樣那樣的不方便,筆者才通過幾個月的研究,找到了一個還不算太完美的解決方案供你們參考,本文將全面介紹各個環節的技術實現方案,最後並附上工程源碼。順便聲明一下,筆者在進行這項工做以前Android開發經驗基本上算是1(不是0是由於之前寫過helloworld),可是C/C++,Java都已經掌握,還在ios上使用objc開發過項目,因此我想Android也差別不大,語言不同,平臺不同,API不同,系統機制不同,其餘應該就同樣了。linux

NDK有哪些API可用?

先把NDK的include打開,普查一下到底NDK提供了哪些接口能夠用。谷/歌還算是有人性,其實除了linux系統級的API外,其實仍是有一些音視頻相關的API的。android

OpenSL,能夠直接在C++層操做音頻採集和播放設備,進行錄音和播放聲音,從API9開始支持。ios

圖片

EGL,能夠在C++層建立OpenGL的繪製環境,用於視頻圖像渲染,還能夠用於一些圖像處理如裁剪、拉伸、旋轉,甚至更高級的濾鏡特效處理也是能夠的。另外不得不說在C++本身建立OpenGL的渲染環境比使用Java層的GLSurfaceView靈活性、可控性、擴展性方面直接提高好幾個數量級。而EGL在API9也已經支持了。git

圖片

OpenGL(ES), NDK在Java層提供了OpenGL接口,而在NDK層也提供了更原生的OpenGL頭文件,而要使用GLSL那就必需要有OpenGLES2.0+了,還好NDK也很早就支持了,OpenGLES2.0在API5就開始支持了,萬幸!!程序員

圖片

OpenMAXAL,這是普查過程當中發現的一個讓人不爽的庫,由於從它的接口定義來看它有例如以比較抽象接口方式提供的播放視頻的功能和打開攝像頭的功能。播放視頻就用不到了,後面本身編解碼本身渲染實現,看到這個打開攝像頭的接口,心中當時是欣喜了一把的,結果是個人MX3竟然告訴我該接口沒實現。那結果就必須從Java層傳攝像頭的數據到C++層了。不過OpenMAXIL,前者的兄弟,卻是個好東西,惋惜谷/歌暫時沒有開放接口。github

圖片

這樣一來,圖像採集就必須從Java層打開Camera,而後獲取到數據以後經過JNI傳遞到C++層了。渲染圖像的View也要從java層建立SurfaceView而後傳遞句柄到C++層進而使用EGL來初始化OpenGL的渲染環境。聲音的採集和播放就和Java不要緊了,底層就能夠直接處理完了。redis

選擇開源框架

ffmpeg: 文件解析,圖像拉伸,像素格式轉換,大多數解碼器,筆者選用的2.7.5版本,有針對ARM的很多優化,解碼速度還算好。ubuntu

x264:  H264的編碼器,新的版本也對ARM有不少優化,若是使用多線程編碼一幀640x480能夠低至3-4毫秒。緩存

mp3lame:  MP3的編碼器,其實測試工程裏面沒用到(測試工程使用的MP4(H264+AAC)的組合),只是習慣性強迫症編譯了加進編碼器列表裏

faac: AAC的編碼器,也是好久沒更新了,編碼速度上算是拖後腿的,因此後面纔有個曲線救國的設計來解決音頻編碼的問題。

完整解決方案圖

圖片

音頻編碼慢的問題

x264和ffmpeg都下載比較新的版本,而後開啓asm,neon等優化選項編譯以後,編解碼速度還能接受。但是FAAC的編碼速度着實仍是有點慢。筆者因而乎想到個辦法,就是存儲臨時文件,在錄製的時候視頻數據直接調用x264編碼,不走ffmpeg中轉(這樣能夠更靈活配置x264參數,達到更快的目的),而音頻數據就直接寫入文件。這樣錄製的臨時文件其實和正兒八經的視頻文件大小差距不大,不會形成磁卡寫入速度慢的瓶頸問題,同時還可解決編輯播放的時候拖動進度條的準確度問題,同時解決關鍵幀抽幀的問題,由於臨時文件都是本身寫的,文件裏什麼內容均可以本身掌控。不得不說一個問題就是定義的抽象視頻文件讀取寫入接口Reader和Writer,而讀取寫入正式MP4文件的實現和讀取寫入臨時文件的實現都是實現這個Reader和Writer的,因此往後想改爲直接錄製的時候就生成MP4只須要初始化的時候new另外一個對象便可。還有一招來解決速度慢的問題就是多線程異步寫入,採集線程拿到數據以後丟給另外一個線程來進行編碼寫入,只要編碼寫入的平均速度跟得上幀率就能夠知足需求。

引入OpenGL2D/3D引擎

當在C++層使用EGL建立了OpenGL的渲染環境以後,就可使用任何C/C++編寫的基於OpenGL框架了。筆者這裏引入了COCOS2D-X來給視頻加一些特效,好比序列幀,粒子效果等。COCOS2D-X自己有本身的渲染線程和OpenGL渲染環境,須要把這些代碼幹掉以後,寫一部分代碼讓COCOS2D-X渲染到你本身建立的EGL環境上。另外COCOS2D-X的對象回收機制是模擬的Objective-C的引用計數和自動回收池方式,工程源碼中的COCOS2D-X回收機制筆者也進行了簡化修改,說實話我的以爲它的引用計數模擬的還能夠,和COM差很少的原理,統一基類就能夠實現,可是自動回收池就不用徹底照搬Objective-C了,不必搞回收池壓棧了,全局一個回收池就夠用了嘛。(純屬我的觀點)

主副線程模式

OpenGL的glMakeCurrent是線程敏感的,你們都知道。和OpenGL相關的全部操做都是線程敏感的,即文理加載,glsl腳本編譯連接,context建立,glDraw操做都要求在同一個線程內。而Android平臺沒有相似iOS上自帶的MainOperationQueue的方式,因此筆者本身設計了一個主副線程模式(我本身取的名字),即主線程就是Android的UI線程,負責UI繪製的響應按鈕Action。而後其餘全部操做都交給副線程來作。也就說每一種用戶的操做的響應函數都不直接幹事,而是學習MFC的方式,post一個消息和數據到副線程。那麼副線程就必然要用單線程調度消息循環和多任務的方式了,消息循環不說了,MFC的模式。單線程調度多任務可能好多童鞋沒接觸過,其實就是將傳統的單線程處理的任務,分紅不少個時間片,讓線程每次只處理一個時間片,而後緩存處理狀態,到下一次輪到它的時候再繼續處理。

好比任務接口是  IMission {bool onMissionStart();  bool onMissionStep(); void onMissionStop();}   調度線程先執行一次onMissionStart若是返回false則執行onMissionStop結束任務;若是前者返回true,則不斷的調用onMissionStep,直到返回false,再執行onMissionStop,任務結束。具體的處理都要封裝成任務接口的實現類,而後丟進任務列表。

試想,這樣的設計架構下,是否是全部的操做都在同一個線程裏了,OpenGL的調用也都在同一個線程裏了,還有附帶的效果就是媽媽不再用擔憂多線程併發處理處處加鎖致使的性能問題和bug問題了,不要懷疑它的性能,由於就算多線程到CPU那一級也變成了單線程了。redis不就是單線程的麼,速度快的槓槓的。

總結一下

  • 使用OpenSL錄音和播音

  • 使用EGL在C++層建立OpenGL環境

  • 改造COCOS2D-X,使用本身建立的OpenGL環境

  • 直接使用x264而非ffmpeg中轉,按最快的編碼方式配置參數,必定記得開啓x264的多線程編碼。

x264和ffmpeg都要下載比較新的,而且編譯的時候使用asm,neon等選項。(筆者是在ubuntu上跨平臺編譯的)

若是錄製的時候直接編碼視頻和音頻速度跟不上就寫入臨時文件,圖像編碼,聲音直接存PCM。

除了Android主線程外,另外只開一個副線程用於調度,具體小模塊耗時的任務就單獨開線程,框架主體上只存在兩個線程,一主一副。

完整工程源碼

使用的API15開發,實際上是能夠低到API9的。

源碼地址:http://download.csdn.net/detail/yangyk125/9416064

操做演示:http://www.tudou.com/programs/view/PvY9MMugbRw/

圖片

圖片

渲染完生成視頻的位置:/SD卡/e4fun/video/*.mp4

須要說明一下的是:

  • 一、com.android.video.camera.EFCameraView類 最前面兩個private字段定義當前選用的攝像頭分辨率寬度和高度,要求當前攝像頭支持這個分辨率。

  • 二、jni/WORKER/EFRecordWorker.cpp的createRecordWorker函數內,定義當前錄製視頻的各類基本參數,請根據測試機器的性能自由配置。

  • 三、jni/WORKER/EFRecordWorker.cpp的on_create_worker函數內,有個設置setAnimationInterval調用,設置OpenGL繪製幀率,和視頻幀率是兩回事,請酌情設置。

感謝一位讀了這篇博客的網友,給我指出了其中能夠優化的地方

  • 一、若是使用ffmpeg開源方案處理音視頻,那麼AAC應該使用fdk_aac而不該該使用好久沒更新的faac。

  • 二、glReadPixels回讀數據效率低下,筆者正在嘗試升級到gles3.0看看能不能有什麼辦法快速獲取渲染結果圖像,若是您知道,請在後面留言,謝謝啦!

在Android上作音視頻處理,若是還想要更快的編解碼,若是是Java層則逃不開MediaCodec,若是是C++層,能夠向下研究,好比OpenMAXIL等等。

後記:

通過半年努力,解決了其中部分有效率問題的地方

(1)編解碼部分

編解碼部分以前文章採用的X264+FFMPEG的開源方案,而繼續學習以後,找到了android上特有的實現方案。

版本<4.4:x264+ffmpeg or 私有API(libstagefright.so)。

版本=4.4:jni反調android.media.MediaCodec or 或者在java層開發。

版本>4.4:NdkMediaCodec(android.media.MediaCodec 的 jni接口)。

(2)AAC更優開源方案

AAC開源方案FDKAAC一直在更新,效率有提高,而faac早就不更新了。so…你懂的。

AAC也可使用MediaCodec或者NdkMediaCodec

(3)OpenGL之framebuffer數據的回讀

GLES版本<3.0:使用glReadPixels 或者 EGLImageKHR(eglCreateImageKHR,glEGLImageTargetTexture2DOES)

GLES版本=3.0:Pixel Pack Buffer + glMapBufferRange。

Android版本>=4.2:還有一個android平臺化的回讀FrameBuffer的方案,那就是新建SurfaceTexture和Surface,而後新建立一個OpenGL Context,一比一再渲染一次,便可將FrameBuffer渲染到這個SurfaceTexture上面,surface還能夠做爲編碼器的輸入。這樣不只能夠快速從渲染結果傳遞數據到編碼器,還能實現跨線程傳遞紋理數據,屬於android平臺自己提供的功能,非opengl自帶能力。之因此是4.2,是由於SurfaceTexture在4.2之後才基本完善,以前各類不穩定。

https://github.com/yangyk125/AndroidVideo

原創做者:花崗岩是甜的 ,原文連接:https://blog.csdn.net/yangyk125/article/details/50571304 

相關文章
相關標籤/搜索