关于这个网上的资料很多,参考了各位前辈的文章,然后结合自己在使用过程中遇到的各种坑,摘录如下。
参考了这位前辈的文章:https://blog.csdn.net/wjskeepmaking/article/details/70080315
1.0 新建工程
1.1 新建一个android工程
工程类型选择,empty activity,在主逻辑中添加代码,这里只是添加了3个label,分别添加了3个textView,用来显示opencl的一些信息,并且用Opencl做一个简单的运算。
// 实例化我们的ocl测试对象
OpenclTest ocl = new OpenclTest();
// 用3个lable显示一些信息
TextView testView2=(TextView)findViewById(R.id.textView2);
TextView testView4=(TextView)findViewById(R.id.textView4);
TextView testView6=(TextView)findViewById(R.id.textView6);
testView6.setText(ocl.testopencl());
testView2.setText(ocl.getPlatformName());
testView4.setText(ocl.getDeviceName());
以下,我们分别定义这些缺失的jni类:
首先是activity_main.xml布局文件,一个大的垂直方向的layout,里面包含三个水平方向的layout:
很简单,不再赘述。
1.2 定义jni接口java文件
接下来我们添加一个新的Java文件
OpenclTest.java
,定义我们的ocl jni接口:package com.example.testjni;
public class OpenclTest {
static {
System.loadLibrary("ocl");
//之后ndk-build出来的so文件
System.loadLibrary("GLES_mali");
// 手机里面pull出来的opencl库so文件
}public String testOpencl(){
return testopencl();
}
public String getplatformName(){
return getPlatformName();
}
public String getdeviceName(){
return getDeviceName();
}// 定义jni接口
public native Stringtestopencl();
public native String getPlatformName();
public native String getDeviceName();
}
这里特别要注意的是:opencl库的名称在不同平台下是不一样的!在高通平台叫libOpenCL.so,而在麒麟或者mtk平台叫libGLES_mali.so
对于Mali GPU,OpenCL Driver驱动对应系统"/system/vendor/lib/egl/libGLES_mali.so"
接下来,就可以开始用ndk编译我们的ocl.so了。
2.0 ndk编译 2.1 生成jni cpp头文件
javac OpenclTest.java -h .
# 这样就生成了`OpenclTest.classcom_example_ocl_demo_OpenclTest.h` 这两个文件mkdir jni
mv com_example_ocl_demo_OpenclTest.h jni
cd jni
2.2 编写ocl.cpp实现上述jni接口
ocl.cpp
内容如下// 这里我指定了使用opencl的版本:1.2,因为之后的opencl头文件是我从github上下载
// 下来的,默认是最新版本,文档中说明了需要在include CL 头文件之前添加版本限定。
// 添不添加无所谓?
#define CL_TARGET_OPENCL_VERSION 120#include
#include
#include
#include
#include
#include"com_example_ocl_demo_OpenclTest.h"// 我们刚刚生成的头文件
#include #define LOG_TAG "test"
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#define LEN(arr) sizeof(arr) / sizeof(arr[0])
#define N 1024
#define NUM_THREAD 128cl_uint num_device;
cl_uint num_platform;
cl_platform_id *platform;
cl_device_id *devices;
cl_int err;
cl_context context;
cl_command_queue cmdQueue;
cl_mem buffer,sum_buffer;
cl_program program ;
cl_kernel kernel;
// cl 代码,需要动态编译的
// 但是在一般的工程中,我们更喜欢把cl函数放到单独的“cl”文件中,这种方法后面会讲到
const char* src[] = {
"__kernel void redution(\n"
" __global int *data,\n"
" __global int *output,\n"
" __local int *data_local\n"
" )\n"
" {\n"
" int gid=get_group_id(0);
\n"
" int tid=get_global_id(0);
\n"
" int size=get_local_size(0);
\n"
" int id=get_local_id(0);
\n"
" data_local[id]=data[tid];
\n"
" barrier(CLK_LOCAL_MEM_FENCE);
\n"
" for(int i=size/2;
i>0;
i>>=1){\n"
"if(idNewStringUTF(buffer);
}// getDeviceName 的实现
JNIEXPORT jstring JNICALL Java_com_example_ocl_1demo_OpenclTest_getDeviceName(JNIEnv *env , jobject thisobject)
{char buffer[1024];
clGetDeviceInfo(devices[0],CL_DEVICE_NAME,sizeof(buffer),buffer,NULL);
return env->NewStringUTF(buffer);
}// 初始化ocl设备
void Init_OpenCL()
{
size_t nameLen1;
char platformName[1024];
err = clGetPlatformIDs(0, 0, &num_platform);
platform=(cl_platform_id*)malloc(sizeof(cl_platform_id)*num_platform);
err = clGetPlatformIDs(num_platform, platform, NULL);
err=clGetDeviceIDs(platform[0],CL_DEVICE_TYPE_GPU,0,NULL,&num_device);
devices=(cl_device_id*)malloc(sizeof(cl_device_id)*num_device);
err=clGetDeviceIDs(platform[0],CL_DEVICE_TYPE_GPU,num_device,devices,NULL);
} void Context_cmd()
{
context=clCreateContext(NULL,num_device,devices,NULL,NULL,&err);
cmdQueue=clCreateCommandQueue(context,devices[0],0,&err);
} void Create_Buffer(int *data)
{buffer=clCreateBuffer(context,CL_MEM_READ_ONLY|CL_MEM_COPY_HOST_PTR,sizeof(int)*N,data,&err);
sum_buffer=clCreateBuffer(context,CL_MEM_WRITE_ONLY,sizeof(int)*num_block,0,&err);
} void Create_program()
{
program=clCreateProgramWithSource(context, LEN(src), src, NULL, NULL);
err=clBuildProgram(program,num_device,devices,NULL,NULL,NULL);
kernel = clCreateKernel(program, "redution", NULL);
}void Set_arg()
{
err=clSetKernelArg(kernel,0,sizeof(cl_mem),&buffer);
err=clSetKernelArg(kernel,1,sizeof(cl_mem),&sum_buffer);
err=clSetKernelArg(kernel,2,sizeof(int)*NUM_THREAD,NULL);
}void Execution()
{
const size_t globalWorkSize[1]={N};
const size_t localWorkSize[1]={NUM_THREAD};
err=clEnqueueNDRangeKernel(cmdQueue,kernel,1,NULL,globalWorkSize,localWorkSize,0,NULL,NULL);
clFinish(cmdQueue);
}void CopyOutResult(int*out)
{
err=clEnqueueReadBuffer(cmdQueue,sum_buffer,CL_TRUE,0,sizeof(int)*num_block,out,0,NULL,NULL);
}inttest()
{
int* in,*out;
num_block=N/NUM_THREAD;
in=(int*)malloc(sizeof(int)*N);
out=(int*)malloc(sizeof(int)*num_block);
for(int i=0;
iNewStringUTF(result);
}
2.3 下载opencl头文件
这里编译还需要依赖opencl的头问题,从github上下载:
git clone https://github.com/KhronosGroup/OpenCL-Headers
把其中的
CL
整个文件夹拷贝到当前目录即可。当前目录结构:$ tree .
.
├── CL
│├── cl.h
│├── cl_d3d10.h
│├── cl_d3d11.h
│├── cl_dx9_media_sharing.h
│├── cl_dx9_media_sharing_intel.h
│├── cl_egl.h
│├── cl_ext.h
│├── cl_ext_intel.h
│├── cl_gl.h
│├── cl_gl_ext.h
│├── cl_platform.h
│├── cl_va_api_media_sharing_intel.h
│├── cl_version.h
│└── opencl.h
├── com_example_ocl_demo_OpenclTest.h
└── ocl.cpp
2.4 编写Android.mk Application.mk
在使用ndk编译cpp文件之前,我们还需要编写编译脚本,在android studio中便是
Android.mk Application.mk
这两个文件。新建Android.mk
# 其实就是指定目录、文件名及最终生成的模块名
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ocl
LOCAL_SRC_FILES := ocl.cpp
# libGLES_mali.so 是从手机中pull出来的,放到 app/src/main/jniLibs/armeabi-v7a/ 中
# 在我的手机上,使用 `adb pull /system/vendor/lib/egl/libGLES_mali.so /path-to-your-project/app/src/main/jniLibs/armeabi-v7a/`
# 同时需要添加 so 路径,否则会提示说找不到。
LOCAL_LDFLAGS += -llog -L/path-to-your-project/app/src/main/jniLibs/armeabi-v7a/ -lGLES_mali
include $(BUILD_SHARED_LIBRARY)
新建Application.mk
APP_STL := c++_static
APP_CPPFLAGS := -frtti -fexceptions -std=c++0x
APP_ABI := armeabi-v7a
# 你的安卓版本对应的 API level
APP_PLATFORM := android-26
2.5 ndk编译生成ocl.so
直接
ndk-build --debug # 编译我们的cpp文件
# 拷贝生成的ocl.so到 jniLibs/armeabi-v7a/ 目录下
cp ../libs/armeabi-v7a/libocl.so ../../../../../jniLibs/armeabi-v7a/
3.0 编译测试运行demo 编译,安装apk,运行,发现闪退:
java.lang.UnsatisfiedLinkError: dlopen failed: library "android.hardware.graphics.common@1.0.so" not found
查看Log发现无法找到某个so文件。
我们查看下
ocl.so
需要依赖的文件:$ arm-linux-androideabi-readelf -dW libocl.so
0x00000001 (NEEDED)Shared library: [libdl.so]
0x00000001 (NEEDED)Shared library: [liblog.so]
0x00000001 (NEEDED)Shared library: [libGLES_mali.so]
0x00000001 (NEEDED)Shared library: [libc.so]
0x00000001 (NEEDED)Shared library: [libm.so]
0x0000000e (SONAME)Library soname: [libocl.so]
查看下
libGLES_mali.so
需要依赖的文件:$ arm-linux-androideabi-readelf -dW libGLES_mali.so
0x00000001 (NEEDED)Shared library: [android.hardware.graphics.common@1.0.so]
0x00000001 (NEEDED)Shared library: [libutils.so]
0x00000001 (NEEDED)Shared library: [liblog.so]
0x00000001 (NEEDED)Shared library: [libdl.so]
0x00000001 (NEEDED)Shared library: [libc++.so]
0x00000001 (NEEDED)Shared library: [libz.so]
0x00000001 (NEEDED)Shared library: [libm.so]
0x00000001 (NEEDED)Shared library: [libc.so]
0x0000000e (SONAME)Library soname: [libGLES_mali.so]
果然是依赖 android.hardware.graphics.common@1.0.so 这个so,我们从手机里pull出来放在 jniLibs/armeabi-v7a/ 中
adb pull /system/lib/android.hardware.graphics.common@1.0.so /path-to-your-project/app/src/main/jniLibs/armeabi-v7a/
发现还是报上面那个错误,google了一下,发现有个帖子和我一样的情况。按照他的方法:
首先,rename android.hardware.graphics.common@1.0.so 成 libfoo.so,同时修改android.mk添加-lfoo,然后在主
activity启动时,就load该so文件,(“Loaded libfoo.so as soon as possible”),再重新测试,发现该so终于被load成功了,
但是该so文件依赖的so文件还是缺失:
java.lang.UnsatisfiedLinkError: dlopen failed: library "libhidlbase.so" not found
我们查看下:
arm-linux-androideabi-readelf -dWlibfoo.so
0x00000001 (NEEDED)Shared library: [libhidltransport.so]
0x00000001 (NEEDED)Shared library: [libhwbinder.so]
0x00000001 (NEEDED)Shared library: [liblog.so]
0x00000001 (NEEDED)Shared library: [libutils.so]
0x00000001 (NEEDED)Shared library: [libcutils.so]
0x00000001 (NEEDED)Shared library: [libc++.so]
0x00000001 (NEEDED)Shared library: [libdl.so]
0x00000001 (NEEDED)Shared library: [libc.so]
0x00000001 (NEEDED)Shared library: [libm.so]
没办法,只能一个个从手机里面拷贝出来,相当繁琐。究其原因,其实是从Android 7.0开始,出于安全原因,某些系统so库无法被第三方应用直接load,没有办法,只能一个一个把所有依赖的so文件
都拷贝出来了。
adb pull \
/system/lib/libhidlbase.so \
/system/lib/libhidltransport.so \
/system/lib/libhwbinder.so \
/system/lib/libutils.so \
/system/lib/libcutils.so \
/system/lib/libc++.so \
/system/lib/libbase.so \
/system/lib/liblzma.so \
/system/lib/libunwind.so \
/system/lib/libvndksupport.so \
/system/lib/libbacktrace.so \
/path-to-your-project/app/src/main/jniLibs/armeabi-v7a/
终于,测试成功,demo成功运行在我的荣耀8,打印出平台名称“ARM Platfrom”,以及设备名称“Mali-T880”,以及ocl计算的一个例子结果输出。
3.1 cl文件单独编译
在编写OpenCL代码时,为了方便起见,我们更喜欢将kernel源代码放在单独的文件中(一般为*.cl)。这各做的缺点在于,程序需要在运行时动态读入文件中的代码为字符串,然后再传递给OpenCL的RT编译、执行。
因此,在可执行文件之外,我们还需要单独分发*.cl文件。比如在cpp文件中加入如下代码:
FILE *program_handle;
size_t program_size;
char *program_buffer;
// 把cl文件的源代码读到内存中
program_handle = fopen(TEST_PROGRAM_FILE, "r");
if(program_handle == NULL) {
perror("Couldn't find the program file");
exit(1);
}
fseek(program_handle, 0, SEEK_END);
program_size = ftell(program_handle);
rewind(program_handle);
program_buffer = (char*)malloc(program_size + 1);
program_buffer[program_size] = '\0';
fread(program_buffer, sizeof(char), program_size, program_handle);
fclose(program_handle);
// 编译生成内核对象kernel
program = clCreateProgramWithSource(context, program_size, (const char**)&program_buffer, NULL, NULL);
free(program_buffer);
在pc上运行没问题,但是在Android平台,会找不到cl文件,导致
fopen
失败,在安卓上运行时,cl代码的相对路径可能和你编译时的相对路径不一致。解决的办法就是把cl文件放入
assets
文件夹中,安卓代码调用getAssets().open("xxx.cl");
然后用BufferedReader
去读出所有的文本作为一个string对象通过参数传递给jni层,这种方式是在运行时导入,另外一种方式可以在编译时包含,通过”include” 预编译命令将cl文件做为字符串导入,可以参考这篇博客
4.0 遇到的一些问题
aarch64-linux-android/bin/ld: skipping incompatible /Users/stv/work/proj/opencl_test/ocl_demo/app/src/main/jniLibs/arm64-v8a//libGLES_mali.so when searching for -lGLES_mali
在用ndk编译的时候,如果ABI选择arm64-v8a就会报错,看起来应该是libGLES_mali.so是32的导致的,所以,只能选择用32位编译。ABI选择armeabi-v7a
dlopen failed: "/data/app/com.example.testjni-JnlWe2K6cm55UsjxdLoz3Q==/lib/arm64/libocl.so" is 32-bit instead of 64-bit
也是32、64位的问题,
libocl.so是我再32位下armeabi-v7a 编译出来的,放到了jniLibs/arm64-v8a中,就报上面的错误了。
所以,arm64-v8a文件夹中必须放64位的程序!而且,这个arm64-v8a是优先查找的目录,如果找不到,才会到armeabi-v7a中找.
"xxx.so" is not accessible for the namespace...
【opencl demo 在华为荣耀8上测试】谷歌从Android N开始,除了那些在Android NDK提供的库之外,限制了应用对系统私有库的加载。既然是限制了,想通过以前的方法去加载库已经行不通了。
所以目前的解决方法就是把应用需要加载的库和需要依赖的库从系统中pull出来,然后集成到自己的应用当中。就像我们之前所做的一样。还有一点,刚开始
调试了很久,也是这个报错。但是问题根源是我开始使用的是
libOpenCL.so
,而不是libGLES_mali.so
。前者是高通平台的opencl driver驱动。后者才是我华为手机上的驱动。可以参考这里