转载请声明出处:https://blog.csdn.net/AndrExpert/article/details/82909572

在Android Studio中使用CMake进行NDK/JNI开发(初级)一文中,我们详细介绍了如何在Android Studio中使用cmake编译环境开发原生库(.so)。本文将在此基础上,进一步学习CmakeLists.txt脚本文件的语法规则,同时阐述如何在AS中编译和使用第三方库,最终生成新的功能原生库。

1. 使用Cmake编译第三方库源码

 为了更好地学习CmakeLists.txt脚本文件的语法规则,这里我们以Lame库编译封装为例,Lame是Mike Cheng于1998年发起的一个开源项目,是目前最好的MP3编码引擎。关于如何将Lame库源码导入到AS,请详见我这篇文章Android直播开发之旅(4):MP3编码格式分析与lame库编译封装。这里我们仅关注该文开源项目Lame4Mp3的CmakeLists.txt,首先看下项目代码结构和lame目录下Lame库源码(部分):

 接下来,我们继续讲解如何在项目中使用Lame库,然后编译成原生库so并打包到APK。首先,我们在Android项目的cpp目录中创建自己的C/C++源文件,然后在该源文件中调用lame库中的API,这里跟C/C++开发过程一样的。LameMp3.c源码如下:

//// Created by jianddongguo on 2018/9/30.//#include #include "LameMp3.h"#include "lame/lame.h"static lame_global_flags *gfp = NULL;JNIEXPORT void JNICALLJava_com_jiangdg_natives_LameMp3_lameInit(JNIEnv *env, jclass type, jint inSampleRate,jint outChannelNum, jint outSampleRate, jint outBitRate,        jint quality) {    if(gfp != NULL){        lame_close(gfp);        gfp = NULL;    }    //  初始化    gfp = lame_init();    LOGI("初始化lame库完成");    //配置参数    lame_set_in_samplerate(gfp,inSampleRate);    lame_set_num_channels(gfp,outChannelNum);    lame_set_out_samplerate(gfp,outSampleRate);    lame_set_brate(gfp,outBitRate);    lame_set_quality(gfp,quality);    lame_init_params(gfp);    LOGI("配置lame参数完成");}JNIEXPORT jint JNICALL        Java_com_jiangdg_natives_LameMp3_lameFlush(JNIEnv *env, jclass type, jbyteArray mp3buf_) {    jbyte *mp3buf = (*env)->GetByteArrayElements(env, mp3buf_, NULL);    jsize len = (*env)->GetArrayLength(env,mp3buf_);    int resut = lame_encode_flush(gfp,mp3buf,len);    (*env)->ReleaseByteArrayElements(env, mp3buf_, mp3buf, 0);    LOG_I("写入mp3数据到文件,返回结果=%d",resut);    return  resut;}JNIEXPORT void JNICALLJava_com_jiangdg_natives_LameMp3_lameClose(JNIEnv *env, jclass type) {    lame_close(gfp);    gfp = NULL;    LOGI("释放lame资源");}JNIEXPORT jint JNICALLJava_com_jiangdg_natives_LameMp3_lameEncode(JNIEnv *env, jclass type, jshortArray letftBuf_,                                              jshortArray rightBuf_, jint sampleRate,                                              jbyteArray mp3Buf_) {    if(letftBuf_ == NULL || mp3Buf_ == NULL){        LOGI("letftBuf和rightBuf 或mp3Buf_不能为空");        return -1;    }    jshort *letftBuf = NULL;    jshort *rightBuf = NULL;    if(letftBuf_ != NULL){        letftBuf = (*env)->GetShortArrayElements(env, letftBuf_, NULL);    }    if(rightBuf_ != NULL){        rightBuf = (*env)->GetShortArrayElements(env, rightBuf_, NULL);    }    jbyte *mp3Buf = (*env)->GetByteArrayElements(env, mp3Buf_, NULL);    jsize readSizes = (*env)->GetArrayLength(env,mp3Buf_);    // 编码    int result = lame_encode_buffer(gfp,letftBuf,rightBuf,sampleRate,mp3Buf,readSizes);    // 释放资源    if(letftBuf_ != NULL){        (*env)->ReleaseShortArrayElements(env, letftBuf_, letftBuf, 0);    }    if(rightBuf_ != NULL){        (*env)->ReleaseShortArrayElements(env, rightBuf_, rightBuf, 0);    }    (*env)->ReleaseByteArrayElements(env, mp3Buf_, mp3Buf, 0);    LOG_I("编码pcm为mp3,数据长度=%d",result);    return  result;}

然后,编写CmakeLists.txt脚本文件,用于指定源文件路径、生成so文件名,引入NDK原生库以及链接so到APK等。CmakeLists.txt内容如下:

