在Android NDK中使用Libavfilter库实现多输入滤波器图形

9
我正在尝试在 Android 应用中使用 overlay 过滤器和多个输入源。基本上,我想在静态图像上叠加多个视频源。
我查看了 ffmpeg 提供的示例,并根据它实现了我的代码,但是似乎不如预期地工作。
在 ffmpeg 过滤示例中,似乎只有一个视频输入。我必须处理多个视频输入,但我不确定我的解决方案是否正确。我已经尝试找到其他示例,但似乎这是唯一的一个。
这是我的代码:
AVFilterContext **inputContexts;
AVFilterContext *outputContext;
AVFilterGraph *graph;

int initFilters(AVFrame *bgFrame, int inputCount, AVCodecContext **codecContexts, char *filters)
{
    int i;
    int returnCode;
    char args[512];
    char name[9];
    AVFilterInOut **graphInputs = NULL;
    AVFilterInOut *graphOutput = NULL;

    AVFilter *bufferSrc  = avfilter_get_by_name("buffer");
    AVFilter *bufferSink = avfilter_get_by_name("buffersink");

    graph = avfilter_graph_alloc();
    if(graph == NULL)
        return -1;

    //allocate inputs
    graphInputs = av_calloc(inputCount + 1, sizeof(AVFilterInOut *));
    for(i = 0; i <= inputCount; i++)
    {
        graphInputs[i] = avfilter_inout_alloc();
        if(graphInputs[i] == NULL)
            return -1;
    }

    //allocate input contexts
    inputContexts = av_calloc(inputCount + 1, sizeof(AVFilterContext *));
    //first is the background
    snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=1/1:pixel_aspect=0", bgFrame->width, bgFrame->height, bgFrame->format);
    returnCode = avfilter_graph_create_filter(&inputContexts[0], bufferSrc, "background", args, NULL, graph);
    if(returnCode < 0)
        return returnCode;
    graphInputs[0]->filter_ctx = inputContexts[0];
    graphInputs[0]->name = av_strdup("background");
    graphInputs[0]->next = graphInputs[1];

    //allocate the rest
    for(i = 1; i <= inputCount; i++)
    {
        AVCodecContext *codecCtx = codecContexts[i - 1];
        snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
                    codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
                    codecCtx->time_base.num, codecCtx->time_base.den,
                    codecCtx->sample_aspect_ratio.num, codecCtx->sample_aspect_ratio.den);
        snprintf(name, sizeof(name), "video_%d", i);

        returnCode = avfilter_graph_create_filter(&inputContexts[i], bufferSrc, name, args, NULL, graph);
        if(returnCode < 0)
            return returnCode;

        graphInputs[i]->filter_ctx = inputContexts[i];
        graphInputs[i]->name = av_strdup(name);
        graphInputs[i]->pad_idx = 0;
        if(i < inputCount)
        {
            graphInputs[i]->next = graphInputs[i + 1];
        }
        else
        {
            graphInputs[i]->next = NULL;
        }
    }

    //allocate outputs
    graphOutput = avfilter_inout_alloc();   
    returnCode = avfilter_graph_create_filter(&outputContext, bufferSink, "out", NULL, NULL, graph);
    if(returnCode < 0)
        return returnCode;
    graphOutput->filter_ctx = outputContext;
    graphOutput->name = av_strdup("out");
    graphOutput->next = NULL;
    graphOutput->pad_idx = 0;

    returnCode = avfilter_graph_parse_ptr(graph, filters, graphInputs, &graphOutput, NULL);
    if(returnCode < 0)
        return returnCode;

    returnCode = avfilter_graph_config(graph, NULL);
        return returnCode;

    return 0;
}

