系列文章目录
FFmpeg源码解析系列(一)目录和编译
FFmpeg源码解析系列(二)主要结构体
ffmpeg源码解析系列(四)结构体之AVIOContext 、URLContext、URLProtocol
ffmpeg源码解析系列(五)结构体之AVCodecContext
这是毕业一年跳槽时被面过的一道题,他问ffmpeg的内存管理是怎样的。当时支支吾吾没有回答出来。
如果再让我回到过去,我会和他说莫欺少年穷~
一、ffmpeg 内存管理相关api
1.1. av_malloc
和 av_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. AVBuffer
和 AVBufferRef
AVBuffer
是 FFmpeg 中引用计数的核心结构,用于管理数据缓冲区的生命周期。AVBufferRef
是 AVBuffer
的引用,使用引用计数机制来确保内存被正确释放。
主要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;}// 使用自定义释放函数来管理 dataAVBufferRef *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); // 如果失败需要手动释放 dataav_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
AVPacket
和 AVFrame
是 FFmpeg 中用于处理音视频数据的核心结构。AVPacket
通常用于存储编码的数据包(例如压缩的音视频数据),而 AVFrame
用于存储解码后的原始音视频数据。
下面是一些常用的内存管理相关 API 及其示例,包括如何分配、引用计数管理和释放 AVPacket
和 AVFrame
。
-
av_packet_alloc
和av_frame_alloc
:用于分配AVPacket
和AVFrame
结构。 -
av_new_packet
:为AVPacket
分配指定大小的数据缓冲区。 -
av_frame_get_buffer
:为AVFrame
分配缓冲区,用于存储图像或音频数据。 -
av_packet_ref
和av_frame_ref
:增加引用计数,使多个指针可以安全地共享相同的数据。 -
av_packet_unref
和av_frame_unref
:减少引用计数,当引用计数为 0 时,释放数据缓冲区。 -
av_packet_free
和av_frame_free
:释放AVPacket
和AVFrame
结构本身。
以下是AVPacket的一个简单例子
#include <libavcodec/avcodec.h>
#include <stdio.h>int main() {// 1. 分配一个 AVPacketAVPacket *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. 分配一个 AVFrameAVFrame *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_malloc
和 av_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_POISONINGif (ptr)memset(ptr, FF_MEMORY_POISON, size);
#endifreturn 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);
#elsefree(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中的引用计数+1atomic_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;//管理dataAVBufferRef *buf[AV_NUM_DATA_POINTERS];//管理extended_dataAVBufferRef **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)//计算视频bufferreturn get_video_buffer(frame, align);else if (frame->nb_samples > 0 &&(av_channel_layout_check(&frame->ch_layout)))//计算音频bufferreturn 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的参数信息回复到默认,避免使用已被解引用的packetget_packet_defaults(pkt);
}
4.7释放内存
void av_packet_free(AVPacket **pkt)
{if (!pkt || !*pkt)return;//释放前先做一次解引用av_packet_unref(*pkt);av_freep(pkt);
}