【FFmpeg】FFmpeg 内存结构 ⑥ ( 搭建开发环境 | AVPacket 创建与释放代码分析 | AVPacket 内存使用注意事项 )

文章目录

  • 一、搭建开发环境
    • 1、开发环境搭建参考
    • 2、项目搭建
  • 二、AVPacket 创建与释放代码分析
    • 1、AVPacket 创建与释放代码
    • 2、Qt 单步调试方法
    • 3、单步调试 - 分析 AVPacket 创建与销毁代码
  • 三、AVPacket 内存使用注意事项
    • 1、谨慎使用 av_init_packet 函数
    • 2、av_init_packet 函数弃用
    • 3、av_init_packet 函数导致内存泄漏的反面示例
    • 4、av_packet_move_ref 函数 后可以使用 av_init_packet 函数
    • 5、av_packet_clone 函数 后不可以使用 av_init_packet 函数
    • 6、av_packet_ref 函数 与 av_packet_unref 函数 成对使用


FFmpeg 4.0 版本源码地址 :

  • GitHub : https://github.com/FFmpeg/FFmpeg/tree/release/4.0
  • GitCode : https://gitcode.com/gh_mirrors/ff/FFmpeg/tree/release/4.0
  • FFmpeg/libavcodec/avpacket.c 源码 : https://gitcode.com/gh_mirrors/ff/FFmpeg/blob/release/4.0/libavcodec/avpacket.c




一、搭建开发环境



开发前 , 先回顾下 开发环境 和 项目 搭建流程 ;


1、开发环境搭建参考


开发环境搭建参考如下博客 :

  • 【FFmpeg】Windows 10 平台 FFmpeg 开发环境搭建 ① ( 安装 Visual Studio 2015 | JavaScript_ProjectSystem 安装包丢失或损坏 )
  • 【FFmpeg】Windows 10 平台 FFmpeg 开发环境搭建 ② ( Qt 配置 MSVC2015 编译器 | 安装 VS2015 并配置 Qt 环境的 C/C++ 编译器 )
  • 【FFmpeg】Windows 10 平台 FFmpeg 开发环境搭建 ③ ( CDB 调试器下载安装 | Qt 中配置 CDB 调试器 | Qt 中配置 32 位 / 64 位的构建套件 )
  • 【FFmpeg】Windows 10 平台 FFmpeg 开发环境搭建 ④ ( FFmpeg 开发库 | 创建项目导入并配置 FFmpeg 开发库 | 拷贝 DLL 动态库到 SysWOW64 目录)

2、项目搭建


参考 【FFmpeg】Windows 10 平台 FFmpeg 开发环境搭建 ④ ( FFmpeg 开发库 | 创建项目导入并配置 FFmpeg 开发库 | 拷贝 DLL 动态库到 SysWOW64 目录) 博客后半部分内容 :

  • 创建 Qt 项目 : 选择 " Non-Qt Project " 下的 " Plain C Application " 类型的项目 , 构建系统使用默认的 qmake , 构建套件选择 MSVC2015 套件 ;

  • 拷贝头文件和库函数 : 将 ffmpeg-4.2.1-win32-dev 开发库目录 , 拷贝到 Qt 工程目录下 , 其中是 FFmpeg 的头文件和库函数 ;
    在这里插入图片描述
    在这里插入图片描述

  • 配置 头文件和库函数 : 在 .pro 配置文件 , 配置 上面拷贝的 头文件 和 函数库 , 完整配置内容如下 :

TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qtSOURCES += main.cwin32 {
INCLUDEPATH += $$PWD/ffmpeg-4.2.1-win32-dev/include
LIBS += $$PWD/ffmpeg-4.2.1-win32-dev/lib/avformat.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/avcodec.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/avdevice.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/avfilter.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/avutil.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/postproc.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/swresample.lib \
$$PWD/ffmpeg-4.2.1-win32-dev/lib/swscale.lib
}
  • 代码引入头文件 : 引入 FFmpeg 库 , 并调用 av_version_info 函数 , 获取 FFmpeg 版本号 ;
#include <stdio.h>                  // 引入标准输入输出头文件
#include "libavutil/avutil.h"        // 引入FFmpeg库中的avutil头文件,avutil包含一些工具函数int main()
{// 输出 "Hello World" 到控制台printf("Hello World\n");// 输出 FFmpeg 的版本信息,av_version_info() 函数返回当前FFmpeg库的版本信息printf("FFmpeg version is %s\n", av_version_info());// 返回0表示程序正常结束return 0;
}
  • 代码运行库配置 : 将下面的文件拷贝到 C:\Windows\SysWOW64 目录 或者 项目的构建目录根目录 中 ;