# 指定Cmake版本cmake_minimum_required(VERSION 3.4.1)# 导入lame第三库相关头文件路径include_directories(src/main/cpp/lame)# 查找lame目录下的所有源文件,将输出结果列表储存在SRC_LIST变量中aux_source_directory(src/main/cpp/lame SRC_LIST)# 设置so动态库最后的输出路径set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})# 指定编译的源码和编译生成so库文件名、类型add_library(             # 生成库的名称,会自动添加前缀"lib"             LameMp3             # 生成库类型,动态库-SHARED,静态库-STATIC             SHARED             # 编译的源文件,包括自己和lame库c或c++源文件             src/main/cpp/LameMp3.c ${SRC_LIST})# 导入NDK原生库-日志库find_library( # 变量,保存log库的路径              log-lib              # 指定需要查找NDK哪个原生库(log库),便于cmake能够定位到              log )# 将相关库链接到LameMp3,可以链接多个库target_link_libraries(                       # 指定被链接库名称,即最终输出库LameMp3                       LameMp3                       # NDK中的log库                       ${log-lib} )

2. 详解CmakeLists.txt语法与实战案例

 在上一小节中,我们学习了如何使用CmakeList.txt脚本文件指定编译动态库所需的源头文件、源文件、引用和链接NDK原生so库等操作。本节就在此基础上详细介绍CmakeLists.txt脚本文件常用的语法规则,然后再给出一个ffmpeg框架在AS中如何使用的实战案例。

2.1 CmakeList.txt语法分析

cmake_minimum_required

  • 原型: cmake_minimum_required(VERSION [...] [FATAL_ERROR])
  • 参数:
     min:执行项目使用的Cmake最低版本;
     max:可选,执行最大版本;
     FATAL_ERROR:2.6版本以上可忽略;
  • 作用:指定项目所需Cmake的最低版本版本。如果真实编译环境的Cmake版本低于min,则停止编译并报error。
  • 实例: cmake_minimum_required(VERSION 3.4.1)

include_directories

  • 原型:include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …])
  • 参数:
     AFTER|BEFORE:指定源文件目录后,通常会被附加到编译的当前目录列表中,但也可以通过AFTERBEFORE来改变这种行为方式;
     SYSTEM:如果给出SYSTEM选项,编译器将被告知该目录是系统包含目录。
     dir1:源文件目录路径,可多个,用一个空格隔开;
  • 作用:导入给定源文件目录
  • 实例:include_directories(src/main/cpp/lame)

aux_source_directory

  • 原型:aux_source_directory( )
  • 参数:
     dir:源文件所在目录;
     variable:用于存储文件名称列表的变量;
  • 作用:收集指定目录中所有源文件的名称,并将列表存储在提供的variable中
  • 实例:aux_source_directory(src/main/cpp/lame SRC_LIST)

set

  • 原型:
    set( … [PARENT_SCOPE])
    set( … CACHE [FORCE])
    set(ENV{} …)
  • 参数:
     variable:给定变量,除了可以自定义变量,Cmake还提供了很多variable值,常见的有CMAKE_LIBRARY_OUTPUT_DIRECTORY用于改变so输出路径,具体详见cmake-variables;
     value:变量的值
     FORCE:由于缓存条目旨在提供用户可设置的值,因此默认情况下不会覆盖现有的缓存条目。 使用FORCE选项覆盖现有条目。
  • 作用:设置Cmake变量,有三种情形
     将当前function和目录范围设定为给定变量variable;
     设置给定的缓存(缓存条目);
     将当前执行环境变量设定为给定值variable;
  • 实例:
    set(libs “${CMAKE_SOURCE_DIR}/src/main/jniLibs”)
    set(SRC_LIST main.c t1.c t2.c)
    set(ANDROID_NDK_REVISION 13)
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY …)

add_library

