如何将FFmpeg提取的视频帧推入FFmpeg缓冲区?
嘿,我之前刚好做过类似的需求,其实你要的逻辑核心就藏在buffersrc.c里的av_buffersrc_add_frame()系列函数里,可能你没把初始化和推入的流程串起来。下面给你一步步拆解可行的实现方案,结合你看过的文件对应理解:
FFmpeg的buffer滤镜(对应buffersrc.c的实现)就是专门用来接收外部帧数据的缓冲区源,你需要先初始化这个缓冲区的参数,再把提取到的帧推入其中,甚至可以结合fifo.c里的帧队列做缓存解耦。
1. 先初始化缓冲区源滤镜
首先要创建滤镜图,并添加buffer滤镜作为输入源,必须指定和你提取的视频帧完全匹配的参数(像素格式、宽高、帧率等),否则推入帧时会报错:
#include <libavfilter/avfilter.h> #include <libavfilter/buffersrc.h> #include <libavfilter/buffersink.h> AVFilterGraph *filter_graph = avfilter_graph_alloc(); if (!filter_graph) { // 内存分配错误处理 return -1; } AVFilterContext *buffersrc_ctx = NULL; const AVFilter *buffersrc = avfilter_get_by_name("buffer"); AVDictionary *opts = NULL; // 配置缓冲区参数,替换成你提取的帧的实际参数 av_dict_set(&opts, "video_size", "1920x1080", 0); av_dict_set(&opts, "pix_fmt", "yuv420p", 0); av_dict_set(&opts, "framerate", "30/1", 0); av_dict_set(&opts, "time_base", "1/30", 0); // 创建缓冲区源上下文 int ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "input_src", NULL, &opts, filter_graph); if (ret < 0) { // 错误处理,比如参数不合法 av_dict_free(&opts); avfilter_graph_free(&filter_graph); return ret; } av_dict_free(&opts);
2. 将提取的视频帧推入缓冲区
假设你已经通过解码流程得到了AVFrame(这部分你应该已经实现了),直接调用av_buffersrc_add_frame()就能把帧推入缓冲区:
AVFrame *extracted_frame = ...; // 你从视频中提取的帧 // 推入帧到缓冲区 ret = av_buffersrc_add_frame(buffersrc_ctx, extracted_frame); if (ret < 0) { // 错误处理:比如帧参数和缓冲区不匹配,或者缓冲区已满 av_frame_free(&extracted_frame); return ret; } // 注意:如果extracted_frame是引用计数帧,推入后不需要立即释放,缓冲区会接管引用 // 如果是自己分配的帧,根据情况决定是否释放
如果需要强制推入(比如忽略一些格式不匹配的警告),可以用av_buffersrc_add_frame_flags(),传入AV_BUFFERSRC_FLAG_KEEP_REF还能保留原帧的引用。
3. 结合FIFO做帧缓存(对应fifo.c的使用)
如果你需要解耦帧提取和缓冲区推入的速度(比如解码太快但后续处理跟不上),可以用AVFrameFifo(基于fifo.c实现的帧队列)先缓存提取的帧,再批量推入缓冲区:
// 初始化FIFO队列,参数和帧一致 AVFrameFifo *frame_fifo = av_frame_fifo_alloc(AV_PIX_FMT_YUV420P, 1920, 1080); if (!frame_fifo) { // 内存分配错误 return -1; } // 将提取的帧加入FIFO ret = av_frame_fifo_write(frame_fifo, extracted_frame); if (ret < 0) { // 错误处理 av_frame_free(&extracted_frame); return ret; } // 从FIFO取出帧推入缓冲区 AVFrame *fifo_frame = av_frame_alloc(); ret = av_frame_fifo_read(frame_fifo, fifo_frame); if (ret >= 0) { av_buffersrc_add_frame(buffersrc_ctx, fifo_frame); } av_frame_free(&fifo_frame);
4. 后续处理:从缓冲区读取帧(可选)
如果推入缓冲区后需要对帧做滤镜处理,你可以添加buffersink滤镜作为输出,连接到缓冲区源,然后读取处理后的帧:
AVFilterContext *buffersink_ctx = NULL; const AVFilter *buffersink = avfilter_get_by_name("buffersink"); ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "output_sink", NULL, NULL, filter_graph); if (ret < 0) { // 错误处理 return ret; } // 连接源和sink滤镜 ret = avfilter_link(buffersrc_ctx, 0, buffersink_ctx, 0); if (ret < 0) { // 错误处理 return ret; } // 配置sink接受的像素格式 enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE}; av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); // 初始化滤镜图 ret = avfilter_graph_config(filter_graph, NULL); if (ret < 0) { // 错误处理 return ret; } // 读取处理后的帧 AVFrame *processed_frame = av_frame_alloc(); ret = av_buffersink_get_frame(buffersink_ctx, processed_frame); if (ret == AVERROR(EAGAIN)) { // 缓冲区还没有足够的帧,需要继续推入 } else if (ret == AVERROR_EOF) { // 所有帧都处理完毕 } else if (ret < 0) { // 错误处理 }
最后提醒:一定要注意内存管理,用完的AVFilterGraph、AVFrame、AVFrameFifo都要调用对应的free函数释放;另外帧的参数必须和缓冲区初始化的参数严格匹配,否则会出现推入失败的情况。
内容的提问来源于stack exchange,提问作者ST94