在这里插入图片描述





二、AVPacket 创建与释放代码分析




1、AVPacket 创建与释放代码


下面的代码是 AVPacket 从声明 , 分配结构体内存 , 分配缓冲区数据内存 , 解除缓冲区数据引用 , 释放缓冲区内存 的完整过程 , 之后会单步调试 , 查看具体的数据信息 ;

void av_packet_1()
{AVPacket *pkt = NULL;    // 定义一个指向 AVPacket 结构体的指针,用于存储数据包int ret = 0;             // 定义一个整型变量 ret,用于存储函数的返回值// 分配一个新的 AVPacket,返回一个指向该数据包的指针pkt = av_packet_alloc();// 为数据包分配内存,并初始化引用计数为1ret = av_new_packet(pkt, MEM_ITEM_SIZE);// 使用 memccpy 函数将 av_packet_1 的数据拷贝到 pkt->data 中// 从 pkt->data 地址开始,复制 MEM_ITEM_SIZE 字节的数据// 复制的数据来源是当前函数 av_packet_1 的地址memccpy(pkt->data, (void *)&av_packet_1, 1, MEM_ITEM_SIZE);// 解除数据包的引用,减少引用计数av_packet_unref(pkt);// 释放数据包所占的内存,并将 pkt 指针设置为 NULLav_packet_free(&pkt);
}

2、Qt 单步调试方法


单步调试上述代码 :

  • 在函数第一行代码位置打上断点 :
    在这里插入图片描述

  • 点击左下角的 " Start debugging of startup project " 按钮 ,

在这里插入图片描述

  • 程序开始运行后 , 停留在 断点 位置 ;
    在这里插入图片描述

  • 切换到指令集操作模式 : 之前在代码模式中 , 无法进行 单步调试 , 参考 【错误记录】Qt 单步调试 F10 快捷键失灵 ( Start and Break on Main 和 单步调试 默认都是 F10 快捷键 | 单步调试技巧 - 转换汇编指令模式 ) 博客 ;
    在这里插入图片描述

  • 在 " 指令集操作模式 " 中 , 按 F10 单步运行 , 然后再切换回代码模式 , 即可进行单步调试 ;
    在这里插入图片描述


3、单步调试 - 分析 AVPacket 创建与销毁代码


再次回到代码模式 , 即可进行单步调试 , 目前代码停留在函数的第三行代码处 ;

此时可以看到 , 声明的 AVPacket 结构体指针值为 0 , 暂时没有赋值 ;
在这里插入图片描述
继续下一步单步调试 , 执行 av_packet_alloc 函数 , 结果如下 , 该函数为 AVPacket 指针分配了内存 , AVPacket 结构体的成员都设置了默认值 , 其中 buf 缓冲区 被设置为了 0 , 也就是 NULL , 暂时没有进行初始化 ;

av_packet_alloc 函数 的 作用 是 : 在 堆内存中 , 为其分配内存空间 , 并为其成员设置默认值 ;

在这里插入图片描述

继续执行下一步 , 调用 av_new_packet 函数时 , 开始为 AVPacket 的数据分配内存 , 并将引用计数设置为 1 ;

此时 AVBuffer 结构体的 AVBufferRef *buf 成员 有了指向 , 不再是 NULL ;

真实的数据是 AVBufferRef 结构体的 AVBuffer *buffer 成员 中 , 其真实数据 和 引用计数 都在该结构体中 , 目前可以看到该结构体在堆内存中的地址是 0xec5240 ;

在这里插入图片描述

执行 memccpy 函数 , 设置数据缓冲区中的数据 , 执行后 , 发现数据发生了改变 ;

在这里插入图片描述

继续向后执行 , 执行 av_packet_unref 函数 , 解除 AVPacket 数据包引用 , 减少引用计数 ;

参考 【FFmpeg】FFmpeg 内存结构 ④ ( AVPacket 函数简介 | av_packet_unref 函数 | av_packet_move_ref 函数 ) 博客 中的 av_packet_unref 函数 分析章节 ;

在这里插入图片描述

最后 执行 av_packet_free 函数 , 释放 AVPacket 结构体 及其 内部的成员 所占用的内存空间 ;

执行完毕后 , 发现 AVPacket 结构体指针的值被置空 ;

在这里插入图片描述





三、AVPacket 内存使用注意事项




1、谨慎使用 av_init_packet 函数


