【音视频】FFmpeg内存模型

打印 上一主题 下一主题

主题 1583|帖子 1583|积分 4749

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 的关联逻辑:
  1. #include <libavutil/buffer.h>
  2. #include <libavcodec/avcodec.h>
  3. int main() {
  4.     // 1. 创建 AVBuffer(分配 1024 字节内存)
  5.     AVBufferRef *buffer_ref = av_buffer_alloc(1024);
  6.     if (!buffer_ref) {
  7.         return -1;
  8.     }
  9.     AVBuffer *buffer = buffer_ref->buffer;  // 获取关联的 AVBuffer
  10.     uint8_t *data = buffer->data;           // AVBuffer 的数据指针
  11.     // 2. 创建 AVPacket,并关联 AVBufferRef
  12.     AVPacket *pkt = av_packet_alloc();
  13.     if (!pkt) {
  14.         av_buffer_unref(&buffer_ref);
  15.         return -1;
  16.     }
  17.     pkt->buf = av_buffer_ref(buffer_ref);  // AVPacket 引用 AVBufferRef
  18.     // 此时,AVPacket 的数据指针 pkt->data 等同于 buffer->data(即同一份数据)
  19.     // 3. 验证引用计数
  20.     printf("Initial refcount: %u\n", buffer->refcount);  // 输出 2(AVBufferRef 自身 + AVPacket 引用)
  21.     // 4. 释放资源
  22.     av_packet_free(&pkt);         // 减少 AVPacket 对 AVBufferRef 的引用
  23.     av_buffer_unref(&buffer_ref);  // 若引用计数归 0,AVBuffer 内存自动释放
  24.     return 0;
  25. }
复制代码
代码剖析


  • 创建 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 数组使用类似逻辑,比方:
    1. AVFrame *frame = av_frame_alloc();
    2. 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分配内存,引

用计数初始化为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() 测试代码

宏界说

  1. #define MEM_ITEM_SIZE (20*1024*102)
  2. #define AVPACKET_LOOP_COUNT 1000
复制代码
测试1

  1. void av_packet_test1()
  2. {
  3.     AVPacket *pkt = NULL;
  4.     int ret = 0;
  5.     pkt = av_packet_alloc();
  6.     ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  7. // 引用计数初始化为1
  8.     memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
  9.     av_packet_unref(pkt);       // 要不要调用
  10.     av_packet_free(&pkt);       // 如果不free将会发生内存泄漏,内部调用了 av_packet_unref
  11. }
复制代码
下面将对 av_packet_test1 函数的代码进行详细剖析:
代码功能概述

av_packet_test1 函数的重要功能是创建一个 AVPacket 对象,为其分配数据缓冲区,尝试向该缓冲区复制数据,之后释放 AVPacket 及其关联的数据缓冲区,以此制止内存走漏。
代码逐行分析

1. 变量声明

  1. AVPacket *pkt = NULL;
  2. int ret = 0;
复制代码


  • pkt:这是一个指向 AVPacket 布局体的指针,初始化为 NULL。AVPacket 是 FFmpeg 里用于存储压缩媒体数据(像视频帧、音频帧等)的布局体。
  • ret:用于存储函数调用的返回值,初始化为 0。
2. 分配 AVPacket 布局体

  1. pkt = av_packet_alloc();
复制代码


  • 调用 av_packet_alloc 函数,在堆上分配一个新的 AVPacket 布局体实例,而且把布局体的成员初始化为默认值。若分配成功,pkt 会指向新分配的 AVPacket;若失败,pkt 为 NULL。
3. 为 AVPacket 分配数据缓冲区

  1. ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  2. // 引用计数初始化为1
复制代码


  • av_new_packet 函数的作用是为 AVPacket 分配一个大小为 MEM_ITEM_SIZE 字节的数据缓冲区。
  • 若分配成功,返回值 ret 为 0,同时 AVPacket 关联的数据缓冲区的引用计数会初始化为 1。
  • 若分配失败,ret 会是一个负的错误码。
4. 复制数据到 AVPacket 的数据缓冲区

  1. 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 引用计数

  1. av_packet_unref(pkt);       // 要不要调用
复制代码


  • av_packet_unref 函数会减少 AVPacket 关联的数据缓冲区的引用计数。当引用计数降为 0 时,会释放现实的数据缓冲区内存,同时把 AVPacket 布局体的成员重置为默认值。
  • 在此处,调用 av_packet_unref 并非必需,因为 av_packet_free 函数内部会自动调用 av_packet_unref。不过,调用它也不会有问题,只是会多执行一次减少引用计数的操作。
