image.png

AVPacket和AVFrame内存模型

从现有的Packet拷贝到一个新Packet的时候,有两种情况:

  1. 两个Packet的buf引用的是同一个数据缓存空间,这时候就要注意数据缓存空间的释放问题;
  2. 两个Packet的buf是引用的不同的数据缓存空间,每个Packet都有数据缓存空间的copy;

第一种方式可以理解为引用计数,有多个对象对一段内存进行操作。

image.png

第二个就是两个对象有各自的数据域,不会互相影响。

image.png

数据域对象AVBuffer中是有引用计数相关的成员的,UML如下:

image.png

对于多个AVPacket共享同一个缓存空间,FFmpeg使用的引用计数的机制:
- 初始化引用计数为0,只有真正分配AVBuffer的时候,引用计数初始化为1;
- 当有新的Packet引用共享的缓存空间的时候,就将引用计数+1;
- 当释放了引用共享空间的Packet,就将引用计数-1;引用计数为0时,就释放了引用的缓存空间AVBuffer。

AVFrame也是使用相同的机制。

AVPacket常用API

image.png

AVFrame常用API

image.png

测试代码

#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <stdio.h>

#define MEM_ITEM_SIZE (1024*20*102)
#define AVPACKET_LOOP_COUNT 1000

void av_packet_test1() {
    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_packet_unref(pkt); // 不需要这一步,av_packet_free中会调用av_packet_unref
    av_packet_free(pkt);
}

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); // 只会初始化结构体,不会分配内存,将data=NULL,但是size!=0,不会被判断为空包
    av_packet_free(pkt); // 会释放data的内存,如果data没有被分配,则会导致内存泄漏
}

void av_packet_test3() {
    AVPacket* pktsrc = NULL;
    AVPacket* pktdst = NULL;
    int ret = 0;

    pktsrc = av_packet_alloc();
    ret = av_new_packet(pktsrc, MEM_ITEM_SIZE);
    memccpy(pktsrc->data, (void*)av_packet_test1, 1, MEM_ITEM_SIZE);\
    pktdst = av_packet_alloc();
    av_packet_move_ref(pktdst, pktsrc); // 执行完后pktsrc会变成空包,即data为NULL,size=0
    av_init_packet(pktsrc); // 没有必要执行这一步,因为av_packet_move_ref中会执行av_init_packet,
    av_packet_free(pktsrc); // 由于data=NULL size=0 所以判断是空包,只会释放结构体,不会释放data
    av_packet_free(pktdst);
}

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);
}

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);
}

int main() {

    // av_packet_test1();
    // av_packet_test2();
    // av_packet_test3();
    av_packet_test5();

    return 0;
}