Android調用第三方C++算法庫

背景

現在越來越多應用包含一些第三方C/C++算法庫, 比如圖像處理, 人臉檢測, 語音識別等等. 第三方提供的算法庫都是C/C++動態(tài)庫(.so), 不同的提供商提供的接口存在差異, 主要分為以下兩種:

  1. 提供Java接口和so庫
    這種類型調用很簡單, 把so庫放到打包到apk或者Android系統中, 通過Java接口調用即可, JNI部分代碼提供商都寫好了.
  1. 只提供C/C++接口和so庫
    此類型調用稍微麻煩些, 需要自己寫編譯規(guī)則和JNI代碼

由于一般算法庫既可能集成在App中, 也有可能集成在Android系統中, 所以大部分算法提供商都是只提供C/C++接口, 這樣就更省事, 只是在App集成稍微麻煩些,下面就講一下這兩種方式如何集成和調用.

App中調用C/C++算法庫

由于提供的算法庫中并沒有JNI部分代碼, 所以我們需要自己寫JNI代碼, 并在native方法中調用算法提供的接口, 最終會產生兩個so庫, 一個包含我們的native代碼, 另一個就是算法庫, 集成步驟如下:

1.編寫JNI代碼

這部分可參看資料很多, 本文不做介紹,直接略過,可參考我之前寫的文章:Android JNI 函數注冊的兩種方式(靜態(tài)注冊/動態(tài)注冊)

2.編寫Android.mk和Application.mk

so庫是通過NDK編譯產生的, 我們需要編寫編譯規(guī)則,其中主要注意的有兩點, 1. 算法商提供的so是以預置的方式(prebuild)進行編譯的. 2.我們寫的native代碼也是編譯成so文件, 并且要依賴于算法so庫.

假設算法提供的so庫名字為libalgo.so, 提供的頭文件為algo.h, 可供調用的方法為 const char* getVersion(), 我們寫的代碼在test.cpp中 , jni目錄結構如下:

jni
│─Android.mk
│─Application.mk
│─test.cpp
│
├─include
│── algo.h
│
├─lib
│── libalgo.so

Android.mk代碼如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := algo
LOCAL_SRC_FILES := lib/libalgo.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
#預編譯算法庫
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.cpp
#依賴的算法庫
LOCAL_SHARED_LIBRARIES := libalgo
#編譯為動態(tài)庫
include $(BUILD_SHARED_LIBRARY)

Application.mk

#編譯arm架構32位so庫, 64位為 arm64-v8a
APP_ABI := armeabi-v7a
APP_PLATFORM := android-14

test.cpp部分代碼如下:

#include "jni.h"
#include "algo.h"

int test(JNIEnv *env,jclass obj)
{
    const char* algoVersion = getVersion();
    //其他代碼....
    return 0;
}

3. 編譯運行

在jni目錄下執(zhí)行ndk-build即可生成兩個so庫, 分別為libtest.so和libalgo.so, 將so打包到apk并在java代碼中加載so庫

System.loadLibrary("test");

加載后調用對應java native方法即可.

注: NDK編譯時候LOCAL_MODULE的名字不需要帶lib前綴, NDK會自動補上, 同時Java 加載時也不需要寫lib前綴,并且我們只需加載libtest.so, 當加載libtest.so時, 會自動加載libalgo.so

Android系統中通過C/C++調用算法庫

對于手機廠商(ODM/OEM)來說, 集成算法都是在直接集成到Android系統中, 這樣效率會比較高, 集成的位置一般是HAL層, 比如Camera美顏, 降噪等算法基本都集成在Camera HAL層.Camera HAL層都是C/C++代碼, 直接調用相應算法庫即可, 但由于Android系統中編譯方式是由Android.mk來組成, 并且和NDK中的Android.mk有差異, 因此主要不同點在于mk文件的編寫, 步驟如下:

同樣假設算法商提供的so庫名字為libalgo.so, 提供的頭文件為algo.h, 可供調用的方法為 const char* getVersion();