av_init_packet 函数 内容如下 , 分析下面的代码 , 可知 该函数会将所有的字段都设置为 默认值 , 尤其是 pkt->buf 字段 , 这是数据缓冲区的引用 , 使用该函数不当 , 可以会导致数据缓冲区内存泄漏 ;

// 初始化 AVPacket 结构体
void av_init_packet(AVPacket *pkt)
{// 设置显示时间戳 (PTS) 为未定义值pkt->pts                  = AV_NOPTS_VALUE;// 设置解码时间戳 (DTS) 为未定义值pkt->dts                  = AV_NOPTS_VALUE;// 设置文件中的位置为 -1 (表示未知)pkt->pos                  = -1;// 设置帧的时长为 0pkt->duration             = 0;#if FF_API_CONVERGENCE_DURATION// 以下代码处理弃用字段 convergence_duration 的初始化FF_DISABLE_DEPRECATION_WARNINGSpkt->convergence_duration = 0; // 设置帧的收敛时间为 0 (已废弃字段)FF_ENABLE_DEPRECATION_WARNINGS
#endif// 设置标志位为 0 (表示无标志)pkt->flags                = 0;// 设置流索引为 0 (初始化为默认值)pkt->stream_index         = 0;// 将引用的缓冲区指针设为 NULLpkt->buf                  = NULL;// 将侧数据的指针设为 NULLpkt->side_data            = NULL;// 设置侧数据元素个数为 0pkt->side_data_elems      = 0;
}

2、av_init_packet 函数弃用


av_init_packet 函数 已经弃用 , FFmpeg 官方不再建议调用该函数 , 可参考 【FFmpeg】FFmpeg 内存结构 ② ( AVPacket 函数简介 | av_packet_alloc 函数 | av_packet_free 函数 | av_new_packet 函数 ) 博客 ,

av_packet_alloc 函数 是 FFmpeg 官方推荐使用的 初始化 AVPacket 结构体的函数 , 并且建议与 av_packet_free 函数 配对使用 ;

av_packet_alloc 函数 中 调用了 av_init_packet 函数 , 已经包含了 av_init_packet 函数的功能 ;


3、av_init_packet 函数导致内存泄漏的反面示例


在下面的代码中 , 在数据释放之前的某个时间点 调用了 av_init_packet 函数 , 导致 AVPacket 的 buf 缓冲区提前置空 , 但是 buf 指向的数据缓冲区还没有释放 , 这段内存就出现了泄漏 , 再也无法进行释放 ;

void av_packet_1()
{AVPacket *pkt = NULL;    // 定义一个指向 AVPacket 结构体的指针,用于存储数据包int ret = 0;             // 定义一个整型变量 ret,用于存储函数的返回值// 分配一个新的 AVPacket,返回一个指向该数据包的指针pkt = av_packet_alloc();// 为数据包分配内存,并初始化引用计数为1ret = av_new_packet(pkt, MEM_ITEM_SIZE);// 使用 memccpy 函数将 av_packet_1 的数据拷贝到 pkt->data 中// 从 pkt->data 地址开始,复制 MEM_ITEM_SIZE 字节的数据// 复制的数据来源是当前函数 av_packet_1 的地址memccpy(pkt->data, (void *)&av_packet_1, 1, MEM_ITEM_SIZE);// 如果在数据释放之前的某个时间点 调用了 av_init_packet 函数// 就会导致 AVPacket 的 buf 缓冲区置空// 但是 buf 指向的数据缓冲区还没有释放 这段内存就出现了泄漏 再也无法进行释放// 此处调用 av_init_packet 函数 就会导致内存泄漏 ☆☆☆av_init_packet(pkt);// 解除数据包的引用,减少引用计数av_packet_unref(pkt);// 释放数据包所占的内存,并将 pkt 指针设置为 NULLav_packet_free(&pkt);
}

4、av_packet_move_ref 函数 后可以使用 av_init_packet 函数


av_packet_move_ref 函数 的 源码如下 :

void av_packet_move_ref(AVPacket *dst, AVPacket *src)
{*dst = *src;av_init_packet(src);src->data = NULL;src->size = 0;
}

参考 https://raw.gitcode.com/gh_mirrors/ff/FFmpeg/raw/release%2F4.0/libavcodec/avpacket.c 源码 ;

在 av_packet_move_ref 函数中 , 调用了 av_init_packet 函数 , 如果在之后继续调用一次 av_init_packet 函数 , 是不会对内存结构产生影响的 , 相当于调用了两次 av_init_packet 函数 ;


5、av_packet_clone 函数 后不可以使用 av_init_packet 函数


av_packet_clone 函数 源码如下 :

