ffmpeg源码分析(六)内存管理

系列文章目录

FFmpeg源码解析系列(一)目录和编译
FFmpeg源码解析系列(二)主要结构体
ffmpeg源码解析系列(四)结构体之AVIOContext 、URLContext、URLProtocol
ffmpeg源码解析系列(五)结构体之AVCodecContext

这是毕业一年跳槽时被面过的一道题,他问ffmpeg的内存管理是怎样的。当时支支吾吾没有回答出来。
如果再让我回到过去,我会和他说莫欺少年穷~

一、ffmpeg 内存管理相关api

1.1. av_mallocav_free

FFmpeg 提供了自定义的内存分配函数 av_malloc 和释放函数 av_free,它们封装了系统的内存分配接口,并提供了一些额外的内存检查功能。

  • av_malloc(size_t size): 用于分配指定大小的内存块。如果分配失败,会返回 NULL

  • av_free(void *ptr): 释放由 av_malloc 分配的内存块。

使用示例,

void *buffer = av_malloc(1024);
if (!buffer) {
    fprintf(stderr, "Memory allocation failed\n");
    return;
}
// 使用 buffer
av_free(buffer);

1.2. AVBufferAVBufferRef

AVBuffer 是 FFmpeg 中引用计数的核心结构,用于管理数据缓冲区的生命周期。AVBufferRefAVBuffer 的引用,使用引用计数机制来确保内存被正确释放。

主要API函数:

  • av_buffer_alloc(size_t size): 分配一个指定大小的缓冲区。

  • AVBufferRef *av_buffer_create(uint8_t *data, size_t size, void (*free)(void *opaque, uint8_t *data),void *opaque, int flags); 已申请的数据使用AVBufferRef来管理

  • av_buffer_ref(AVBufferRef *buf): 增加缓冲区的引用计数。

  • av_buffer_unref(AVBufferRef buf): 释放一个缓冲区的引用,当引用计数为0时,释放缓冲区。

以下是一个简单例子

#include <libavutil/buffer.h>
#include <stdio.h>

// 自定义释放函数
void custom_free(void *opaque, uint8_t *data) {
    printf("Freeing buffer: %p\n", data);
    // 如果有其他的释放逻辑,比如释放 opaque 指针,需要在这里进行
    av_free(data);
}

int main() {
    // 1. 使用 av_buffer_alloc 分配一个指定大小的缓冲区
    size_t size = 1024;  // 缓冲区大小
    AVBufferRef *buf1 = av_buffer_alloc(size);
    if (!buf1) {
        fprintf(stderr, "Failed to allocate buffer\n");
        return -1;
    }
    printf("Buffer allocated with size: %zu\n", size);

    // 2. 使用 av_buffer_create 管理已申请的缓冲区
    uint8_t *data = av_malloc(size);  // 手动分配数据
    if (!data) {
        fprintf(stderr, "Failed to manually allocate data\n");
        av_buffer_unref(&buf1);
        return -1;
    }
    
    // 使用自定义释放函数来管理 data
    AVBufferRef *buf2 = av_buffer_create(data, size, custom_free, NULL, 0);
    if (!buf2) {
        fprintf(stderr, "Failed to create buffer with custom free\n");
        av_free(data);  // 如果失败需要手动释放 data
        av_buffer_unref(&buf1);
        return -1;
    }
    printf("Buffer created with custom free function\n");

    // 3. 使用 av_buffer_ref 增加缓冲区的引用计数
    AVBufferRef *buf_ref = av_buffer_ref(buf2);
    if (!buf_ref) {
        fprintf(stderr, "Failed to reference buffer\n");
        av_buffer_unref(&buf2);
        av_buffer_unref(&buf1);
        return -1;
    }
    printf("Buffer reference count increased\n");

    // 4. 释放缓冲区的引用,引用计数减到 0 时,缓冲区被释放
    av_buffer_unref(&buf_ref);
    printf("Buffer reference count decreased\n");

    // 释放原始缓冲区
    av_buffer_unref(&buf2);
    av_buffer_unref(&buf1);
    printf("Buffers released\n");

    return 0;
}

1.3 AVPacket 和 AVFrame

AVPacketAVFrame 是 FFmpeg 中用于处理音视频数据的核心结构。AVPacket 通常用于存储编码的数据包(例如压缩的音视频数据),而 AVFrame 用于存储解码后的原始音视频数据。

