【音视频】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 的关联逻辑:

c 复制代码
#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;
}

代码解析

  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 数组使用类似逻辑,例如:

    c 复制代码
    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()

测试代码

宏定义

cpp 复制代码
#define MEM_ITEM_SIZE (20*1024*102)
#define AVPACKET_LOOP_COUNT 1000

测试1

cpp 复制代码
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. 变量声明
c 复制代码
AVPacket *pkt = NULL;
int ret = 0;
  • pkt:这是一个指向 AVPacket 结构体的指针,初始化为 NULLAVPacket 是 FFmpeg 里用于存储压缩媒体数据(像视频帧、音频帧等)的结构体。
  • ret:用于存储函数调用的返回值,初始化为 0。
2. 分配 AVPacket 结构体
c 复制代码
pkt = av_packet_alloc();
  • 调用 av_packet_alloc 函数,在堆上分配一个新的 AVPacket 结构体实例,并且把结构体的成员初始化为默认值。若分配成功,pkt 会指向新分配的 AVPacket;若失败,pktNULL
3. 为 AVPacket 分配数据缓冲区
c 复制代码
ret = av_new_packet(pkt, MEM_ITEM_SIZE); // 引用计数初始化为1
  • av_new_packet 函数的作用是为 AVPacket 分配一个大小为 MEM_ITEM_SIZE 字节的数据缓冲区。
  • 若分配成功,返回值 ret 为 0,同时 AVPacket 关联的数据缓冲区的引用计数会初始化为 1。
  • 若分配失败,ret 会是一个负的错误码。
4. 复制数据到 AVPacket 的数据缓冲区
c 复制代码
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 引用计数
c 复制代码
av_packet_unref(pkt);       // 要不要调用
  • av_packet_unref 函数会减少 AVPacket 关联的数据缓冲区的引用计数。当引用计数降为 0 时,会释放实际的数据缓冲区内存,同时把 AVPacket 结构体的成员重置为默认值。
  • 在此处,调用 av_packet_unref 并非必需,因为 av_packet_free 函数内部会自动调用 av_packet_unref。不过,调用它也不会有问题,只是会多执行一次减少引用计数的操作。
6. 释放 AVPacket 结构体
c 复制代码
av_packet_free(&pkt);       // 如果不free将会发生内存泄漏,内部调用了 av_packet_unref
  • av_packet_free 函数会释放 AVPacket 结构体所占用的内存。在释放之前,它会自动调用 av_packet_unref 处理 AVPacket 关联的数据缓冲区,确保数据缓冲区的引用计数被正确处理,避免内存泄漏。
  • 该函数接收一个指向 AVPacket 指针的指针作为参数,释放后会把传入的指针置为 NULL,防止出现悬空指针。

测试2

cpp 复制代码
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 分配
c 复制代码
AVPacket *pkt = NULL;
int ret = 0;

pkt = av_packet_alloc();
  • 声明一个指向 AVPacket 的指针 pkt 并初始化为 NULL,同时声明一个用于存储函数返回值的变量 ret
  • 调用 av_packet_alloc 函数在堆上分配一个新的 AVPacket 结构体实例,并将其地址赋给 pkt
2. 为 AVPacket 分配数据缓冲区
c 复制代码
ret = av_new_packet(pkt, MEM_ITEM_SIZE);
  • av_new_packet 函数为 AVPacket 分配一个大小为 MEM_ITEM_SIZE 字节的数据缓冲区。
  • 若分配成功,pkt->data 指向新分配的缓冲区,同时数据缓冲区的引用计数初始化为 1。
3. 复制数据到 AVPacket 的数据缓冲区
c 复制代码
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)
c 复制代码
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
c 复制代码
av_packet_free(&pkt);
  • av_packet_free 函数会先调用 av_packet_unref 来处理 AVPacket 关联的数据缓冲区,然后释放 AVPacket 结构体本身的内存。
  • 但由于 av_init_packet 已经将 pkt->buf 设置为 NULLav_packet_unref 无法正确释放数据缓冲区,只释放了 AVPacket 结构体本身的内存。