// 克隆一个 AVPacket
AVPacket *av_packet_clone(const AVPacket *src)
{// 分配一个新的 AVPacket 对象AVPacket *ret = av_packet_alloc();// 如果分配失败,直接返回 NULLif (!ret)return ret;// 复制源数据包 (src) 的内容到新分配的数据包 (ret)// 如果复制失败,释放新分配的数据包内存if (av_packet_ref(ret, src))av_packet_free(&ret);// 返回克隆的数据包指针 (如果成功,则指向新数据包;失败则返回 NULL)return ret;
}

av_packet_clone 函数 相当于 av_packet_alloc 函数 + av_packet_ref 函数 的 组合函数 ;

调用完 av_packet_clone 函数 后 , 引用计数会增加 1 , 如果原来引用计数是 1 , 则新的引用计数是 2 ;

如果在 av_packet_clone 函数 后 调用 av_init_packet 函数 , 会导致新的 AVPacket 的 buf 成员被置空 , 之后 调用 av_packet_free 函数 无法访问到 AVPacket 的 buf 成员 , 自然无法将引用计数自减 1 , 这样就导致了 内存泄漏 ;


6、av_packet_ref 函数 与 av_packet_unref 函数 成对使用


av_packet_ref 函数 和 av_packet_unref 函数 需要成对使用 , 它们的主要目的是对 AVPacket 对象的引用计数进行管理 , 从而避免内存泄漏或重复释放 ;

在使用 av_packet_ref 引用数据包时 , 引用计数会增加 ;

如果在某些地方不再需要这个引用 , 必须使用 av_packet_unref 释放它 ;

否则 , 会导致内存泄漏 ;

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

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

相关文章

D94【python 接口自动化学习】- pytest进阶之fixture用法

day94 pytest的fixture详解 学习日期&#xff1a;20241210 学习目标&#xff1a;pytest基础用法 -- pytest的fixture详解 学习笔记&#xff1a; fixture的介绍 fixture是 pytest 用于将测试前后进行预备、清理工作的代码处理机制。 fixture相对于setup和teardown来说有以…

2024首届世界酒中国菜国际地理标志产品美食文化节成功举办篇章

2024首届世界酒中国菜国际地理标志产品美食文化节成功举办&#xff0c;开启美食文化交流新篇章 近日&#xff0c;首届世界酒中国菜国际地理标志产品美食文化节在中国国际地理标志大厦成功举办&#xff0c;这场为期三天的美食文化盛会吸引了来自世界各地的美食爱好者、行业专家…

AI发展与LabVIEW程序员就业

人工智能&#xff08;AI&#xff09;技术的快速发展确实对许多行业带来了变革&#xff0c;包括自动化、数据分析、软件开发等领域。对于LabVIEW程序员来说&#xff0c;AI的崛起确实引发了一个值得关注的问题&#xff1a;AI会不会取代他们的工作&#xff0c;导致大量失业&#x…

展柜设计公司平面布置小程序的分析与设计springboot+论文源码调试讲解

3系统的需求分析 需求分析的任务是通过详细调查展柜设计公司平面布置小程序软件所需的对象&#xff0c;充分了解系统的工作概况&#xff0c;明确功能实现的各种需求&#xff0c;然后在此基础上确定系统的功能。系统必须充分考虑今后可能的扩充和改变。 3.1可行性分析 通过对…

家校通小程序实战教程10部门管理前后端连接

目录 1 加载后端的数据2 为什么不直接给变量赋值3 保存部门信息4 最终的效果5 总结 现在部门管理已经完成了后端功能和前端开发&#xff0c;就需要在前端调用后端的数据完成界面的展示&#xff0c;而且在录入部门信息后需要提交到数据库里&#xff0c;本篇我们介绍一下前后端如…

spark-sql 备忘录