下面是一些常用的内存管理相关 API 及其示例,包括如何分配、引用计数管理和释放 AVPacketAVFrame

  • av_packet_allocav_frame_alloc:用于分配 AVPacketAVFrame 结构。

  • av_new_packet:为 AVPacket 分配指定大小的数据缓冲区。

  • av_frame_get_buffer:为 AVFrame 分配缓冲区,用于存储图像或音频数据。

  • av_packet_refav_frame_ref:增加引用计数,使多个指针可以安全地共享相同的数据。

  • av_packet_unrefav_frame_unref:减少引用计数,当引用计数为 0 时,释放数据缓冲区。

  • av_packet_freeav_frame_free:释放 AVPacketAVFrame 结构本身。

以下是AVPacket的一个简单例子

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

int main() {
    // 1. 分配一个 AVPacket
    AVPacket *pkt = av_packet_alloc();
    if (!pkt) {
        fprintf(stderr, "Failed to allocate AVPacket\n");
        return -1;
    }
    printf("AVPacket allocated\n");

    // 2. 为 AVPacket 分配数据
    int size = 1024;
    if (av_new_packet(pkt, size) < 0) {
        fprintf(stderr, "Failed to allocate data for AVPacket\n");
        av_packet_free(&pkt);
        return -1;
    }
    printf("AVPacket data allocated with size: %d\n", size);

    // 模拟填充数据
    for (int i = 0; i < size; i++) {
        pkt->data[i] = i % 256;
    }

    // 3. 增加引用计数,pkt_ref 和pkt共享一份数据
    AVPacket *pkt_ref = av_packet_alloc();
    if (!pkt_ref || av_packet_ref(pkt_ref, pkt) < 0) {
        fprintf(stderr, "Failed to reference AVPacket\n");
        av_packet_free(&pkt);
        av_packet_free(&pkt_ref);
        return -1;
    }
    printf("AVPacket reference count increased\n");

    // 4. 释放引用
    av_packet_unref(pkt_ref);
    printf("AVPacket reference count decreased\n");

    // 释放原始数据
    av_packet_unref(pkt);
    printf("AVPacket released\n");

    // 释放 AVPacket 结构本身,
    av_packet_free(&pkt);
    av_packet_free(&pkt_ref);
    printf("AVPacket structures freed\n");

    return 0;
}

以下是AVFrame的一个简单例子

#include <libavutil/frame.h>
#include <stdio.h>

int main() {
    // 1. 分配一个 AVFrame
    AVFrame *frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Failed to allocate AVFrame\n");
        return -1;
    }
    printf("AVFrame allocated\n");

    // 假设要处理 YUV420P 格式的图像
    frame->format = AV_PIX_FMT_YUV420P;
    frame->width = 640;
    frame->height = 480;

    // 2. 为 AVFrame 分配缓冲区
    if (av_frame_get_buffer(frame, 32) < 0) {  // 32 表示对齐要求
        fprintf(stderr, "Failed to allocate buffer for AVFrame\n");
        av_frame_free(&frame);
        return -1;
    }
    printf("AVFrame buffer allocated\n");

    // 3. 增加引用计数
    AVFrame *frame_ref = av_frame_alloc();
    if (!frame_ref || av_frame_ref(frame_ref, frame) < 0) {
        fprintf(stderr, "Failed to reference AVFrame\n");
        av_frame_free(&frame);
        av_frame_free(&frame_ref);
        return -1;
    }
    printf("AVFrame reference count increased\n");

    // 4. 释放引用
    av_frame_unref(frame_ref);
    printf("AVFrame reference count decreased\n");

    // 释放原始帧
    av_frame_unref(frame);
    printf("AVFrame released\n");

    // 释放 AVFrame 结构
    av_frame_free(&frame);
    av_frame_free(&frame_ref);
    printf("AVFrame structures freed\n");

    return 0;
}

二、av_mallocav_free

2.1 头文件

libavutil/mem.h

2.2 av_malloc

2.2.1 功能

av_malloc 分配指定大小的内存,并根据需要对齐内存。它首先检查分配的大小是否超过了最大允许的分配大小(max_alloc_size),然后根据平台可用的内存分配方法选择适当的对齐策略。

