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

news2024/12/18 0:22:56

文章目录

  • 一、搭建开发环境
    • 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 -= qt

SOURCES += main.c

win32 {
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();

    // 为数据包分配内存,并初始化引用计数为1
    ret = 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 指针设置为 NULL
    av_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;

    // 设置帧的时长为 0
    pkt->duration             = 0;

#if FF_API_CONVERGENCE_DURATION
    // 以下代码处理弃用字段 convergence_duration 的初始化
    FF_DISABLE_DEPRECATION_WARNINGS
    pkt->convergence_duration = 0; // 设置帧的收敛时间为 0 (已废弃字段)
    FF_ENABLE_DEPRECATION_WARNINGS
#endif

    // 设置标志位为 0 (表示无标志)
    pkt->flags                = 0;

    // 设置流索引为 0 (初始化为默认值)
    pkt->stream_index         = 0;

    // 将引用的缓冲区指针设为 NULL
    pkt->buf                  = NULL;

    // 将侧数据的指针设为 NULL
    pkt->side_data            = NULL;

    // 设置侧数据元素个数为 0
    pkt->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();

    // 为数据包分配内存,并初始化引用计数为1
    ret = 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 指针设置为 NULL
    av_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();

    // 如果分配失败,直接返回 NULL
    if (!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.coloradmin.cn/o/2261314.html

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

相关文章

C# DLT645 97/07数据采集工具

电表模拟器 97协议测试 07协议测试 private void btnSend_Click(object sender, EventArgs e) {string addr txtAddr.Text.Trim();string data txtDataFlg.Text.Trim();byte control 0x01;switch (cmbControl.SelectedIndex){case 0: control (byte)0x01; break;// 97协议c…

颜色代码表: 一站式配色方案设计工具集网站

大家好&#xff0c;我是一名设计师&#xff0c;同时也是一名开发者。平时的工作中&#xff0c;相信很多设计师和我一样经常遇到一个问题&#xff1a;设计配色方案时&#xff0c;工具太分散了。寻找颜色搭配灵感需要去一个网站&#xff0c;颜色代码转换要开另一个&#xff0c;检…

Android显示系统(13)- 向SurfaceFlinger提交Buffer

Android显示系统&#xff08;01&#xff09;- 架构分析 Android显示系统&#xff08;02&#xff09;- OpenGL ES - 概述 Android显示系统&#xff08;03&#xff09;- OpenGL ES - GLSurfaceView的使用 Android显示系统&#xff08;04&#xff09;- OpenGL ES - Shader绘制三角…

WebSocket 与 Server-Sent Events (SSE) 的对比与应用

目录 ✨WebSocket&#xff1a;全双工通信的利器&#x1f4cc;什么是 WebSocket&#xff1f;&#x1f4cc;WebSocket 的特点&#x1f4cc;WebSocket 的优点&#x1f4cc;WebSocket 的缺点&#x1f4cc;WebSocket 的适用场景 ✨Server-Sent Events (SSE)&#xff1a;单向推送的轻…

Mysql 深度分页查询优化

Mysql 分页优化 1. 问题根源 问题&#xff1a; mysql在数据量大的时候&#xff0c;深度分页数据偏移量会增大&#xff0c;导致查询效率越来越低。 问题根源&#xff1a; 当使用 LIMIT 和 OFFSET 进行分页时&#xff0c;MySQL 必须扫描 OFFSET LIMIT 行&#xff0c;然后丢弃前…

SpringBoot - 动态端口切换黑魔法

文章目录 关键技术点核心原理Code 关键技术点 利用 Spring Boot 内嵌 Servlet 容器 和 动态端口切换 的方式实现平滑更新的方案&#xff0c;关键技术点如下&#xff1a; Servlet 容器重新绑定端口&#xff1a;Spring Boot 使用 ServletWebServerFactory 动态设置新端口。零停…

linux(CentOS8)安装PostgreSQL16详解

文章目录 1 下载安装包2 安装3 修改远程连接4 开放端口 1 下载安装包 官网下载地址&#xff1a;https://www.postgresql.org/download/ 选择对应版本 2 安装 #yum源 yum -y install wget https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redha…

spring学习(spring-bean实例化(无参构造与有参构造方法实现)详解)

目录 一、spring容器之bean的实例化。 &#xff08;1&#xff09;"bean"基本概念。 &#xff08;2&#xff09;spring-bean实例化的几种方式。 二、spring容器使用"构造方法"的方式实例化bean。 &#xff08;1&#xff09;无参构造方法实例化bean。 &#…

ElasticSearch学习5

基本Rest命令说明&#xff1a; method url地址 描述 PUT&#xff08;创建,修改&#xff09; localhost:9200/索引名称/类型名称/文档id 创建文档&#xff08;指定文档id&#xff09; POST&#xff08;创建&#xff09; localhost:9200/索引名称/类型名称 创建文档&…

分享本周所学——三维重建算法3D Gaussian Splatting(3DGS)

大家好&#xff0c;欢迎来到《分享本周所学》第十二期。本人是一名人工智能初学者&#xff0c;刚刚读完大二。前几天自学了一下3D Gaussian Splatting&#xff08;3DGS&#xff09;&#xff0c;觉得非常有意思。写这篇文章主要是因为网上大部分关于3DGS的文章都比较晦涩&#x…

【中工开发者】鸿蒙商城app

这学期我学习了鸿蒙&#xff0c;想用鸿蒙做一个鸿蒙商城app&#xff0c;来展示一下。 项目环境搭建&#xff1a; 1.开发环境&#xff1a;DevEco Studio2.开发语言&#xff1a;ArkTS3.运行环境&#xff1a;Harmony NEXT base1 软件要求&#xff1a; DevEco Studio 5.0.0 Rel…

【Qt】按钮类控件:QPushButton、QRadioButton、QCheckBox、ToolButton

目录 QPushButton 例子&#xff1a; QRadioButton 例子&#xff1a; 按钮的常见信号函数 单选按钮分组 例子&#xff1a; QCheckButton 例子&#xff1a; QToolButton QWidget的常见属性及其功能对于它的派生类控件都是有效的(也就是Qt中的各种控件)&#xff0c;包括…

UI框架DevExpress XAF v24.2新功能预览 - .NET Core / .NET增强

DevExpress XAF是一款强大的现代应用程序框架&#xff0c;允许同时开发ASP.NET和WinForms。DevExpress XAF采用模块化设计&#xff0c;开发人员可以选择内建模块&#xff0c;也可以自行创建&#xff0c;从而以更快的速度和比开发人员当前更强有力的方式创建应用程序。 在上文中…

ArrayList源码分析、扩容机制面试题,数组和List的相互转换,ArrayList与LinkedList的区别

目录 1.java集合框架体系 2. 前置知识-数组 2.1 数组 2.1.1 定义&#xff1a; 2.1.2 数组如何获取其他元素的地址值&#xff1f;&#xff08;寻址公式&#xff09; 2.1.3 为什么数组索引从0开始呢&#xff1f;从1开始不行吗&#xff1f; 3. ArrayList 3.1 ArrayList和和…

阿里云服务器手动部署LNMP环境【官方文档注意事项】

这是官方文档 注意&#xff1a; 要添加安全组&#xff0c;端口为80。否则最后用浏览器访问公网IP没有结果。 Mysql密码策略要求密码至少包含一个大写字母、一个小写字母、一个数字和一个特殊字符&#xff0c;并且密码总长度至少为 8 个字符。sudo mysqladmin -uroot -p<ol…

Invalid default value for ‘gender‘,mysql在idea中字符集设置,default

默认值default创建错误的&#xff0c;设置数据库字符集 我的错误&#xff1a;Invalid default value for ‘gender’ -- 修改数据库字符集 alter database db01 charset utf8;

240004基于Jamva+ssm+maven+mysql的房屋租赁系统的设计与实现

基于ssmmavenmysql的房屋租赁系统的设计与实现 1.项目描述2.运行环境3.项目截图4.源码获取 1.项目描述 该项目在原有的基础上进行了优化&#xff0c;包括新增了注册功能&#xff0c;房屋模糊查询功能&#xff0c;管理员和用户信息管理等功能&#xff0c;以及对网站界面进行了优…

使用Navicat从SQL Server导入表数据到MySQL

在表上右键选择导入向导 选择ODBC 1.内输入ip即可&#xff0c;不需要端口号 一定要勾选允许保存密码 选择需要的表&#xff0c;下一步 根据需求&#xff0c;可修改表名、是否新建表 根据需求修改不同表的字段类型和长度 按需选择导入方式

STM32F407+LAN8720A +LWIP +FreeRTOS ping通

使用STM32CUBEIDE自带的 LWIP和FreeROTS 版本说明STM32CUBEIDE 操作如下1. 配置RCC/SYS2. 配置ETH/USART3. 配置EHT_RESET/LED4. 配置FreeRTOS5. 配置LWIP6. 配置时钟7. 生成单独的源文件和头文件,并生成代码8. printf重定义9. ethernetif.c添加lan8720a复位10. MY_LWIP_Init …

用 Python Turtle 绘制经典汤姆猫:重温卡通角色的经典魅力

用 Python Turtle 绘制经典汤姆猫&#xff1a;重温卡通角色的经典魅力 &#x1f438; 前言 &#x1f438;&#x1f41e;往期绘画>>点击进所有绘画&#x1f41e;&#x1f40b; 效果图 &#x1f40b;&#x1f409; 代码 &#x1f409; &#x1f438; 前言 &#x1f438; 汤…