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

时间:2014-03-14 20:06:14

标签: android android-ndk ffmpeg

我正在尝试将overlay过滤器与多个输入源一起用于Android应用。基本上,我想在静态图像上叠加多个视频源。 我查看了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过滤器的输入。

    < / LI>
  2. 我使用scale过滤器和单个输入进行了一些测试。似乎AVFilterInOut所需的avfilter_graph_parse_ptr结构名称必须为in。我尝试过不同的版本:in_1in_link_1。他们都没有工作,我也找不到任何与此相关的文件。

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

3 个答案:

答案 0 :(得分:9)

我找到了解决问题的简单方法。 这涉及将avfilter_graph_parse_ptr替换为avfilter_graph_parse2并将bufferbuffersink过滤器添加到filters的{​​{1}}参数。

因此,在您有一个背景图像和一个输入视频的简单情况下,avfilter_graph_parse2参数的值应如下所示:

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

答案 1 :(得分:1)

我无法添加评论,所以我只想添加你可以修复&#34;输出垫&#34;默认&#34;与过滤器实例的类型视频&#34;背景&#34;没有连接到任何目的地的缓冲区&#34;没有水槽。过滤器将自动为您制作水槽。所以你添加了太多的垫子

答案 2 :(得分:0)

就我而言,我进行了如下转换:

[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”