6. 释放 AVPacket 布局体

  1. av_packet_free(&pkt);       // 如果不free将会发生内存泄漏,内部调用了 av_packet_unref
复制代码


  • av_packet_free 函数会释放 AVPacket 布局体所占用的内存。在释放之前,它会自动调用 av_packet_unref 处理 AVPacket 关联的数据缓冲区,确保数据缓冲区的引用计数被精确处理,制止内存走漏。
  • 该函数吸收一个指向 AVPacket 指针的指针作为参数,释放后会把传入的指针置为 NULL,防止出现悬空指针。
测试2

  1. void av_packet_test2(){    AVPacket *pkt = NULL;    int ret = 0;    pkt = av_packet_alloc();
  2.     ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  3.     memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
  4.     printf("size1 = %p",pkt->buf);    av_init_packet(pkt);
  5.         // 这个时间init就会导致内存无法释放    printf("size2 = %p",pkt->buf);    av_packet_free(&pkt);
  6. }
复制代码
代码功能概述

该函数的重要目的是创建一个 AVPacket 对象,为其分配数据缓冲区,向缓冲区复制数据,然后释放 AVPacket 及其关联的数据缓冲区。
代码逐行分析

1. 变量声明与 AVPacket 分配

  1. AVPacket *pkt = NULL;
  2. int ret = 0;
  3. pkt = av_packet_alloc();
复制代码


  • 声明一个指向 AVPacket 的指针 pkt 并初始化为 NULL,同时声明一个用于存储函数返回值的变量 ret。
  • 调用 av_packet_alloc 函数在堆上分配一个新的 AVPacket 布局体实例,并将其地点赋给 pkt。
2. 为 AVPacket 分配数据缓冲区

  1. ret = av_new_packet(pkt, MEM_ITEM_SIZE);
复制代码


  • av_new_packet 函数为 AVPacket 分配一个大小为 MEM_ITEM_SIZE 字节的数据缓冲区。
  • 若分配成功,pkt->data 指向新分配的缓冲区,同时数据缓冲区的引用计数初始化为 1。
3. 复制数据到 AVPacket 的数据缓冲区

  1. 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)

  1. 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

  1. av_packet_free(&pkt);
复制代码


  • av_packet_free 函数会先调用 av_packet_unref 来处理 AVPacket 关联的数据缓冲区,然后释放 AVPacket 布局体自己的内存。
  • 但由于 av_init_packet 已经将 pkt->buf 设置为 NULL,av_packet_unref 无法精确释放数据缓冲区,只释放了 AVPacket 布局体自己的内存。
测试3

  1. void av_packet_test3(){    AVPacket *pkt = NULL;    AVPacket *pkt2 = NULL;    int ret = 0;    pkt = av_packet_alloc();
  2.     ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  3.     memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
  4.     pkt2 = av_packet_alloc();   // 必须先alloc
  5.     av_packet_move_ref(pkt2, pkt);//内部实在也调用了av_init_packet    av_init_packet(pkt);
  6.     av_packet_free(&pkt);
  7.     av_packet_free(&pkt2);}
复制代码
代码逐行分析

1. 变量声明

  1. AVPacket *pkt = NULL;
  2. AVPacket *pkt2 = NULL;
  3. int ret = 0;
复制代码


  • 声明了两个指向 AVPacket 布局体的指针 pkt 和 pkt2,并初始化为 NULL。
  • 声明一个整型变量 ret,用于存储函数调用的返回值。
2. 分配并初始化第一个 AVPacket

  1. pkt = av_packet_alloc();
  2. ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  3. 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

  1. pkt2 = av_packet_alloc();   // 必须先alloc
复制代码
使用 av_packet_alloc() 分配一个新的 AVPacket 布局体,并将其地点赋给 pkt2。在进行数据引用转移之前,必须先为 pkt2 分配内存。
4. 转移数据引用

  1. av_packet_move_ref(pkt2, pkt); // 内部其实也调用了av_init_packet
复制代码
av_packet_move_ref(pkt2, pkt) 函数将 pkt 的数据引用转移到 pkt2 上。调用该函数后,pkt 不再拥有数据缓冲区的引用,其成员会被重置为默认值,而 pkt2 接管数据缓冲区的引用。
5. 再次调用 av_init_packet

  1. av_init_packet(pkt);