2.2.2 源码解析
void *av_malloc(size_t size)
{
    void *ptr = NULL;
    //检查请求的内存大小 size 是否超过了最大允许分配大小(max_alloc_size)。使用了 C11 标准库中的 atomic_load_explicit 函数,以确保读取是线程安全的。如果 size 超过 max_alloc_size,则函数返回 NULL。
    if (size > atomic_load_explicit(&max_alloc_size, memory_order_relaxed))
        return NULL;
    //根据不同平台选择不同的内存对齐方式,如果没有使用malloc
    ...
      
    //如果 ptr 为 NULL 且 size 为 0,这段代码将尝试分配 1 字节的内存。这是为了处理某些系统上 malloc(0) 返回 NULL 的情况。
    if(!ptr && !size) {
        size = 1;
        ptr= av_malloc(1);
    }
  //在编译时,如果启用了内存填充选项(CONFIG_MEMORY_POISONING),这段代码会将分配的内存全部填充为特定的值(FF_MEMORY_POISON)。这通常用于调试,以便更容易检测未初始化或已释放的内存使用问题。
#if CONFIG_MEMORY_POISONING
    if (ptr)
        memset(ptr, FF_MEMORY_POISON, size);
#endif
    return ptr;
}

2.3 av_free

2.3.1 功能

释放内存

2.3.2 源码解析
//正常的free函数,有内存对齐的malloc的情况需要特殊处理
void av_free(void *ptr)
{
#if HAVE_ALIGNED_MALLOC
    _aligned_free(ptr);
#else
    free(ptr);
#endif
}

//对 av_free 的行为进行了封装,加入了一些额外的操作来管理内存的释放和指针的更新。这个函数特别适用于那些需要在释放内存的同时将指针设为 NULL 的场景。注意这里要传入二级指针,比如你想释放ptr,需要调用av_freep(&ptr);
void av_freep(void *arg)
{
    //定义一个 void * 类型的局部变量 val,用于存储 arg 指向的内存地址。
    void *val;
    //使用 memcpy 从 arg 指向的地址中拷贝数据到 val 中。这里拷贝的大小是 sizeof(val),即指针的大小。这样,val 就保存了 arg 指向的实际内存地址。
    memcpy(&val, arg, sizeof(val));
    //将 arg 指向的内存区域更新为 NULL。这通过 memcpy 实现,将 &(void *){ NULL }(一个临时的 NULL 指针)拷贝到 arg 指向的位置。此时,arg 指向的内存中的值被设置为 NULL。即如果传入&ptr,那么ptr将被置空
    memcpy(arg, &(void *){ NULL }, sizeof(val));
    //调用 av_free 释放 val 指向的内存块。这里的 val 是之前从 arg 拷贝过来的地址。
    av_free(val);
}

三、AVBuffer和AVBufferRef

3.1 头文件

libavutil/buffer.h

3.2 结构体

3.2.1 AVBuffer结构体

AVBuffer 是 FFmpeg 内部用于表示内存缓冲区的数据结构。它包含了实际的数据指针和相关的管理信息


struct AVBuffer {
    uint8_t *data;//这个buffer存储的数据
    size_t size; //这个buffer存储的数据大小

    atomic_uint refcount;//对该内存缓冲区的引用的数量
    //释放该数据的回调,类似析构函数
    void (*free)(void *opaque, uint8_t *data);
    void *opaque;
    int flags;
    int flags_internal;
};
3.2.2 AVBufferRef结构体

AVBufferRef 是一个引用计数的包装器,用于管理 AVBuffer 的生命周期。它包含一个指向 AVBuffer 的指针和一个引用计数器。

typedef struct AVBufferRef {
    AVBuffer *buffer;
    uint8_t *data; //和buffer中的data内容相同
    size_t   size; //和buffer中的size内容相同
} AVBufferRef;

3.3 引用计数机制

对于多个指针共享同一个缓存空间,FFmpeg使用的引用计数的机制(reference-count):

初始化引用计数为0,只有真正分配AVBuffer的时候,引用计数初始化为1;

当有新的Packet引用共享的缓存空间时,就将引用计数+1;

当引用计数为0时,释放AVBuffer中的内容。

相当于通过C语言实现了C++的智能指针。如果对该机制还不太了解可以了解下C++的智能指针的相关概念。

3.4 创建AVBufferRef

av_buffer_create 先申请了一个AVBuffer,然后再创建AVBufferRef