该函数的filters参数被传递给avfilter_graph_parse_ptr,它看起来像这样:[background] scale=512x512 [base]; [video_1] scale=256x256 [tmp_1]; [base][tmp_1] overlay=0:0 [out]。在调用avfilter_graph_config后,会出现警告:Output pad "default" with type video of the filter instance "background" of buffer not connected to any destination和错误Invalid argument。请问我做错了什么?编辑:我发现了两个问题:
  1. 似乎avfilter_graph_parse_ptr的描述有点模糊。参数ouputs表示图形的当前输出列表,在我的情况下,这是graphInputs变量,因为这些是buffer过滤器的输出。参数inputs表示图形的当前输入列表,在这种情况下,这是graphOutput变量,因为它代表buffersink过滤器的输入。

  2. 我尝试使用scale过滤器和单个输入进行了一些测试。似乎avfilter_graph_parse_ptr所需的AVFilterInOut结构的名称需要为in。我尝试了不同的版本:in_1in_link_1。它们都不起作用,我也没有找到任何相关的文档。

所以问题仍然存在。如何实现具有多个输入的过滤器图形?
3个回答

13

我已经找到了问题的简单解决方案。这需要将avfilter_graph_parse_ptr替换为avfilter_graph_parse2,并将bufferbuffersink过滤器添加到avfilter_graph_parse2filters参数中。

因此,在一个简单的情况下,当您有一个背景图像和一个输入视频时,filters参数的值应该是这样的:

buffer=video_size=1024x768:pix_fmt=2:time_base=1/25:pixel_aspect=3937/3937 [in_1]; buffer=video_size=1920x1080:pix_fmt=0:time_base=1/180000:pixel_aspect=0/1 [in_2]; [in_1] [in_2] overlay=0:0 [result]; [result] buffersink

avfilter_graph_parse2将使所有的图连接并初始化所有的过滤器。输入缓冲区和输出缓冲区的过滤器上下文可以在最后从图形本身中检索。这些被用于从过滤器图中添加/获取帧。

简化版代码如下:

AVFilterContext **inputContexts;
AVFilterContext *outputContext;
AVFilterGraph *graph;

int initFilters(AVFrame *bgFrame, int inputCount, AVCodecContext **codecContexts)
{
    int i;
    int returnCode;
    char filters[1024];
    AVFilterInOut *gis = NULL;
    AVFilterInOut *gos = NULL;

    graph = avfilter_graph_alloc();
    if(graph == NULL)
    {
        printf("Cannot allocate filter graph.");        
        return -1;
    }

    //build the filters string here
    // ...

    returnCode = avfilter_graph_parse2(graph, filters, &gis, &gos);
    if(returnCode < 0)
    {
        cs_printAVError("Cannot parse graph.", returnCode);
        return returnCode;
    }

    returnCode = avfilter_graph_config(graph, NULL);
    if(returnCode < 0)
    {
        cs_printAVError("Cannot configure graph.", returnCode);
        return returnCode;
    }

    //get the filter contexts from the graph here

    return 0;
}

1
我遇到了完全相同的问题,我正在尝试在JPG上叠加一个带有alpha通道的视频,我可以轻松地在命令行上完成这个操作,但是我很难将所有内容整合到代码中。你能分享一下代码吗?非常感谢。 - user361526
你能添加一下如何使用avfilter_graph_get_filter从图形中获取上下文吗?我无法确定自动创建的滤镜会有什么名称。或者,有没有其他方法可以接收上下文以便使用它们?我指的是你的"//get the filter contexts from the graph here"注释。 - TheSHEEEP
@TheSHEEEP,当执行avfilter_graph_parse2(...); avfilter_graph_config(graph, NULL)时,您将看到过滤器形成了一个链接列表。可以通过avfilter_graph_get_filter(graph, "Parsed_buffer_0");avfilter_graph_get_filter(graph, "Parsed_buffer_1")获取in_1in_2过滤器上下文。您最好调试程序并检查图形的filters字段,它是从解析字符串构建的链接列表。 - pingsoli

1
对于我的情况,我有一个类似这样的转换:
[0:v]pad=1008:734:144:0:black[pad];[pad][1:v]overlay=0:576[out]

如果您尝试使用命令行运行ffmpeg,它将会正常工作:
ffmpeg -i first.mp4 -i second.mp4 -filter_complex "[0:v]pad=1008:734:144:0:black[pad];[pad][1:v]overlay=0:576[out]" -map "[out]" -map 0:a output.mp4

