用Cmake编译程序时,链接到FFmpeg库
一、前言
可喜可贺,折腾了一晚上终于把这个勾八链接成功了,已经要吐了。看到下面控制台的输出,吾心甚慰呀😭
[100%] Linking CXX executable rknn_yolov5_demo
[100%] Built target rknn_yolov5_demo
[100%] Built target rknn_yolov5_demo
Install the project...
下面总结一下,我之前链接失败的几点原因:
-
忽略了系统架构之间的差别
Cmake工具运行的环境是Ubuntu18.04操作系统,系统架构为X86_64;而我的目标环境是嵌入式操作系统,该系统安装在RKNN 1808(瑞芯微)开发板上,系统架构为aarch(ARM 64);
这导致链接时出现了千奇百怪的错误,几度差点心态崩溃
-
盲目拷贝文件,没考虑各种依赖问题
上面第一点屡试不爽后,我转移了目标,让甲方开发人员在RKNN开发板上先安装了FFmpeg工具,然后我直接将板子上的共享库so文件和头文件拷贝了过来。
继续尝试链接,依然失败,而且此次的问题比上次更多了,疯狂查阅资料发现是我只拷贝了文件却忽略了这些文件的依赖,因此惨败。
直到第3次,我尝试自己对FFmpeg进行交叉编译【见另外一篇文章:ubuntu下交叉编译ffmpeg到目标架构为aarch架构的系统-CSDN博客】,最终才得以链接成功,不过嘛我好像跑题了嘿嘿,我是要介绍怎么链接FFmpeg而不是怎么正确地搞到FFmpeg相关东西。
二、包含头文件和链接共享库
-
找到共享库的路径和头文件所在路径(小声说:其实如果你是自己编译的ffmpeg,你应该知道在哪)
find / -name "libav*
运行命令后,操作系统会在整个文件系统中查找这个东西,然后返回位置,比如我的返回:
/home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavfilter.so /home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavcodec.so /home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavdevice.so /home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavutil.so /home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavformat.so
注意,返回的可能有多个不同路径的结果,此时你就需要甄别,哪些是当前系统的,哪些是用在目标系统的
然后,就可以打开【/home/fy/LIBS/ffmpeg5.0.1_linux_arm64】这个文件夹,头文件和共享库都在这个文件夹的子文件夹下,瞅瞅我的:
(base) root@110kmg49ac7fk-0:/home/fy/LIBS/ffmpeg5.0.1_linux_arm64# ls bin include lib share
上面列出了4个文件夹,其中【include】是头文件的目录,【lib】是共享库文件的目录
-
包含头文件
打开你用于编译C++程序的CMakeLists文件,在任何位置,当然,一般写在中下部,写下面的语句,设置头文件的目录:
set(FFMPEG_INCLUDE_DIRS /home/fy/LIBS/ffmpeg5.0.1_linux_arm64/include ) include_directories(${FFMPEG_INCLUDE_DIRS})
- 【set】该关键字在Cmake工具里用于设置一个变量
- 【FFMPEG_INCLUDE_DIRS】该字符串为变量的名字
- 【/home/fy/LIBS/ffmpeg5.0.1_linux_arm64/include】该串为头文件的目录,现在这个是我的,你应该修改为你的
- 【include_directories】该关键字用于包含一个目录
- 【${FFMPEG_INCLUDE_DIRS}】这个表示我们的头文件目录了,里面的变量介绍过了,【${}】用来引用一个变量
因为【include_directories】的参数就是一个路径,而我们的变量【FFMPEG_INCLUDE_DIRS】就是给我们的路径起了个别名而已,因此上面的语句还可以简化为
include_directories(/home/fy/LIBS/ffmpeg5.0.1_linux_arm64/include)
-
链接共享库
在上面包含头文件的语句下面可以接着写下面的设置变量语句
set(FFMPEG_LIBRARIES /home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavcodec.so /home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavformat.so /home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavutil.so /home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libswscale.so )
该语句使得【FFMPEG_LIBRARIES】能代表参数中的四个共享库路径
然后,最终写一条链接语句
target_link_libraries(rknn_yolov5_demo stdc++fs ${FFMPEG_LIBRARIES} ${RKNN_API_LIB} dl)
-
【target_link_libraries】关键字,顾名思义,就是设置目标链接库
-
【rknn_yolov5_demo】这是链接的目标,表示这些库给谁用,在这里它是一个一组文件的别名,下面是我的定义,由于设置了链接目标,那么下面3个文件编译后,均可以使用链接到的库。
add_executable(rknn_yolov5_demo src/rga_func.c src/postprocess.cc src/main.cc )
-
【stdc++fs】表示C++标准文件系统库,这是我自己项目用的,所以读者不必了解,介绍它仅为了讲解的完备性
后面是其他的要链接的库了,由于都是变量,所以用了【${}】 ,至于后面还有个【dl】,则表示链接到动态链接库,也就是共享库咯
-
三、实例
下面是我写的一个完整的编译C++程序并链接到了FFmpeg库的CMakeLists代码,我写了详细的注释,如果还是有疑惑可以在评论区讨论。
这只是一个测试程序实际情况可恨复杂得多
# 设置项目名称为 FFmpegTest
project(FFmpegTest)
# 设置编译器选项,这里设置了C和C++的,具体参数自行百度,为了美观此处不赘述
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g -s -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall -g -s -O3")
# 指定 FFmpeg 的头文件目录
include_directories(/home/fy/LIBS/ffmpeg5.0.1_linux_arm64/include)
# 指定 FFmpeg 的动态链接库路径变量 FFMPEG_LIBRARIES,有2个是so.60是因为我的目标系统需要
set(FFMPEG_LIBRARIES
/home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavcodec.so.60
/home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavformat.so.60
/home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libavutil.so
/home/fy/LIBS/ffmpeg5.0.1_linux_arm64/lib/libswscale.so
)
# 添加源文件
add_executable(SourceFile
test.cpp
)
# 链接FFmpeg库,链接到的库给SourceFile这个可执行文件(由test.cpp编译得到)用
target_link_libraries(SourceFile ${FFMPEG_LIBRARIES} dl)
-
创建一个目录用来存放构建的东西,并进入目录
(base) root@110kmg49ac7fk-0:/17106/Pengcaiping/FFmpeg# mkdir build (base) root@110kmg49ac7fk-0:/17106/Pengcaiping/FFmpeg# cd build/
-
开始生成后面两个参数是指定交叉编译器的路径;第二段代码是控制台的返回信息
(base) root@110kmg49ac7fk-0:/17106/Pengcaiping/FFmpeg/build# cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=/17106/Pengcaiping/gcc-linaro-6.4.1-2017.08-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc -DCMAKE_CXX_COMPILER=/17106/Pengcaiping/gcc-linaro-6.4.1-2017.08-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-g++
# ······此处省略约2000字母的输出,下面的才是核心 -- Configuring done -- Generating done -- Build files have been written to: /17106/Pengcaiping/FFmpeg/build
上面的信息表示生成这一步成功了
-
开始编译,出现后面的输出表示编译成功
(base) root@110kmg49ac7fk-0:/17106/Pengcaiping/FFmpeg/build# make
Scanning dependencies of target SourceFile [ 50%] Building CXX object CMakeFiles/SourceFile.dir/test.o [100%] Linking CXX executable SourceFile [100%] Built target SourceFile
-
拷贝build目录下的SourceFile可执行文件到目标系统(RKNN1808下的aarch64架构的Linux系统)
[root@rk1808:/home/user]$ ls 1472340076-1-192.mp4 SourceFile ffmpeg rknn testlib
从以上 输出证明我拷贝过去了
-
拷贝库文件过去,放到testlib目录下
[root@rk1808:/home/user/testlib]$ ls libavcodec.so.60 libavformat.so.60 libavutil.so.58 libswscale.so.7
证明我拷过去了
-
指定链接库的目录路径,此时程序运行时如果需要链接库就会来这个目录下找
[root@rk1808:/home/user]$ export LD_LIBRARY_PATH=testlib:$LD_LIBRARY_PATH
-
运行程序
[root@rk1808:/home/user]$ ./SourceFile Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '1472340076-1-192.mp4': Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 encoder : Lavf58.29.100 description : Packed by Bilibili XCoder v2.0.2 Duration: 00:05:23.14, start: 0.000000, bitrate: 1231 kb/s Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1280x720, 1153 kb/s, 30 fps, 30 tbr, 16k tbn (default) Metadata: handler_name : VideoHandler vendor_id : [0][0][0][0] Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 68 kb/s (default) Metadata: handler_name : SoundHandler vendor_id : [0][0][0][0] FFmpeg test successful!
程序正确运行,读取了视频信息,并且输出了,这说明我们前面的工作都是有效的。
再看看我的cpp源码吧
#include <iostream> // 包含输入输出流库
#include <string> // 包含字符串库
extern "C" {
#include <libavformat/avformat.h> // 包含 FFmpeg 格式处理库
#include <libavutil/imgutils.h> // 包含 FFmpeg 图像工具库
#include <libavutil/pixdesc.h> // 包含 FFmpeg 像素格式库
}
int main() {
avformat_network_init(); // 初始化 FFmpeg 网络模块
const char* filename = "1472340076-1-192.mp4"; // 设置视频文件名
AVFormatContext *format_ctx = nullptr; // 声明格式上下文指针,初始化为空指针
int ret = avformat_open_input(&format_ctx, filename, nullptr, nullptr); // 打开输入文件并将格式上下文赋给 format_ctx
if (ret != 0) { // 检查是否成功打开文件
std::cerr << "Error opening input file" << std::endl; // 输出错误信息到标准错误流
return 1; // 返回错误码
}
ret = avformat_find_stream_info(format_ctx, nullptr); // 获取流信息
if (ret < 0) { // 检查是否成功获取流信息
std::cerr << "Error finding stream information" << std::endl; // 输出错误信息到标准错误流
avformat_close_input(&format_ctx); // 关闭输入文件并释放资源
return 1; // 返回错误码
}
av_dump_format(format_ctx, 0, filename, 0); // 打印格式信息到标准输出流
avformat_close_input(&format_ctx); // 关闭输入文件并释放资源
std::cout << "FFmpeg test successful!" << std::endl; // 输出成功信息到标准输出流
return 0; // 返回成功码
}
四、心灵的救赎
现在是北京时间2024年4月16日2时30分,现在这种时间睡觉实际已经成为了我的日常。
幸运的是,这样的日常倒不是为谁所迫,自己也还愿意这样,享受每天吸收知识感觉。
感觉组里没有搞科研的环境,看着师兄们的时间都被杂事占据了,被横向占据了,这样的生活与我当初想象中的科研生活还是相去甚远。
就到这吧,下面抄点句子,补一补心灵,然后就可以进入梦乡了,如果你也凌晨在看我的文章,且看到了这,那么:陌生人,祝你好梦。
- 生命是华丽错觉,时间是贼,偷走一切。 ——阿信《如烟》
- 世界有缺陷,可能性才大 。 ——朱光潜
- 人生的意义,就在于一直在找寻的路上,寻寻觅觅,我们也变得越来越丰富。