AVBufferRef *av_buffer_create(uint8_t *data, size_t size,
                              void (*free)(void *opaque, uint8_t *data),
                              void *opaque, int flags) {
    AVBufferRef *ret;
    AVBuffer *buf = av_mallocz(sizeof(*buf));
    if (!buf)
        return NULL;

    ret = buffer_create(buf, data, size, free, opaque, flags);
    if (!ret) {
        av_free(buf);
        return NULL;
    }
    return ret;
}


static AVBufferRef *buffer_create(AVBuffer *buf, uint8_t *data, size_t size,
                                  void (*free)(void *opaque, uint8_t *data),
                                  void *opaque, int flags)
{
    AVBufferRef *ref = NULL;

    buf->data     = data;
    buf->size     = size;
    buf->free     = free ? free : av_buffer_default_free;
    buf->opaque   = opaque;

    atomic_init(&buf->refcount, 1);

    buf->flags = flags;

    ref = av_mallocz(sizeof(*ref));
    if (!ref)
        return NULL;

    ref->buffer = buf;
    ref->data   = data;
    ref->size   = size;

    return ref;
}

3.5 增加引用

AVBufferRef *av_buffer_ref(const AVBufferRef *buf)
{
    AVBufferRef *ret = av_mallocz(sizeof(*ret));

    if (!ret)
        return NULL;
    //拷贝buf中的指针
    *ret = *buf;
    //将AVBuffer中的引用计数+1
    atomic_fetch_add_explicit(&buf->buffer->refcount, 1, memory_order_relaxed);

    return ret;
}

3.6 减少引用

void av_buffer_unref(AVBufferRef **buf)
{
    if (!buf || !*buf)
        return;
    //把buffer和null替换,那就是av_buffer_unref。
    buffer_replace(buf, NULL);
}

static void buffer_replace(AVBufferRef **dst, AVBufferRef **src)
{
    AVBuffer *b;
    //获取 AVBuffer 指针:
    b = (*dst)->buffer;
    
    if (src) {
        //内存替换
        **dst = **src;
        av_freep(src);
    } else
        //释放dst及内存
        av_freep(dst);
    //减少 AVBuffer 的引用计数并检查是否需要释放:atomic_fetch_sub_explicit返回的是减少之前的值
    if (atomic_fetch_sub_explicit(&b->refcount, 1, memory_order_acq_rel) == 1) {

        int free_avbuffer = !(b->flags_internal & BUFFER_FLAG_NO_FREE);
        b->free(b->opaque, b->data);
        if (free_avbuffer)
            av_free(b);
    }
}

四、AVPacket和AVFrame

4.1 头文件

libavcodec/packet.h

libavcodec/frame.h

4.2 依赖关系

在这里插入图片描述

4.3 结构体

4.3.1 AVPacket

AVPacket 结构体在 FFmpeg 中用于表示媒体数据包,包含音频或视频数据及其相关信息。结构体仅列出内存管理相关。

typedef struct AVPacket {
    //如果 buf 不为空,AVPacket 使用引用计数机制来管理其数据缓冲区的生命周期。
    //AVBufferRef 提供引用计数功能,当 AVPacket 不再使用时,引用计数会减少,并在引用计数为 0 时释放内存。
      //如果 buf 为 NULL,则 AVPacket 的数据不使用引用计数管理。这种情况下,内存管理由 AVPacket 本身负责。
    AVBufferRef *buf;
  
    //指向实际的数据缓冲区。data 是一个指针,指向存储音频或视频数据的内存区域。
    //如果 buf 不为空,data 指向的内存由 AVBufferRef 管理。data 只是 AVBufferRef 内部数据的指针,不直接负责内存管理。
    //如果 buf 为空,data 指向的内存可能需要显式释放,通常在不再使用 AVPacket 时释放 data 所指向的内存。
    uint8_t *data;
    int   size;
   
     //提供一个 AVBufferRef,供 API 用户自由使用。FFmpeg 不会检查这个缓冲区的内容,只在 AVPacket 被取消引用时调用 av_buffer_unref() 来处理。
    //用户可以使用 opaque_ref 存储附加的私有数据。
    //FFmpeg 会在 AVPacket 取消引用时调用 av_buffer_unref() 来释放 opaque_ref。
    //在 av_packet_copy_props() 调用时,会创建新的 AVBufferRef 引用到 opaque_ref。
    AVBufferRef *opaque_ref;

} AVPacket;
4.3.2 AVFrame