复制代码
这一步是多余且可能会带来问题的操作。因为 av_packet_move_ref 已经将 pkt 的成员重置为默认值,再次调用 av_init_packet(pkt) 并不会有额外的作用。而且如果 av_packet_move_ref 出现非常,这一步操作可能会进一步破坏 pkt 的状态。
6. 释放 AVPacket

  1. av_packet_free(&pkt);
  2. 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. 变量声明

  1. AVPacket *pkt = NULL;
  2. // av_packet_alloc()没有必要,因为av_packet_clone内部有调用 av_packet_alloc
  3. AVPacket *pkt2 = NULL;
  4. int ret = 0;
复制代码


  • pkt 和 pkt2 是指向 AVPacket 布局体的指针,初始化为 NULL。pkt 用于后续创建和操作一个 AVPacket 对象,pkt2 则用于克隆 pkt。
  • ret 是一个整型变量,用于存储函数调用的返回值,方便后续判断操作是否成功。
2. 创建并初始化第一个 AVPacket

  1. pkt = av_packet_alloc();
  2. ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  3. 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

  1. 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 的引用管理

  1. av_init_packet(pkt);
  2.   // 这里是故意去做init的操作,让这个函数出现内存走漏
复制代码


  • av_init_packet(pkt):该函数会将 pkt 的成员重置为默认值,其中包罗将 pkt->buf 置为 NULL。pkt->buf 是一个指向 AVBufferRef 的指针,用于管理数据缓冲区的引用计数。将其置为 NULL 会破坏 pkt 与数据缓冲区的引用关系,导致后续无法精确处理引用计数。
5. 释放 pkt

  1. av_packet_free(&pkt);
  2.    // 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

  1. av_packet_free(&pkt2); // 触发引用计数变为1,但因为不是0,所以buf不会被释放,导致内存泄漏
复制代码


  • av_packet_free(&pkt2):同样,该函数会先调用 av_packet_unref 来减少 pkt2 关联的数据缓冲区的引用计数,然后释放 pkt2 布局体自己的内存。由于 pkt 之前没有精确减少引用计数,此时调用 av_packet_unref 只会将数据缓冲区的引用计数减为 1,而不是 0。因此,数据缓冲区的内存不会被释放,从而导致内存走漏。
测试5

  1. void av_packet_test5(){    AVPacket *pkt = NULL;    AVPacket *pkt2 = NULL;    int ret = 0;    pkt = av_packet_alloc();
  2. //    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);
  3.     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);
  4.     pkt2 = av_packet_alloc();   // 必须先alloc
  5.     av_packet_move_ref(pkt2, pkt); // av_packet_move_ref//    av_init_packet(pkt);
  6.   //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);
  7.     av_packet_free(&pkt2);}
复制代码
代码功能概述

该函数重要用于测试 FFmpeg 中 AVPacket 的内存分配、数据操作、引用转移、引用计数管理以及释放等操作,同时通过打印引用计数来观察这些操作对引用计数的影响,以此展示引用计数管理不当可能导致的问题。
代码逐行分析

1. 变量声明

  1. AVPacket *pkt = NULL;
  2. AVPacket *pkt2 = NULL;
  3. int ret = 0;
复制代码
声明两个 AVPacket 指针 pkt 和 pkt2 并初始化为 NULL,同时声明一个整型变量 ret 用于存储函数调用的返回值。
2. 分配 pkt 并查抄引用计数

  1. pkt = av_packet_alloc();
  2. 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 分配数据缓冲区并查抄引用计数

  1. ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  2. 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 的数据缓冲区

  1. memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
复制代码
将 av_packet_test1 指向的数据复制到 pkt->data 所指向的缓冲区中,最多复制 MEM_ITEM_SIZE 个字节,遇到值为 1 的字符时停止复制。
5. 分配 pkt2 并转移引用

  1. pkt2 = av_packet_alloc();   
  2. 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 并查抄引用计数

  1. av_packet_ref(pkt, pkt2);
  2. av_packet_ref(pkt, pkt2);     
  3. if(pkt->buf)        
  4. {   
  5.     printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
  6.                av_buffer_get_ref_count(pkt->buf));
  7. }
  8. if(pkt2->buf)        
  9. {   
  10.     printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
  11.                av_buffer_get_ref_count(pkt2->buf));
  12. }