测试3

cpp 复制代码
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. 变量声明
c 复制代码
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;
  • 声明了两个指向 AVPacket 结构体的指针 pktpkt2,并初始化为 NULL
  • 声明一个整型变量 ret,用于存储函数调用的返回值。
2. 分配并初始化第一个 AVPacket
c 复制代码
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
c 复制代码
pkt2 = av_packet_alloc();   // 必须先alloc

使用 av_packet_alloc() 分配一个新的 AVPacket 结构体,并将其地址赋给 pkt2。在进行数据引用转移之前,必须先为 pkt2 分配内存。

4. 转移数据引用
c 复制代码
av_packet_move_ref(pkt2, pkt); // 内部其实也调用了av_init_packet

av_packet_move_ref(pkt2, pkt) 函数将 pkt 的数据引用转移到 pkt2 上。调用该函数后,pkt 不再拥有数据缓冲区的引用,其成员会被重置为默认值,而 pkt2 接管数据缓冲区的引用。

5. 再次调用 av_init_packet
c 复制代码
av_init_packet(pkt);

这一步是多余且可能会带来问题的操作。因为 av_packet_move_ref 已经将 pkt 的成员重置为默认值,再次调用 av_init_packet(pkt) 并不会有额外的作用。并且如果 av_packet_move_ref 出现异常,这一步操作可能会进一步破坏 pkt 的状态。

6. 释放 AVPacket
c 复制代码
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. 变量声明

c 复制代码
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

c 复制代码
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

c 复制代码
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 的引用管理

c 复制代码
av_init_packet(pkt);  // 这里是故意去做init的操作,让这个函数出现内存泄漏
  • av_init_packet(pkt):该函数会将 pkt 的成员重置为默认值,其中包括将 pkt->buf 置为 NULLpkt->buf 是一个指向 AVBufferRef 的指针,用于管理数据缓冲区的引用计数。将其置为 NULL 会破坏 pkt 与数据缓冲区的引用关系,导致后续无法正确处理引用计数。

5. 释放 pkt

c 复制代码
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

c 复制代码
av_packet_free(&pkt2); // 触发引用计数变为1,但因为不是0,所以buf不会被释放,导致内存泄漏
  • av_packet_free(&pkt2):同样,该函数会先调用 av_packet_unref 来减少 pkt2 关联的数据缓冲区的引用计数,然后释放 pkt2 结构体本身的内存。由于 pkt 之前没有正确减少引用计数,此时调用 av_packet_unref 只会将数据缓冲区的引用计数减为 1,而不是 0。因此,数据缓冲区的内存不会被释放,从而导致内存泄漏。

测试5

c 复制代码
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. 变量声明
c 复制代码
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;

声明两个 AVPacket 指针 pktpkt2 并初始化为 NULL,同时声明一个整型变量 ret 用于存储函数调用的返回值。

2. 分配 pkt 并检查引用计数
c 复制代码
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 分配数据缓冲区并检查引用计数
c 复制代码
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 的数据缓冲区
c 复制代码
memccpy(pkt->data, (void *)&av_packet_test1, 1, MEM_ITEM_SIZE);

av_packet_test1 指向的数据复制到 pkt->data 所指向的缓冲区中,最多复制 MEM_ITEM_SIZE 个字节,遇到值为 1 的字符时停止复制。

5. 分配 pkt2 并转移引用
c 复制代码
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 并检查引用计数
c 复制代码
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
c 复制代码
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)
c 复制代码
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
c 复制代码
av_packet_free(&pkt);
av_packet_free(&pkt2);

使用 av_packet_free 分别释放 pktpkt2 所指向的 AVPacket 结构体。

存在的问题

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

测试6

c 复制代码
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. 变量声明
c 复制代码
AVPacket *pkt = NULL;
AVPacket *pkt2 = NULL;
int ret = 0;

