最近项目中需要集成ffmpeg,所以需要用到cmake配置文件相关语法知识,所以在这里做个相关的记录。 只要了解本文的cmake基础语法知识,我想对NDK/JNI开发或者集成其他c/c++库都有帮助。
首先可先看下项目的目录结构,所有的cpp源文件和第三方库都放在cpp目录下
指令说明
message
该指令可以输出一些打印信息,能帮助定位我们设置的变量等是否正确
下面是上面截图message指令的输出信息,大家做个参考,实际可把源码download下来看下实际的输出结果
-----
D:/Android/ASProject/NDKDemo/app/src/main/cpp
D:/Android/ASProject/NDKDemo/app/src/main/cpp
ndkdemo
D:/Android/ASProject/NDKDemo/app/src/main/cpp/CMakeLists.txt
D:/Android/ASProject/NDKDemo/app/src/main/cpp
arm64-v8a
set
# set(arg value) 前面是变量名,后面是变量值
项目中不管引入第三方库还是自己写方法供别人调用,都要提供头文件,在include头文件的时候,以免头文件路径过长,可以使用set变量将路径设置在变量中,在后面include头文件的时候,直接include这个变量就可以了。
set(FFMPEG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/ffmpeg/include)
set(JVM_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/jvm/include)
set(ADD_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/add/include)
# 项目中引入需要使用的头文件
include_directories(
${FFMPEG_INCLUDE_DIR}
${JVM_INCLUDE_DIR}
${ADD_INCLUDE_DIR}
)
set还可以将多个值设置成一个变量,例如:
set(FFMPEG_LIBS ${CMAKE_SOURCE_DIR}/ffmpeg/libs)
# 设置ffmpeg库变量
set(FFMPEG_VAR_LIBS
${FFMPEG_LIBS}/${ANDROID_ABI}/libavformat.so
${FFMPEG_LIBS}/${ANDROID_ABI}/libavcodec.so
${FFMPEG_LIBS}/${ANDROID_ABI}/libavfilter.so
${FFMPEG_LIBS}/${ANDROID_ABI}/libswresample.so
${FFMPEG_LIBS}/${ANDROID_ABI}/libswscale.so
${FFMPEG_LIBS}/${ANDROID_ABI}/libavutil.so
)
在这里,首先设置了FFMPEG库的路径,然后设置了FFMPEG_VAR_LIBS这个变量,这很有用;因为后面再link ffmpeg库的时候,可以直接link这个变量就可以了。如果不明白说的什么意思,可以先不管,等后面指令示例写出来就明白什么意思了。
include_directories
这个指令就是很简单,需要引入头文件的路径,这样在调用函数的时候才能找到对应的函数
add_subdirectory
这个指令一般是添加子目录。这个子目录不简单,它是一个单独的cmake结构,有CMakeLists.txt文件,一般是单独的一个算法或者方法库。例如在项目中,这个add模块,就是一个单独的加法算法,同时有CMakeLists.txt文件管理这个算法。
add_subdirectory(${CMAKE_SOURCE_DIR}/jvm)
注意这个命令是添加包含的源文件的目录进行编译。如果是已经编译好的so,当把so添加到jniLibs目录下之后,只需要target_link_libraries这个编译好的目录即可
add_library
这个指令是添加我们自己写或者别人写的cpp源文件,例如在这个项目中,main.cpp是我们写的cpp源文件,我们需要把它编译库供Android的java层调用,那么就要使用该指令
add_library(main SHARED
jni/main.cpp
)
说明一下:
main这个变量名称可以自己随便命名
SHARED表示将这些源文件编译成动态库so;与之相对应的STATIC,表示编译成静态库.a
jni/main.cpp 这个就是源文件相对于CMakeLists.txt文件的相对路径
当cpp源文件越来越多的时候,上面写法就很繁琐,所以可以使用file命令把所有cpp源文件打包,设置在一个变量里面,直接引用变量即可
file(GLOB SRCS
${CMAKE_SOURCE_DIR}/jni/*.cpp)
add_library(main SHARED
${SRCS}
)
target_link_libraries
这个指令就是link第三方库或者自己写的库。
注意FFMPEG_VAR_LIBS这个变量,这就是上面通过set指令设置的ffmpeg库的变量,在link的时候,直接使用这个变量就可以了。
target_link_libraries(
main
${FFMPEG_VAR_LIBS}
Add
jvm
android
log)
add_library(
${CMAKE_PROJECT_NAME} SHARED
native-lib.cpp)
target_link_libraries(
${CMAKE_PROJECT_NAME}
main
android
log)
通过上面的配置,在编译项目的时候,就会生成对应的库文件
然后java层直接load对应的so就可以使用上面cpp源文件写的方法了
System.loadLibrary("ndkdemo")
从后面两个指令target_link_libraries可以看出:
# 从该项目中可以看出,静态库.a和动态库so可以同时编译,且动态库可以link静态库
# 如果涉及到多个子文件夹,它们是不同的算法或者项目,当项目依赖它们的时候,
# 可以将他们编译成静态库,然后编译生成我们需要的动态库so的时候,link它们就好了
# 这样做的好处就是如果其他项目需要这些算法,只需要引入上面生成的动态库就好了
# 否则就需要引入多个so
FFMPEG的另外一种引入方式
有时候,有些同学引入ffmpeg的时候,将so全部放在了Android项目的jniLibs目录,这个时候就需要另外一种引入方式,修改一下cmake配置就好了,这里就直接贴cmake的配置了。
set(FFMPEG_LIBS ${CMAKE_SOURCE_DIR}/../jniLibs)
# 指定在链接目标时找到库文件所需路径
# 调用add_executable()或add_library()创建目标后,再调用link_directories()是没有效果的
link_directories(${FFMPEG_LIBS}/${ANDROID_ABI})
set(FFMPEG_VAR_LIBS
avformat
avcodec
avfilter
swresample
swscale
avutil
)
target_link_libraries(
main
${FFMPEG_VAR_LIBS}
Add
jvm
android
log)
src/main/jniLibs目录下存放ffmpeg库
以上配置,在程序运行的时候,也能找到ffmpeg。因为jniLibs是Android标准目录,在构建的时候,会自动将jniLibs目录下的so打包进apk; 同时target_link_libraries的时候,只需要ffmpeg库的名称即可,因为是通过名称去找对应的so
本文涉及到的项目源文件:NDKDemo