1. 在Android源碼中預編譯算法庫

比如我經常在高通平臺HAL層集成Camera相關算法, 則在 hardware/qcom/camera/QCamera2/HAL/ 文件夾下新建目錄, 用于存放算法庫和頭文件,以及Android.mk, 假設目錄為 hardware/qcom/camera/QCamera2/HAL/algo/
目錄內容如下:

algo
│─Android.mk
│
├─include
│── algo.h
│
├─lib
│── libalgo.so

Android.mk內容

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#默認不會加上lib前綴
LOCAL_MODULE := libalgo
#不管是release還是debug版本, 都編譯這個模塊
LOCAL_MODULE_TAGS := optional
#so文件路徑以及名字 
LOCAL_SRC_FILES := lib/libalgo.so
LOCAL_MODULE_STEM := $(LOCAL_MODULE)
#需要指定文件后綴
LOCAL_MODULE_SUFFIX := $(suffix $(LOCAL_SRC_FILES))
#編譯32位算法庫
LOCAL_MULTILIB := 32
#表明預編譯的是動態(tài)庫
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
include $(BUILD_PREBUILT)

可以看到源碼編譯和NDK編譯Android.mk的內容有較大差異, 主要表現在如下方面:

  • LOCAL_MODULE默認不會加上lib前綴
  • 要指定文件后綴
  • 要說明編譯的文件類型(動態(tài)庫, 靜態(tài)庫,apk等等)
  • 預編譯使用 BUILD_PREBUILT而不是 PREBUILT_SHARED_LIBRARY

2. 在調用的模塊Android.mk中加入依賴庫文件

預編譯完成后, 要告訴調用模塊, 需要依賴這個so庫和頭文件, 同樣以在Camera HAL層調用為例,
修改 hardware/qcom/camera/QCamera2/HAL/Android.mk, 修改內容如下:

#部分代碼省略
LOCAL_C_INCLUDES := \
        $(LOCAL_PATH)/../util \
        $(LOCAL_PATH)/wrapper

# 引入調用算法庫的頭文件
LOCAL_C_INCLUDES += \
        $(LOCAL_PATH)/algo/include
#部分代碼省略
LOCAL_SHARED_LIBRARIES += libqdMetaData libqservice libbinder

# 表明當前HAL模塊依賴我們要調用的算法庫
LOCAL_SHARED_LIBRARIES += libalgo

#部分代碼省略
#在最末尾出引入我們預編譯的Android.mk, 不然默認系統不會執(zhí)行我們寫的預編譯用的Android.mk
include $(LOCAL_PATH)/algo/Android.mk

3. 調用算法庫

調用算法庫方法和App中一樣, 引入頭文件調用對應函數即可. 比如在 QCamera2HWI.cpp 中進行調用的話, 修改如下:

// 部分代碼省略...
#include <binder/Parcel.h>
#include <binder/IServiceManager.h>
#include <utils/RefBase.h>
#include <QServiceUtils.h>

#include "QCamera2HWI.h"
#include "QCameraMem.h"

// 引入算法庫對應的頭文件
#include "algo.h"

// 在startPreview函數中進行調用
int QCamera2HardwareInterface::startPreview()
{
    ATRACE_CALL();
    int32_t rc = NO_ERROR;
    // 部分代碼省略...

   // 調用
    const char* algoVersion = getVersion();
}

3. 編譯運行

完成上述內容后, 直接編譯系統刷機運行即可, 也可以模塊編譯, 即上面的例子中, 可以使用如下命令編譯(前提是之前已經全部編譯過系統了): mmm hardware/qcom/camera/QCamera2/HAL/
執(zhí)行完成后, 有兩個so庫需要push到手機對應位置 camera.xxx.solibalgo.so, camera.xxx.so 是Camera HAL層編譯完生成的so, xxx代表使用的平臺比如 msm8953, 另一個就是我們集成的算法庫, push后需重啟手機生效.

如果編譯的時候出現一些找不到函數的一些錯誤, 可以看看這篇文章: 一些算法集成遇到的問題

