测试用例说明

测试函数 功能描述
av_packet_test1 验证 av_packet_allocav_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_allocav_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

底层实际做的是:创建一个 AVBufferAVBufferRef,绑定到 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->bufav_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);之前的情况:pktbuf指向@0x1464ed25b40 ,而pkt2buf 指向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->bufpkt->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) 做了什么?
  1. pkt2->buf = av_buffer_ref(pkt->buf);(增加引用计数)
  2. pkt2->data = pkt->data;(同样指向)
  3. 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);
}
  • 执行情况:
  1. 在执行完pkt = av_packet_alloc();但是没有执行到ret = av_new_packet(pkt, MEM_ITEM_SIZE);之前,并没有数据,也没有 buffer ,pkt->buf == NULL ,所以不打印任何信息

  2. ret = av_new_packet(pkt, MEM_ITEM_SIZE);完之后,分配了一个 buffer,并将 pkt->bufpkt->datapkt->size 设置好

    内部:调用了 av_buffer_alloc(),返回 ref_count = 1,此时打印,会输出1

  3. 此时,执行到:

    pkt2 = av_packet_alloc();   // 必须先alloc
    av_packet_move_ref(pkt2, pkt); // av_packet_move_ref

pkt 所有权转移给 pkt2,pkt变为空包, 但是引用计数仍然是 1

  1. av_packet_ref(pkt, pkt2); pkt 再次引用 pkt2 所持有的 AVBufferRef,引用计数 +1 → ref_count = 2

  2. av_packet_ref(pkt, pkt2); ,再次引用同一个 buffer,引用计数 +1 → ref_count = 3

  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));
    }
  1. av_packet_unref(pkt);   // 将为2
    av_packet_unref(pkt);   // 做第二次是没有用的,仍为2
    

第二次 av_packet_unref(pkt)

  • 由于上一步已经把 pkt->buf == NULL,因此这一行无效,什么都不做。
  • 这意味着:
    • 引用计数仍为 2(即使调用了两次 unref,只成功执行了1次)
  • 背景拓展:多个 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

  1. 进行到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

  • 对于指针字段(如 databufside_data):

    • 指针值被复制了,但不分配新内存
    • pkt2->data == pkt->data(指向同一块数据);
    • pkt2->buf == pkt->buf(指向同一个 AVBufferRef);
    • pkt2->side_data == pkt->side_data(如果存在);
  • 这会带来什么问题?

    1. 引用计数没有增加
    • pkt2->buf 指向的是同一个 AVBufferRef
    • 但引用计数仍然是 1,这意味着两个结构体都以为自己是“唯一持有者”;
    • 如果两个都调用 av_packet_unref()双重释放 → 崩溃或内存破坏
    1. 浅拷贝带来的副作用
    • 改动 pkt2->data[0] = 123;pkt->data[0] 也变了;
    • 如果 pkt2 修改 side_data,也影响 pkt
    • 所以看起来是两份,其实是一份。
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