复制代码


  • 两次调用 av_packet_ref(pkt, pkt2) 使 pkt 和 pkt2 共享数据缓冲区,数据缓冲区的引用计数变为 3(初始 1 次 + 两次引用各加 1)。
  • 若 pkt->buf 和 pkt2->buf 不为 NULL,则分别打印它们的引用计数。
7. 两次调用 av_packet_unref(pkt) 并查抄 pkt->buf

  1. av_packet_unref(pkt);   
  2. av_packet_unref(pkt);   
  3. if(pkt->buf)
  4.     printf("pkt->buf没有被置NULL\n");
  5. else
  6.     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)

  1. if(pkt2->buf)        
  2. {   
  3.     printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
  4.                av_buffer_get_ref_count(pkt2->buf));
  5. }
  6. av_packet_unref(pkt2);
复制代码


  • 若 pkt2->buf 不为 NULL,则打印 pkt2 的引用计数,此时为 2。
  • 调用 av_packet_unref(pkt2) 使引用计数减 1 变为 1。
9. 释放 pkt 和 pkt2

  1. av_packet_free(&pkt);
  2. av_packet_free(&pkt2);
复制代码
使用 av_packet_free 分别释放 pkt 和 pkt2 所指向的 AVPacket 布局体。
存在的问题

代码存在内存走漏问题。由于第二次调用 av_packet_unref(pkt) 无效,且只调用了一次 av_packet_unref(pkt2),数据缓冲区的引用计数最终仍为 1,没有降为 0,导致数据缓冲区的内存无法被释放。
测试6

  1. void av_packet_test6(){    AVPacket *pkt = NULL;    AVPacket *pkt2 = NULL;    int ret = 0;    pkt = av_packet_alloc();
  2.     ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  3.     memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);
  4.     pkt2 = av_packet_alloc();   // 必须先alloc
  5.     *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);
  6.     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);
  7.     av_packet_free(&pkt2);}
复制代码
代码功能概述

av_packet_test6 函数重要演示了 FFmpeg 中 AVPacket 的内存分配、数据复制、引用计数管理以及释放等操作,同时通过打印引用计数来观察不同操作对引用计数的影响
代码逐行分析

1. 变量声明

  1. AVPacket *pkt = NULL;
  2. AVPacket *pkt2 = NULL;
  3. int ret = 0;
复制代码
声明了两个指向 AVPacket 布局体的指针 pkt 和 pkt2,并初始化为 NULL。同时声明了一个整型变量 ret,用于存储函数调用的返回值。
2. 分配并初始化 pkt

  1. pkt = av_packet_alloc();
  2. ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  3. 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 的内容

  1. pkt2 = av_packet_alloc();   // 必须先alloc
  2. *pkt2 = *pkt;   // 有点类似  pkt可以重新分配内存
复制代码


  • av_packet_alloc():分配一个新的 AVPacket 布局体,并将其地点赋给 pkt2。
  • *pkt2 = *pkt;:这行代码直接将 pkt 的内容复制到 pkt2 中,包罗 pkt 的数据指针 data 和 AVBufferRef 指针 buf。此时 pkt 和 pkt2 指向同一个数据缓冲区,且数据缓冲区的引用计数没有增加。
4. 打印 pkt 的引用计数

  1. if(pkt->buf)        // 打印referenc-counted,必须保证传入的是有效指针
  2. {   
  3.     printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
  4.                av_buffer_get_ref_count(pkt->buf));
  5. }
复制代码
如果 pkt->buf 不为 NULL,则打印 pkt 关联的数据缓冲区的引用计数,此时引用计数仍为 1。
5. 调用 av_init_packet(pkt)

  1. av_init_packet(pkt);
复制代码
av_init_packet(pkt) 会将 pkt 的成员重置为默认值,其中包罗将 pkt->buf 置为 NULL,这一步不影响引用计数
6. 打印 pkt2 的引用计数

  1. if(pkt2->buf)        // 打印referenc-counted,必须保证传入的是有效指针
  2. {   
  3.     printf("%s(%d) ref_count(pkt) = %d\n", __FUNCTION__, __LINE__,
  4.                av_buffer_get_ref_count(pkt2->buf));
  5. }
