視訊中磨皮、美顏功能已成為剛需,那麼如何在Android短視訊中實現720P磨皮美顏錄製?本篇文章中,網易雲信資深開發工程師將向大家介紹具體的操作方法。
相關閱讀推薦
在Android上要實現一個錄製功能,需要有幾個方面的知識儲備:自定義相機的開發、視訊資料格式的瞭解、編碼相關知識以及視訊合成技術,同時如果需要美顏、磨皮等濾鏡操作還需要一定的openGL的知識。如果有需要深入音視訊方面開發的同學建議先了解下上述的基本知識點。
既然要實現720P、30幀,同時又要對視訊資料進行濾鏡處理的錄製,那我們首先就要確定一個正確的實現方案。如果方案選錯了,那即使實現了錄製功能,但效能達不到30幀或是CPU消耗太大手機發燙那就不好了。
視訊的編碼錄製主要是軟編和硬編兩種方案:
軟編即採用CPU對相機採集的原始資料進行編碼後再和音訊一起合併成一個MP4等格式的檔案。
優點是技術相對成熟,網上開源的編碼以及合成庫很多,實現相對較快,同時相容性比較好。
缺點是CPU暫用率高,效能差的手機無法達到720P的30幀,同時引用了大量的第三方庫,導致包很大。
軟編的具體實現方案如下圖所示,流程相對清晰簡單:
硬編即採用手機提供的硬編介面,利用硬體晶片直接進行編碼合成。
優點是速度快、效率高、CPU佔用極少,即使長時間高清錄製也不會發燙,同時由於使用系統API,庫相對較小。
缺點是某些奇葩機型需要處理相容性問題,同時Android上的硬編跟Surface以及openGL關係比較密切,網上相關知識較少,需要自己摸索踩坑。
硬編的主要流程如下圖所示,可以看到所有的資料,從採集、編碼、顯示以及合成都在GPU裡面進行流轉。
結合上面分析的兩種方案我們可以看到,在Android這類移動平臺上,使用第二種硬編的方式是比較合適的。由於短視訊的本地錄製不像直播等場景對頻寬的要求比較大,需要動態調節編碼器位元速率幀率的情況,本地錄製可以將編碼器的位元速率設定的比較高,也不需要動態改變解析度。因此採用硬體編碼的方式既可以省CPU的效能又可以實現720P的高效編碼。
確定了方案之後,我們就著重講一下硬編方案的各個步驟的實現方式。
自定義相機的開發
我們知道根據Android的系統Camera API,可以通過setPreviewDisplay介面給Camera設定一個SurfaceView的SurfaceHolder就可以讓Camera採集的資料顯示到SurfaceView上了。這個介面的好處是系統幫我們處理了相機採集的各種角度同時進行了繪製,如果只是簡單的錄製可以這麼使用,但我們需要對相機採集的資料進行濾鏡處理,那這個介面就不合適了。
因此我們需要用到另外一個介面 setPreviewTexture:
通過給Camera設定一個SurfaceTexture,可以將Camera採集的資料先對映到這個SurfaceTexture上,然後我們根據建立這個SurfaceTexture的TextureID來獲取GPU上的Camera資料
濾鏡以及本地繪製
我們通過SurfaceTexture繫結的TextureID可以獲取到Camera採集到GPU上的視訊資料。然後可以將TextureID送給一些第三方濾鏡庫進行美顏濾鏡或是自己編寫Shader進行磨皮和美白。自己編寫Shader需要opengl以及影象演算法方面的知識,通常需要專門的開發人員,這裡就不做詳細的展開了(當然最簡單的就是接入網易雲短視訊SDK了,裡面實現了磨皮、美顏和多款濾鏡)。
本地繪製主要靠openGL進行繪製,我們需要先在Camera的採集回撥執行緒上建立一個EGLContext以及EGLDisplay和EGLSurface, 其中EGLContext是openGL在該執行緒上的上下文,EGLDisplay是一塊GPU中的虛擬顯示區,主要用於快取GPU上的視訊資料,EGLSurface為具體顯示的View到openGL上的對映,是真正繪製到View上的工具。當接受到Camera採集回撥的一幀資料後,我們先通過SurfaceTexture.updateTexImage()方法,將Camera採集的資料對映到SurfaceTexture。然後根據glsl語言將TextureID對應的資料繪製到EGLDisplay上,這裡需要注意的是,Camera採集是有角度的,橫豎屏下角度不同,可以通過SurfaceTexture的getTransformMatrix方法獲取角度矩陣,然後把矩陣傳給EGLDisplay進行旋轉。EGLDisplay旋轉繪製完成後通過eglSwapBuffers方法就可以將EGLDisplay上的資料拷貝到EGLSurface上進行顯示了。Android 系統中的GLSurfaceView最後就是通過eglSwapBuffers將資料顯示到我們看到的螢幕上的。
硬體編碼
Android上的硬體編碼主要靠MediaCodeC API實現的,下面是MediaCodeC比較經典的一張資料處理圖。
從圖中我們看到,MediaCodeC主要處理流程就是:
- 建立並配置一個 MediaCodec 物件
- 迴圈直到完成:
如果輸出緩衝區就緒,複製輸出緩衝區的資料
- 釋放 MediaCodec 物件
從Android的官方文件我們看到,MediaCodeC支援ByteBuffers和Surface兩種輸入方式,文件也指明瞭Surface方式可以提高編碼效率,而且我們上面的Camera資料也是採集到的SurfaceTexture,因此我們這裡使用Surface方式作為輸入源。
我們在上面顯示部分提到EGLSurface是作為真正輸出顯示的模組,MediaCodec也是。我們先通過MediaCodec建立一個Surface,然後將這個Surface繫結到一個EGLSurface,當Camera採集的資料回撥時,我們只要重複一次繪製模組的操作,將Camera採集到SurfaceTexture上的資料swapBuffers到EGLSurface 上就可以了。然後迴圈MediaCodec輸出緩衝區,MediaCodec就會將編碼後的資料返回給我們了。這樣做的好處就是將顯示和編碼完全分離了,即使我們沒有UI View的情況下也可以進行編碼,比如在不同Activity之間切換也不會影響我們的正常編碼。
視訊合成
Android上視訊合成主要通過MediaMuxer API實現。MediaMuxer類相對比較簡單,特別是配合MediaCodec使用。 我們只需要通過 addTrack 來新增視訊和音訊通道介面。AddTrack 介面需要傳入一個MediaFormat物件,MediaFormat即媒體格式類,用於描述媒體的格式引數,如視訊幀率、音訊取樣率等。還好我們使用了MediaCodeC,MediaCodeC會返回MediaFormat給我們,如果是使用軟編然後用MediaMuxer進行合併的話,這裡有一個比較大的坑,如果手動建立MediaFormat物件的話,一定要記得設定"csd-0"和"csd-1"這兩個引數:其中"csd-0"和"csd-1"對應的是視訊的sps和pps,對於AAC音訊的話,對應的是ADTS。不設定的話會崩潰的。 設定完這些之後,只要編碼器來一幀資料,我們送到MediaMuxer中可以出來MP4了。
最後大體的流程圖就是:
以上就是在Android短視訊中實現720P磨皮美顏錄製的分享。
另外,想要獲取更多產品乾貨、技術乾貨,記得關注網易雲信部落格。