FFmpeg 新手向内存处理详解:彻底掌握 AVPacket/AVBuffer 的生命周期
测试用例说明
| 测试函数 | 功能描述 |
|---|---|
av_packet_test1 |
验证 av_packet_alloc 与 av_packet_free 的正确配对使用,检测内存泄漏。 |
av_packet_test2 |
测试误用 av_init_packet 可能导致的内存泄漏问题。 |
av_packet_test3 |
测试 av_packet_move_ref 后使用 av_init_packet 的安全性。 |
av_packet_test4 |
验证 av_packet_clone 函数的正确使用方式。 |
av_packet_test5 |
测试 av_packet_ref 引用计数机制,验证多次引用与释放的安全性。 |
av_packet_test6 |
测试 AVPacket 结构体直接赋值的效果(类似 av_packet_move_ref)。 |
引入:为什么FFmpeg处理流程中需要对AVPacket和AVFrame内存管理?
- 数据经历多个阶段:读取 → 解封装 → 解码 → 处理 → 编码 → 输出,每一步都要操作这些数据。
如果每一步都复制数据,不仅 慢,而且 浪费内存,因此引入了内存管理
实际内存结构:
AVPacket / AVFrame
│
└──► AVBufferRef(引用对象)
│
└──► AVBuffer(真正的数据块)
AVBuffer是真正的数据块,而AVBufferRef引用了AVBuffer,带有引用计数,当引用计数变为0时候,AVBuffer才释放
- 引用计数是针对底层的数据 buffer(即实际的数据内存),而不是
AVPacket本身
- 当你“复制引用”给另一个模块或变量时:引用计数+1
例如: av_frame_ref()、av_packet_ref()
- 当你“声明自己不再需要”这份数据时:引用计数-1
例如:av_frame_unref()、av_packet_unref()
AVPacket【AVFrame】生命周期关系图:

验证 av_packet_alloc 与 av_packet_free 的正确配对使用,检测内存泄漏:
void av_packet_test1(){
AVPacket *pkt=null;//此时 pkt 是空的,未分配结构体;
int ret = 0;//一个返回值变量,将用于判断后续函数调用是否成功
pkt = av_packet_alloc();//分配一个空的 AVPacket 结构体,返回指针赋给 pkt;
ret = av_new_packet(pkt, MEM_ITEM_SIZE);//为pkt分配内存空间
av_packet_unref(pkt);//将 pkt->buf 引用计数 -1
}
ret = av_new_packet(pkt,MEM_ITEM_SIZE);
这部分是为这个 pkt 分配 MEM_ITEM_SIZE 字节的 buffer 空间,并设置 pkt->size;
底层实际做的是:创建一个 AVBuffer 和 AVBufferRef,绑定到 pkt->buf,同时 pkt->data 指向这块 buffer;
此时,引用计数 = 1;
👉 如果失败(ret < 0),应当释放 pkt。
av_packet_unref(pkt);
这部分将 pkt->buf 引用计数 -1,将 pkt 对应的底层 packet buffer 置空。此外,若此时refcount=0,则会释放这块buffer!!!!
- 执行情况:
可以看到刚开始pkt值为NULL【还没有执行到pkt = av_packet_alloc();时】

执行完pkt = av_packet_alloc();之后,pkt变为一个空壳结构体,但指针有效【值为@0x229847c59c0】

执行完ret = av_new_packet(pkt,MEM_ITEM_SIZE);之后,pkt->data非NULL(指向buffer),pkt->size为MEM_ITEM_SIZE,pkt->buf为非NULL(引用计数=1)

执行完av_packet_unref(pkt);之后,pkt->buf被av_buffer_ref()清空,但 pkt 本身的内存不会被释放(即 pkt 指针仍然指向有效的 AVPacket 对象)

av_packet_free(&pkt); 执行后,不仅释放 pkt->buf 指向的数据(如果引用计数为 1),还释放了 pkt 本身的内存;pkt 指针也会被置为 NULL,以防悬空指针。