用法1

  • 原型:
    add_library(< name> [STATIC | SHARED | MODULE]
    [EXCLUDE_FROM_ALL]
    [source1] [source2 …])
  • 参数:
    name:被构建库目标名称
    [STATIC | SHARED ..:被构建库的类型,STATIC为静态库,生成的库 以libname.a形式存在,SHARED 为动态库,生成的库以libname.so形式存在;
    EXCLUDE_FROM_ALL:对应的一些属性会在目标被创建时被设置;
    [source1] [source2 ...]:指定源文件,多个文件由一个空格隔开;
  • 作用:添加一个库
  • 实例: add_library(LameMp3 SHARED src/main/cpp/LameMp3.c ${SRC_LIST})

用法2

  • 原型:
    add_library(< name> IMPORTED
    [GLOBAL])
  • 参数:
    name:导入已知库的名称;
    SHARED|STATIC|IMPORTED..:通常为SHARED IMPORTED;
  • 作用:导入一个已经存在的库。注:导入库一般配合set_target_properties使用
  • 实例: add_library(libswscale-4 SHARED IMPORTED )
    find_library
  • 原型:find_library ( name1 [path1 path2 …])
  • 参数:
     name1 :
  • 作用:在path1查找相关库,并将其路径保存到指定变量name中
  • 实例:find_library( log-lib log )

set_directory_properties

  • 原型:
    set_target_properties(target1 target2 …
    PROPERTIES prop1 value1
    prop2 value2 …)
  • 参数:
    target1 target2 ...:目标名称;
    prop1 prop2 ...:代表属性,取值为INCLUDE_DIRECTORIES、IMPORTED_LOCATION、LINK_DIRECTORIES等;
    value1 value2 ...:被设置的属性值;
  • 作用:设置目标属性值
  • 实例:set_target_properties(libavcodec-57 PROPERTIES IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libavcodec-57.so")

target_link_libraries

  • 原型:
    target_link_libraries(< target> … < item>… …)
  • 参数:
    target:目标库
    item:被连接的库
  • 作用:将若干库链接到目标库文件,需要注意的是链接的顺序应该符合gcc链接的顺序规则,即被链接的库放在依赖它的库的后面,比如lib1依赖于lib2,lib2依赖于lib3,那么target_link_libraries(name lib1 lib2 lib3)。
  • 实例:target_link_libraries(LameMp3 ${log-lib} )

add_subdirectory

  • 原型:
    add_subdirectory(source_dir [binary_dir]
    [EXCLUDE_FROM_ALL])
  • 参数:
    source_dir:外部文件夹目录
    binary_dir:用于指定外部文件夹在输出文件夹中的位置
  • 作用:添加外部项目文件夹到build任务列表中,该文件夹可包含自己的CmakeLists.txt文件。
  • 实例:add_subdirectory( ${CASSDK_DIR}/cassdk cassdk.out)

find_library

  • 原型:
    find_library (< VAR> name1 [path1 path2 …])
    find_library (
    < VAR>
    name | NAMES name1 [name2 …] [NAMES_PER_DIR]
    [HINTS path1 [path2 … ENV var]]
    [PATHS path1 [path2 … ENV var]]
    [PATH_SUFFIXES suffix1 [suffix2 …]]
    [DOC “cache documentation string”]
    [NO_DEFAULT_PATH]
    [NO_CMAKE_ENVIRONMENT_PATH]
    [NO_CMAKE_PATH]
    [NO_SYSTEM_ENVIRONMENT_PATH]
    [NO_CMAKE_SYSTEM_PATH]
    [CMAKE_FIND_ROOT_PATH_BOTH |
    ONLY_CMAKE_FIND_ROOT_PATH |
    NO_CMAKE_FIND_ROOT_PATH]
    )
  • 参数:
    name1 [name2 ...]:为库指定一个或多个可能的名称
    PATHS:需指定要搜索的目录
  • 作用:查找库所在路径。cmake会在目录中查找,如果所有目录中都没有,值log-lib就会被赋为NO_DEFAULT_PATH
  • 实例:find_library(log-lib log)
2.2 实战案例:使用ffmpeg第三方框架

 在第一个小节中,我们着重介绍了如何在AS中使用Cmake编译第三方源码,使得能够轻易的调用第三方源码中的API实现开发相关的功能动态库。本节将以使用ffmpeg开源框架解析rtsp数据流为例,阐述导入和使用已知第三方库的方法步骤。导入流程如下:

1. 创建Android项目,使其支持C/C++开发,详见 Android NDK开发之旅(2):Android Studio中使用CMake进行NDK/JNI开发(初级)。然后,将ffmpeg相关头文件(compat…ffmpeg.h,如何获取详见Amdroid直播开发之旅(5):详解ffmpeg编译与在Android平台上的移植)拷贝到cpp目录下,如下如所示。其中nativesurface.cpp…vdecode.cpp这几个文件为自定义的C/C++开发源文件。示意图如下:

2. 在工程的main目录下创建jniLibs目录,将编译好的ffmpeg相关库(.so、.a)拷贝到jniLibs目录下,注意不同的架构需放入相应的目录,常见的架构有“armeabi”、“armeabi-v7a”、"x86"等等。示意图如下:

3. 编写CmakeLists.txt脚本文件编译规则,需要实现包括导入ffmpeg相关头文件、导入ffmpeg库、指定自定义构建库信息以及将ffmpeg库链接到自定义库等。CmakeLists.txt代码如下:

cmake_minimum_required(VERSION 3.4.1)set(CMAKE_VERBOSE_MAKEFILE on)# 设置so输出路径set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})# 赋值变量libsset(libs "${CMAKE_SOURCE_DIR}/src/main/jniLibs")# 导入ffmpeg相关头文件include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include)#-----------------------------导入ffmpeg库--------------------------------------add_library(libavcodec-57 SHARED IMPORTED )set_target_properties(libavcodec-57 PROPERTIES    IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libavcodec-57.so")add_library(libavdevice-57 SHARED IMPORTED )set_target_properties(libavdevice-57 PROPERTIES    IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libavdevice-57.so")add_library(libavfilter-6 SHARED IMPORTED )set_target_properties(libavfilter-6 PROPERTIES    IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libavfilter-6.so")add_library(libavformat-57 SHARED IMPORTED )set_target_properties(libavformat-57 PROPERTIES    IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libavformat-57.so")add_library(libavutil-55 SHARED IMPORTED )set_target_properties(libavutil-55 PROPERTIES    IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libavutil-55.so")add_library(libpostproc-54 SHARED IMPORTED )set_target_properties(libpostproc-54 PROPERTIES    IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libpostproc-54.so")add_library(libswresample-2 SHARED IMPORTED )set_target_properties(libswresample-2 PROPERTIES    IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libswresample-2.so")add_library(libswscale-4 SHARED IMPORTED )set_target_properties(libswscale-4 PROPERTIES    IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libswscale-4.so")#------------------------------配置、链接动态库--------------------------------#设置变量CMAKE_CXX_FLAGG修改编译选项[只针对c和c++编译器]#这里使cmkae编译带有c++11特性,其中-std用于指定c/c++标准;# -fexceptions用于开启编译器异常捕获机制;-frtti用于支持RTTI,配合异常处理set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -fexceptions -frtti")#指定自定义构建库信息add_library( # 库名称             decstream             # 库类型为动态库             SHARED             # 将被编译的源文件             src/main/cpp/nativesurface.cpp             src/main/cpp/vdecode.cpp             src/main/cpp/util.cpp             src/main/cpp/openstream.cpp)#查找NDK中原生库log-libfind_library( # Sets the name of the path variable.              log-lib              # Specifies the name of the NDK library that              # you want CMake to locate.              log)#将ffmpeg库、原生库android、log链接到自定义库decstream target_link_libraries(decstream android log    libavcodec-57    libavdevice-57    libavfilter-6    libavformat-57    libavutil-55    libpostproc-54    libswresample-2    libswscale-4    ${log-lib}    )

4. 修改app的gradle文件,配置cmake参数、指定编译的目标平台、关联CmakeLists.txt文件。其中,externalNativeBuild{…}可用于指定cmake编译选项等,ndk选项用于指定需要编译哪种平台的so。代码如下:

android {    compileSdkVersion 26    defaultConfig {        applicationId "com.jiangdg.ffmpeg"        minSdkVersion 15        targetSdkVersion 22        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"       // 配置本地的build选项        externalNativeBuild {            cmake {               // 是否让CMake构建原生库时支持“NEON”,默认的是不支持                // DANDROID_TOOLCHAIN支持CMake构建原生库                arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang","-DCMAKE_BUILD_TYPE=Release"                //'-DANDROID_STL=gnustl_static'                // 指定使用c++11特效,开启异常处理机制                cppFlags "-std=c++11","-frtti", "-fexceptions"            }        }        // 设置输出指定目标平台so        ndk{            abiFilters 'armeabi'        }    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }   // 指定CmakeLists.txt的路径    externalNativeBuild {        cmake {            path 'CMakeLists.txt'        }    }}

 考虑到本文的主题以及篇幅不宜过长,这里就不详细解析如何使用ffmpeg框架解析rtsp数据流的代码实现,该部分将在ffmpeg框架系列文章中进行深入讲解。

更多相关文章

  1. Android(安卓)Studio查看Android(安卓)5.x源码的步骤详解
  2. Android文件相关:RandomAccessFile介绍与使用demo
  3. Android(安卓)ClassLoader
  4. android 数据存储之 SharedPreference
  5. Android(安卓)反编译
  6. 从Android到React Native开发(一、入门)
  7. Android学习路线(二十七)键值对(SharedPreferences)存储
  8. 两个Android选择文件对话框
  9. Android(安卓)Studio中Gradle的Daemon

随机推荐

  1. 如何在mac本上安装android sdk
  2. Android 中文 API (26) ―― SeekBar
  3. Analyzing Android Malware
  4. Android Studio TV开发教程(一)处理电视硬
  5. 如何在activity中控制屏幕点亮与关闭
  6. Android占位符,Java占位符
  7. android零碎要点---android开发者的福音,5
  8. Android Studio在线调试Android Framewor
  9. Android:改变 Toolbar 的文字和溢出图标颜
  10. 从android游戏框架看其生命周期