【音视频】FFmpeg内存模型

FFmpeg内存模型

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

  • 两个Packet的buf引用的是同一数据缓存空间,这时候要注意数据缓存空间的释放问题;
  • 两个Packet的buf引用不同的数据缓存空间,每个Packet都有数据缓存空间的copy;
    在这里插入图片描述

在这里插入图片描述

关系介绍

  • AVBuffer:核心数据容器,存储实际数据(data),通过 refcount 实现引用计数,free() 用于定义内存释放逻辑。
  • AVBufferRef:作为 AVBuffer 的引用,允许不同组件(如 AVPacket、AVFrame)共享同一份数据。它内部的 buffer 指向 AVBuffer,datasize 本质是对 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;
}

代码解析

  1. 创建 AVBuffer:通过 av_buffer_alloc 分配内存,返回 AVBufferRef。此时,AVBufferRef 的 buffer 指向内部的 AVBuffer,data 指向实际内存。

  2. 关联 AVPacket:使用 av_buffer_ref 让 AVPacket 的 buf 引用 AVBufferRef。此时,AVPacket 的 data 与 AVBuffer 的 data 指向同一块内存。

  3. 引用计数:每次 av_buffer_ref 会增加引用计数(refcount),av_buffer_unref 减少计数。当计数为 0 时,AVBuffer 自动释放内存,避免手动管理内存的繁琐与风险。

  4. 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 结构体的指针,初始化为 NULLAVPacket 是 FFmpeg 里用于存储压缩媒体数据(像视频帧、音频帧等)的结构体。
  • ret:用于存储函数调用的返回值,初始化为 0。
2. 分配 AVPacket 结构体
pkt = av_packet_alloc();
  • 调用 av_packet_alloc 函数,在堆上分配一个新的 AVPacket 结构体实例,并且把结构体的成员初始化为默认值。若分配成功,pkt 会指向新分配的 AVPacket;若失败,pktNULL
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 设置为 NULLav_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 结构体的指针 pktpkt2,并初始化为 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;
  • pktpkt2 是指向 AVPacket 结构体的指针,初始化为 NULLpkt 用于后续创建和操作一个 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_refpkt 的数据引用复制到新的 AVPacket 上。因此,调用该函数后,pktpkt2 共享同一份数据缓冲区,数据缓冲区的引用计数变为 2。

4. 故意破坏 pkt 的引用管理

av_init_packet(pkt);  // 这里是故意去做init的操作,让这个函数出现内存泄漏
  • av_init_packet(pkt):该函数会将 pkt 的成员重置为默认值,其中包括将 pkt->buf 置为 NULLpkt->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 置为 NULLav_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 指针 pktpkt2 并初始化为 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->bufNULL,所以不会打印引用计数。
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. 多次引用 pktpkt2 并检查引用计数
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) 使 pktpkt2 共享数据缓冲区,数据缓冲区的引用计数变为 3(初始 1 次 + 两次引用各加 1)。
  • pkt->bufpkt2->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. 释放 pktpkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);