测试误用 av_init_packet 可能导致的内存泄漏问题
void av_packet_test2()
{
AVPacket *pkt = NULL;
int ret = 0;
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
// memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
av_init_packet(pkt); // 这个时候init就会导致内存无法释放
av_packet_free(&pkt);
}
使用av_init_packet(pkt)之后,会清空 pkt->buf!造成严重后果,此时pkt->buf = NULL; // !!! 将 pkt->buf 指针置空导致内存无法释放[av_init_packet(pkt) 只清空 pkt->buf 这个指针(设为 NULL)等字段,不会也无法释放 AVBuffer 的内存或内容]

此时,尽管执行完av_packet_free(&pkt); pkt 本身被释放,但是pkt->data 所指内存未释放 ❌
-
结论
不要在使用
av_new_packet()后调用av_init_packet()!- 它是过时 API,只用于旧版兼容。
- 会破坏新的 AVBufferRef 引用计数机制。
测试 av_packet_move_ref 后是否可使用 av_init_packet
void av_packet_test3()
{
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
// memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
pkt2 = av_packet_alloc(); // 必须先alloc
av_packet_move_ref(pkt2, pkt);//内部其实也调用了av_init_packet
av_init_packet(pkt);
av_packet_free(&pkt);
av_packet_free(&pkt2);
}
执行av_packet_move_ref(pkt2,pkt);之前的情况:pkt的buf指向@0x1464ed25b40 ,而pkt2 的buf 指向NULL

执行av_packet_move_ref(pkt2,pkt);之后,刚才pkt中的buffer指向的地址都给了pkt2 :

注:在执行av_packet_move_ref(pkt2,pkt);的过程中,其实已经内部调用了av_init_packet(pkt)了,而且安全性更高,若再执行av_init_packet(pkt); 意义不大。
- 结论:
现代 FFmpeg 中,永远不要手动调用 av_init_packet(),使用 av_packet_unref() 或 av_packet_move_ref() 即可
验证 av_packet_clone 函数的正确使用方式
void av_packet_test4()
{
AVPacket *pkt = NULL;
// av_packet_alloc()没有必要,因为av_packet_clone内部有调用 av_packet_alloc
AVPacket *pkt2 = NULL;
int ret = 0;
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
// memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
pkt2 = av_packet_clone(pkt); // av_packet_alloc()+av_packet_ref()
av_init_packet(pkt);
av_packet_free(&pkt);
av_packet_free(&pkt2);
}
执行av_packet_clone(pkt);之前的情况,可以看到pkt目前有数据和buffer,但是pkt2为空

执行完pkt2 = av_packet_clone(pkt);之后:pkt2->buf 和 pkt->buf 指向同一个 AVBufferRef,同时pkt2->data == pkt->data,引用计数+1

