近期由于要在移动端优化图像的处理速度,经过短暂调研发现用OpenCL或许可以达到优化的效果,因此就开始在Android Studio上配置ocl的库(使用平台是Mac下的Android Studio)。遇到的坑不少,所以详细记录一下以备用。前提:具有一定的NDK编译基础!

首先我们通过OpenCL的官方Demo来作为例子,该链接下有“OpenCL samples for Android”的分类下有两个Sample Demo,随便哪个都行。下载下来可以看到是使用Eclipse编译的,而熟悉Android

Studio的朋友也知道,在AS下编译使用NDK和Eclipse的差别还是很大的。将下载下来的Eclipse工程导入AS中(这个应该都会,就不说了),然后点击运行......用脚趾头想当然是跑不起来的啦,不急,继续往下撸。

首先看一下工程的jni目录下的文件,一共三个(以AndroidBasicOpenCL工程为例):Android.mk、Application.mk、step.cpp,make文件我们暂且不表,工程中使用的JNI代码都是在step.cpp文件当中(Sample的作者也是很懒,连.h文件都懒得写(╯°Д°)╯︵ ┻━┻),点开该c++文件我们就发现一大堆头文件找不到的错误,一些是android NDK自带的库函数,最重要的还是#include头文件找不到。本文章的主要问题就是解决该头文件的引用问题。

顺着思路往下走,既然找不到头文件,就只好手动引用进去了,遍历整个工程发现OCL的头文件一个影子也没见到,那就去Github上自己下吧!将列表里的所有头文件都下载下来,并在工程里jni目录下新建OpenCL文件夹,把所有的头文件都Copy进去,看下图:

project_structure.png

再编译一下发现还是找不到,为啥?编过NDK的童鞋都知道是make文件没有配置过。那就去Android.mk里面看看吧,原始导入到AS的make文件是长这个样子的:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# To allow building native part of application with OpenCL,
# OpenCL header files and statically linked library are required.
# They are used from Intel OpenCL SDK installation directories,
# that are determined below:
ifeq ($(TARGET_ARCH),x86)
OCL_POSTFIX := android32
else ifeq ($(TARGET_ARCH),x86_64)
OCL_POSTFIX := android64
else
# Unsupported target, do nothing
endif
ifneq ($(OS),Windows_NT)
ifndef INTELOCLSDKROOT
INTELOCLSDKROOT := /etc/alternatives/opencl-intel-tools
endif
# Setting LOCAL_CFLAGS with -I is not good in comparison to LOCAL_C_INCLUDES
# according to NDK documentation, but this only variant that works correctly
LOCAL_CFLAGS += -I$(INTELOCLSDKROOT)/include
LOCAL_MODULE := step
LOCAL_SRC_FILES := step.cpp
LOCAL_LDFLAGS += -llog -ljnigraphics -L$(INTELOCLSDKROOT)/lib64/$(OCL_POSTFIX) -lOpenCL
else
# Setting LOCAL_CFLAGS with -I is not good in comparison to LOCAL_C_INCLUDES
# according to NDK documentation, but this only variant that works correctly
LOCAL_CFLAGS += -I"$(INTELOCLSDKROOT)\include"
LOCAL_MODULE := step
LOCAL_SRC_FILES := step.cpp
LOCAL_LDFLAGS += -llog -ljnigraphics -L"$(INTELOCLSDKROOT)\lib\$(OCL_POSTFIX)" -lOpenCL
endif
LOCAL_ARM_MODE :=arm
include $(BUILD_SHARED_LIBRARY)

由于这里是Mac OS平台下,所以直接看ifneq ($(OS),Windows_NT)下的逻辑,这里定义了一个常量INTELOCLSDKROOT下的路径,推测是编写该Sample的作者的PC系统路径下的OpenCL函数库所在,原生默认的无论在MacOS下还是在Windows下都验证过该路径并不存在,因此这里暂时忽略之。这个路径既然没什么卵用,那与之相关的代码不妨也全部注释掉自己写,首先了解NDK编译的童鞋应该知道LOCAL_MODULE和LOCAL_SRC_FILES的含义,在这里我们也就用到这两个,其他的都可以用#注释掉无视。我习惯在Gradle文件中去构建make中的部分系统变量,这里也把Gradle的代码贴出来:

import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "com.intel.sample.androidbasicocl"
minSdkVersion 14
targetSdkVersion 19
ndk {
moduleName "step"
stl "stlport_static"
ldLibs "log","z","-I${file("src/main/libs")}".toString()
cFlags += "-I${file("src/main/jni/OpenCL")}".toString()
}
}
sourceSets.main {
jniLibs.srcDir 'src/main/libs'
jni.srcDirs = []
}
task ndkBuild(type: Exec) {
def ndkDir = android.ndkDirectory.getAbsolutePath()
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine ndkDir + '/ndk-build.cmd', '-C', file('src/main/jni').absolutePath
} else {
commandLine ndkDir + '/ndk-build', '-C', file('src/main/jni').absolutePath
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
buildTypes {
release
{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}

重点看ndk编译模块下的代码,NDK编译的模块名是step,stl支持c++的静态库,这些没啥好说的,关键是ldLibs以及cFlags两个系统变量。ldLibs声明链接时使用到的库是android的log和z,后面一个库很关键,是OpenCL的so库,后面再说。cFlags则是声明编译gcc时的flag,对应前文加入的OpenCL头文件。再下面的ndkBuild task可以忽略,因为本来是想在AS上直接编so的,后来发现不行,就用命令行来编了。

前面有说到需要使用OpenCL的so库放入工程当中,不然就算找到了头文件也会在编译的时候因为找不到cl的相关引用而报错。那么这个so哪里来呢?可以从网上找,资源还是有的,就不推荐了,我是从手机系统里抠!出!来!的!没错,因为有部分手机是不支持OCL的(看是否支持可以下载名为“OpenCL-Z”的应用来测试),刚好我的测试机是支持的,所以我从手机根目录/system/vendor/lib/libOpenCL.so的路径下找到了这个库,并Copy出来放到了工程jniLibs/armeabi/目录下,其他CPU架构的类似,不表。好了,准备工作就绪,下面就需要编译成so库了。

不知道为什么,我的Mac上把NDK的路径放入.bash_profile里面还是不能引用,那就只能用绝对路径玩了,先知道自己的NDK ndk-build编译文件的路径(Windows下应该是ndk-build.cmd)的文件路径,一般都是/Users/YourName/Library/Android/sdk/ndk-bundle,然后在bash命令行下进入到工程的jni目录下面,运行/Users/YourName/Library/Android/sdk/ndk-bundle/ndk-build果然还是报错!(╯°Д°)╯︵ ┻━┻错误是

ndk-build error.png

直接写解决方法:在Application.mk文件当中添加以下代码:

APP_BUILD_SCRIPT := Android.mk

同样在jni目录下,用bash命令行写:

/Users/YourName/Library/Android/sdk/ndk-bundle/ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk

至此,编译成功!

compile success.png

Log显示编出来的so是放在了libs/armeabi/目录下面,把库移到jniLibs/armeabi/下面,并把生成的这个文件删除,即可运行成功。