【音视频】FFmpeg内存模型
FFmpeg内存模型从现有的Packet拷贝一个新Packet的时间,有两种情况:
[*]两个Packet的buf引用的是同一数据缓存空间,这时间要注意数据缓存空间的释放问题;
[*]两个Packet的buf引用不同的数据缓存空间,每个Packet都有数据缓存空间的copy;
https://i-blog.csdnimg.cn/direct/517992c1e25e46f8a640f8da5572d820.png
https://i-blog.csdnimg.cn/direct/086d14b58c5948c594d753eaf039c69e.png
关系先容
[*]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;// 获取关联的 AVBuffer
uint8_t *data = buffer->data; // AVBuffer 的数据指针
// 2. 创建 AVPacket,并关联 AVBufferRef
AVPacket *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 = av_buffer_ref(buffer_ref);// AVFrame 引用 AVBufferRef
通过这种筹划,FFmpeg 实现了高效的内存共享与自动释放,减少内存走漏风险。
一个 AVBuffer 布局体通常存储 一个分量 的数据,而非完备的 YUV 所有分量。以常见的平面格式(如 YUV420P)为例:
[*]Y 分量:单独由一个 AVBuffer 存储,对应 AVFrame->buf 关联的 AVBufferRef 指向的 AVBuffer。
[*]U 分量:由另一个 AVBuffer 存储,对应 AVFrame->buf 关联的 AVBufferRef 指向的 AVBuffer。
[*]V 分量:再由一个 AVBuffer 存储,对应 AVFrame->buf 关联的 AVBufferRef 指向的 AVBuffer。
这种筹划下,每个 AVBuffer 负责管理单一数据平面(分量)的内存,通过 AVFrame->buf 数组中的多个 AVBufferRef,实现对 YUV 各分量的独立内存管理(如分配、引用计数、释放等)。若为打包格式(如 YUV420P 非平面形式),虽数据存储方式不同,但 AVBuffer 仍遵循“单一内存块管理”原则,不会同时存储多个独立分量的完备数据。
更为准确的模型
https://i-blog.csdnimg.cn/direct/a6e479f4ddb4481ab8c9586de386f312.png
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分配内存,引
用计数初始化为1int 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);分配 AVFramevoid av_frame_free(AVFrame **frame);释放 AVFrameint 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);
// 引用计数初始化为1
memccpy(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(); // 必须先alloc
av_packet_move_ref(pkt2, pkt);//内部实在也调用了av_init_packet av_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(); // 必须先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);} 代码功能概述
该函数重要用于测试 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");
else
printf("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_S16
frame->channel_layout = AV_CH_LAYOUT_STEREO; //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREO
ret = av_frame_get_buffer(frame, 0); // 根据格式分配内存
if(frame->buf && frame->buf)
printf("%s(%d) 1 frame->buf->size = %d\n", __FUNCTION__, __LINE__, frame->buf->size); //受frame->format等参数影响
if(frame->buf && frame->buf)
printf("%s(%d) 1 frame->buf->size = %d\n", __FUNCTION__, __LINE__, frame->buf->size); //受frame->format等参数影响
if(frame->buf && frame->buf) // 打印referenc-counted,必须保证传入的是有效指针
printf("%s(%d) ref_count1(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf));
ret = av_frame_make_writable(frame); // 当frame本身为空时不能make writable
printf("av_frame_make_writable ret = %d\n", ret);
if(frame->buf && frame->buf) // 打印referenc-counted,必须保证传入的是有效指针
printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf));
av_frame_unref(frame);
if(frame->buf && frame->buf) // 打印referenc-counted,必须保证传入的是有效指针
printf("%s(%d) ref_count3(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf));
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)
printf("%s(%d) 1 frame->buf->size = %d\n", __FUNCTION__, __LINE__, frame->buf->size); //受frame->format等参数影响
if(frame->buf && frame->buf)
printf("%s(%d) 1 frame->buf->size = %d\n", __FUNCTION__, __LINE__, frame->buf->size); //受frame->format等参数影响
如果 frame->buf 或 frame->buf 不为 NULL,则打印它们的大小。缓冲区大小受 frame->format 等参数的影响。
因为AV_SAMPLE_FMT_S16格式只有一个通道,因此只有buf有数据;如果是AV_SAMPLE_FMT_S16P有2个通道,即buf和buf都有效
6. 打印初始引用计数
if(frame->buf && frame->buf) // 打印referenc-counted,必须保证传入的是有效指针
printf("%s(%d) ref_count1(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf));
如果 frame->buf 不为 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) // 打印referenc-counted,必须保证传入的是有效指针
printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf));
如果 frame->buf 不为 NULL,则打印可写操作后的引用计数。如果发生了数据复制,引用计数可能会改变。
9. 排除 AVFrame 的引用
av_frame_unref(frame);
调用 av_frame_unref 函数排除 frame 对其数据缓冲区的引用,减少数据缓冲区的引用计数。如果引用计数降为 0,则释放数据缓冲区。
10. 打印排除引用后的引用计数
if(frame->buf && frame->buf) // 打印referenc-counted,必须保证传入的是有效指针
printf("%s(%d) ref_count3(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf));
如果 frame->buf 不为 NULL,则打印排除引用后的引用计数。通常情况下,引用计数应该为 0。
11. 释放 AVFrame 布局体
av_frame_free(&frame);
调用 av_frame_free 函数释放 frame 所指向的 AVFrame 布局体。
更多资料:https://github.com/0voice
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]