如何使用FFmpeg滤镜实现左右分屏?现有代码故障求助
FFmpeg 6.1 YUV分屏处理代码问题排查与修复
以下是代码中存在的核心问题及修正方案:
一、主要问题点
1. 滤镜链路逻辑错误
原滤镜字符串存在三个关键问题:
pad滤镜未指定输入源,无有效基础画布- 最终滤镜链未显式链接到输出节点
[out] - 分屏尺寸计算错误:380+380=760,超过pad设置的720宽度,导致画面溢出
2. AVFrame数据处理错误
调用av_frame_get_buffer为帧分配内存后,直接覆盖data指针指向外部缓冲区,会引发:
- 内存泄漏(分配的内存未被释放)
av_buffersrc_add_frame尝试释放不属于它的内存,触发崩溃或异常
3. 变量重复定义
循环内重复定义nSize变量,覆盖全局定义,易引发逻辑混淆
4. 滤镜输入输出链接错误
avfilter_graph_parse_ptr的输入链表传递方式有误,导致滤镜节点无法正确链接
5. Buffersink未指定输出格式
未设置buffersink的像素格式,可能导致输出格式与预期不符
二、修正后的完整代码
#include <stdio.h> #include <string> #include <thread> extern "C" { #include <libavcodec/avcodec.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> #include <libavformat/avformat.h> #include <libavutil/avutil.h> #include <libavutil/imgutils.h> #include <libavutil/opt.h> } using namespace std; #pragma comment(lib,"avcodec.lib") #pragma comment(lib,"avdevice.lib") #pragma comment(lib,"avfilter.lib") #pragma comment(lib,"avformat.lib") #pragma comment(lib,"avutil.lib") #pragma comment(lib,"swresample.lib") #pragma comment(lib,"swscale.lib") static void saveFrame(AVFrame* frame, FILE* out) { int width = frame->width; int height = frame->height; // 保存Y分量 for (int i = 0; i < height; i++) { fwrite(frame->data[0] + i * frame->linesize[0], 1, width, out); } // 保存U分量 for (int i = 0; i < height / 2; i++) { fwrite(frame->data[1] + i * frame->linesize[1], 1, width / 2, out); } // 保存V分量 for (int i = 0; i < height / 2; i++) { fwrite(frame->data[2] + i * frame->linesize[2], 1, width / 2, out); } } int main(int argc, char** argv) { int ret; FILE* fp1 = NULL; FILE* fp = NULL; AVFilterContext* buffer_ctx1; AVFilterContext* buffer_ctx2; AVFilterContext* buffersink_ctx; AVFilterGraph* filter_graph; AVFilterInOut* inputs = NULL; AVFilterInOut* outputs = NULL; char filter_args[1024] = { 0 }; char err_buf[512]; const char* infile1 = "D:\\windows-ffmpeg\\x64\\output_1.yuv"; const char* outfile = "D:\\windows-ffmpeg\\x64\\output_w.yuv"; fp1 = fopen(infile1, "rb"); if (!fp1) { fprintf(stderr, "输入文件打开失败: %s\n", infile1); return -1; } fp = fopen(outfile, "wb"); if (!fp) { fprintf(stderr, "输出文件打开失败: %s\n", outfile); fclose(fp1); return -1; } filter_graph = avfilter_graph_alloc(); if (!filter_graph) { fprintf(stderr, "滤镜图分配失败\n"); goto end; } // 创建第一个输入缓冲区 if (avfilter_graph_create_filter(&buffer_ctx1, avfilter_get_by_name("buffer"), "in0", "video_size=720x480:pix_fmt=yuv420p:time_base=1/24:pixel_aspect=1/1", NULL, filter_graph) < 0) { fprintf(stderr, "创建输入缓冲区in0失败\n"); goto end; } // 创建第二个输入缓冲区 if (avfilter_graph_create_filter(&buffer_ctx2, avfilter_get_by_name("buffer"), "in1", "video_size=720x480:pix_fmt=yuv420p:time_base=1/24:pixel_aspect=1/1", NULL, filter_graph) < 0) { fprintf(stderr, "创建输入缓冲区in1失败\n"); goto end; } // 创建输出缓冲区,指定输出格式为YUV420P const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE }; buffersink_ctx = avfilter_graph_alloc_filter(filter_graph, avfilter_get_by_name("buffersink"), "out"); if (!buffersink_ctx) { fprintf(stderr, "创建输出缓冲区失败\n"); goto end; } ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { fprintf(stderr, "设置输出像素格式失败\n"); goto end; } // 构建滤镜输入输出链表 inputs = avfilter_inout_alloc(); outputs = avfilter_inout_alloc(); if (!inputs || !outputs) { fprintf(stderr, "分配输入输出链表失败\n"); goto end; } // 输出节点:链接到buffersink outputs->name = av_strdup("out"); outputs->filter_ctx = buffersink_ctx; outputs->pad_idx = 0; outputs->next = NULL; // 输入节点1:in0链接到buffer_ctx1 inputs->name = av_strdup("in0"); inputs->filter_ctx = buffer_ctx1; inputs->pad_idx = 0; inputs->next = avfilter_inout_alloc(); // 输入节点2:in1链接到buffer_ctx2 inputs->next->name = av_strdup("in1"); inputs->next->filter_ctx = buffer_ctx2; inputs->next->pad_idx = 0; inputs->next->next = NULL; // 修正后的滤镜链:左右分屏,各360x480,合成720x480 sprintf(filter_args, "[in0]scale=360:480[left];" "[in1]scale=360:480[right];" "[left][right]hstack=inputs=2[out]"); // 解析滤镜链 ret = avfilter_graph_parse_ptr(filter_graph, filter_args, &outputs, &inputs, NULL); if (ret < 0) { av_strerror(ret, err_buf, sizeof(err_buf)); fprintf(stderr, "解析滤镜链失败: %s\n", err_buf); goto end; } // 配置滤镜图 ret = avfilter_graph_config(filter_graph, NULL); if (ret < 0) { av_strerror(ret, err_buf, sizeof(err_buf)); fprintf(stderr, "配置滤镜图失败: %s\n", err_buf); goto end; } const int frame_size = 720 * 480 * 3 / 2; uint8_t* frame_buffer = (uint8_t*)malloc(frame_size); if (!frame_buffer) { fprintf(stderr, "分配帧缓冲区失败\n"); goto end; } while (1) { // 读取一帧YUV数据 int read_size = fread(frame_buffer, 1, frame_size, fp1); if (read_size != frame_size) { if (feof(fp1)) fprintf(stdout, "文件读取完毕\n"); else fprintf(stderr, "读取文件失败\n"); break; } // 准备输入帧1 AVFrame* src_frame1 = av_frame_alloc(); if (!src_frame1) { fprintf(stderr, "分配帧内存失败\n"); break; } src_frame1->width = 720; src_frame1->height = 480; src_frame1->format = AV_PIX_FMT_YUV420P; ret = av_frame_get_buffer(src_frame1, 32); if (ret < 0) { av_strerror(ret, err_buf, sizeof(err_buf)); fprintf(stderr, "获取帧缓冲区失败: %s\n", err_buf); av_frame_free(&src_frame1); break; } // 复制YUV数据到帧缓冲区 int y_size = 720 * 480; memcpy(src_frame1->data[0], frame_buffer, y_size); memcpy(src_frame1->data[1], frame_buffer + y_size, y_size / 4); memcpy(src_frame1->data[2], frame_buffer + y_size * 5 / 4, y_size / 4); // 发送帧到第一个输入缓冲区 ret = av_buffersrc_add_frame(buffer_ctx1, src_frame1); if (ret < 0) { av_strerror(ret, err_buf, sizeof(err_buf)); fprintf(stderr, "发送帧到in0失败: %s\n", err_buf); av_frame_free(&src_frame1); break; } av_frame_free(&src_frame1); // 准备输入帧2(这里复用同一帧数据,实际可替换为另一文件的帧) AVFrame* src_frame2 = av_frame_alloc(); if (!src_frame2) { fprintf(stderr, "分配帧内存失败\n"); break; } src_frame2->width = 720; src_frame2->height = 480; src_frame2->format = AV_PIX_FMT_YUV420P; ret = av_frame_get_buffer(src_frame2, 32); if (ret < 0) { av_strerror(ret, err_buf, sizeof(err_buf)); fprintf(stderr, "获取帧缓冲区失败: %s\n", err_buf); av_frame_free(&src_frame2); break; } // 复制YUV数据到帧缓冲区 memcpy(src_frame2->data[0], frame_buffer, y_size); memcpy(src_frame2->data[1], frame_buffer + y_size, y_size / 4); memcpy(src_frame2->data[2], frame_buffer + y_size * 5 / 4, y_size / 4); // 发送帧到第二个输入缓冲区 ret = av_buffersrc_add_frame(buffer_ctx2, src_frame2); if (ret < 0) { av_strerror(ret, err_buf, sizeof(err_buf)); fprintf(stderr, "发送帧到in1失败: %s\n", err_buf); av_frame_free(&src_frame2); break; } av_frame_free(&src_frame2); // 获取处理后的帧 AVFrame* out_frame = av_frame_alloc(); if (!out_frame) { fprintf(stderr, "分配输出帧内存失败\n"); break; } ret = av_buffersink_get_frame(buffersink_ctx, out_frame); if (ret < 0) { if (ret != AVERROR_EOF) { av_strerror(ret, err_buf, sizeof(err_buf)); fprintf(stderr, "获取输出帧失败: %s\n", err_buf); } av_frame_free(&out_frame); break; } // 保存输出帧 saveFrame(out_frame, fp); av_frame_unref(out_frame); av_frame_free(&out_frame); } free(frame_buffer); end: // 释放资源 avfilter_inout_free(&inputs); avfilter_inout_free(&outputs); avfilter_graph_free(&filter_graph); if (fp1) fclose(fp1); if (fp) fclose(fp); fprintf(stdout, "处理完成\n"); return 0; }
三、关键修正说明
优化滤镜链:
- 使用
hstack滤镜直接实现左右分屏,比多次overlay更简洁高效 - 将分屏尺寸调整为360x480,确保总宽度为720,匹配原始分辨率
- 使用
修复AVFrame数据处理:
- 调用
av_frame_get_buffer后,使用memcpy将外部缓冲区的数据复制到帧的内部缓冲区,避免内存管理错误
- 调用
规范资源管理:
- 统一释放所有分配的资源(滤镜图、输入输出链表、帧内存、文件句柄)
- 处理
AVERROR_EOF等正常结束情况
明确格式设置:
- 在buffer源滤镜中显式使用
yuv420p代替数值0,提高可读性 - 为buffersink指定输出像素格式,确保输出为YUV420P
- 在buffer源滤镜中显式使用
内容的提问来源于stack exchange,提问作者aaadddzxc
相关产品推荐
相关产品推荐

