用Cmake编译程序时,链接到FFmpeg库

news2024/12/25 9:06:19

用Cmake编译程序时,链接到FFmpeg库

一、前言

可喜可贺,折腾了一晚上终于把这个勾八链接成功了,已经要吐了。看到下面控制台的输出,吾心甚慰呀😭

[100%] Linking CXX executable rknn_yolov5_demo
[100%] Built target rknn_yolov5_demo
[100%] Built target rknn_yolov5_demo
Install the project...

下面总结一下,我之前链接失败的几点原因:

  1. 忽略了系统架构之间的差别

    Cmake工具运行的环境是Ubuntu18.04操作系统,系统架构为X86_64;而我的目标环境是嵌入式操作系统,该系统安装在RKNN 1808(瑞芯微)开发板上,系统架构为aarch(ARM 64);

    这导致链接时出现了千奇百怪的错误,几度差点心态崩溃
    在这里插入图片描述

  2. 盲目拷贝文件,没考虑各种依赖问题

    上面第一点屡试不爽后,我转移了目标,让甲方开发人员在RKNN开发板上先安装了FFmpeg工具,然后我直接将板子上的共享库so文件和头文件拷贝了过来。

    继续尝试链接,依然失败,而且此次的问题比上次更多了,疯狂查阅资料发现是我只拷贝了文件却忽略了这些文件的依赖,因此惨败。

    直到第3次,我尝试自己对FFmpeg进行交叉编译【见另外一篇文章:ubuntu下交叉编译ffmpeg到目标架构为aarch架构的系统-CSDN博客】,最终才得以链接成功,不过嘛我好像跑题了嘿嘿,我是要介绍怎么链接FFmpeg而不是怎么正确地搞到FFmpeg相关东西。

二、包含头文件和链接共享库

  1. 找到共享库的路径和头文件所在路径(小声说:其实如果你是自己编译的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】是共享库文件的目录

  2. 包含头文件

    打开你用于编译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)
    
  3. 链接共享库

    在上面包含头文件的语句下面可以接着写下面的设置变量语句

    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分,现在这种时间睡觉实际已经成为了我的日常。

幸运的是,这样的日常倒不是为谁所迫,自己也还愿意这样,享受每天吸收知识感觉。

感觉组里没有搞科研的环境,看着师兄们的时间都被杂事占据了,被横向占据了,这样的生活与我当初想象中的科研生活还是相去甚远。

就到这吧,下面抄点句子,补一补心灵,然后就可以进入梦乡了,如果你也凌晨在看我的文章,且看到了这,那么:陌生人,祝你好梦。

  • 生命是华丽错觉,时间是贼,偷走一切。 ——阿信《如烟》
  • 世界有缺陷,可能性才大 。 ——朱光潜
  • 人生的意义,就在于一直在找寻的路上,寻寻觅觅,我们也变得越来越丰富。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1613064.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[数据结构]——排序——插入排序

目录 ​编辑 1 .插入排序 1.基本思想&#xff1a; 2.直接插入排序&#xff1a; ​编辑 1.代码实现 2.直接插入排序的特性总结&#xff1a; 3.希尔排序( 缩小增量排序 ) 1.预排序 2.预排序代码 3.希尔排序代码 4.希尔排序的特性总结&#xff1a; 1 .插入排序 1.基本思…

C语言联合体详解

下午好诶&#xff0c;今天小眼神给大家带来一篇C语言联合体详解的文章~ 目录 联合体 1. 联合体类型的声明 2. 联合体的特点 代码一&#xff1a; 代码二&#xff1a; 3. 相同成员的结构体和联合体对比 ​编辑4. 联合体大小的计算 5. 联合体的优点 联合体 1. 联合体…

电脑显示缺失d3dx9_43.dll文件如何修复?分享5种详细的修复方法

在日常使用计算机的过程中&#xff0c;当我们尝试启动某个软件或运行一款游戏时&#xff0c;系统可能会弹出一个错误提示信息&#xff0c;明确指出“d3dx9_43.dll文件缺失”。这个情况表明&#xff0c;作为Windows操作系统中不可或缺的一部分&#xff0c;DirectX 9.0c的一个关键…

数电期末复习(二)逻辑代数基础

这里写目录标题 2.1 二值逻辑变量与基本逻辑运算2.1.1 与运算2.1.2 或运算2.1.3 非运算2.1.4 常用复合逻辑运算 2.2 逻辑函数的建立及其表示方法2.2.1 真值表表示2.2.2 逻辑函数表达式表示2.2.3 逻辑图表示方法2.2.4 波形图表示方法 2.3 逻辑代数2.3.1 逻辑代数的基本定律和恒等…

MySQL数据库基础知识(数据库/表的基础操作 + 基本类型)

文章目录 数据库的操作显示当前数据库服务器上有哪些数据库创建数据库使用数据库删除数据库 常用数据类型数值类型字符串类型日期类型小结(主要使用) 表的操作创建表查看表结构列出当前数据库的表删除表 注释MySQL创建的数据库/表存储在系统的位置 数据库的操作 输入的单词之间…

算法入门——二分查找

目录 1、二分模板 2、习题 1.704.二分查找 2.35.搜索插入位置 3.744. 寻找比目标字母大的最小字母 4.69. x 的平方根 5.1351. 统计有序矩阵中的负数 6.74. 搜索二维矩阵 7.34. 在排序数组中查找元素的第一个和最后一个位置 8.33. 搜索旋转排序数组 9.153. 寻找旋转排…

