CMake学习笔记

CMake是一个跨平台的编译构建工具,可以用简单的语句来描述所有平台的编译过程。他能够输出各种各样的MakeFile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。

CMake的学习资料较少,只能通过github的各种例子进行学习。不过仍然建议在学习之前看一下《CMake实践》这本中文书,本CMake系列是依据github上的cmake-examples进行翻译总结。 英文github地址

从hello-cmake开始

学习编程语言从hello world开始,学习cmake那就从hello-cmake开始吧,文件树如下

1./
2├── CMakeLists.txt
3├── build
4└── main.cpp

main.cpp

1#include <iostream>
2
3int main(){
4    std::cout << "Hello cmake" << std::endl;
5    return 0;
6}

CMakeLists.txt

 1# 设置CMake最小版本
 2cmake_minimum_required(VERSION 3.5) 
 3
 4# 设置工程名
 5project (hello_cmake) 
 6
 7# 生成名称为hello_cmake的可执行文件
 8#add_executable(hello_cmake main.cpp) 
 9
10# 生成与工程同名的二进制文件
11add_executable(${PROJECT_NAME} main.cpp) 

CMake构建包含一个项目名称,上面的命令会自动生成一些变量,在使用多个项目时引用某些变量会更加容易。比如生成了:PROJECT_NAME 这个变量。PROJECT_NAME是变量名,${PROJECT_NAME}是变量值,值为hello_cmake。

add_executable()

add_executable()命令指定某些源文件生成可执行文件,本例中是main.cpp。 add_executable()函数的第一个参数是可执行文件名,第二个参数是要编译的源文件列表。

变量CMAKE_BINARY_DIR指向 cmake命令的根文件夹,所有二进制文件在这个文件夹里产生。使用外部构建,我们可以创建一个可以位于文件系统上任何位置的构建文件夹。 所有临时构建和目标文件都位于此目录中,以保持源代码树的整洁。

运行下述代码,新建build构建文件夹,并运行cmake命令

1mkdir build
2cd build/
3cmake ..

可以看到,build文件夹下生成了许多二进制文件,如果要从头开始重新创建cmake环境,只需删除构建目录build,然后重新运行cmake,非常方便,推荐使用外部构建,内部构件则是直接在工程根目录执行cmake,临时文件会和源代码文件在一起,导致工程非常乱,不推荐。

 1./
 2├── CMakeLists.txt
 3├── build
 4│   ├── CMakeCache.txt
 5│   ├── CMakeFiles
 6│   │   ├── 3.20.5
 7│   │   │   ├── CMakeCCompiler.cmake
 8│   │   │   ├── CMakeCXXCompiler.cmake
 9│   │   │   ├── CMakeDetermineCompilerABI_C.bin
10│   │   │   ├── CMakeDetermineCompilerABI_CXX.bin
11│   │   │   ├── CMakeSystem.cmake
12│   │   │   ├── CompilerIdC
13│   │   │   │   ├── CMakeCCompilerId.c
14│   │   │   │   ├── a.out
15│   │   │   │   └── tmp
16│   │   │   └── CompilerIdCXX
17│   │   │       ├── CMakeCXXCompilerId.cpp
18│   │   │       ├── a.out
19│   │   │       └── tmp
20│   │   ├── CMakeDirectoryInformation.cmake
21│   │   ├── CMakeOutput.log
22│   │   ├── CMakeTmp
23│   │   ├── Makefile.cmake
24│   │   ├── Makefile2
25│   │   ├── TargetDirectories.txt
26│   │   ├── cmake.check_cache
27│   │   ├── hello_cmake.dir
28│   │   │   ├── DependInfo.cmake
29│   │   │   ├── build.make
30│   │   │   ├── cmake_clean.cmake
31│   │   │   ├── compiler_depend.make
32│   │   │   ├── compiler_depend.ts
33│   │   │   ├── depend.make
34│   │   │   ├── flags.make
35│   │   │   ├── link.txt
36│   │   │   ├── main.cpp.o
37│   │   │   ├── main.cpp.o.d
38│   │   │   └── progress.make
39│   │   └── progress.marks
40│   ├── Makefile
41│   ├── cmake_install.cmake
42│   └── hello_cmake
43└── main.cpp

VERBOSE = 1