Android.mk注意事項

集成過程中免不了要寫寫mk代碼, Android.mk編寫過程中有些坑, 在此給大家說下我遇到過的坑.

  • include其他Android.mk時, include語句要放在mk文件末尾

    編寫Android.mk文件經常需要引入其他目錄或者子目錄mk文件, 一般做法有兩種:

    # include指定目錄mk
    include $(LOCAL_PATH)/Denoise/Android.mk
    # include 當前文件夾子目錄下的mk
    include $(call all-subdir-makefiles)
    

    在mk中使用這兩個mk語句都需要放在文件夾末尾,因為mk中的變量是全局的,如果放在文件中間,會改變當前的一些變量值,比如LOCAL_PATH值變?yōu)槟鉯nclude的目錄了,這樣目錄就會出錯,導致編譯失敗.

  • include $(call all-subdir-makefiles)只會引入當前目錄下一級子目錄的mk, 更深層次mk不會被引入

    這個是系統定義的函數, 默認只會引入當前目錄下一級的Android.mk, 更深層次的mk需要自己手動引入, 或者每個目錄都放一個mk, 其中只寫include $(call all-subdir-makefiles)這個語句.

  • 對于在已經賦值過的變量, 追加值使用 +=, := 會覆蓋之前的值

  • 使用#注釋代碼時,如果使用了\, 會把所有代碼都注釋掉,而不是注釋一行

    下面的#注釋會將所有變量都注釋掉,而不是注釋QCamera2Factory.cpp這一行.

 LOCAL_SRC_FILES := \
#        QCamera2Factory.cpp \
         QCamera2Hal.cpp \
         QCamera2HWI.cpp \
        QCameraMem.cpp \

總結

  1. NKD和Android源碼中的Android.mk(makefile)的語法有差異, NDK編譯只是針對C/C++代碼,而Android源碼中的Android.mk是用來編譯整個系統的.
  2. NDK編譯的mk中, LOCAL_MODULE會自動加上前綴lib, 預編譯使用PREBUILT_SHARED_LIBRARY, Android系統中LOCAL_MODULE不會被添加前綴, 預編譯使用BUILD_PREBUILT, 且要指定后綴名, 編譯文件的類型
  3. 兩種編譯方式都需使用LOCAL_SHARED_LIBRARIES 來表明依賴的so庫, 并且加載動態(tài)庫時會自動加載對應依賴的動態(tài)庫.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末抛腕,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爬迟,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機子房,發(fā)現死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來就轧,“玉大人证杭,你說我怎么就攤上這事《视” “怎么了解愤?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乎莉。 經常有香客問我送讲,道長,這世上最難降的妖魔是什么惋啃? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任哼鬓,我火速辦了婚禮,結果婚禮上边灭,老公的妹妹穿的比我還像新娘异希。我一直安慰自己,他們只是感情好绒瘦,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布称簿。 她就那樣靜靜地躺著,像睡著了一般惰帽。 火紅的嫁衣襯著肌膚如雪憨降。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天善茎,我揣著相機與錄音券册,去河邊找鬼。 笑死,一個胖子當著我的面吹牛烁焙,可吹牛的內容都是我干的航邢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼骄蝇,長吁一口氣:“原來是場噩夢啊……” “哼膳殷!你這毒婦竟也來了?” 一聲冷哼從身側響起九火,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤赚窃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后岔激,有當地人在樹林里發(fā)現了一具尸體勒极,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年虑鼎,在試婚紗的時候發(fā)現自己被綠了辱匿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡炫彩,死狀恐怖匾七,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情江兢,我是刑警寧澤昨忆,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站杉允,受9級特大地震影響邑贴,放射性物質發(fā)生泄漏。R本人自食惡果不足惜叔磷,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一痢缎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧世澜,春花似錦、人聲如沸署穗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽案疲。三九已至封恰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間褐啡,已是汗流浹背诺舔。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人低飒。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓许昨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親褥赊。 傳聞我的和親對象是個殘疾皇子糕档,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容