政企版 WPS Pro 专业版注册安装教程

政企版 WPS Pro 专业版安装及激活步骤 第 1 步&#xff1a;下载压缩包&#xff08;内含注册码&#xff09;【无解压密码】。 第 2 步&#xff1a;解压缩后&#xff0c;运行 exe 文件&#xff0c;默认步骤安装即可。 第 3 步&#xff1a;安装完成后&#xff0c;新建一个 Word …

【ThinkPHP框架教程·Part-04】URL访问模式

文章目录 一、URL解析1、URL解析格式2、URL解析示例说明3、设置URL重写 二&#xff0e;URL 兼容模式 本章节我们来简单了解一下 ThinkPHP6.0 的 URL 访问模式&#xff0c;解析它的访问方法。 一、URL解析 ThinkPHP 框架非常多的操作都是通过 URL 来实现的。 1、URL解析格式 由…

利用RFID无线射频技术,实现商品防伪和溯源信息管理

近几年来&#xff0c;伴随着信息化产业的快速发展&#xff0c;企业对于产品在生产、流通、分销和零售等环节的实时跟踪和监管的需求日趋强烈。同时企业在经营过程中不可避免地要在不同区域实行差异化的经销商拿货价格&#xff0c;从而导致窜货现象时有发 生&#xff0c;为企业带…

二叉树链式结构的实现-二叉树的前序 中序 后序 层序遍历

一、二叉树的结构了解 二叉树是&#xff1a; 空树非空&#xff1a;根节点&#xff0c;根节点的左子树、根节点的右子树组成的。 前序&#xff1a; 根 左子树 右子树 --》先根 中序&#xff1a;左子树 根 右子树 --》中根 后序&#xff1a;左子树 右子树 根 --》后根 层序&…

命理八字之答案之书前端uniapp效果实现

#uniapp# #答案之书# 不讲废话&#xff0c;先上截图 <div class"padding"><div class"flex align-center justify-center" style"padding-top:100px;"><div class"radarContainer"><div id"radarBox"…

C#医学实验室/检验信息管理系统(LIS系统)源码

目录 检验系统的总体目标 LIS主要包括以下功能&#xff1a; LIS是集&#xff1a;申请、采样、核收、计费、检验、审核、发布、质控、耗材控制等检验科工作为一体的信息管理系统。LIS系统不仅是自动接收检验数据&#xff0c;打印检验报告&#xff0c;系统保存检验信息的工具&a…

关于Android绘制这一遍就够了

Android绘制基础 Android平台提供了一套完整的UI框架&#xff0c;其中包括了绘制组件和绘制API。在Android中&#xff0c;绘制主要涉及到两个核心概念&#xff1a;Canvas和Paint。 Canvas Canvas是Android中的一个类&#xff0c;它代表了绘图的画布。你可以在这个画布上进行…

CAS Client使用以及执行原理

CAS Client使用以及执行原理 流程介绍 CAS Client是利用Java Web中的Filter进行实现认证功能&#xff0c;客户端对CAS Server的认证流程分为以下步骤&#xff1a; 访问CAS Client服务 由于当前session中未检测到认证信息&#xff0c;会重定向到CAS Server地址进行认证 在CA…

11.Ribbon负载均衡策略及修改

ZoneAvoidanceRule 默认使用的规则 修改规则 第一种方式&#xff1a;定义IRule的Bean,作用于全局。 SpringBootApplication MapperScan("com.xkj.org.mapper") public class OrderApplication {public static void main(String[] args) {SpringApplication.run(Ord…

设计模式——2_A 访问者(Visitor)

文章目录 定义图纸一个例子&#xff1a;如何给好奇宝宝提供他想知道的内容菜单、菜品和配方Menu(菜单) & Cuisine(菜品)Material(物料、食材) 产地、有机蔬菜和卡路里Cuisine & Material 访问者VisitorCuisine & Material 碎碎念访问者和双分派访问者和代理写在最后…

C#基础|StringBuilder字符串如何高效处理。

哈喽&#xff0c;你好&#xff0c;我是雷工。 字符串处理在C#程序开发中是使用频率比较高的&#xff0c;但常规的字符串处理方式对内存占用比较多&#xff0c;为了优化内存&#xff0c;减少不必要的内存浪费&#xff0c;引入了StringBuilder类。 下面学习下StringBuilder类的使…

插入排序动态展示3(Python可视化源代码)

修改了“开始”命令按钮&#xff0c;每次单击“开始”&#xff0c;都重新排序。 Python代码 import tkinter as tk import random import timeclass InsertionSortVisualizer:def __init__(self, root, canvas_width800, canvas_height400, num_bars10):self.root rootself.…

wasm 系列之 WebAssembly 和 emscripten 暴力上手

wasm 是什么&#xff1f; wasm 是 WebAssembly 的缩写。wasm 不是传统意义上的汇编语言&#xff0c;而是一种编译的中间字节码&#xff0c;可以在浏览器和其他 wasm runtime 上运行非 JavaScript 类型的语言&#xff0c;只要能被编译成 wasm&#xff0c;譬如 kotlin/wasm、Rus…

鸿蒙OpenHarmony【轻量系统编写“Hello World”程序】 (基于Hi3861开发板)

编写“Hello World”程序 下方将通过修改源码的方式展示如何编写简单程序&#xff0c;输出“Hello world”。请在下载的源码目录中进行下述操作。 前提条件 已参考鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到…