复制代码
如果 pkt2->buf 不为 NULL,则打印 pkt2 关联的数据缓冲区的引用计数,此时引用计数仍为 1。
7. 释放 pkt 和 pkt2

  1. av_packet_free(&pkt);
  2. 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

  1. void av_frame_test1()
  2. {
  3.     AVFrame *frame = NULL;
  4.     int ret = 0;
  5.     frame = av_frame_alloc();// 没有类似的AVPacket的av_new_packet的API
  6.     // 1024 *2 * (16/8) =
  7.     frame->nb_samples     = 1024;
  8.     frame->format         = AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16
  9.     frame->channel_layout = AV_CH_LAYOUT_STEREO;    //AV_CH_LAYOUT_MONO AV_CH_LAYOUT_STEREO
  10.     ret = av_frame_get_buffer(frame, 0);    // 根据格式分配内存
  11.     if(frame->buf && frame->buf[0])
  12.         printf("%s(%d) 1 frame->buf[0]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[0]->size);    //受frame->format等参数影响
  13.     if(frame->buf && frame->buf[1])
  14.         printf("%s(%d) 1 frame->buf[1]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[1]->size);    //受frame->format等参数影响
  15.     if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
  16.         printf("%s(%d) ref_count1(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
  17.     ret = av_frame_make_writable(frame);    // 当frame本身为空时不能make writable
  18.     printf("av_frame_make_writable ret = %d\n", ret);
  19.     if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
  20.         printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
  21.     av_frame_unref(frame);
  22.     if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
  23.         printf("%s(%d) ref_count3(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
  24.     av_frame_free(&frame);
  25. }
复制代码
1. 变量声明

  1. AVFrame *frame = NULL;
  2. int ret = 0;
复制代码
声明一个指向 AVFrame 布局体的指针 frame 并初始化为 NULL,同时声明一个整型变量 ret 用于存储函数调用的返回值。
2. 分配 AVFrame 布局体

  1. frame = av_frame_alloc();// 没有类似的AVPacket的av_new_packet的API
复制代码
调用 av_frame_alloc 函数在堆上分配一个新的 AVFrame 布局体,并将其地点赋给 frame。与 AVPacket 的 av_new_packet 不同,av_frame_alloc 仅分配 AVFrame 布局体自己,不分配现实的数据缓冲区。
3. 设置 AVFrame 的参数

  1. // 1024 *2 * (16/8) =
  2. frame->nb_samples     = 1024;
  3. frame->format         = AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S16P AV_SAMPLE_FMT_S16
  4. 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 分配数据缓冲区

  1. ret = av_frame_get_buffer(frame, 0);    // 根据格式分配内存
复制代码
调用 av_frame_get_buffer 函数根据 frame 中设置的参数(如 format、channel_layout 和 nb_samples)为 AVFrame 分配数据缓冲区。如果分配成功,frame->buf 数组将指向分配的缓冲区。
5. 打印缓冲区大小

  1. if(frame->buf && frame->buf[0])
  2.     printf("%s(%d) 1 frame->buf[0]->size = %d\n", __FUNCTION__, __LINE__, frame->buf[0]->size);    //受frame->format等参数影响
  3. if(frame->buf && frame->buf[1])
  4.     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. 打印初始引用计数

  1. if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
  2.     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 可写

  1. ret = av_frame_make_writable(frame);    // 当frame本身为空时不能make writable
  2. printf("av_frame_make_writable ret = %d\n", ret);
复制代码
调用 av_frame_make_writable 函数确保 frame 是可写的。如果 frame 已经是可写的,则直接返回;否则,会复制一份数据并使 frame 指向新的可写缓冲区,而且使引用计数减一。打印该函数的返回值,返回值为 0 体现成功。
8. 打印可写操作后的引用计数

  1. if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
  2.     printf("%s(%d) ref_count2(frame) = %d\n", __FUNCTION__, __LINE__, av_buffer_get_ref_count(frame->buf[0]));
复制代码
如果 frame->buf[0] 不为 NULL,则打印可写操作后的引用计数。如果发生了数据复制,引用计数可能会改变。
9. 排除 AVFrame 的引用

  1. av_frame_unref(frame);
复制代码
调用 av_frame_unref 函数排除 frame 对其数据缓冲区的引用,减少数据缓冲区的引用计数。如果引用计数降为 0,则释放数据缓冲区。
10. 打印排除引用后的引用计数

  1. if(frame->buf && frame->buf[0])        // 打印referenc-counted,必须保证传入的是有效指针
  2.     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 布局体

  1. av_frame_free(&frame);
复制代码
调用 av_frame_free 函数释放 frame 所指向的 AVFrame 布局体。
更多资料:https://github.com/0voice

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81429

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表