可以看到已经生成了MakeFile,现在直接执行make指令即可,运行make命令时,输出仅显示构建状态。 要查看用于调试目的的完整输出,可以在运行make时添加VERBOSE = 1标志:

包含头文件 —— hello headers

文件数如下所示:

 1./
 2├── CMakeLists.txt
 3├── build
 4├── include
 5│   └── Hello.h
 6└── src
 7    ├── Hello.cpp
 8    └── main.cpp
 9
103 directories, 4 files

Hello.h

 1// 声明了Hello类,Hello的方法是print()
 2#ifndef __HELLO_H__
 3#define __HELLO_H__
 4
 5class Hello {
 6public:
 7    void print();
 8};
 9
10#endif // __HELLO_H__

Hello.cpp

1// 实现了Hello::print()
2#include <iostream>
3
4#include "Hello.h"
5
6void Hello::print(){
7    std::cout << "Hello Headers!" << std::endl;
8}

main.cpp

1#include "Hello.h"
2
3int main(int argc, char *argv[]) {
4    Hello hi;
5    hi.print();
6    return 0;
7}

CMakeLists.txt

 1# 设置CMake最小版本
 2cmake_minimum_required(VERSION 3.5) 
 3
 4# 设置工程名
 5project (hello_headers) 
 6
 7# 创建一个变量,名字叫SOURCE。它包含了所有的cpp文件。
 8set(SOURCES src/main.cpp src/Hello.cpp)
 9
10# 用所有的源文件生成一个可执行文件,因为这里定义了SOURCE变量,所以就不需要罗列cpp文件了
11# 等价于命令:add_executable(hello_headers src/Hello.cpp src/main.cpp)
12# 不建议使用源文件变量这种方式,同时也不建议使用file(GLOB SOURCES "src/*.cpp")等通配符
13# 而是像add_executable(hello_headers src/Hello.cpp src/main.cpp)一个一个列出
14add_executable(hello_headers ${SOURCES})
15
16# 设置这个可执行文件hello_headers需要包含的库的路径
17target_include_directories(hello_headers
18    PRIVATE
19    ${PROJECT_SOURCE_DIR}/include # PROJECT_SOURCE_DIR指工程顶层目录
20)
21
22# 使用message打印CMake定义的变量
23message("CMAKE_SOURCE_DIR = "${CMAKE_SOURCE_DIR})
24message("CMAKE_CURRENT_SOURCE_DIR = "${CMAKE_CURRENT_SOURCE_DIR})
25message("PROJECT_SOURCE_DIR = "${PROJECT_SOURCE_DIR})
26message("CMAKE_BINARY_DIR = "${CMAKE_BINARY_DIR})
27message("CMAKE_CURRENT_BINARY_DIR = "${CMAKE_CURRENT_BINARY_DIR})
28message("PROJECT_BINARY_DIR = "${PROJECT_BINARY_DIR})

CMake语法指定了许多变量,可用于帮助您在项目或源代码树中找到有用的目录。 其中一些包括:

Variable Info
CMAKE_SOURCE_DIR 根源代码目录,工程顶层目录。暂认为就是PROJECT_SOURCE_DIR
CMAKE_CURRENT_SOURCE_DIR 当前处理的 CMakeLists.txt 所在的路径
PROJECT_SOURCE_DIR 工程顶层目录
CMAKE_BINARY_DIR 运行cmake的目录。外部构建时就是build目录
CMAKE_CURRENT_BINARY_DIR The build directory you are currently in.当前所在build目录
PROJECT_BINARY_DIR 暂认为就是CMAKE_BINARY_DIR

message()

在CMakeLists中,利用message()命令输出一下这些变量。另外,这些变量不仅可以在CMakeLists中使用,同样可以在源代码.cpp中使用:

注意:file(GLOB SOURCES “src/*.cpp”)等通配符、源文件变量这些使用都是不太推荐的,除非源代码极多的情况,否则还是一个一个列出即可。

target_include_directories()

上面已经介绍了如何包含其他目录,当有其他需要包含的文件夹(文件夹里有头文件)时,可以使用以下命令使编译器知道它们: target_include_directories()。 编译此目标时,这将使用-I标志将这些目录添加到编译器中,例如 -I /目录/路径

1# 设置这个可执行文件hello_headers需要包含的库的路径
2target_include_directories(hello_headers
3    PRIVATE
4    ${PROJECT_SOURCE_DIR}/include # PROJECT_SOURCE_DIR指工程顶层目录
5)

包含静态库 —— static library

文件树如下:

 1./
 2├── CMakeLists.txt
 3├── build
 4├── include
 5│   └── static
 6│       └── mylib.h
 7└── src
 8    ├── main.cpp
 9    └── mylib.cpp
10
114 directories, 4 files

mylib.h

1#ifndef __MYLIB_H__
2#define __MYLIB_H__
3
4class MyLib{
5public:
6    void exec();
7};
8
9#endif // __MYLIB_H__

mylib.cpp

1#include <iostream>
2#include "static/mylib.h"
3
4void MyLib::exec(){
5    std::cout << "Hello Static Library!" << std::endl;
6}

main.cpp

1#include "static/mylib.h"
2
3int main(int argc, char *argv[]){
4    MyLib lib;
5    lib.exec();
6    return 0;
7}

先生成静态库:

CMakeList.txt

 1cmake_minimum_required(VERSION 3.5)
 2project(hello_library)
 3
 4# 创建静态库:库的源文件 mylib.cpp 生成静态库 my_library
 5add_library(my_library STATIC 
 6    src/mylib.cpp
 7)
 8
 9# target_include_directories为一个目标(可能是一个库library也可能是可执行文件)添加头文件路径
10target_include_directories(my_library
11    PUBLIC 
12        ${PROJECT_SOURCE_DIR}/include
13)
14
15# ----------------------------------------------------------------
16# 指定用哪个源文件生成可执行文件
17add_executable(main 
18    src/main.cpp
19)
20# 链接可执行文件和静态库
21target_link_libraries( main
22    PRIVATE 
23        my_library
24)
25# 链接库和包含头文件都有关于scope这三个关键字的用法

包含动态库 —— shared library

 1./
 2├── CMakeLists.txt
 3├── build
 4├── include
 5│   └── shared
 6│       └── mylib.h
 7└── src
 8    ├── main.cpp
 9    └── mylib.cpp
10
114 directories, 4 files

代码和Static Library差不多,主要是CMakeLists.txt不同

 1cmake_minimum_required(VERSION 3.5)
 2project(hello_library)
 3
 4# 创建静态库:库的源文件 mylib.cpp 生成动态库 my_library
 5add_library(my_library SHARED 
 6    src/mylib.cpp
 7)
 8
 9# target_include_directories为一个目标(可能是一个库library也可能是可执行文件)添加头文件路径
10target_include_directories(my_library
11    PUBLIC 
12        ${PROJECT_SOURCE_DIR}/include
13)
14
15# ----------------------------------------------------------------
16# 指定用哪个源文件生成可执行文件
17add_executable(main 
18    src/main.cpp
19)
20# 链接可执行文件和动态库
21target_link_libraries( main
22    PRIVATE 
23        my_library
24)
25# 链接库和包含头文件都有关于scope这三个关键字的用法

设置构建类型 —— build-type

1./
2├── CMakeLists.txt
3├── build
4└── main.cpp

main.cpp

1#include <iostream>
2
3int main(){
4    std::cout << "hello build" << std::endl;
5    return 0;
6}

CMakeLists.txt

 1# 设置CMake最小版本
 2cmake_minimum_required(VERSION 3.5) 
 3
 4# 设置工程名
 5project (build_type) 
 6
 7# 如果没有指定则设置默认编译方式
 8if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
 9  # 在命令行中输出message里的信息
10  message("Setting build type to 'RelWithDebInfo' as none was specified.")
11
12  # 不管CACHE里有没有设置过CMAKE_BUILD_TYPE这个变量,都强制赋值这个值为RelWithDebInfo
13  # STRING "Choose the type of build." 就是在使用cmake-gui的时候的文本提示
14  set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
15
16  # 当使用cmake-gui的时候,设置构建级别的四个可选项
17  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
18    "MinSizeRel" "RelWithDebInfo")
19endif()
20
21add_executable(cmake_examples_build_type main.cpp)
22
23# 命令的具体解释在CMake解析中,这里的注释只说明注释后每一句的作用

优化级别

CMake具有许多内置的构建配置,可用于编译工程。 这些配置指定了代码优化的级别,以及调试信息是否包含在二进制文件中。

这些优化级别,主要有:

  • Release —— 不可以打断点调试,程序开发完成后发行使用的版本,占的体积小。 它对代码做了优化,因此速度会非常快,

在编译器中使用命令: -O3 -DNDEBUG 可选择此版本。

  • Debug ——调试的版本,体积大。

在编译器中使用命令: -g 可选择此版本。

  • MinSizeRel—— 最小体积版本

在编译器中使用命令:-Os -DNDEBUG可选择此版本。

  • RelWithDebInfo—— 既优化又能调试。

在编译器中使用命令:-O2 -g -DNDEBUG可选择此版本。

在命令行运行CMake的时候, 使用cmake命令行的-D选项配置编译类型

1cmake .. -DCMAKE_BUILD_TYPE=Release

set()命令

需要注意set()命令,该命令可以为普通变量、缓存变量、环境变量赋值。

处可以设置零个或多个参数。多个参数将以 分号分隔的列表 形式加入,以形成要设置的实际变量值。零参数将导致未设置普通变量。见 unset() 命令显式取消设置变量。

所以此处学习SET命令需要分为设置普通变量,缓存变量以及环境变量三种类别来学习。

普通变量

1set(<variable> <value>... [PARENT_SCOPE])

设置的变量值作用域属于整个CMakeLists.txt 文件。(一个工程可能有多个CMakeLists.txt),当这个语句中加入PARENT_SCOPE后,表示要设置的变量是父目录中的CMakeLists.txt设置的变量,比如有如下目录树:

1├── CMakeLists.txt
2└── src
3    └── CMakeLists.txt

并且在顶层的CMakeLists.txt中包含了src目录:add_subdirectory(src),那么顶层的CMakeLists.txt就是父目录,如果父目录中有变量Bang,在子目录中可以直接使用(比如用message输出Bang,值是父目录中设置的值)并且利用set()修改该变量Bang的值,但是如果希望在出去该子CMakeLists.txt对该变量做出的修改能够得到保留,那么就需要在set()命令中加入Parent scope这个变量。当然,如果父目录中本身没有这个变量,子目录中仍然使用了parent scope,那么出了这个作用域后,该变量仍然不会存在。

缓存变量

1set(<variable> <value>... CACHE <type> <docstring> [FORCE])
  • 首先什么是CACHE变量,就是在运行cmake的时候,变量的值可能会被缓存到一份文件里即build命令下的CMakeCache.txt,当你重新运行cmake的时候,那些变量会默认使用这个缓存里的值。这个变量是全局变量,整个CMake工程都可以使用该变量。
  • 在这个文件里,只要运行cmake ..命令,自动会出现一些值,比如 CMAKE_INSTALL_PREFIX ,如果设置 set(CMAKE_INSTALL_PREFIX “/usr”) ,虽然CACHE缓存文件里还有这个CMAKE_INSTALL_PREFIX 变量,但是因为我们显示得设置了一个名为CMAKE_INSTALL_PREFIX 的正常变量,所以之后使用CMAKE_INSTALL_PREFIX ,值是我们设置的普通变量的值。
  • 如果加上CACHE关键字,则设置的这个变量会被写入缓存文件中(但如果本身缓存文件中有这个变量,则不会覆盖缓存中的变量)。只有加上FORCE关键字,这个被写入文件的值会覆盖之前文件中存在的同名变量。

比如

1set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)

这句话,就是强制在缓存文件中覆盖CMAKE_BUILD_TYPE这个变量,将这个变量设置为RelWithDebInfo。而STRING "Choose the type of build."参数在使用cmake-gui的时候起作用,在界面上会出现一个下拉框供给用户选择来设置CMAKE_BUILD_TYPE变量。里的一行文字作为提示。

但这个下拉框里的内容,需要使用随后的set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")这个命令来设置,也就是所谓的设置string缓存条目属性。

设置编译选项 —— compile flags

首先说一下什么是编译标志(或者叫编译选项)。可执行文件的生成离不开编译和链接,那么如何编译,比如编译时使用C++的哪一个标准?这些编译设置都在CMAKE_CXX_FLAGS变量中。(C语言编译选项是CMAKE_C_FLAGS)

1./
2├── CMakeLists.txt
3├── build
4└── main.cpp
5
61 directory, 2 files

main.cpp

 1#include <iostream>
 2
 3int main(int argc, char *argv[]){
 4   std::cout << "Hello Compile Flags!" << std::endl;
 5
 6   // only print if compile flag set
 7#ifdef EX2
 8  std::cout << "Hello Compile Flag EX2!" << std::endl;
 9#endif
10
11#ifdef EX3
12  std::cout << "Hello Compile Flag EX3!" << std::endl;
13#endif
14
15   return 0;
16}

CMakeLists.txt

 1# 设置CMake最小版本
 2cmake_minimum_required(VERSION 3.5)
 3
 4# 设置工程名
 5project (compile_flags)
 6
 7# 强制设置默认C++编译标志变量为缓存变量
 8set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE)
 9
10add_executable(cmake_example_complie_flags main.cpp)
11
12# 为可执行文件添加私有编译定义【只推荐这种方式】
13target_compile_definitions(cmake_example_complie_flags 
14  PRIVATE EX3
15)

target_compile_definitions

在现代CMake中设置C ++标志的推荐方法是专门针对某个目标(target)设置标志,可以通过target_compile_definitions()函数设置某个目标的编译标志

1target_compile_definitions(cmake_examples_compile_flags
2    PRIVATE EX3
3)

如果这个目标是一个库(cmake_examples_compile_flags),编译器在编译目标时添加定义-DEX3 ,并且选择了范围PUBLIC或INTERFACE,该定义-DEX3也将包含在链接此目标(cmake_examples_compile_flags)的所有可执行文件中。 注意,本语句使用了PRIVATE,所以编译选项不会传递。

1target_compile_definitions(<target>
2   <INTERFACE|PUBLIC|PRIVATE> [items1...]
3   <INTERFACE|PUBLIC|PRIVATE> [items2...] 
4   ...
5)

是给 target 添加编译选项, target 指的是由 add_executable()产生的可执行文件或 add_library()添加进来的库。<INTERFACE|PUBLIC|PRIVATE>指的是[items...] 选项可以传播的范围, PUBLIC and INTERFACE会传播 <target> INTERFACE_COMPILE_DEFINITIONS 属性, PRIVATE and PUBLIC 会传播 target COMPILE_DEFINITIONS 属性。

CMAKE_CXX_FLAGS

CMAKE_CXX_FLAGS是编译标志,默认为空,要设置其他默认编译标志,则需要如下使用:

1set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE)

强制设置默认C++编译标志变量为缓存变量,在build type已经说过,该缓存变量被定义在文件中,相当于全局变量,源文件中也可以使用这个变量。这个变量原本包含的参数仍然存在,只是添加了EX2。

CACHE STRING "Set C++ Compiler Flags" FORCE命令是为了强制将CMAKE_CXX_FLAGS变量放到CMakeCache.txt文件中。

类似设置CMAKE_CXX_FLAGS,还可以设置其他选项:

  • 设置C编译标志: CMAKE_C_FLAGS
  • 设置链接标志:CMAKE_LINKER_FLAGS.

通过命令也可以设置全局C编译器标志(不推荐)

1cmake .. -DCMAKE_CXX_FLAGS="-DEX3"

通过set (CMAKE_CXX_FLAGS... 这种方式会为该目录或所有包含的子目录中的所有目标全局设置一个编译器标志,所以依旧是不推荐。(不推荐)

最好的方式还是直接使用 target_compile_definitions() 函数只为目标设置对应的编译选项 。

包含三方库 —— including third party library

1./
2├── CMakeLists.txt
3├── build
4└── main.cpp
5
61 directory, 2 files

首先需要三方库,下面以引入Boost库为例,先安装Boost库

1brew install boost
2
3...
4
5# 这是boost库的位置
6/usr/local/Cellar/boost/1.76.0
7zchanglin@mbp 1.76.0 % ls
8INSTALL_RECEIPT.json	README.md		include			lib

main.cpp

 1#include <iostream>
 2#include <boost/shared_ptr.hpp>
 3#include <boost/filesystem.hpp>
 4
 5// Boost库
 6int main(int argc, char *argv[]){
 7    std::cout << "Hello Third Party Include!" << std::endl;
 8
 9    // use a shared ptr
10    boost::shared_ptr<int> isp(new int(4));
11
12    // trivial use of boost filesystem
13    boost::filesystem::path path = "/usr/share/cmake/modules";
14    if(path.is_relative()) {
15        std::cout << "Path is relative" << std::endl;
16    } else {
17        std::cout << "Path is not relative" << std::endl;
18    }
19
20   return 0;
21}

CMakeLists.txt

 1# 设置CMake最小版本
 2cmake_minimum_required(VERSION 3.5)
 3
 4# 设置工程名
 5project (compile_flags)
 6
 7# 查找Boost1.76.0这个库
 8find_package(Boost 1.76.0 REQUIRED COMPONENTS filesystem system)
 9
10# check if boost was found
11if(Boost_FOUND)
12    message ("boost found success")
13    include_directories(${Boost_INCLUDE_DIRS})
14else()
15    message (FATAL_ERROR "Cannot find Boost")
16endif()
17
18# Add an executable
19add_executable(third_party_include main.cpp)
20
21# link against the boost libraries
22target_link_libraries( third_party_include
23    PRIVATE
24        Boost::filesystem
25)
26
27message("Boot头文件路径:"${Boost_INCLUDE_DIRS})

几乎所有不平凡的项目都将要求包含第三方库,头文件或程序。 CMake支持使用 find_package() 函数查找这些工具的路径。 这将从CMAKE_MODULE_PATH中的文件夹列表中搜索格式为“ FindXXX.cmake”的CMake模块。 在linux上,默认搜索路径将是/usr/share/cmake/Modules,此示例要求将Boost库安装在默认系统位置。

1find_package(Boost 1.76.0 REQUIRED COMPONENTS filesystem system)

Boost-库名称, 这是用于查找模块文件FindBoost.cmake的一部分

1.76.0 - 需要的boost库最低版本

REQUIRED - 告诉模块这是必需的,如果找不到会报错

COMPONENTS - 要查找的库列表。从后面的参数代表的库里找boost

可以使用更多参数,也可以使用其他变量。 在后面的示例中提供了更复杂的设置。

检查是否找到包

大多数被包含的包将设置变量XXX_FOUND,该变量可用于检查软件包在系统上是否可用。

1if(Boost_FOUND)
2    message ("boost found")
3    include_directories(${Boost_INCLUDE_DIRS})
4else()
5    message (FATAL_ERROR "Cannot find Boost")
6endif()

找到包后,它会自动导出变量,这些变量可以通知用户在哪里可以找到库,头文件或可执行文件。 与XXX_FOUND变量类似,它们与包绑定在一起,通常记录在FindXXX.cmake文件的顶部。

本例中的变量

1Boost_INCLUDE_DIRS - boost头文件的路径

include_directories

1include_directories ([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

将指定目录添加到编译器的头文件搜索路径之下,指定的目录被解释成当前源码路径的相对路径。

默认情况下,include_directories命令会将目录添加到列表最后,可以通过命令设置CMAKE_INCLUDE_DIRECTORIES_BEFORE变量为ON来改变它默认行为,将目录添加到列表前面。也可以在每次调用include_directories命令时使用AFTERBEFORE选项来指定是添加到列表的前面或者后面。如果使用SYSTEM选项,会把指定目录当成系统的搜索目录。该命令作用范围只在当前的CMakeLists.txt。


CMake如何引入FFmpeg

虽然前面用到了CMake去引入FFmpeg,但是是通过设置 C_FLAG/C_XX_FALG 属性来设置的链接库,这种方式就等于在链接的时候使用了-L 选项,其实这算是一种方式,但却不是一种优雅的方式,很多时候CMake在设置了 target_link_libraries却没有生效,其实是因为设置顺序的问题,对于一个目标文件,在 add_executable 之前就应该设置link_directories,下面是一个引入FFmpeg日志库的 Demo:

 1cmake_minimum_required (VERSION 3.8)
 2
 3# 设置工程名称
 4project ("FFmpegAPI")
 5
 6# 设置FFmpeg的头文件目录和库目录
 7set(ffmpeg_include_dir "D:\\FFmpeg\\ffmpeg-4.4-full_build-shared\\include")
 8set(ffmpeg_lib_dir "D:\\FFmpeg\\ffmpeg-4.4-full_build-shared\\lib")
 9
10# 添加头文件目录
11include_directories(ffmpeg_include_dir)
12
13# 添加链接库目录
14link_directories(ffmpeg_lib_dir)
15
16# 将源代码添加到此项目的可执行文件
17add_executable (FFmpegAPI "FFmpegAPI.c" )
18
19# 目标依赖库
20target_link_libraries(
21        FFmpegAPI
22        avutil
23)

所以应该在add_executable 之前就应该设置link_directories,这一点是非常重要的!

所以应该在add_executable 之前就应该设置link_directories,这一点是非常重要的!

所以应该在add_executable 之前就应该设置link_directories,这一点是非常重要的!

转载自 《cmake-examples-Chinese 》 https://sfumecjf.github.io/cmake-examples-Chinese/

注:部分内容有更改。

Android中引入FFmpeg & RTMP

引入FFmpeg & RTMP的静态库到自定义的动态库中:

 1├─app
 2│  ├─libs
 3│  └─src
 4│      ├─androidTest
 5│      ├─main
 6│      │  ├─cpp
 7│      │  │  ├─ffmpeg
 8│      │  │  │  ├─include
 9│      │  │  │  │  ├─libavcodec
10│      │  │  │  │  ├─libavfilter
11│      │  │  │  │  ├─libavformat
12│      │  │  │  │  ├─libavutil
13│      │  │  │  │  ├─libswresample
14│      │  │  │  │  └─libswscale
15│      │  │  │  └─libs
16│      │  │  │      └─armeabi-v7a
17│      │  │  │         └─libavcodec.a
18│      │  │  │         └─libavfilter.a
19│      │  │  │         └─libavformat.a
20│      │  │  │         └─libavutil.a
21│      │  │  │         └─libswresample.a
22│      │  │  │         └─libswscale.a
23│      │  │  ├─rtmp
24│      │  │  │   └─libs
25│      │  │  │       └─armeabi-v7a
26│      │  │  │          └─librtmp.a
27│      │  │  ├─CMakeLists.txt
28│      │  │  └─native-lib.cpp
29│      │  ├─java
30│      │  └─res
31│      └─test
32│          └─java
33└─gradle
34    └─wrapper

CMakeList.txt

 1cmake_minimum_required(VERSION 3.10.2)
 2
 3project("xxxx")
 4
 5set(FFMPEG ${CMAKE_SOURCE_DIR}/ffmpeg) # ffmpeg path
 6set(RTMP ${CMAKE_SOURCE_DIR}/rtmp) # rtmp path
 7
 8include_directories(${FFMPEG}/include)
 9
10# 批量导入源文件
11file(GLOB SRC_FILES *.cpp)
12
13link_directories(
14        ${FFMPEG}/libs/${CMAKE_ANDROID_ARCH_ABI}
15        ${RTMP}/libs/${CMAKE_ANDROID_ARCH_ABI}
16)
17
18add_library(
19        native-lib
20        SHARED
21        ${SRC_FILES})
22
23find_library(
24        log-lib
25        log)
26
27target_link_libraries(
28        native-lib
29        ${log-lib}
30        # 忽略顺序的方式,导入
31        -Wl,--start-group
32        avcodec avfilter avformat avutil swresample swscale
33        -Wl,--end-group
34        z # libz.so库,是FFmpeg需要用ndk的z库,FFMpeg需要额外支持libz.so
35        rtmp
36        android
37        OpenSLES
38)

app build.gradle

 1plugins {
 2    id 'com.android.application'
 3}
 4
 5android {
 6    defaultConfig {
 7		...
 8        externalNativeBuild {
 9            cmake {
10                cppFlags "-std=c++11"
11            }
12        }
13
14        ndk {
15            abiFilters  'armeabi-v7a'
16        }
17    }
18
19    externalNativeBuild {
20        cmake {
21            path "src/main/cpp/CMakeLists.txt"
22            version "3.10.2"
23        }
24    }
25}
26
27dependencies {
28	......
29}

注意点:

1、在add_executable 之前就应该设置link_directories

2、引入FFMpeg等库时,通过extern C的方式引入,否则默认是C++ Header:

 1#include <jni.h>
 2#include <string>
 3
 4extern "C"
 5{
 6#include "libavutil/avutil.h"
 7}
 8
 9extern "C"
10JNIEXPORT jstring JNICALL
11Java_cn_tim_xxx_XXXPlayer_getFFmpegVersion(JNIEnv *env, jobject thiz) {
12    // TODO: implement getFFmpegVersion()
13    std::string info  = "FFmpeg version = ";
14    info.append(av_version_info());
15    return env->NewStringUTF(info.c_str());
16}

FFmpeg4.2.1 static lib download url