后续执行av_init_packet(pkt);是危险的,不建议
- 结论:
pkt2 = av_packet_clone(pkt);的内部其实是:
pkt2 = av_packet_alloc();
av_packet_ref(pkt2, pkt);
av_packet_ref(pkt2, pkt)做了什么?
pkt2->buf = av_buffer_ref(pkt->buf);(增加引用计数)pkt2->data = pkt->data;(同样指向)pkt2->size = pkt->size;(值拷贝)
测试 av_packet_ref 引用计数机制,验证多次引用与释放的安全性。
void av_packet_test5()
{
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
pkt = av_packet_alloc(); //
if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
av_buffer_get_ref_count(pkt->buf));
}
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
av_buffer_get_ref_count(pkt->buf));
}
// memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
pkt2 = av_packet_alloc(); // 必须先alloc
av_packet_move_ref(pkt2, pkt); // av_packet_move_ref
// av_init_packet(pkt); //av_packet_move_ref
av_packet_ref(pkt, pkt2);
av_packet_ref(pkt, pkt2); // 多次ref如果没有对应多次unref将会内存泄漏
if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
av_buffer_get_ref_count(pkt->buf));
}
if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
av_buffer_get_ref_count(pkt2->buf));
}
av_packet_unref(pkt); // 将为2
av_packet_unref(pkt); // 做第二次是没有用的
if(pkt->buf)
printf("pkt->buf没有被置NULL\n");
else
printf("pkt->buf已经被置NULL\n");
if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
av_buffer_get_ref_count(pkt2->buf));
}
av_packet_unref(pkt2);
av_packet_free(&pkt);
av_packet_free(&pkt2);
}
- 执行情况:
-
在执行完
pkt = av_packet_alloc();但是没有执行到ret = av_new_packet(pkt, MEM_ITEM_SIZE);之前,并没有数据,也没有buffer,pkt->buf == NULL,所以不打印任何信息 -
ret = av_new_packet(pkt, MEM_ITEM_SIZE);完之后,分配了一个 buffer,并将pkt->buf、pkt->data、pkt->size设置好内部:调用了
av_buffer_alloc(),返回ref_count = 1,此时打印,会输出1 -
此时,执行到:
pkt2 = av_packet_alloc(); // 必须先alloc
av_packet_move_ref(pkt2, pkt); // av_packet_move_ref
pkt 所有权转移给 pkt2,pkt变为空包, 但是引用计数仍然是 1
-
av_packet_ref(pkt, pkt2);pkt再次引用pkt2所持有的AVBufferRef,引用计数 +1 →ref_count = 2 -
av_packet_ref(pkt, pkt2);,再次引用同一个buffer,引用计数 +1 →ref_count = 3 -
此时打印两个计数,都为
3
if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
av_buffer_get_ref_count(pkt->buf));
}
if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
av_buffer_get_ref_count(pkt2->buf));
}
-
av_packet_unref(pkt); // 将为2 av_packet_unref(pkt); // 做第二次是没有用的,仍为2
第二次 av_packet_unref(pkt):
- 由于上一步已经把
pkt->buf == NULL,因此这一行无效,什么都不做。 - 这意味着:
- 引用计数仍为 2(即使调用了两次
unref,只成功执行了1次)
- 引用计数仍为 2(即使调用了两次
- 背景拓展:多个
AVPacket可以共享同一个AVBuffer,只要你用av_packet_ref()引用它。
此时,
if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
av_buffer_get_ref_count(pkt2->buf));
}
这部分输出结果就为2
- 进行到
av_packet_unref(pkt2);,此时引用计数再-1,变为1,但是,我们此时已经无法将其数字打印出来,因为pkt2->buf已然置NULL ,计数剩1,此时,会导致严重性问题,buffer无法被释放掉了!!!!!!!!
- 由此得出结论:
av_packet_ref 多次,必须相应调用 av_packet_unref 相同次数!,否则会导致最后的buffer无法被释放掉,导致内存泄漏!!!
测试 AVPacket 结构体直接赋值的效果(类似 av_packet_move_ref)
void av_packet_test6()
{
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
// memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
pkt2 = av_packet_alloc(); // 必须先alloc
*pkt2 = *pkt; // 有点类似 pkt可以重新分配内存
av_init_packet(pkt);
av_packet_free(&pkt);
av_packet_free(&pkt2);
}
-
浅拷贝
*pkt2 = *pkt做了什么? -
它只是把
pkt的所有字段逐字节地复制到pkt2中; -
对于指针字段(如
data、buf、side_data):- 指针值被复制了,但不分配新内存;
pkt2->data == pkt->data(指向同一块数据);pkt2->buf == pkt->buf(指向同一个AVBufferRef);pkt2->side_data == pkt->side_data(如果存在);
-
这会带来什么问题?
- 引用计数没有增加:
pkt2->buf指向的是同一个AVBufferRef;- 但引用计数仍然是
1,这意味着两个结构体都以为自己是“唯一持有者”; - 如果两个都调用
av_packet_unref()→ 双重释放 → 崩溃或内存破坏。
- 浅拷贝带来的副作用:
- 改动
pkt2->data[0] = 123;,pkt->data[0]也变了; - 如果
pkt2修改side_data,也影响pkt; - 所以看起来是两份,其实是一份。
更多推荐


所有评论(0)