FFmpeg内存模型
从现有的Packet拷贝一个新Packet的时候,有两种情况:
- 两个Packet的buf引用的是同一数据缓存空间,这时候要注意数据缓存空间的释放问题;
- 两个Packet的buf引用不同的数据缓存空间,每个Packet都有数据缓存空间的copy;
关系介绍
- AVBuffer:核心数据容器,存储实际数据(
data
),通过refcount
实现引用计数,free()
用于定义内存释放逻辑。 - AVBufferRef:作为 AVBuffer 的引用,允许不同组件(如 AVPacket、AVFrame)共享同一份数据。它内部的
buffer
指向 AVBuffer,data
和size
本质是对 AVBuffer 对应成员的“转发”。 - AVPacket/AVFrame:通过 AVBufferRef 管理数据。例如,AVPacket 的
buf
成员、AVFrame 的buf
数组,都依赖 AVBufferRef 实现数据共享与内存管理。
代码示例
以下代码演示 AVBuffer、AVBufferRef、AVPacket 的关联逻辑:
#include <libavutil/buffer.h>
#include <libavcodec/avcodec.h>int main() {// 1. 创建 AVBuffer(分配 1024 字节内存)AVBufferRef *buffer_ref = av_buffer_alloc(1024);if (!buffer_ref) {return -1;}AVBuffer *buffer = buffer_ref->buffer; // 获取关联的 AVBufferuint8_t *data = buffer->data; // AVBuffer 的数据指针// 2. 创建 AVPacket,并关联 AVBufferRefAVPacket *pkt = av_packet_alloc();if (!pkt) {av_buffer_unref(&buffer_ref);return -1;}pkt->buf = av_buffer_ref(buffer_ref); // AVPacket 引用 AVBufferRef// 此时,AVPacket 的数据指针 pkt->data 等同于 buffer->data(即同一份数据)// 3. 验证引用计数printf("Initial refcount: %u\n", buffer->refcount); // 输出 2(AVBufferRef 自身 + AVPacket 引用)// 4. 释放资源av_packet_free(&pkt); // 减少 AVPacket 对 AVBufferRef 的引用av_buffer_unref(&buffer_ref); // 若引用计数归 0,AVBuffer 内存自动释放return 0;
}
代码解析
-
创建 AVBuffer:通过
av_buffer_alloc
分配内存,返回 AVBufferRef。此时,AVBufferRef 的buffer
指向内部的 AVBuffer,data
指向实际内存。 -
关联 AVPacket:使用
av_buffer_ref
让 AVPacket 的buf
引用 AVBufferRef。此时,AVPacket 的data
与 AVBuffer 的data
指向同一块内存。 -
引用计数:每次
av_buffer_ref
会增加引用计数(refcount
),av_buffer_unref
减少计数。当计数为 0 时,AVBuffer 自动释放内存,避免手动管理内存的繁琐与风险。 -
AVFrame 同理:AVFrame 的
buf
数组使用类似逻辑,例如:AVFrame *frame = av_frame_alloc(); frame->buf[0] = av_buffer_ref(buffer_ref); // AVFrame 引用 AVBufferRef
通过这种设计,FFmpeg 实现了高效的内存共享与自动释放,减少内存泄漏风险。
一个 AVBuffer
结构体通常存储 一个分量 的数据,而非完整的 YUV 所有分量。以常见的平面格式(如 YUV420P)为例:
- Y 分量:单独由一个
AVBuffer
存储,对应AVFrame->buf[0]
关联的AVBufferRef
指向的AVBuffer
。 - U 分量:由另一个
AVBuffer
存储,对应AVFrame->buf[1]
关联的AVBufferRef
指向的AVBuffer
。 - V 分量:再由一个
AVBuffer
存储,对应AVFrame->buf[2]
关联的AVBufferRef
指向的AVBuffer
。
这种设计下,每个 AVBuffer
负责管理单一数据平面(分量)的内存,通过 AVFrame->buf
数组中的多个 AVBufferRef
,实现对 YUV 各分量的独立内存管理(如分配、引用计数、释放等)。若为打包格式(如 YUV420P 非平面形式),虽数据存储方式不同,但 AVBuffer
仍遵循“单一内存块管理”原则,不会同时存储多个独立分量的完整数据。
更为精确的模型
FFmpeg内存模型-引用计数
对于多个AVPacket共享同一个缓存空间,FFmpeg使用的引用计数的机制(reference-count):
- 初始化引用计数为0,只有真正分配AVBuffer的时候,引用计数初始化为1;
- 当有新的Packet引用共享的缓存空间时,就将引用计数+1;
- 当释放了引用共享空间的Packet,就将引用计数-1;引用计数为0时,就释放掉引用的缓存空间AVBuffer。
- AVFrame也是采用同样的机制。
AVPacket常用API
AVPacket *av_packet_alloc(void); | 分配AVPacket 这个时候和buffer没有关系 |
---|---|
void av_packet_free(AVPacket **pkt); | 释放AVPacket 和_alloc对应 |
void av_init_packet(AVPacket *pkt); | 初始化AVPacket 只是单纯初始化pkt字段 |
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() |
测试代码
宏定义
#define MEM_ITEM_SIZE (20*1024*102)
#define AVPACKET_LOOP_COUNT 1000
测试1
void av_packet_test1()
{AVPacket *pkt = NULL;int ret = 0;pkt = av_packet_alloc();ret = av_new_packet(pkt, MEM_ITEM_SIZE); // 引用计数初始化为1memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);av_packet_unref(pkt); // 要不要调用av_packet_free(&pkt); // 如果不free将会发生内存泄漏,内部调用了 av_packet_unref
}
下面将对 av_packet_test1
函数的代码进行详细剖析:
代码功能概述
av_packet_test1
函数的主要功能是创建一个 AVPacket
对象,为其分配数据缓冲区,尝试向该缓冲区复制数据,之后释放 AVPacket
及其关联的数据缓冲区,以此避免内存泄漏。
代码逐行分析
1. 变量声明
AVPacket *pkt = NULL;
int ret = 0;
pkt
:这是一个指向AVPacket
结构体的指针,初始化为NULL
。AVPacket
是 FFmpeg 里用于存储压缩媒体数据(像视频帧、音频帧等)的结构体。ret
:用于存储函数调用的返回值,初始化为 0。
2. 分配 AVPacket
结构体
pkt = av_packet_alloc();
- 调用
av_packet_alloc
函数,在堆上分配一个新的AVPacket
结构体实例,并且把结构体的成员初始化为默认值。若分配成功,pkt
会指向新分配的AVPacket
;若失败,pkt
为NULL
。
3. 为 AVPacket
分配数据缓冲区
ret = av_new_packet(pkt, MEM_ITEM_SIZE); // 引用计数初始化为1
av_new_packet
函数的作用是为AVPacket
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。- 若分配成功,返回值
ret
为 0,同时AVPacket
关联的数据缓冲区的引用计数会初始化为 1。 - 若分配失败,
ret
会是一个负的错误码。
4. 复制数据到 AVPacket
的数据缓冲区
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
memccpy
是 C 标准库中的函数,用于在内存之间复制数据。pkt->data
指向AVPacket
的数据缓冲区,也就是目标内存区域。(void *)&av_packet_test1
是源内存区域的地址,av_packet_test1
可能是一个自定义的变量或结构体。1
是要查找的字符,当在源数据中碰到这个字符时,复制操作会停止。MEM_ITEM_SIZE
是最多要复制的字节数。
5. 减少 AVPacket
引用计数
av_packet_unref(pkt); // 要不要调用
av_packet_unref
函数会减少AVPacket
关联的数据缓冲区的引用计数。当引用计数降为 0 时,会释放实际的数据缓冲区内存,同时把AVPacket
结构体的成员重置为默认值。- 在此处,调用
av_packet_unref
并非必需,因为av_packet_free
函数内部会自动调用av_packet_unref
。不过,调用它也不会有问题,只是会多执行一次减少引用计数的操作。
6. 释放 AVPacket
结构体
av_packet_free(&pkt); // 如果不free将会发生内存泄漏,内部调用了 av_packet_unref
av_packet_free
函数会释放AVPacket
结构体所占用的内存。在释放之前,它会自动调用av_packet_unref
处理AVPacket
关联的数据缓冲区,确保数据缓冲区的引用计数被正确处理,避免内存泄漏。- 该函数接收一个指向
AVPacket
指针的指针作为参数,释放后会把传入的指针置为NULL
,防止出现悬空指针。
测试2
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);printf("size1 = %p",pkt->buf);av_init_packet(pkt); // 这个时候init就会导致内存无法释放printf("size2 = %p",pkt->buf);av_packet_free(&pkt);
}
代码功能概述
该函数的主要目的是创建一个 AVPacket
对象,为其分配数据缓冲区,向缓冲区复制数据,然后释放 AVPacket
及其关联的数据缓冲区。
代码逐行分析
1. 变量声明与 AVPacket
分配
AVPacket *pkt = NULL;
int ret = 0;pkt = av_packet_alloc();
- 声明一个指向
AVPacket
的指针pkt
并初始化为NULL
,同时声明一个用于存储函数返回值的变量ret
。 - 调用
av_packet_alloc
函数在堆上分配一个新的AVPacket
结构体实例,并将其地址赋给pkt
。
2. 为 AVPacket
分配数据缓冲区
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
av_new_packet
函数为AVPacket
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。- 若分配成功,
pkt->data
指向新分配的缓冲区,同时数据缓冲区的引用计数初始化为 1。
3. 复制数据到 AVPacket
的数据缓冲区
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
- 使用
memccpy
函数将av_packet_test1
指向的数据复制到pkt->data
所指向的缓冲区中,最多复制MEM_ITEM_SIZE
个字节,当遇到值为 1 的字符时停止复制。
4. 调用 av_init_packet(pkt)
av_init_packet(pkt);
- 这一步是问题所在。
av_init_packet
是一个已被弃用的函数,其作用是将AVPacket
结构体的成员初始化为默认值。 - 调用
av_init_packet
会将pkt->buf
设置为NULL
,同时还会重置其他一些成员。 - 由于
pkt->buf
被设置为NULL
,后续调用av_packet_free
时,av_packet_unref
无法正确识别之前分配的数据缓冲区,从而导致数据缓冲区的引用计数无法正确处理,最终造成内存泄漏。
5. 释放 AVPacket
av_packet_free(&pkt);
av_packet_free
函数会先调用av_packet_unref
来处理AVPacket
关联的数据缓冲区,然后释放AVPacket
结构体本身的内存。- 但由于
av_init_packet
已经将pkt->buf
设置为NULL
,av_packet_unref
无法正确释放数据缓冲区,只释放了AVPacket
结构体本身的内存。
测试3
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(); // 必须先allocav_packet_move_ref(pkt2, pkt);//内部其实也调用了av_init_packetav_init_packet(pkt);av_packet_free(&pkt);av_packet_free(&pkt2);
}
代码逐行分析
1. 变量声明
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
- 声明了两个指向
AVPacket
结构体的指针pkt
和pkt2
,并初始化为NULL
。 - 声明一个整型变量
ret
,用于存储函数调用的返回值。
2. 分配并初始化第一个 AVPacket
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
av_packet_alloc()
:分配一个新的AVPacket
结构体,并将其地址赋给pkt
。av_new_packet(pkt, MEM_ITEM_SIZE)
:为pkt
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。如果分配成功,pkt->data
指向新分配的缓冲区,同时数据缓冲区的引用计数初始化为 1。返回值存储在ret
中,若返回值小于 0 则表示分配失败。memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE)
:将av_packet_test1
指向的数据复制到pkt->data
所指向的缓冲区中,最多复制MEM_ITEM_SIZE
个字节,当遇到值为 1 的字符时停止复制。
3. 分配第二个 AVPacket
pkt2 = av_packet_alloc(); // 必须先alloc
使用 av_packet_alloc()
分配一个新的 AVPacket
结构体,并将其地址赋给 pkt2
。在进行数据引用转移之前,必须先为 pkt2
分配内存。
4. 转移数据引用
av_packet_move_ref(pkt2, pkt); // 内部其实也调用了av_init_packet
av_packet_move_ref(pkt2, pkt)
函数将 pkt
的数据引用转移到 pkt2
上。调用该函数后,pkt
不再拥有数据缓冲区的引用,其成员会被重置为默认值,而 pkt2
接管数据缓冲区的引用。
5. 再次调用 av_init_packet
av_init_packet(pkt);
这一步是多余且可能会带来问题的操作。因为 av_packet_move_ref
已经将 pkt
的成员重置为默认值,再次调用 av_init_packet(pkt)
并不会有额外的作用。并且如果 av_packet_move_ref
出现异常,这一步操作可能会进一步破坏 pkt
的状态。
6. 释放 AVPacket
av_packet_free(&pkt);
av_packet_free(&pkt2);
av_packet_free(&pkt)
:释放pkt
所指向的AVPacket
结构体。由于之前av_packet_move_ref
已经将pkt
的数据引用转移走,此时pkt
不持有数据缓冲区的引用,所以只会释放AVPacket
结构体本身的内存。av_packet_free(&pkt2)
:释放pkt2
所指向的AVPacket
结构体及其关联的数据缓冲区。因为pkt2
持有数据缓冲区的引用,调用av_packet_free
会先调用av_packet_unref
减少数据缓冲区的引用计数,当引用计数降为 0 时释放数据缓冲区的内存,然后释放AVPacket
结构体本身的内存。
测试4
av_packet_test4
函数是一个用于测试 AVPacket
内存管理和引用计数机制的函数,其目的是展示由于不当使用 av_init_packet
而导致的内存泄漏问题。下面我们对该函数进行详细的逐行分析。
1. 变量声明
AVPacket *pkt = NULL;
// av_packet_alloc()没有必要,因为av_packet_clone内部有调用 av_packet_alloc
AVPacket *pkt2 = NULL;
int ret = 0;
pkt
和pkt2
是指向AVPacket
结构体的指针,初始化为NULL
。pkt
用于后续创建和操作一个AVPacket
对象,pkt2
则用于克隆pkt
。ret
是一个整型变量,用于存储函数调用的返回值,方便后续判断操作是否成功。
2. 创建并初始化第一个 AVPacket
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
av_packet_alloc()
:动态分配一个新的AVPacket
结构体,并将其地址赋给pkt
。av_new_packet(pkt, MEM_ITEM_SIZE)
:为pkt
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。如果分配成功,pkt->data
指向该缓冲区,同时数据缓冲区的引用计数初始化为 1。ret
存储该函数的返回值,若小于 0 则表示分配失败。memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE)
:将av_packet_test1
指向的数据复制到pkt->data
所指向的缓冲区中,最多复制MEM_ITEM_SIZE
个字节,当遇到值为 1 的字符时停止复制。
3. 克隆 AVPacket
pkt2 = av_packet_clone(pkt); // av_packet_alloc()+av_packet_ref(), 调用该函数后,pkt和pkt2对应的buf引用计数变成2
av_packet_clone(pkt)
:该函数相当于先调用av_packet_alloc
分配一个新的AVPacket
结构体,再调用av_packet_ref
将pkt
的数据引用复制到新的AVPacket
上。因此,调用该函数后,pkt
和pkt2
共享同一份数据缓冲区,数据缓冲区的引用计数变为 2。
4. 故意破坏 pkt
的引用管理
av_init_packet(pkt); // 这里是故意去做init的操作,让这个函数出现内存泄漏
av_init_packet(pkt)
:该函数会将pkt
的成员重置为默认值,其中包括将pkt->buf
置为NULL
。pkt->buf
是一个指向AVBufferRef
的指针,用于管理数据缓冲区的引用计数。将其置为NULL
会破坏pkt
与数据缓冲区的引用关系,导致后续无法正确处理引用计数。
5. 释放 pkt
av_packet_free(&pkt); // pkt在调用av_init_packet后,对应的buf被置为NULL,在调用av_packet_free没法做引用计数-1的操作
av_packet_free(&pkt)
:该函数会先调用av_packet_unref
来减少pkt
关联的数据缓冲区的引用计数,然后释放pkt
结构体本身的内存。但由于之前av_init_packet(pkt)
已经将pkt->buf
置为NULL
,av_packet_unref
无法找到对应的AVBufferRef
,也就无法减少数据缓冲区的引用计数。因此,数据缓冲区的引用计数仍然为 2。
6. 释放 pkt2
av_packet_free(&pkt2); // 触发引用计数变为1,但因为不是0,所以buf不会被释放,导致内存泄漏
av_packet_free(&pkt2)
:同样,该函数会先调用av_packet_unref
来减少pkt2
关联的数据缓冲区的引用计数,然后释放pkt2
结构体本身的内存。由于pkt
之前没有正确减少引用计数,此时调用av_packet_unref
只会将数据缓冲区的引用计数减为 1,而不是 0。因此,数据缓冲区的内存不会被释放,从而导致内存泄漏。
测试5
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(); // 必须先allocav_packet_move_ref(pkt2, pkt); // av_packet_move_ref
// av_init_packet(pkt); //av_packet_move_refav_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); // 将为2av_packet_unref(pkt); // 做第二次是没有用的if(pkt->buf)printf("pkt->buf没有被置NULL\n");elseprintf("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);
}
代码功能概述
该函数主要用于测试 FFmpeg 中 AVPacket
的内存分配、数据操作、引用转移、引用计数管理以及释放等操作,同时通过打印引用计数来观察这些操作对引用计数的影响,以此展示引用计数管理不当可能导致的问题。
代码逐行分析
1. 变量声明
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
声明两个 AVPacket
指针 pkt
和 pkt2
并初始化为 NULL
,同时声明一个整型变量 ret
用于存储函数调用的返回值。
2. 分配 pkt
并检查引用计数
pkt = av_packet_alloc();
if(pkt->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
av_packet_alloc()
分配一个新的AVPacket
结构体给pkt
。- 由于此时
pkt
还未分配数据缓冲区,pkt->buf
为NULL
,所以不会打印引用计数。
3. 为 pkt
分配数据缓冲区并检查引用计数
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
if(pkt->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
av_new_packet(pkt, MEM_ITEM_SIZE)
为pkt
分配一个大小为MEM_ITEM_SIZE
的数据缓冲区,分配成功后pkt->buf
指向该缓冲区,引用计数初始化为 1。- 若
pkt->buf
不为NULL
,则打印当前pkt
的引用计数。
4. 复制数据到 pkt
的数据缓冲区
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
将 av_packet_test1
指向的数据复制到 pkt->data
所指向的缓冲区中,最多复制 MEM_ITEM_SIZE
个字节,遇到值为 1 的字符时停止复制。
5. 分配 pkt2
并转移引用
pkt2 = av_packet_alloc();
av_packet_move_ref(pkt2, pkt);
av_packet_alloc()
分配一个新的AVPacket
结构体给pkt2
。av_packet_move_ref(pkt2, pkt)
将pkt
的数据引用转移到pkt2
上,之后pkt
不再持有数据引用,其成员被重置,pkt->buf
变为NULL
。
6. 多次引用 pkt
到 pkt2
并检查引用计数
av_packet_ref(pkt, pkt2);
av_packet_ref(pkt, pkt2);
if(pkt->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
if(pkt2->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));
}
- 两次调用
av_packet_ref(pkt, pkt2)
使pkt
和pkt2
共享数据缓冲区,数据缓冲区的引用计数变为 3(初始 1 次 + 两次引用各加 1)。 - 若
pkt->buf
和pkt2->buf
不为NULL
,则分别打印它们的引用计数。
7. 两次调用 av_packet_unref(pkt)
并检查 pkt->buf
av_packet_unref(pkt);
av_packet_unref(pkt);
if(pkt->buf)printf("pkt->buf没有被置NULL\n");
elseprintf("pkt->buf已经被置NULL\n");
- 第一次调用
av_packet_unref(pkt)
时,引用计数减 1 变为 2。 - 第二次调用
av_packet_unref(pkt)
时,由于第一次调用后pkt->buf
已经被置为NULL
(当引用计数降为 0 时会发生),所以这次调用不会有实际效果。 - 根据
pkt->buf
是否为NULL
打印相应信息。
8. 检查 pkt2
的引用计数并调用 av_packet_unref(pkt2)
if(pkt2->buf)
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));
}
av_packet_unref(pkt2);
- 若
pkt2->buf
不为NULL
,则打印pkt2
的引用计数,此时为 2。 - 调用
av_packet_unref(pkt2)
使引用计数减 1 变为 1。
9. 释放 pkt
和 pkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);
使用 av_packet_free
分别释放 pkt
和 pkt2
所指向的 AVPacket
结构体。
存在的问题
代码存在内存泄漏问题。由于第二次调用 av_packet_unref(pkt)
无效,且只调用了一次 av_packet_unref(pkt2)
,数据缓冲区的引用计数最终仍为 1,没有降为 0,导致数据缓冲区的内存无法被释放。
测试6
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可以重新分配内存if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));}av_init_packet(pkt);if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));}av_packet_free(&pkt);av_packet_free(&pkt2);
}
代码功能概述
av_packet_test6
函数主要演示了 FFmpeg 中 AVPacket
的内存分配、数据复制、引用计数管理以及释放等操作,同时通过打印引用计数来观察不同操作对引用计数的影响
代码逐行分析
1. 变量声明
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
声明了两个指向 AVPacket
结构体的指针 pkt
和 pkt2
,并初始化为 NULL
。同时声明了一个整型变量 ret
,用于存储函数调用的返回值。
2. 分配并初始化 pkt
pkt = av_packet_alloc();
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
av_packet_alloc()
:分配一个新的AVPacket
结构体,并将其地址赋给pkt
。av_new_packet(pkt, MEM_ITEM_SIZE)
:为pkt
分配一个大小为MEM_ITEM_SIZE
字节的数据缓冲区。如果分配成功,pkt->data
指向新分配的缓冲区,同时数据缓冲区的引用计数初始化为 1。返回值存储在ret
中,若返回值小于 0 则表示分配失败。memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE)
:将av_packet_test1
指向的数据复制到pkt->data
所指向的缓冲区中,最多复制MEM_ITEM_SIZE
个字节,当遇到值为 1 的字符时停止复制。
3. 分配 pkt2
并复制 pkt
的内容
pkt2 = av_packet_alloc(); // 必须先alloc
*pkt2 = *pkt; // 有点类似 pkt可以重新分配内存
av_packet_alloc()
:分配一个新的AVPacket
结构体,并将其地址赋给pkt2
。*pkt2 = *pkt;
:这行代码直接将pkt
的内容复制到pkt2
中,包括pkt
的数据指针data
和AVBufferRef
指针buf
。此时pkt
和pkt2
指向同一个数据缓冲区,且数据缓冲区的引用计数没有增加。
4. 打印 pkt
的引用计数
if(pkt->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt->buf));
}
如果 pkt->buf
不为 NULL
,则打印 pkt
关联的数据缓冲区的引用计数,此时引用计数仍为 1。
5. 调用 av_init_packet(pkt)
av_init_packet(pkt);
av_init_packet(pkt)
会将 pkt
的成员重置为默认值,其中包括将 pkt->buf
置为 NULL
,这一步不影响引用计数
6. 打印 pkt2
的引用计数
if(pkt2->buf) // 打印referenc-counted,必须保证传入的是有效指针
{ printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,av_buffer_get_ref_count(pkt2->buf));
}
如果 pkt2->buf
不为 NULL
,则打印 pkt2
关联的数据缓冲区的引用计数,此时引用计数仍为 1。
7. 释放 pkt
和 pkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);
av_packet_free(&pkt)
:由于之前av_init_packet(pkt)
已经将pkt->buf
置为NULL
,av_packet_free
只会释放pkt
结构体本身的内存,不会对数据缓冲区的引用计数产生影响。av_packet_free(&pkt2)
:释放pkt2
所指向的AVPacket
结构体及其关联的数据缓冲区。因为pkt2
持有数据缓冲区的引用,调用av_packet_free
会先调用av_packet_unref
减少数据缓冲区的引用计数,因此这里的引用计数变成了0
,会释放数据缓冲区的内存,然后释放AVPacket
结构体本身的内存。
测试7
void av_frame_test1()
{AVFrame *frame = NULL;int ret = 0;frame = av_frame_alloc();// 没有类似的AVPacket的av_new_packet的API// 1024 *2 * (16/8) =frame->nb_samples = 1024;frame->format = AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16frame->channel_layout = AV_CH_LAYOUT_STEREO; //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREOret = av_frame_get_buffer(frame, 0); // 根据格式分配内存if(frame->buf && frame->buf[0])printf("%s(%d) 1 frame->buf[0]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[0]->size); //受frame->format等参数影响if(frame->buf && frame->buf[1])printf("%s(%d) 1 frame->buf[1]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[1]->size); //受frame->format等参数影响if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count1(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));ret = av_frame_make_writable(frame); // 当frame本身为空时不能make writableprintf("av_frame_make_writable ret = %d\n", ret);if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));av_frame_unref(frame);if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count3(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));av_frame_free(&frame);
}
1. 变量声明
AVFrame *frame = NULL;
int ret = 0;
声明一个指向 AVFrame
结构体的指针 frame
并初始化为 NULL
,同时声明一个整型变量 ret
用于存储函数调用的返回值。
2. 分配 AVFrame
结构体
frame = av_frame_alloc();// 没有类似的AVPacket的av_new_packet的API
调用 av_frame_alloc
函数在堆上分配一个新的 AVFrame
结构体,并将其地址赋给 frame
。与 AVPacket
的 av_new_packet
不同,av_frame_alloc
仅分配 AVFrame
结构体本身,不分配实际的数据缓冲区。
3. 设置 AVFrame
的参数
// 1024 *2 * (16/8) =
frame->nb_samples = 1024;
frame->format = AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16
frame->channel_layout = AV_CH_LAYOUT_STEREO; //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREO
frame->nb_samples
:设置音频帧中的样本数量为 1024。frame->format
:设置音频样本的格式为AV_SAMPLE_FMT_S16
,即 16 位有符号整数。frame->channel_layout
:设置音频的声道布局为立体声(AV_CH_LAYOUT_STEREO
)。
4. 为 AVFrame
分配数据缓冲区
ret = av_frame_get_buffer(frame, 0); // 根据格式分配内存
调用 av_frame_get_buffer
函数根据 frame
中设置的参数(如 format
、channel_layout
和 nb_samples
)为 AVFrame
分配数据缓冲区。如果分配成功,frame->buf
数组将指向分配的缓冲区。
5. 打印缓冲区大小
if(frame->buf && frame->buf[0])printf("%s(%d) 1 frame->buf[0]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[0]->size); //受frame->format等参数影响
if(frame->buf && frame->buf[1])printf("%s(%d) 1 frame->buf[1]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[1]->size); //受frame->format等参数影响
如果 frame->buf[0]
或 frame->buf[1]
不为 NULL
,则打印它们的大小。缓冲区大小受 frame->format
等参数的影响。
因为AV_SAMPLE_FMT_S16
格式只有一个通道,因此只有buf[0]
有数据;如果是AV_SAMPLE_FMT_S16P
有2个通道,即buf[0]
和buf[1]
都有效
6. 打印初始引用计数
if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count1(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
如果 frame->buf[0]
不为 NULL
,则打印其引用计数。初始时,引用计数通常为 1。
7. 使 AVFrame
可写
ret = av_frame_make_writable(frame); // 当frame本身为空时不能make writable
printf("av_frame_make_writable ret = %d\n", ret);
调用 av_frame_make_writable
函数确保 frame
是可写的。如果 frame
已经是可写的,则直接返回;否则,会复制一份数据并使 frame
指向新的可写缓冲区,并且使引用计数减一。打印该函数的返回值,返回值为 0 表示成功。
8. 打印可写操作后的引用计数
if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
如果 frame->buf[0]
不为 NULL
,则打印可写操作后的引用计数。如果发生了数据复制,引用计数可能会改变。
9. 解除 AVFrame
的引用
av_frame_unref(frame);
调用 av_frame_unref
函数解除 frame
对其数据缓冲区的引用,减少数据缓冲区的引用计数。如果引用计数降为 0,则释放数据缓冲区。
10. 打印解除引用后的引用计数
if(frame->buf && frame->buf[0]) // 打印referenc-counted,必须保证传入的是有效指针printf("%s(%d) ref_count3(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
如果 frame->buf[0]
不为 NULL
,则打印解除引用后的引用计数。通常情况下,引用计数应该为 0。
11. 释放 AVFrame
结构体
av_frame_free(&frame);
调用 av_frame_free
函数释放 frame
所指向的 AVFrame
结构体。
更多资料:https://github.com/0voice