wordcount sc.textFile("../data/data.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(__).collect 读取json 文件 并通过sql 执行 join 查询 public static void main(String[] args) {SparkSession session SparkSession.builder().master(&qu…

Java并发编程学习(二)

线程的状态 有说5种的&#xff0c;有说6种的 5种的&#xff0c;从操作系统层面来讲 初始状态&#xff1a;也就是语言层面创建了线程对象&#xff0c;还未与操作系统线程关联。Java中也就是new了一个线程&#xff0c;还未调用。可运行状态&#xff1a;&#xff08;就绪状态&a…

Docker方式安装人人影视离线完整安装包

本文软件由网友 ルリデ 推荐&#xff1b; 上周&#xff0c;人人影视创始人宣布将人人影视二十年字幕数据开源分享 目前提供了两种使用方式&#xff1a; “在线应用” &#xff1a;意味着需要有互联网才可以使用。官方提供了网站&#xff1a;https://yyets.click “离线使用” …

Leetcode 3389. Minimum Operations to Make Character Frequencies Equal

Leetcode 3389. Minimum Operations to Make Character Frequencies Equal 1. 解题思路2. 代码实现 题目链接&#xff1a;3389. Minimum Operations to Make Character Frequencies Equal 1. 解题思路 这一题从答题从test的结果来说来说做出的人很少&#xff0c;主要确实有些…

大文件处理的终极武器:Yield详解

【大文件处理的终极武器&#xff1a;Yield详解】&#x1f680; 一、大文件处理的痛点 内存限制数据量巨大传统方法效率低 二、Yield解决方案 def read_large_file(file_path):with open(file_path, r) as file:# 每次只读取一行&#xff0c;而不是全文for line in file:yie…

SpringBoot 学习

SpringBoot 学习 什么是 Springboot Spring Boot 是 Spring 提供的一个子项目&#xff0c;用于快速构建 Spring 应用程序 传统的问题&#xff1a; 导入依赖繁琐项目配置繁琐 SpringBoot 的特性 起步依赖&#xff1a;整合所有 web 的依赖配置好了自动配置&#xff1a;bean…

到达率的变化动态调整服务器的服务率,实现负载均衡,提高资源利用效率

中心可以根据任务到达率的变化动态调整服务器的服务率,实现负载均衡,提高资源利用效率 服务率和到达率 中心可以根据任务到达率的变化动态调整服务器的服务率,实现负载均衡,提高资源利用效率服务率(Service Rate)到达率(Arrival Rate)控制参数实现负载均衡的方法在云计…

最新全开源IM即时通讯系统源码(PC+WEB+IOS+Android)部署指南

全开源IM&#xff08;即时通讯&#xff09;系统源码部署是一个复杂但系统的过程&#xff0c;涉及多个组件和步骤。以下是一个详细的部署指南&#xff0c;旨在帮助开发者或系统管理员成功部署一个全开源的IM系统&#xff0c;如OpenIM。      IM即时通讯系统源码准备工作   …

CAD c# 生成略缩图预览

代码如下&#xff1a; using (Transaction tr currentdb.TransactionManager.StartTransaction()){//当前数据库开启事务using (Database tempdb new Database(false, true)) //创建临时数据库(两个参数&#xff1a;是否创建符号表&#xff0c;不与当前文档关联){try{Bitmap …

CloudberryDB(二) 演化路线图

CloudberryDB 制定了演化路线图&#xff08;https://github.com/orgs/cloudberrydb/discussions/369&#xff09;并在逐步改进&#xff0c;这是 Cloudberry Database 发挥独特价值之处。 计划、正在进行或已完成的一些工作。 支持轻松升级 PostgreSQL 内核版本。 原有 Greenp…

单片机:实现呼吸灯(附带源码)

单片机实现呼吸灯详细解读 呼吸灯是一种常见的灯光效果&#xff0c;广泛应用于电子产品、汽车、家居照明等领域。其基本特性是通过逐渐增亮和减弱的方式&#xff0c;使得灯光呈现出“呼吸”的效果&#xff0c;给人一种平缓、舒适的视觉感受。在嵌入式系统中&#xff0c;呼吸灯…

[面试题]--索引用了什么数据结构?有什么特点?

答&#xff1a;使用了B树&#xff1a; 时间复杂度&#xff1a;O(logN),可以有效控制树高 B树特点&#xff1a; 1.叶子节点之间有相互链接的作用&#xff0c;会指向下一个相近的兄弟节点。 MySQL在组织叶子节点使用的是双向链表 2.非叶子节点的值都保存在叶子节点当中 MySQL非叶…

ansible自动化运维(五)roles角色管理

Roles角色管理 角色&#xff08;roles&#xff09;是ansible自1.2版本开始引入的新特性&#xff0c;用于层次性&#xff0c;结构化地组织playbook。 roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单的…

操作系统:文件系统

目录 1、文件 概念&#xff1a; UNIX文件分类&#xff1a; 2、文件系统 3、文件的访问方式 顺序访问 随机访问 4、文件的组织 逻辑组织 物理组织 5、倒排结构&#xff08;了解&#xff09; 5、文件目录 文件控制块&#xff08;FCB&#xff09; ​编辑 目录项 单…

单元测试-FATAL ERROR in native method: processing of -javaagent failed

文章目录 前言单元测试-FATAL ERROR in native method: processing of -javaagent failed1. 报错信息2. 解决方案 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运…