声明了两个指向 AVPacket 结构体的指针 pktpkt2,并初始化为 NULL。同时声明了一个整型变量 ret,用于存储函数调用的返回值。

2. 分配并初始化 pkt
c 复制代码
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 的内容
c 复制代码
pkt2 = av_packet_alloc();   // 必须先alloc
*pkt2 = *pkt;   // 有点类似  pkt可以重新分配内存
  • av_packet_alloc():分配一个新的 AVPacket 结构体,并将其地址赋给 pkt2
  • *pkt2 = *pkt;:这行代码直接将 pkt 的内容复制到 pkt2 中,包括 pkt 的数据指针 dataAVBufferRef 指针 buf。此时 pktpkt2 指向同一个数据缓冲区,且数据缓冲区的引用计数没有增加。
4. 打印 pkt 的引用计数
c 复制代码
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)
c 复制代码
av_init_packet(pkt);

av_init_packet(pkt) 会将 pkt 的成员重置为默认值,其中包括将 pkt->buf 置为 NULL,这一步不影响引用计数

6. 打印 pkt2 的引用计数
c 复制代码
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
c 复制代码
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

c 复制代码
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[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 writable
    printf("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. 变量声明

c 复制代码
AVFrame *frame = NULL;
int ret = 0;

声明一个指向 AVFrame 结构体的指针 frame 并初始化为 NULL,同时声明一个整型变量 ret 用于存储函数调用的返回值。

2. 分配 AVFrame 结构体

c 复制代码
frame = av_frame_alloc();// 没有类似的AVPacket的av_new_packet的API

调用 av_frame_alloc 函数在堆上分配一个新的 AVFrame 结构体,并将其地址赋给 frame。与 AVPacketav_new_packet 不同,av_frame_alloc 仅分配 AVFrame 结构体本身,不分配实际的数据缓冲区。

3. 设置 AVFrame 的参数

c 复制代码
// 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 分配数据缓冲区

c 复制代码
ret = av_frame_get_buffer(frame, 0);    // 根据格式分配内存

调用 av_frame_get_buffer 函数根据 frame 中设置的参数(如 formatchannel_layoutnb_samples)为 AVFrame 分配数据缓冲区。如果分配成功,frame->buf 数组将指向分配的缓冲区。

5. 打印缓冲区大小

c 复制代码
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. 打印初始引用计数

c 复制代码
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 可写

c 复制代码
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. 打印可写操作后的引用计数

c 复制代码
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 的引用

c 复制代码
av_frame_unref(frame);

调用 av_frame_unref 函数解除 frame 对其数据缓冲区的引用,减少数据缓冲区的引用计数。如果引用计数降为 0,则释放数据缓冲区。

10. 打印解除引用后的引用计数

c 复制代码
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 结构体

c 复制代码
av_frame_free(&frame);

调用 av_frame_free 函数释放 frame 所指向的 AVFrame 结构体。

更多资料: https://github.com/0voice

相关推荐
hjjdebug7 小时前
全面介绍AVFilter 的添加和使用
ffmpeg·avfilter
邪恶的贝利亚8 小时前
基于 FFmpeg 的音视频处理基础原理与实验探究
ffmpeg·音视频
Antonio91513 小时前
【音视频】AAC-ADTS分析
ffmpeg·音视频·aac
科技小E13 小时前
EasyRTC音视频实时通话:打造高清低延迟的远程会议新生态
网络·音视频
这被禁忌的游戏13 小时前
网页下载的m3u8格式文件使用FFmpeg转为MP4
ffmpeg
hunandede14 小时前
ffmpeg 硬解码相关知识
ffmpeg
StudyWinter21 小时前
【FFmpeg从入门到精通】第四章-FFmpeg转码
ffmpeg·音视频
weixin_424381001 天前
下载油管视频 - yt-dlp
音视频
EQ-雪梨蛋花汤1 天前
【Unity笔记】Unity音视频播放监听器封装笔记:VideoPlayer + AudioSource事件触发与编辑器扩展
笔记·unity·音视频