FFmpeg 与 C++ 构建音视频处理全链路实战(三)—— FFmpeg 内存模型

经过前面文章的 FFmpeg 编程实践,相信你已经对AVPacketAVFrame这两个核心结构体不再陌生。当我们编写代码时,频繁调用unref系列 API 释放内存的操作,或许让你心生疑惑:这些函数究竟是如何实现内存释放的?又该在何时准确调用,才能避免埋下内存泄漏的隐患?

在 FFmpeg 编程的浩瀚征途中,内存泄漏就像隐匿于迷雾中的 “幽灵”,悄无声息却极具破坏力。哪怕只是一个细微的疏忽,都可能让程序的内存占用如失控的气球般不断膨胀,最终导致性能严重下降,甚至引发程序崩溃。想要彻底征服这个难缠的 “幽灵”,深入理解 FFmpeg 的内存模型是必经之路,而其中的关键,就藏在AVPacketAVFrame这两个核心结构体的内存管理机制中。它们如同精密钟表里不可或缺的齿轮,而内部的buf引用计数系统,则恰似让齿轮顺滑运转的 “润滑油”,每一次精准咬合,都关乎着整个程序的稳定与高效。接下来,让我们层层拆解,揭开 FFmpeg 内存管理的神秘面纱,探寻其中的运行奥秘。

一、FFmpeg 内存管理的重要性与挑战

在多媒体处理领域,音视频数据量庞大且处理流程复杂。每一帧视频、每一段音频数据都需要占用大量的内存空间。如果内存管理不当,不仅会造成资源浪费,还可能导致程序出现各种难以调试的问题。想象一下,一个拥挤的车站,每时每刻都有大量乘客下车,如果到站的乘客不离开站台就会越来越多人拥挤在空间有限的站台,最终场面会变得混乱不堪。FFmpeg 程序中的内存管理也是如此,合理的内存分配、使用和及时释放,是保证程序稳定高效运行的关键。

然而,FFmpeg 的内存管理面临诸多挑战。一方面,它需要处理多种不同类型的媒体数据,每种数据的存储和处理方式都有所不同;另一方面,FFmpeg 的 API 设计注重灵活性和高效性,这就要求开发者对其内存模型有深入的理解,才能正确地使用相关接口,避免内存泄漏等问题。

二、AVPacket/AVFrame:音视频数据的 “包裹” 与引用计数

avpacket和avframe再引用计数方面设计相同,下面以avpacket为例。

(一)AVPacket 的基本结构与作用

AVPacket是 FFmpeg 中用于存储压缩音视频数据的结构体,它就像是一个专门用来运输音视频数据的 “包裹”。这个 “包裹” 里不仅装着实际的音视频数据(存储在data指针指向的内存区域),还包含了许多重要的元信息,如数据的大小(size)、时间戳(ptsdts)、所属的流索引(stream_index)等。这些元信息对于音视频的解码、播放和处理至关重要,就像包裹上的收件人信息、快递单号等,指引着数据在 FFmpeg 的各个模块中准确传递。

(二)AVPacket 的 buf 引用计数系统

AVPacket内部,AVBufferRef类型的buf成员是实现内存管理和引用计数的核心。引用计数机制通过记录AVPacket内存被引用的次数,来决定何时释放内存,它就像一个精准的 “使用计数器”,精确控制着内存的生命周期。

当使用av_packet_alloc创建一个AVPacket时,若不进行额外操作,其buf指针初始化为NULL,此时不存在引用计数的概念,因为尚未关联实际的内存缓冲区。只有当通过av_new_packet等函数为AVPacket分配数据内存时,才会创建AVBuffer并初始化引用计数为 1。

当使用av_packet_refAVPacket进行引用操作时,实际上是增加了目标AVPacketbuf引用计数。例如:

AVPacket* pkt1 = av_packet_alloc();
// 此时pkt->buf为NULL,无引用计数
av_new_packet(pkt1, 1024); 
// 执行后,pkt->buf指向新分配的内存,引用计数为1
AVPacket* pkt2 = av_packet_alloc();
av_packet_ref(pkt2, pkt1);
// 此时pkt1->buf和pkt2->buf指向同一块内存,引用计数均为2

这种共享内存的方式极大提升了数据传递效率,多个AVPacket可共用同一块内存区域,减少不必要的内存拷贝。

(三)AVPacket 在接收与拷贝操作中的引用计数变化

1.接收操作(如av_read_frame
在解封装过程中,av_read_frame从解封装器获取AVPacket时,解封装器会将内部的AVPacket传递出来,并将该AVPacketbuf引用计数增加 1 。此时调用者获得的AVPacket已持有有效的引用,若后续不再使用,直接调用av_packet_unref即可安全释放。例如:

AVPacket* pkt = av_packet_alloc();
int ret = av_read_frame(ifmt_ctx, pkt);
if (ret >= 0) {// 此时pkt->buf引用计数至少为1(解码器内部已引用)// 处理pkt数据...av_packet_unref(pkt); // 引用计数减1,若变为0则释放内存
}

若希望在多个地方使用该AVPacket,可通过av_packet_ref创建新的引用:

AVPacket* pkt2 = av_packet_alloc();
av_packet_ref(pkt2, pkt1);
// 此时pkt1->buf和pkt2->buf引用计数均为2
av_packet_unref(pkt1); 
// 引用计数减为1,内存不释放
av_packet_unref(pkt2); 
// 引用计数减为0,释放关联内存

 2.拷贝操作(如av_packet_copy
av_packet_copy函数会复制AVPacket的元数据(如sizepts等),并且会自动处理buf引用计数。(等同于av_packet_alloc()+av_packet_unref()):

AVPacket* src_pkt = av_packet_alloc();
av_new_packet(src_pkt, 1024); 
AVPacket* dst_pkt = av_packet_alloc();
dst_pkt = av_packet_copy( src_pkt);

 若想完全独立复制数据,可使用av_packet_move_ref,它会将源AVPacketbuf所有权转移给目标AVPacket,同时重置源AVPacketbuf

AVPacket* src_pkt = av_packet_alloc();
av_new_packet(src_pkt, 1024); 
AVPacket* dst_pkt = av_packet_alloc();
av_packet_move_ref(dst_pkt, src_pkt);
// 此时dst_pkt拥有buf所有权,引用计数为1
// src_pkt的buf变为NULL,不再引用内存

三、AVPacket/AVFrame常用API

AVPacket常用API 

函数原型说明
AVPacket *av_packet _alloc (void);
分配 AVPacket,此时buffer为空
void av_packet _free (AVPacket **pkt);
释放 AVPacket对象,包含buf的释放
void av_init_packet(AVPacket *pkt);
初始化 AVPacket,经初始换avpacket字段(4.0后已弃用,功能被包含在_alloc内)
int av_new_packet(AVPacket *pkt, int size);
AVPacket buf 分配内存,引用计数置1
int av_packet_ref(AVPacket *dst, const AVPacket *src);
增加引用计数
void av_packet_ unref (AVPacket *pkt);
减少引用计数
void av_packet_move_ref (AVPacket *dst, AVPacket *src);
转移引用计数(转移所属权)
AVPacket *av_packet_clone(const AVPacket *src);
等于
av_packet_alloc()+av_packet_ref()

AVFrame常用API  

函数原型说明
AVFrame *av_frame _alloc (void);
分配AVFrame
void av_frame _free (AVFrame **frame);
释放AVFrame
int av_frame_ ref (AVFrame *dst, const AVFrame *src);
增加引用计数
void av_frame_ unref (AVFrame *frame);
减少引用计数
void av_frame_ move_ref (AVFrame *dst, AVFrame *src);
转移引用计数(转移所属权)
int av_frame_get_buffer (AVFrame *frame, int align);
根据AVFrame分配内存
AVFrame *av_frame_clone(const AVFrame *src);
等于
av_frame_alloc()+av_frame_ref()

四、总结

FFmpeg 的内存模型,尤其是AVPacketAVFrame的 buf 引用计数系统,是保证音视频处理程序稳定运行的关键。理解它们的工作原理、在不同操作中的引用计数变化,以及正确的内存分配、使用和释放方法,是每一个 FFmpeg 开发者的必修课。

在实际编程过程中,我们要时刻牢记引用计数这个 “账本”,小心处理每一次的引用、拷贝和释放操作,避免因内存管理不当而引入的各种问题。只有这样,我们才能真正驾驭 FFmpeg 这个强大的多媒体处理工具,开发出高效、稳定的音视频应用程序。希望通过本文的详细讲解,能帮助你在 FFmpeg 编程的道路上走得更加顺畅,不再被内存泄漏这个 “幽灵” 所困扰。

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

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

相关文章

c 中的哈希表

哈希是一种可以接受各种类型、大小的输入,输出一个固定长度整数的过程。你可以将哈希理解成一种特殊的映射,哈希映射,将一个理论无限的集合A映射到有限整数集合B上。 哈希函数:哈希函数是哈希过程的核心,它决定了哈希映…

【一次成功!】Ubuntu22.04安装cartographer

之前在ubuntu20.04上成功安装cartographer,但是翻遍全网都没找到官方的22.04安装教程,然后找到小鱼的,试了一下,一次成功,连接如下: gd2l-ros2/docs/humble/chapt10/get_started/2.Carto介绍及安装.md at …

【WPF】Opacity 属性的使用

在WPF(Windows Presentation Foundation)中,Opacity 属性是定义一个元素透明度的属性,其值范围是从 0.0(完全透明)到 1.0(完全不透明)。由于 Opacity 是在 UIElement 类中定义的&…

8天Python从入门到精通【itheima】-6~10

目录 7节-开发出第一个Python程序: 1.在cmd窗口写下第一个最简单的程序:Hello World!!! 9节: 1.如何卸载python: 2.报错:不是可运行的程序 ​编辑 3.报错:无法初始化设备PRN: 4.报错:语法错误——非法的字符 10节-python解释器: 1.python解释器的原理: 2.解…

Mac 3大好用的复制粘贴管理工具对比

剪贴板管理器是查看复制粘贴历史记录的工具,几乎是每个苹果电脑用户必备工具。市面上的工具很多,我结合了功能丰富、设计简洁、交互便利整理了目前3款头部剪贴板应用 Paste、PasteNow、PasteMe。 Paste 优势:老牌剪切板应用,功能…

2025年全国青少年信息素养大赛初赛模拟测试网站崩了的原因及应对比赛流程

2025年全国青少年信息素养大赛初赛模拟测试昨天开始,由于同一时间涌入太多的人,导致网站的服务器奔了,出现了各种状况,导致很多人没有模拟上,大家今天可以刷新或者提前打开网页。 有的是一直“转圈圈”,有的…

02 | 大模型微调 | 从0学习到实战微调 | 从数学概率到千亿参数大模型

一、导读 作为非AI专业技术开发者(我是小小爬虫开发工程师😋) 本系列文章将围绕《大模型微调》进行学习(也是我个人学习的笔记,所以会持续更新),最后以上手实操模型微调的目的。 (…

十四、继承与组合(Inheritance Composition)

十四、继承与组合(Inheritance & Composition) 引言 C最引人注目的特性之一是代码复用。组合:在新类中创建已有类的对象。继承:将新类作为已有类的一个类型来创建。 14.1 组合的语法 Useful.h //C14:Useful.h #ifndef US…

2025年5月-信息系统项目管理师高级-软考高项一般计算题

决策树和期望货币值 加权算法 自制和外购分析 沟通渠道 三点估算PERT 当其他条件一样时,npv越大越好

OpenJDK 17 中线程启动的完整流程用C++ 源码详解

1. 线程创建入口(JNI 层) 当 Java 层调用 Thread.start() 时,JVM 通过 JNI 进入 JVM_StartThread 函数: JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))// 1. 检查线程状态,防止重复启动if (java_…

Spring MVC参数传递

本内容采用最新SpringBoot3框架版本,视频观看地址:B站视频播放 1. Postman基础 Postman是一个接口测试工具,Postman相当于一个客户端,可以模拟用户发起的各类HTTP请求,将请求数据发送至服务端,获取对应的响应结果。 2. Spring MVC相关注解 3. Spring MVC参数传递 Spri…

Python面向对象编程(OOP)深度解析:从封装到继承的多维度实践

引言 面向对象编程(Object-Oriented Programming, OOP)是Python开发中的核心范式,其三大特性——​​封装、继承、多态​​——为构建模块化、可维护的代码提供了坚实基础。本文将通过代码实例与理论结合的方式,系统解析Python OOP的实现机制与高级特性…

0.66kV0.69kV接地电阻柜常规配置单

0.66kV/0.69kV接地电阻柜是变压器中性点接地电阻柜中的特殊存在,主要应用于低压柴油发电机组220V、火力发电厂380V、煤炭企业660V/690V等电力系统或电力用户1000V的低压系统中。 我们来看看0.66kV0.69kV接地电阻柜配置单: 配置特点如下: 1…

矩阵短剧系统:如何用1个后台管理100+小程序?深度解析多端绑定技术

短剧行业效率革命!一套系统实现多平台内容分发、数据统管与流量聚合 在短剧行业爆发式增长的今天,内容方和运营者面临两大核心痛点:多平台运营成本高与流量分散难聚合。传统模式下,每个小程序需独立开发后台,导致人力…

CSS可以继承的样式汇总

CSS可以继承的样式汇总 在CSS中,以下是一些常见的可继承样式属性: 字体属性:包括 font-family (字体系列)、 font-size (字体大小)、 font-weight (字体粗细)、 font-sty…

BFS算法篇——打开智慧之门,BFS算法在拓扑排序中的诗意探索(上)

文章目录 引言一、拓扑排序的背景二、BFS算法解决拓扑排序三、应用场景四、代码实现五、代码解释六、总结 引言 在这浩瀚如海的算法世界中,有一扇门,开启后通向了有序的领域。它便是拓扑排序,这个问题的解决方法犹如一场深刻的哲学思考&#…

【Qt开发】信号与槽

目录 1,信号与槽的介绍 2,信号与槽的运用 3,自定义信号 1,信号与槽的介绍 在Qt框架中,信号与槽机制是一种用于对象间通信的强大工具。它是在Qt中实现事件处理和回调函数的主要方法。 信号:窗口中&#x…

数据库基础:概念、原理与实战示例

在当今信息时代,数据已经成为企业和个人的核心资产。无论是社交媒体、电子商务、金融交易,还是物联网设备,几乎所有的现代应用都依赖于高效的数据存储和管理。数据库(Database)作为数据管理的核心技术,帮助…

前端-HTML基本概念

目录 什么是HTML 常用的浏览器引擎是什么? 常见的HTML实体字符 HTML注释 HTML语义化是什么?为什么要语义化?一定要语义化吗? 连续空格如何渲染? 声明文档类型 哪些字符集编码支持简体中文? 如何解…

Linux进程信号处理(26)

文章目录 前言一、信号的处理时机处理情况“合适”的时机 二、用户态与内核态概念重谈进程地址空间信号的处理过程 三、信号的捕捉内核如何实现信号的捕捉?sigaction 四、信号部分小结五、可重入函数六、volatile七、SIGCHLD 信号总结 前言 这篇就是我们关于信号的最…