之前工作大部分時(shí)間是在集成第三方圖像處理算法, 其中主要是雙攝虛化(Bokeh)相關(guān), 在此總結(jié)一下其中遇到的一些問(wèn)題和解決方法.
集成方式
第三方算法公司提供的SDK都是C/C++動(dòng)態(tài)庫(kù)(.so) + 頭文件的方式, 集成到手機(jī)中通常就兩種方式:
- 集成到單獨(dú)的App中
- 集成到Android系統(tǒng)源碼中
兩種方式優(yōu)缺點(diǎn)如下:
- 優(yōu)點(diǎn): 通用性較好, 針對(duì)不同平臺(tái)(QCOM/MTK)或者廠商不需要額外修改代碼, 缺點(diǎn):性能表現(xiàn)一般, 無(wú)法用到系統(tǒng)的一些獨(dú)特的硬件(GPU, DSP等)
- 優(yōu)點(diǎn): 性能較好, 最大限度利用硬件資源, 缺點(diǎn): 通用性很差, 每個(gè)平臺(tái)集成代碼都有些差異.
使用哪種集成方式一般取決于具體算法使用場(chǎng)景: 比如對(duì)應(yīng)Camera360這樣的通用應(yīng)用, 就必須集成到App中, 而對(duì)于一些作為系統(tǒng)亮點(diǎn)或者差異化功能的賣(mài)點(diǎn)的廠商, 自然是集成到Android系統(tǒng)中, 像我做過(guò)的雙攝手機(jī)項(xiàng)目, 由于特殊性, 就必須集成到系統(tǒng)中.
兩種集成方式代碼方面稍有差異, 詳細(xì)內(nèi)容請(qǐng)看: Android調(diào)用第三方C++算法庫(kù)
extern C 問(wèn)題
雖然C++號(hào)稱完全兼容C, 也想取代C, 但最終并沒(méi)有完全實(shí)現(xiàn), 不同算法公司或者Android系統(tǒng)中不同模塊, 使用的語(yǔ)言也不完全相同, 有的使用C, 有的用C++, 因此集成過(guò)程中會(huì)有一些兼容問(wèn)題, 主要表現(xiàn)如下:
-
算法接口是C++, 調(diào)用的模塊是C代碼
此類(lèi)問(wèn)題要一般都是要求算法提供商提供C接口(只改接口部分, 修改并不多), C代碼在Android系統(tǒng)中還是比較多的, 比較成熟的算法提供商一般默認(rèn)都是提供C接口的, 這樣就能兼容C和C++了
-
算法是C接口, 調(diào)用用的C++
因?yàn)镃++兼容C, 看起來(lái)沒(méi)問(wèn)題, 但是由于頭文件中沒(méi)有加
extern C
, 會(huì)導(dǎo)致以C++方式編譯后, 產(chǎn)生的函數(shù)名和算法中C接口的函數(shù)名不同, 所以會(huì)出現(xiàn)找不到調(diào)用的函數(shù)問(wèn)題, 這種情況在引入的頭文件中加入extern C
即可.
undefined reference to xxx 快速定位
此類(lèi)錯(cuò)誤一般有兩種情況
- 忘記在調(diào)用算法庫(kù)的模塊的Android.mk中加入
LOCAL_SHARED_LIBRARIES := libxxx
如果LOCAL_SHARED_LIBRARIES
變量在當(dāng)前mk中定義過(guò), 則需使用LOCAL_SHARED_LIBRARIES += libxxx
- 算法庫(kù)和頭文件不對(duì)應(yīng),缺少函數(shù)
這種情況一般是算法提供商的問(wèn)題, 確定是否是這個(gè)問(wèn)題可以使用linux中提供的nm
命令,如:
nm -D xxx路徑/libxxx.so
, 看看輸出信息中是否有你要找的那個(gè)個(gè)函數(shù)名稱, 沒(méi)有就說(shuō)明是算法庫(kù)和頭文件不對(duì)應(yīng).
注: nm -D
輸出信息中, 如果是C算法庫(kù), 函數(shù)名和頭文件中是一樣的, 如果是C++算法庫(kù), 則稍有差異, C++由于有重載特性, 函數(shù)名編譯后會(huì)帶有參數(shù)類(lèi)型和返回值信息以及命名空間信息.
比如: 對(duì)于函數(shù)void startPreview(int flag)
, 如果是C方式編譯, 通過(guò)nm -D
輸出的函數(shù)名字為startPreview
, C++則是_Z12startPreviewi
, 比如我們看下libandroid_runtime.so里面和Camera預(yù)覽相關(guān)的函數(shù)
$ nm -D system/lib/libandroid_runtime.so |grep -i preview
U _ZN7android6Camera11stopPreviewEv
U _ZN7android6Camera12startPreviewEv
U _ZN7android6Camera14previewEnabledEv
U _ZN7android6Camera16setPreviewTargetERKNS_2spINS_22IGraphicBufferProducerEEE
U _ZN7android6Camera23setPreviewCallbackFlagsEi
U _ZN7android6Camera24setPreviewCallbackTargetERKNS_2spINS_22IGraphicBufferProducerEEE
可以看到相關(guān)接口都是C++的.
can not load library
代碼運(yùn)行時(shí)找不到so庫(kù)的問(wèn)題一般也分為兩種情況:
-
算法庫(kù)沒(méi)有打包到ROM包中
這種情況一般對(duì)于初學(xué)者經(jīng)常發(fā)生, 通常會(huì)發(fā)生在將so庫(kù)預(yù)置到Android系統(tǒng)中, 比如我們通過(guò)如下代碼預(yù)置一個(gè)so庫(kù)
include $(CLEAR_VARS) LOCAL_MODULE := libxxx LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := libs/armeabi-v7a/$(LOCAL_MODULE).so LOCAL_MODULE_STEM := $(LOCAL_MODULE) LOCAL_MODULE_SUFFIX := $(suffix $(LOCAL_SRC_FILES)) LOCAL_MULTILIB := 32 LOCAL_MODULE_CLASS := SHARED_LIBRARIES include $(BUILD_PREBUILT)
如果只有這些代碼, 模塊編譯(mmm)也能正常編譯, so庫(kù)也會(huì)被編譯到/system/lib/下, 但
make clean
全編譯的時(shí)候, 這個(gè)libxxx.so是不會(huì)被編譯到out目錄中的, 因?yàn)槟阒皇穷A(yù)置到系統(tǒng)中, 確沒(méi)有告訴系統(tǒng)那個(gè)地方要用到這個(gè)so, 所以make的時(shí)候就會(huì)被默認(rèn)剔除, 可通過(guò)在要調(diào)用的模塊的Android.mk中加入LOCAL_SHARED_LIBRARIES += libxxx
或者LOCAL_REQUIRED_MODULES := libxxx
來(lái)解決此問(wèn)題, 表明有模塊在使用這個(gè)so. 另外使用LOCAL_SHARED_LIBRARIES
會(huì)使當(dāng)前編譯模塊依賴于libxxx, 即其他地方加載當(dāng)前so文件中時(shí)會(huì)自動(dòng)去加載libxxx.so, 如果這個(gè)庫(kù)是JNI的so庫(kù), 也可通過(guò)LOCAL_JNI_SHARED_LIBRARIES := libxxx
來(lái)表明依賴關(guān)系 -
缺少依賴庫(kù)文件
缺少依賴庫(kù)是指算法商提供的so庫(kù)可能依賴系統(tǒng)或者其他第三方庫(kù), 但你沒(méi)有直接看出來(lái), 如果錯(cuò)誤信息出現(xiàn)了
can not load library libxxx
,而且這個(gè)libxxx在系統(tǒng)或者算法商提供的列表里并沒(méi)有, 這種情況一般是算法商漏提供了一些so庫(kù)或者不同平臺(tái)公共庫(kù)不一樣(比如高通平臺(tái)OpenCL接口庫(kù)名稱為libOpenCL.so
, MTK平臺(tái)則是libGLES_mali.so
), 這種情況可以使用linux命令readelf
來(lái)查看依賴的庫(kù), 方法如下:$ readelf -d out/target/product/msm8909w/system/lib/hw/bluetooth.default.so Dynamic section at offset 0x19085c contains 40 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libcutils.so] 0x00000001 (NEEDED) Shared library: [libdl.so] 0x00000001 (NEEDED) Shared library: [liblog.so] 0x00000001 (NEEDED) Shared library: [libz.so] 0x00000001 (NEEDED) Shared library: [libpower.so] 0x00000001 (NEEDED) Shared library: [libprotobuf-cpp-full.so] 0x00000001 (NEEDED) Shared library: [libmedia.so] 0x00000001 (NEEDED) Shared library: [libutils.so] 0x0000000e (SONAME) Library soname: [bluetooth.default.so]
上面是我截取輸出中的一部分, 可以看到有很多依賴的so, 比如你要打印LOG, 就要依賴Android系統(tǒng)中l(wèi)iblog.so這個(gè)庫(kù),通過(guò)此命令就很容易知道有沒(méi)有漏掉一些算法庫(kù).
YUV數(shù)據(jù)對(duì)齊(stride/scanline)
由于芯片硬件特性, 做硬件JPEG編碼時(shí), 圖片寬高如果都是處理器位數(shù)(32或者64, 也有可能是其他數(shù)值, 取決于芯片平臺(tái))的整數(shù)倍, 能得到更好的性能, 所以如果預(yù)覽或者拍照的圖片寬高相不是關(guān)數(shù)值的整數(shù)倍, 比如msm8953平臺(tái)是64位對(duì)齊, 拍照設(shè)置的圖片尺寸為2592x1944,在HAL層獲取的yuv數(shù)據(jù)實(shí)際寬高為2624x1984,如果用相關(guān)工具看這個(gè)yuv圖片, 圖片右邊和下邊有無(wú)效像素(綠邊或者黑邊或者對(duì)應(yīng)像素的延伸). 對(duì)齊后的寬在QCOM平臺(tái)寬稱為stride(步長(zhǎng)),即相鄰兩行圖像數(shù)據(jù)之間的間隔, 高稱之為scanline,即有多少行數(shù)據(jù)可以進(jìn)行讀取.
一個(gè)簡(jiǎn)單計(jì)算對(duì)齊后寬高的函數(shù)(只針對(duì)2冪次方對(duì)齊, 2,4,8,16...)
int align(int target, int align)
{
return (target + align -1) & (~(align - 1));
}
//示例:
align(2592, 64);// 得到 2624
當(dāng)然一般平臺(tái)返回的yuv數(shù)據(jù)都有相關(guān)信息記錄stride和scanline, 高通平臺(tái)獲取方式請(qǐng)看:高通(QCOM)平臺(tái)HAL層獲取預(yù)覽/拍照/錄像YUV數(shù)據(jù) ,MTK平臺(tái)相關(guān)信息都在IImageBuffer
這個(gè)類(lèi)中, 詳細(xì)信息可以看下這個(gè)類(lèi)的相關(guān)定義, 由于我這里沒(méi)有源碼(博客要及時(shí)寫(xiě), 不然后面再寫(xiě)想驗(yàn)證東西或者看代碼, 發(fā)現(xiàn)沒(méi)有相關(guān)環(huán)境了......), 就不說(shuō)明了.
YUV數(shù)據(jù)格式
由于平臺(tái)的差異QCOM/MTK, 使用的yuv數(shù)據(jù)格式也有差異, QCOM平臺(tái)預(yù)覽格式為yuv420sp
,即NV21, 拍照HAL層格式也是NV21.
MTK平臺(tái)預(yù)覽默認(rèn)為yuv420p
, 即YV21, 拍照HAL層默認(rèn)情況為yuv422
的一種格式, 但MTK平臺(tái)提供了接口用于申請(qǐng)不同格式的YUV數(shù)據(jù), 我自己嘗試過(guò)在MT6750T平臺(tái)申請(qǐng)拍照的YV12數(shù)據(jù), 能正常得到相關(guān)數(shù)據(jù).
預(yù)覽格式可以通過(guò)dumpsys media.camera來(lái)查看, 一般支持多種格式, 可以通過(guò)設(shè)置參數(shù)的方式控制預(yù)覽格式, 拍照也一樣:
//查看支持的預(yù)覽格式, adb方式, 當(dāng)然也可以通過(guò)Camera的API
adb shell dumpsys media.camera |grep preview-format-values // linux
adb shell dumpsys media.camera |findstr preview-format-values //windows
注意:QCOM平臺(tái)更改預(yù)覽格式會(huì)導(dǎo)致exif中的縮略圖出現(xiàn)異常色塊, 這個(gè)問(wèn)題很多QCOM平臺(tái)都有, 原因是縮略圖是根據(jù)預(yù)覽的數(shù)據(jù)進(jìn)行JPEG編碼生成的, 但編碼時(shí)默認(rèn)用的NV21格式, 我們的預(yù)覽不是NV21格式就會(huì)有問(wèn)題, 是個(gè)bug.
加載系統(tǒng)so庫(kù)失敗
從Android 7.0開(kāi)始, 非系統(tǒng)App, 無(wú)法通過(guò)System.loadLibrary("libname");
加載系統(tǒng)庫(kù),會(huì)出現(xiàn)如下錯(cuò)誤:
java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib/libxxx.so" needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
, Google之所以這樣做是為了加強(qiáng)系統(tǒng)安全性, 這種情況一般只有將App預(yù)置到系統(tǒng)中, 第三方安裝的App就沒(méi)法調(diào)用系統(tǒng)庫(kù)了, 有些人可能說(shuō)可以把系統(tǒng)庫(kù)pull出來(lái)放到App中, 但這不可行的, 原因有兩點(diǎn):
- 系統(tǒng)庫(kù)本身會(huì)依賴其他庫(kù), pull一個(gè)庫(kù)是無(wú)法運(yùn)行的, 都pull出來(lái)顯然不太現(xiàn)實(shí), 嚴(yán)重增加apk體積
- 不同系統(tǒng)庫(kù)在不同平臺(tái)會(huì)有差異, 導(dǎo)致表現(xiàn)會(huì)有差異
所以非系統(tǒng)App還是調(diào)用通用API吧, 當(dāng)然如果你的App只是針對(duì)一個(gè)平臺(tái)或者一個(gè)機(jī)型, 就當(dāng)我沒(méi)說(shuō)......