NDK開發-Android下攝像頭YUV數據獲取與H264編碼(FFmpeg、x264)總結

涉及知識點:

  • Camera2 API使用
  • YUV420P與YUV420SP(NV21)格式轉換
  • h264文件格式
  • FFmpeg工程
  • x264解碼器

這次就先記錄一下開發過程,因爲牽涉到的很多技術問題都不太清楚,Android的知識都不太記得了,還有一些知識牽扯到這些開源工程的內部實現,待以後慢慢學習再寫。
這個小demo就是做一個攝像頭數據的獲取與編碼存儲,由於這個demo的目的是爲了給學習直播技術打基礎,所以對編碼速度有一定要求。
指標:編碼耗費時間必須在33.3ms以下
指標說明:由於攝像頭採集速度一般在30fps左右,所以編碼速度不能小於這個值,否則就會出現編碼速度跟不上視頻獲取速度的情況,造成幀的積壓與丟失。

很久沒有寫過Android代碼了,Android開發的資料還是豐富,官方文檔比較詳細,再看FFmpeg的文檔,非核心的函數也就有個返回值類型的說明了

  • Camera 2 API
    https://developer.android.google.cn/reference/android/hardware/camera2/package-summary
    Camera 2 API是Android API level 21以後添加的攝像頭相關庫,取代了Camera 1,使用方法有區別,Camera 1其實看起來更清晰,用起來很方便,但是據說Camera 2的優化更好,更省電。

  • YUV420P與NV21格式
    YUV420P格式和NV21都是對YUV數據進行採樣得到的,NV21別名YUV420SP,其中Y是針對亮度(灰度)取樣的值,U與V都是色度取樣的值,420是YUV的採樣比,四個Y分量對應一個U分量和一個V分量,那爲什麼不寫成411格式呢,YUV411與YUV420的區別在於他們的採樣方式不同,如圖:
    在這裏插入圖片描述
    420格式,U的取樣在水平方向上的步長是2,在豎直方向上也是2,V同理
    411格式,U、V的取樣在豎直方向上分辨率與Y一樣,水平方向上是Y的1/4

瞭解了YUV420採樣方式後,來看一下它的儲存方式,NV21與YUV420P的差別也在於存儲方式
YUV420P的存儲方式是先把所有Y存起來(YYYYYYYY),再存U(UU),再存V(VV)
而NV21的存儲方式是先存Y(YYYYYYYY),再將UV交叉存儲(VUVU)
在這裏插入圖片描述
所以NV21與YUV420P的轉換也就很清晰了
安卓手機獲取的格式一般是NV21,而解碼器一般支持的格式是YUV420P,所以,解碼之前要進行一下轉換。

注:我在做的時候,有一個小問題,利用Camera 2 和imagereader獲取到的YUV數據,雖然我知道是NV21格式的,但是發現它用三個數組將NV21數據存起來。
按理說既然UV交叉存儲那一般應該用兩個數組表示即可,而且Y數據的數量應該是像素總數,比如1920×1080的數據,數組大小就應該是2073600,UV數據總和應該是Y的1/2,也就是1036800
但是我發現它三個數組大小分別是2073600,1036800,1036800,爲什麼憑空多了一組1/2大小的數據

在這裏插入圖片描述
於是將數據輸出後發現,第三個數組與第二個數組其實是相同的,不過是錯位的,第三個數組相比第二個數組整體右移了一個字節,最前面用了一個值來填充
有意思的是我一開始進行轉換的時候,編碼後我打開視頻發現我的顏色有的不對,藍色變爲橙色,非常有趣,後將VU數據調換,按正常輸出,得知是由於VU數據搞反了,有空可以瞭解一下具體的原理。

  • h264文件格式

https://blog.csdn.net/h514434485/article/details/52064945

  • FFmpeg工程

之前通過雷神的博客瞭解到FFmpeg工程,用來做過攝像頭獲取到的H264數據的解碼
海迪康ipcamera客戶端開發紀實
FFmpeg是一款非常強大的音視頻處理工程,C語言編寫,寫的非常漂亮,可讀性很強,下一步繼續閱讀FFplay的源碼,和FFmpeg的編碼部分源碼
這次一開始用FFmpeg做H264的編碼工作,首先利用NDK在linux平臺上對FFmpeg與x264進行了交叉編譯,使之可在arm架構下運行,接下來的工作就是對FFmpeg進行參數調優。但是效果不理想後直接試用x264工程。

  • 編碼器調優總結

一開始使用ffmpe編碼(ffmpe的264視頻編碼也是基於X264庫),編碼一幀的時間非常不穩定,但都不滿足要求(一幀處理時間應達到33ms以下),一般在80ms以上,偶爾100-200ms
此時的ffmpeg設置是,ultrafast,zerolatency(無幀緩存),線程數3-6差距都不大(手機4核處理器,設置6是因爲看到有說線程數實際要除以1.5),與1比稍有提升
處理時間包括:

  1. 將NV21數據轉換成YUV420P的時間
  2. ffmpeg與X264編碼器之間的調用時間
  3. x264編碼器編碼的時間
  4. 寫文件的時間

寫文件時間佔用非常少,所以推測瓶頸在於ffmpeg這步調用,由於不是內部編碼器,所以可能要進行內存之間的複製
重新編譯了x264庫,配置了AS,重寫了一個調用264庫的類,封裝好給java層調用,編碼速度有了質的提升。
然後優化了一下轉換函數,縮減到10ms以下,編碼時間在20ms左右,基本上能滿足編碼需求,在多線程操作時,可以看出編碼速度已經可以匹配獲取圖像的速度,但是發現慢慢會落後待編碼幀,查找原因在於有時解碼速度會變成40-50ms一幀,這就造成慢慢拉大差距,我猜測是由於多線程導致,處理器沒有分配到。

各種參數調優後,發現編碼器的多線程編碼採用自適應比寫死好,比如幀編碼的並行數,片編碼的並行數,將其設爲自動

param.i_threads = 0; //幀編碼並行數,0爲自動
param.b_sliced_threads = 0; // 片編碼並行數,0爲自動

但是在性能提升後還是存在幀的堆積,說明解碼線程還是經常沒有被調度,導致時間拉長,所以決定在java層動手,干涉CPU的調度,之前用的是thread類的線程優先級設置方式,發現不適用於android,後採用android.os.Process類的設置優先級函數setThreadPriority(int )
與thread類的優先級衡量數值不同(10爲最高),查android文檔發現適合視頻線程的數值是-10,值越小優先級越高,直到-19。

這樣以後x264在編碼速度的優化上已經達到需求了。下一步進行編碼質量的優化(對碼率進行調優,縮小碼率,不影響質量),以及查看FFmpeg的源碼,看一看編碼流程,到底時間消耗在哪裏,且爲什麼有些設置不起作用,加上音頻的編碼功能,以及做一下音視頻同步及封裝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章