AVFrame 结构体在 FFmpeg 中用于表示解码后的音频或视频帧。该结构体的内存管理机制主要涉及几个字段,包括数据缓冲区的引用计数、私有数据处理和扩展数据管理。

typedef struct AVFrame {
    //指向包含音频或视频数据的缓冲区。对于视频帧,每个指针可能指向图像的一个平面,对于音频帧,每个指针可能指向一个通道的数据。
    //这些指针应指向在 buf 或 extended_buf 中管理的内存区域。
    //当AVFrame 被释放时,指针指向的内存会通过引用计数机制处理,具体取决于 buf 和 extended_buf 的内容。
    uint8_t *data[AV_NUM_DATA_POINTERS];
  
    //数组用于描述数据平面的每一行的大小。这可能包括额外的填充字节,用于对齐
    int linesize[AV_NUM_DATA_POINTERS];
    //extended_data 提供对 data 数组无法容纳的额外数据的访问。
    //需要注意的是,extended_data 中的指针也应指向在 buf 或 extended_buf 中管理的内存区域。
    uint8_t **extended_data;
  
    //管理data
    AVBufferRef *buf[AV_NUM_DATA_POINTERS];
    //管理extended_data
    AVBufferRef **extended_buf;
    //和AVPacket中的opaque_ref类似,此处不赘述
    AVBufferRef *opaque_ref;

}

4.4 内存分配

avpacket和avframe差不多,此处不赘述。主要步骤有两步,一是申请一个packet或者frame,另外一个是申请packet和frame中的具体数据。packet的数据大小需要手动指定,frame中的数据大小可以使用**av_frame_get_buffer**计算出来。

//申请一个packet
AVPacket *av_packet_alloc(void)
{
    AVPacket *pkt = av_malloc(sizeof(AVPacket));
    if (!pkt)
        return pkt;
    //默认初始化
    get_packet_defaults(pkt);

    return pkt;
}

//给packet分配实际数据空间
int av_new_packet(AVPacket *pkt, int size)
{
    AVBufferRef *buf = NULL;
    int ret = packet_alloc(&buf, size);
    if (ret < 0)
        return ret;

    get_packet_defaults(pkt);
    pkt->buf      = buf;
    pkt->data     = buf->data;
    pkt->size     = size;

    return 0;
}

int av_frame_get_buffer(AVFrame *frame, int align)
{
    if (frame->format < 0)
        return AVERROR(EINVAL);

    if (frame->width > 0 && frame->height > 0)
        //计算视频buffer
        return get_video_buffer(frame, align);
    else if (frame->nb_samples > 0 &&
             (av_channel_layout_check(&frame->ch_layout)))
        //计算音频buffer
        return get_audio_buffer(frame, align);

    return AVERROR(EINVAL);
}

4.5 增加引用

int av_packet_ref(AVPacket *dst, const AVPacket *src) {
    int ret;
    dst->buf = NULL;
    //拷贝packet中的属性信息
    ret = av_packet_copy_props(dst, src);
    if (ret < 0)
        goto fail;

    //如果src中的buf不存在,说明src中不是用引用计数来管理内存的,那只要把数据拷贝过去就可以了
    if (!src->buf) {
        ret = packet_alloc(&dst->buf, src->size);
        if (ret < 0)
            goto fail;
        av_assert1(!src->size || src->data);
        if (src->size)
            memcpy(dst->buf->data, src->data, src->size);

        dst->data = dst->buf->data;
    } else {
        //增加对src中buf的引用
        dst->buf = av_buffer_ref(src->buf);
        if (!dst->buf) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        dst->data = src->data;
    }

    dst->size = src->size;

    return 0;
fail:
    av_packet_unref(dst);
    return ret;
}

4.6 减少引用

void av_packet_unref(AVPacket *pkt)
{
    //...
    av_buffer_unref(&pkt->opaque_ref);
    av_buffer_unref(&pkt->buf);
    //将pkt的参数信息回复到默认,避免使用已被解引用的packet
    get_packet_defaults(pkt);
}

4.7释放内存

void av_packet_free(AVPacket **pkt)
{
    if (!pkt || !*pkt)
        return;
    //释放前先做一次解引用
    av_packet_unref(*pkt);
    av_freep(pkt);
}

  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值