最近项目上需要将微信语音silk文件格式进行编解码,在silk、PCM、AMR、MP3间进行格式转换,将silk-wasm项目的C/C++部分进行迁移,通过CMake和JNI集成到Android项目中,之前有在Linux环境通过NDK进行交叉编译生成so文件,试想有没更简单的方式也能交叉编译生成动态库文件,发现在AS IDE也是可以完成这个工作的。
项目源码:https://github.com/751496032/silk-codec
在Linux环境下通过NDK实现交叉编译库
在Android Studio中准备安装NDK工具链:
可以展开下载对应的版本
步骤:
第一个步骤,这里就不详细展开讲了,是JNI的基础内容,具体可以参考文章
Android NDK—JNI基础
第二步骤,在cpp目录下创建CMakeLists.txt文件,并在文件内配置当前项目的CMake构建信息,内容如下:
cmake_minimum_required(VERSION 3.18.1)
# 项目名称
project(silk)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Os -flto")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Os -flto")
# 添加大小优化选项 对优化大小很重要
# -Os: 优化代码大小的编译选项 -O0 -O1-3
if(CMAKE_BUILD_TYPE STREQUAL "Release")
# 大小优化
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Os")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os")
# 链接优化
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -Wl,--gc-sections")
endif()
message(${CMAKE_CXX_FLAGS_RELEASE} )
message(${CMAKE_BUILD_TYPE})
# 添加源文件目录 cpp文件,值为存储在变量中
# key value = 目录 变量名称
aux_source_directory(src SRC_LIST)
aux_source_directory(. NATIVE_LIB)
message(${SRC_LIST}) # src/index.cpp
message(${NATIVE_LIB}) # ./native-lib.cpp
# 添加所有的SILK源文件 c文件
file(GLOB SILK_SRC
libSilkCodec/silk/src/SILK_SDK_SRC_ARM_v1.0.9/src/*.c
)
file(GLOB SILK_CODEC libSilkCodec/src/*.c)
message(${SILK_CODEC}) # decoder.c 和 encoder.c 文件
# 添加所有的头文件目录 h文件
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src
# ${CMAKE_CURRENT_SOURCE_DIR}/include
libSilkCodec/silk/src/SILK_SDK_SRC_ARM_v1.0.9/interface/
libSilkCodec/silk/src/SILK_SDK_SRC_ARM_v1.0.9/src/
libSilkCodec/src/include
)
# 添加库文件目录
link_directories(
${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI}
${CMAKE_CURRENT_BINARY_DIR}
)
# CMAKE_CURRENT_SOURCE_DIR: 代码内置的库文件目录,当前项目暂时没有
# CMAKE_CURRENT_BINARY_DIR:当前项目交叉编译生成的库文件目录
message(${CMAKE_CURRENT_SOURCE_DIR})
message(${CMAKE_CURRENT_BINARY_DIR})
# 创建silk-codec共享库 添加所有的C和C++源文件
add_library(silk-codec SHARED
${SRC_LIST}
${NATIVE_LIB}
${SILK_SRC}
${SILK_CODEC}
# libSilkCodec/src/encoder.c
# libSilkCodec/src/decoder.c
)
# 查找 Android 日志库
find_library(log-lib log)
# 链接所有的库
target_link_libraries(silk-codec
${log-lib}
android
)
# 移除调试符号
# 在Release模式下移除调试信息 ,优化大小 一定要放在末尾
if(CMAKE_BUILD_TYPE STREQUAL "Release")
# 方法1:使用strip命令
# add_custom_command(TARGET silk-codec POST_BUILD
# COMMAND ${CMAKE_STRIP} --strip-all $
# )
# 方法2:使用编译标志
# target_compile_options(silk-codec PRIVATE
# -fvisibility=hidden
# -fdata-sections
# -ffunction-sections
# )
# 方法3:使用链接标志
# set_target_properties(silk-codec PROPERTIES
# LINK_FLAGS "-Wl,--strip-all -Wl,--gc-sections"
# )
endif()
这里重点说下aux_source_directory
、include_directories
、link_directories
三者的区别和作用,首先都是搜集文件,但搜集的文件类型有所不同,在此我们先了解下什么是源文件、头文件、库文件。
静态库:
// Windows: .lib
// Linux/Unix: .a
// 示例:libexample.a, example.lib
// 使用示例
target_link_libraries(your_target
${CMAKE_SOURCE_DIR}/lib/libexample.a
)
动态库:
// Windows: .dll
// Linux: .so
// macOS: .dylib
// Android: .so
// 示例:libexample.so, example.dll
// 使用示例
target_link_libraries(your_target
${CMAKE_SOURCE_DIR}/lib/libexample.so
)
aux_source_directory
收集所有的源文件并保存变量中,比如上例中的变量SRC_LIST、NATIVE_LIB,变量的值分别是
src/index.cpp
./native-lib.cpp
语法:
// https://cmake.org/cmake/help/latest/command/aux_source_directory.html#command:aux_source_directory
// dir: 文件相对目录 variable:变量名称
aux_source_directory( )
另外也可以file变量指定目录下文件,然后保存到变量中。
include_
directories
添加头文件的搜索路径,在现代CMake可以用 target_
include_directories
替代
语法:
// https://cmake.org/cmake/help/latest/command/include_directories.html#command:include_directories
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
link_directories
添加库文件的搜索路径 ,在现代CMake可以用 target_
link_directories
替代
语法:
// https://cmake.org/cmake/help/latest/command/link_directories.html#link-directories
link_directories([AFTER|BEFORE] directory1 [directory2 ...])
add_library
将所有的源文件创建动态库或静态库。
语法:
// https://cmake.org/cmake/help/latest/command/add_library.html#command:add_library
add_library( [] [EXCLUDE_FROM_ALL] ...)
target_link_libraries
将所有的库链接到目标库上。
语法:
// https://cmake.org/cmake/help/latest/command/target_link_libraries.html#target-link-libraries
target_link_libraries( ... - ... ...)
CMake语法中其中带
<>
是必填的,带[]
是可选参数,...
是变长参数
最后小结CMake配置文件的基本步骤:
在build.gradle文件中,进行将将CMake配置文件依赖关联到Gradle中,如下:
android {
defaultConfig {
// 注释2
ndk {
//noinspection ChromeOsAbiSupport
abiFilters "arm64-v8a" // 生成 arm64-v8a / armeabi-v7a so文件
}
// 注释3
externalNativeBuild {
cmake {
cppFlags ""
// arguments 参数会传递到CMakeLists文件中
// -DCMAKE_BUILD_TYPE 配置生成so包的环境,不配默认是Debug
arguments "-DANDROID_STL=c++_shared"
}
}
}
// 注释1
externalNativeBuild {
cmake {
// CMakeLists.txt路径
path "src/main/cpp/CMakeLists.txt"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// 注释4
// 配置Release生成so包
// https://developer.android.google.cn/ndk/guides/symbol-visibility?hl=zh-cn
externalNativeBuild {
cmake {
arguments "-DCMAKE_BUILD_TYPE=Release"
"-DANDROID_OPTIMIZATION_LEVEL=z" // 使用-Oz优化
cFlags "-Os", "-fvisibility=hidden"
cppFlags "-Os", "-fvisibility=hidden"
}
}
}
}
}
arguments "-DCMAKE_BUILD_TYPE=Release"
的DCMAKE_BUILD_TYPE值可以CMakeLists.txt文件中拿到,然后做一些特殊处理,比如构建优化等等。详细可以参考Android NDK官方文档
到这里基本完成了所有的配置项,通过Make 项目就可以生成动态库,或者执行Gradle任务命令生成。
动态so库文件会存放在build/intermediates/cxx
目录下。
另外aar包也会生成存放在build/outputs/aar
目录下,aar包内置了动态so文件,在项目中可以直接使用aar包,如下:
剩下的就是集成到项目中使用了 ,这里就不展开细讲了。