基本上,增加第一个视频的整体大小,然后将第二个视频重叠。经过多次尝试,与此线程相同的问题,我解决了它。FFMPEG文档中的视频过滤示例(https://ffmpeg.org/doxygen/2.1/doc_2examples_2filtering_video_8c-example.html)运行良好,在深入研究后,这个方法很好用:
    filterGraph = avfilter_graph_alloc();
    NULLC(filterGraph);

    bufferSink = avfilter_get_by_name("buffersink");
    NULLC(bufferSink);
    filterInput = avfilter_inout_alloc();
    AVBufferSinkParams* buffersinkParams = av_buffersink_params_alloc();
    buffersinkParams->pixel_fmts = pixelFormats;

    FFMPEGHRC(avfilter_graph_create_filter(&bufferSinkContext, bufferSink, "out", NULL, buffersinkParams, filterGraph));

    av_free(buffersinkParams);

    filterInput->name = av_strdup("out");
    filterInput->filter_ctx = bufferSinkContext;
    filterInput->pad_idx = 0;
    filterInput->next = NULL;

    filterOutputs = new AVFilterInOut*[inputFiles.size()];
    ZeroMemory(filterOutputs, sizeof(AVFilterInOut*) * inputFiles.size());
    bufferSourceContext = new AVFilterContext*[inputFiles.size()];
    ZeroMemory(bufferSourceContext, sizeof(AVFilterContext*) * inputFiles.size());

    for (i = inputFiles.size() - 1; i >= 0 ; i--)
    {
        snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", 
            videoCodecContext[i]->width, videoCodecContext[i]->height, videoCodecContext[i]->pix_fmt, videoCodecContext[i]->time_base.num, videoCodecContext[i]->time_base.den, videoCodecContext[i]->sample_aspect_ratio.num, videoCodecContext[i]->sample_aspect_ratio.den);

        filterOutputs[i] = avfilter_inout_alloc();
        NULLC(filterOutputs[i]);
        bufferSource = avfilter_get_by_name("buffer");
        NULLC(bufferSource);
        sprintf(args2, outputTemplate, i);
        FFMPEGHRC(avfilter_graph_create_filter(&bufferSourceContext[i], bufferSource, "in", args, NULL, filterGraph));

        filterOutputs[i]->name = av_strdup(args2);
        filterOutputs[i]->filter_ctx = bufferSourceContext[i];
        filterOutputs[i]->pad_idx = 0;
        filterOutputs[i]->next = i < inputFiles.size() - 1 ? filterOutputs[i + 1] : NULL;
    }

    FFMPEGHRC(avfilter_graph_parse_ptr(filterGraph, description, &filterInput, filterOutputs, NULL));
    FFMPEGHRC(avfilter_graph_config(filterGraph, NULL));

变量类型与上面的例子相同,args和args2都是char[512],其中outputTemplate为"%d:v",基本上是从过滤表达式中输入的视频ID。需要注意以下几点:
  • args中的视频信息需要正确,time_base和sample_aspect_ration是从格式上下文的视频流中复制的。
  • 实际上,输入是我们的输出,反之亦然。
  • 所有输入过滤器(filterOutputs)的过滤器名称均为“in”。

1
我无法添加评论,所以我想补充一点,您可以通过不使用任何sink来修复“背景”过滤器实例的视频类型的“默认”输出板与未连接到任何目标的缓冲区的问题。 过滤器将自动为您创建sink。 因此,您正在添加太多pads。

是的,那最终是我做的。 - gookman
大家好,你们从哪里获取FFMPEG 'C' API的文档,特别是关于滤镜的文档。我想使用叠加滤镜并从图像创建视频,但我无法找到任何有关使用'C' API的文档或示例。我已经在这个问题上挣扎了15天,但所有的努力都没有结果。虽然有很多关于通过命令来完成此操作的信息,但我想通过'C API'来实现。请指导我!!! - M Abdul Sami

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接