使用 av_packet_free 分别释放 pktpkt2 所指向的 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 结构体的指针 pktpkt2,并初始化为 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 的数据指针 dataAVBufferRef 指针 buf。此时 pktpkt2 指向同一个数据缓冲区,且数据缓冲区的引用计数没有增加。
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. 释放 pktpkt2
av_packet_free(&pkt);
av_packet_free(&pkt2);
  • av_packet_free(&pkt):由于之前 av_init_packet(pkt) 已经将 pkt->buf 置为 NULLav_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。与 AVPacketav_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 中设置的参数(如 formatchannel_layoutnb_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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/902482.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.2软考系统架构设计师:系统架构的定义与作用 - 练习题附答案及超详细解析

系统架构定义与作用综合知识单选题 题目覆盖核心概念、发展历程、设计原则、评估标准及易混淆点&#xff0c;附答案解析&#xff1a; 1. 系统架构的标准定义源自于以下哪个标准&#xff1f; A. ISO/IEC 9126 B. IEEE 1471-2000 C. TOGAF 9.2 D. ITIL v4 答案&#xff1a;B 简…

go语言对http协议的支持

http&#xff1a;无状态协议&#xff0c;是互联网中使用http使用http实现计算机和计算机之间的请求和响应 使用纯文本方式发送和接受协议数据&#xff0c;不需要借助专门工具进行分析就知道协议中的数据 服务器端的几个概念 Request&#xff1a;用户请求的信息&#xff0c;用…

iscsi服务端安装及配置

1. 安装targetcli软件包 yum install -y targetcli 2. 启动target服务 systemctl start target systemctl enable target 3. 配置防火墙 firewall-cmd --add-port"3260/tcp" 3. 准备一个物理分区&#xff08;或者逻辑分区&#xff09;…

解决 MongoDB 查询中的 `InvalidMongoDbApiUsageException` 错误

您在使用 Spring Data MongoDB 时遇到了 InvalidMongoDbApiUsageException 异常&#xff0c;错误信息如下&#xff1a; “由于 com.mongodb.BasicDocument 的限制&#xff0c;您无法添加第二个 ‘null’ 条件。查询已经包含 ‘{ “KaTeX parse error: Expected }, got EOF at e…

一个关于相对速度的假想的故事-4

回到公式&#xff0c; 正写速度叠加和倒写速度叠加的倒写相等&#xff0c;这就是这个表达式所要表达的意思。但倒写叠加用的是减法&#xff0c;而正写叠加用的是加法。当然是这样&#xff0c;因为正写叠加要的是单位时间上完成更远的距离&#xff0c;而倒写叠加说的是单位距离需…

重学React(一):描述UI

背景&#xff1a;React现在已经更新到19了&#xff0c;文档地址也做了全面的更新&#xff0c;上一次系统性的学习还是在16-17的大版本更新。所以&#xff0c;现在就开始重新学习吧&#xff5e; 学习内容&#xff1a; React官网教程&#xff1a;https://zh-hans.react.dev/lea…

AI大模型:(二)2.3 预训练自己的模型

目录 1.预训练原理 2.预训练范式 1.未标注数据 2.标注数据 3.有正确答案、也有错误答案 3.手撕transform模型 3.1.transform模型代码 3.2.训练数据集 3.3.预训练 3.4.推理 4.如何选择模型

gradle可用的下载地址(免费)

这几天接手一个老项目&#xff0c;想找gradle老版本的&#xff0c;但一搜&#xff0c;虽然在CSDN上搜索出来一堆&#xff0c;但都是收费&#xff0c;有些甚至要几十积分(吃相有点难看了)。 我找了一个能访问的地址&#xff0c;特地分享出来&#xff0c;有需要的自取&#xff01…

vue3新增特性

一、Vue 3 新增特性 1. Composition API 概述: Composition API 提供了一种更灵活和强大的方式来组织和复用逻辑。适用于复杂组件和逻辑复用场景。主要功能: setup 函数:组件的入口点,用于定义响应式数据、方法、生命周期钩子等。响应式 API:ref 和 reactive 提供更细粒度…

前端性能优化全攻略:JavaScript 优化、DOM 操作、内存管理、资源压缩与合并、构建工具及性能监控

1 为什么需要性能优化&#xff1f; 1.1 性能优化的核心价值&#xff1a;用户体验与业务指标 性能优化不仅是技术层面的追求&#xff0c;更是直接影响用户体验和业务成败的关键因素。 用户体验&#xff08;UX&#xff09;&#xff1a; 响应速度&#xff1a;用户期望页面加载时…

【Unity笔记】Unity + OpenXR项目无法启动SteamVR的排查与解决全指南

图片为AI生成 一、前言 随着Unity在XR领域全面转向OpenXR标准&#xff0c;越来越多的开发者选择使用OpenXR来构建跨平台的VR应用。但在项目实际部署中发现&#xff1a;打包成的EXE程序无法正常启动SteamVR&#xff0c;或者SteamVR未能识别到该应用。本文将以“Unity OpenXR …

Curl用法解析

Curl 用法解析 简介 Curl 是一个强大的命令行工具&#xff0c;主要用于从服务器发送 HTTP 请求并获取数据。它广泛应用于调试 RESTful API、文件上传下载、模拟用户交互等多种场景。下面是一些基本用法及常见参数的分析&#xff1a; 基础用法 curl [options] [URL]其中最基…

C语言教程(十一):C 语言中四种主要作用域及作用域嵌套遮蔽

一、引言 在 C 语言里&#xff0c;作用域指的是程序中变量、函数、类型等标识符能够被使用的范围。C 语言里有四种主要的作用域&#xff1a;块作用域、函数作用域、文件作用域和原型作用域&#xff0c;下面为你展开介绍&#xff1a; 二、块作用域 定义&#xff1a;块作用域是 C…

初次尝试Ghidra

最近看京东读书上有本书叫《Ghidra权威指南》&#xff0c;竟然是美国国家安全局出品的逆向工具&#xff0c;我真是孤陋寡闻&#xff0c;第一次听说。赶紧试试。 Release Ghidra 11.3.2 NationalSecurityAgency/ghidra GitHub 最新版本竟然是上周发布的&#xff0c;看来很活…

乐视系列玩机---乐视2 x620 x628等系列线刷救砖以及刷写第三方twrp 卡刷第三方固件步骤解析

乐视2 x620 x628 x626等,搭载了Helio X20处理器,mtk6797芯片。 通过博文了解💝💝💝 1💝💝💝-----详细解析乐视2 x620系列黑砖线刷救砖的步骤 2💝💝💝----官方两种更新卡刷步骤以及刷写第三方twrp过程与资源 3💝💝💝----乐视2 mtk系列机型救砖 刷…

web原生API AbortController网络请求取消方法使用介绍:防止按钮重复点击提交得最佳方案

在前端开发中&#xff0c;取消网络请求是一个常见的需求&#xff0c;尤其是在用户频繁操作或需要中断长时间请求的场景下。 AbortController 主要用于 ​优雅地管理和取消异步操作&#xff1a; 浏览器原生 API 一、代码解析 1. ​创建 AbortController 实例 const controlle…

2025智能驾驶趋势评估

以下是对2025年智能驾驶趋势的评估&#xff1a; 技术发展 • 自动驾驶级别提升&#xff1a;2025年有望成为L3级自动驾驶的商用元年。L3级自动驾驶技术开始从高端车型向20万元以下价格带下沉&#xff0c;部分车企如江淮和华为合作的尊界S800、小鹏汽车等都在积极推进L3级自动驾驶…

Spring MVC DispatcherServlet 的作用是什么? 它在整个请求处理流程中扮演了什么角色?为什么它是核心?

DispatcherServlet 是 Spring MVC 框架的绝对核心和灵魂。它扮演着前端控制器&#xff08;Front Controller&#xff09;的角色&#xff0c;是所有进入 Spring MVC 应用程序的 HTTP 请求的统一入口点和中央调度枢纽。 一、 DispatcherServlet 的核心作用和职责&#xff1a; 请…

Linux 内核中 cgroup 子系统 cpuset 是什么?

cpuset 是 Linux 内核中 cgroup&#xff08;控制组&#xff09; 的一个子系统&#xff0c;用于将一组进程&#xff08;或任务&#xff09;绑定到特定的 CPU 核心和 内存节点&#xff08;NUMA 节点&#xff09;上运行。它通过限制进程的 CPU 和内存资源的使用范围&#xff0c;优…

【MATLAB第115期】基于MATLAB的多元时间序列的ARIMAX的预测模型

【MATLAB第115期】基于MATLAB的多元时间序列的ARIMAX的预测模型 ‌一、简介 ARIMAX‌&#xff08;Autoregressive Integrated Moving Average with eXogenous inputs&#xff09;模型是一种结合自回归&#xff08;AR&#xff09;、差分&#xff08;I&#xff09;、移动平均&a…