作者: Sam (甄峰) sam_code@hotmail.com
最近尝试再次编译OpenCV4Android。因为Android SDK, NDK,
Cmake版本问题,意料之中的遇到N多问题。分析和解决问题大都需要修改CMake file. 现记录一些信息如下。
0.
CMake基本语法记录:
0.1. CMake中的赋值:
-
set(variable value1 value2 value3 ... valueN)
调用这个命令后,variable变量将是一个列表,其中包含值"value1,value2,...valueN"。
-
set(myvar "a" "b")
-
message("${myvar}")
-
message(${myvar})
此时,控制台中将分别打印出"a;b"和"ab"。这是因为,不带引号时,${myvar}是一个列表,包含了两个值,而message中相当于接收到了两个参数"a"、"b",因此输出"ab"。而带有引号时,引号中的内容整体将作为一个参数存在
0.2. CMake中循环:
CMake中的循环有两种:foreach()...endforeach()和while()...endwhile()
cmake_minimum_required(VERSION 2.6)
#project (V4L2_Utils)
set(mylist "a" "b" c "d")
foreach(_var ${mylist})
message("current var: ${_var}")
endforeach()
#cmake ../ -G"Unix Makefiles"
结果:
current var: a
current var: b
current var: c
current var: d
还有一种比较实用的方法是:foreach(loop_var RANGE start stop [step])
cmake_minimum_required(VERSION 2.6)
#project (V4L2_Utils)
set(mylist "a" "b" c "d")
foreach(_var ${mylist})
message("current var: ${_var}")
endforeach()
set(result 0)
foreach(_var RANGE 0 100)
math(EXPR result "${result}+${_var}")
endforeach()
message("from 0 plus to 100 is:${result}")
#cmake ../ -G"Unix Makefiles"
结果:
current var: a
current var: b
current var: c
current var: d
from 0 plus to 100 is: 5050
0.3. CMake中使用Maco:
Start recording a macro for later invocation as a
command.
开始记录一个宏,未来可以把它当作命令来调用:
macro(< name > [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endmacro( < name >)
例:
macro(sum outvar)
set(_args ${ARGN})
message("${ARGN}")
list(LENGTH _args argLength)
if(NOT argLength LESS 4)
message(FATAL_ERROR "to much
args!")
endif()
set(result 0)
foreach(_var ${ARGN})
math(EXPR
result "${result}+${_var}")
endforeach()
set(${outvar} ${result})
endmacro()
sum(addResult 1 2 3)
message("Result is
:${addResult}")
"${ARGN}"是CMake中的一个变量,指代宏中传入的多余参数。因为我们这个宏sum中只定义了一个参数"outvar",其余需要求和的数字都是不定形式传入的,所以需要先将多余的参数传入一个单独的变量中
1.
OpenCV4Android(OpenCV3.1)编译时遇到问题:
1.0: 编译准备工作:
Sam要编译32bit和64bit两个版本,所以创建了两个不同的设置环境变量的脚本:
setenv_32bit, setenv_64bit.
setenv_32bit:
export ANDROID_NDK=/opt/android-ndk-r14b/
export ANDROID_SDK=/home/sam/Android/Sdk/
export ANDROID_ABI=armeabi-v7a
export ANT_HOME=/usr/share/ant
export PATH=${PATH}:${ANT_HOME}/bin
setenv_64bit:
export ANDROID_NDK=/opt/android-ndk-r14b/
export ANDROID_SDK=/home/sam/Android/Sdk/
export ANDROID_ABI=arm64-v8a
export ANT_HOME=/usr/share/ant
export PATH=${PATH}:${ANT_HOME}/bin
开始编译:
$cd platforms
$sh scripts/cmake_android_arm.sh
1.1: 错误抓取和分析:
CMake Error at platforms/android/android.toolchain.cmake:812
(message):
Specified Android native API level
'android-8' is not supported by your
NDK/toolchain.
Call Stack (most recent call first):
platforms/build_android_arm/CMakeFiles/3.9.1/CMakeSystem.cmake:6
(include)
CMakeLists.txt:95 (project)
问题解析:
概念1:Android native API
level:
在使用NDK编译时,需要指定系统头文件和库所在目录。而Android版本如此之多,要使用哪个版本的头文件和库,就是个问题。
在Application.mk中,可以使用:APP_PLATFORM=android-12
来指定系统头文件和系统库采用NDK/platforms/android-12/下的版本。
那Android native API level就指这个版本。
--sysroot=/opt/android-ndk-r14b/platforms/android-12/arch-arm
如在NDK R10e中,platforms目录包含从android-3到android-21.
在NDK R14中,platforms目录包含从android-9到android-24.
那如果指定的APP_PLATFORM不包含在NDK指定目录内呢?例如:
APP_PLATFORM=android-8, 但又使用NDK R14.
那系统头文件和库该使用哪个呢?
在NDK编译时,它会指定当前最新的版本:
--sysroot=/opt/android-ndk-r14b/platforms/android-24/arch-arm
那问题就清晰了, OpenCV Cmake 认为我们指定了Android Native
API为8。但NDK R14又不支持。
这就奇怪了,我们并没有指定Native API阿。看CMakefile
A:找到报错地点:
list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS
"${ANDROID_NATIVE_API_LEVEL}" __levelIdx )
if( __levelIdx EQUAL -1 )
message( SEND_ERROR "Specified Android
native API level 'android-${ANDROID_NATIVE_API_LEVEL}' is not
supported by your NDK/toolchain." )
利用list(FIND),
查找list--ANDROID_SUPPORTED_NATIVE_API_LEVELS中是否有${ANDROID_NATIVE_API_LEVEL},如果有,则返回index到
--levelIdx.
显然,这个list中并没有。
那就需要看哪里得到两个值:
list: ANDROID_SUPPORTED_NATIVE_API_LEVELS
ANDROID_NATIVE_API_LEVEL
B: 找两个变量:
B1:list ANDROID_SUPPORTED_NATIVE_API_LEVELS:
__DETECT_NATIVE_API_LEVEL(
ANDROID_SUPPORTED_NATIVE_API_LEVELS
"${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h"
)
这个宏__DETECT_NATIVE_API_LEVEL()的功能是,从NDK/sysroot/usr/include/android/api-leve文件中,获取它所支持的api-level.
B2: ANDROID_NATIVE_API_LEVEL:
__INIT_VARIABLE(
ANDROID_NATIVE_API_LEVEL
ENV_ANDROID_NATIVE_API_LEVEL
ANDROID_API_LEVEL
ENV_ANDROID_API_LEVEL
ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL
ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME}
ANDROID_DEFAULT_NDK_API_LEVEL )
它最终是因为:set( ANDROID_DEFAULT_NDK_API_LEVEL 8 )
而被设置为8.
8: 不包含在9-26的list中,所以报错。
解决方法;
到这一步,解决方法就很明显了:
cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DANDROID_NATIVE_API_LEVEL=14
-DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@
../..
GDB:
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g
-ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")