You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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;
}

三、关键修正说明

  1. 优化滤镜链:

    • 使用hstack滤镜直接实现左右分屏,比多次overlay更简洁高效
    • 将分屏尺寸调整为360x480,确保总宽度为720,匹配原始分辨率
  2. 修复AVFrame数据处理:

    • 调用av_frame_get_buffer后,使用memcpy将外部缓冲区的数据复制到帧的内部缓冲区,避免内存管理错误
  3. 规范资源管理:

    • 统一释放所有分配的资源(滤镜图、输入输出链表、帧内存、文件句柄)
    • 处理AVERROR_EOF等正常结束情况
  4. 明确格式设置:

    • 在buffer源滤镜中显式使用yuv420p代替数值0,提高可读性
    • 为buffersink指定输出像素格式,确保输出为YUV420P

内容的提问来源于stack exchange,提问作者aaadddzxc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 08:27:03