FFMPEG利用滤镜,代码实现视频多宫格效果

好久没有更新学习的记录了,虽然确实是因为连着加班了一个多月,但是懈怠也是不可否认的。

滤镜是FFMPEG 中一个很重要的功能,还需要继续学习加深理解。上一篇是利用滤镜为视频添加时间戳,这次是用滤镜实现多宫格的效果。

基本概念

要实现上面的效果,需要用到多个滤镜。这里有几个概念需要明确,滤镜、滤镜链和滤镜图

        滤镜分为3种:Source FIlter、Sink Filter、Transform Filter。Source FIlter是源,只有输出没有输入;Sink Filter是终端,没有输入只有输出;Transform Filter介于Source FIlter、Sink Filter之间,既有输入,也有输出。

        滤镜链,顾名思义是由多个滤镜首位相连组成的。

        滤镜图由一连串的滤镜链组成,多个滤镜链之间通过分号“;”分隔。

        在代码中,可以用[in_1]...[in_N]filter_name=arguments[out_1]...[outN]格式的字符串表示一个滤镜。一个滤镜可以包含多个输入或输出,输入和输出都被称为pad中括号“[]”用来描述滤镜的输入和输出pad,filter_name是滤镜的名字,例如在添加文字水印时需要用到drawtext滤镜。arguments可以对滤镜进实现更多的参数控制,还是以drawtext为例,drawtext=fontcolor=red:fontsize=50:text='xxxxxxxx',这里我只是截取了一部分,可以看出arguments部分采用key=value键值对的形式表示一个参数,多个参数之间使用冒号“:”分隔,另外value如果是字符串,可以使用单引号“'”标记参数字符串的起始和结束,引号内部使用"\"对特殊字符进行转义。

        本次修改基于上一次为视频添加时间戳滤镜,会使用到多个滤镜组成滤镜图,因此对应的初始部分代码需要调整,为了方便对照,之前的代码我没有删除只是注释掉了。下面是关键部分代码:

//滤镜容器和缓存
AVFilterGraph   *filter_graph = nullptr;
AVFilterContext *buffersink_ctx = nullptr;
AVFilterContext **buffersrc_ctx = nullptr;//修改为指针数组

int CWorkThread::InitFilter(AVCodecContext *codecContext)
{
    char args[512];
    int ret = 0;
    int videoNum = 3;
    //获取源滤镜和Sink滤镜
    //const AVFilter *buffersrc = avfilter_get_by_name("buffer");
    AVFilter **buffersrc = (AVFilter**)av_malloc(videoNum*sizeof(AVFilter*));
    const AVFilter *buffersink = avfilter_get_by_name("buffersink");

    //初始化滤镜输入和输出
    //AVFilterInOut *outputs = avfilter_inout_alloc();
    AVFilterInOut **outputs = (AVFilterInOut**)av_malloc(videoNum*sizeof(AVFilterInOut*));
    AVFilterInOut *inputs = avfilter_inout_alloc();

    //滤镜的描述  
    unsigned int myt= 10+time(NULL);
    std::string stime = std::to_string(myt);
    std::string filters_descr = "color=c=green@1:s=1920*1080[x0];[in0]scale=w=954:h=536[inn0];[x0][inn0]overlay=0:0[x1];[in1]negate[ine1];[ine1]scale=w=954:h=536[inn1];[x1][inn1]overlay=960:542[x2];[in2]drawtext=fontfile=arial.ttf:fontcolor=red:fontsize=50:x=0:y=0:text='%{pts\\:localtime\\:"+stime+"\\:%Y-%m-%d\\ \\%H\\\\\\:%M\\\\\\:%S}'[ine2];[ine2]scale=w=954:h=536[inn2];[x2][inn2]overlay=962:2[x3];[x3]null[out]";
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };

    buffersrc_ctx = (AVFilterContext**)av_malloc(videoNum*sizeof(AVFilterContext*));
    for (int i = 0; i < videoNum; i++)
    {
        buffersrc[i] = NULL;
        buffersrc[i] = (AVFilter*)avfilter_get_by_name("buffer");
        buffersrc_ctx[i] = NULL;
        outputs[i] = avfilter_inout_alloc();
    }

    do {
        //创建滤镜容器
        filter_graph = avfilter_graph_alloc();
        if (!outputs || !inputs || !filter_graph)
        {
            ret = AVERROR(ENOMEM);
            break;
        }

        //初始化数据帧的格式
        sprintf_s(args, sizeof(args),
            "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
            codecContext->width, codecContext->height, codecContext->pix_fmt,
           codecContext->time_base.num, codecContext->time_base.den,
            codecContext->sample_aspect_ratio.num, codecContext->sample_aspect_ratio.den);

        //输入数据缓存
        //ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
            args, NULL, filter_graph);
       
        ret = avfilter_graph_create_filter(&(buffersrc_ctx[0]), buffersrc[0], "in0",
            args, NULL, filter_graph);
        ret = avfilter_graph_create_filter(&(buffersrc_ctx[1]), buffersrc[1], "in1",
            args, NULL, filter_graph);
        ret = avfilter_graph_create_filter(&(buffersrc_ctx[2]), buffersrc[2], "in2",
            args, NULL, filter_graph);

        if (ret < 0) {
            break;
        }

        //输出数据缓存
        ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
            NULL, NULL, filter_graph);

        if (ret < 0)
        {
           break;
        }

        //设置输出的pix_fmt
        ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
            AV_PIX_FMT_YUV420P, AV_OPT_SEARCH_CHILDREN);
        if (ret < 0)
        {
           break;
        }

        //设置滤镜的端点
        /*outputs->name = av_strdup("in");
        outputs->filter_ctx = buffersrc_ctx;
        outputs->pad_idx = 0;
        outputs->next = NULL;*/

        outputs[0]->name = av_strdup("in0");
        outputs[0]->filter_ctx = buffersrc_ctx[0];
        outputs[0]->pad_idx = 0;
        outputs[0]->next = outputs[1];

        outputs[1]->name = av_strdup("in1");
        outputs[1]->filter_ctx = buffersrc_ctx[1];
        outputs[1]->pad_idx = 0;
        outputs[1]->next = outputs[2];

        outputs[2]->name = av_strdup("in2");
        outputs[2]->filter_ctx = buffersrc_ctx[2];
        outputs[2]->pad_idx = 0;
        outputs[2]->next = NULL;


        inputs->name = av_strdup("out");
        inputs->filter_ctx = buffersink_ctx;
        inputs->pad_idx = 0;
        inputs->next = NULL;

        //初始化滤镜
        if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr.c_str(),
            &inputs, outputs, NULL)) < 0)
        {
            break;
        }
        //滤镜生效
        if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
        {
            break;
        }

    } while (false);

    //释放对应的输入输出
    avfilter_inout_free(&inputs);
    avfilter_inout_free(outputs);
    return ret;
}

为了方便演示,向不同滤镜添加了相同的视频帧,本次实现了3宫格的效果,因此需要添加3次。 

  ret = av_buffersrc_add_frame_flags(buffersrc_ctx[0], scaled_frame,AV_BUFFERSRC_FLAG_KEEP_REF);
  ret = av_buffersrc_add_frame_flags(buffersrc_ctx[1], scaled_frame,AV_BUFFERSRC_FLAG_KEEP_REF);
  ret = av_buffersrc_add_frame_flags(buffersrc_ctx[2], scaled_frame,AV_BUFFERSRC_FLAG_KEEP_REF);

        滤镜的描述字符串如下,里面使用了color滤镜,用于生成一个颜色,输出的pad被标记为[X0];scale滤镜用于图像缩放,上面添加的视频帧被缩放为954*536大小,然后输出标记为[inn0]的pad; overlay滤镜可以将一个视频覆盖到另一个视频的顶部,[x0]和[inn0]作为overlay的两个输入pad,o然后overlay生成一个输出pad[x1];后面的字符串只是套娃,[x1]将和新的被缩放的视频帧[inn1]作为另一个overlay滤镜的输入pad。这个字符串很长,第一次看需要明白基本的套路,不然容易晕,所以我也只用到了3个滤镜链。熟悉之后,可以分段生成滤镜链,最后拼接成滤镜图。

std::string filters_descr = "color=c=green@1:s=1920*1080[x0];[in0]scale=w=954:h=536[inn0];[x0][inn0]overlay=0:0[x1];[in1]negate[ine1];[ine1]scale=w=954:h=536[inn1];[x1][inn1]overlay=960:542[x2];[in2]drawtext=fontfile=arial.ttf:fontcolor=red:fontsize=50:x=0:y=0:text='%{pts\\:localtime\\:"+stime+"\\:%Y-%m-%d\\ \\%H\\\\\\:%M\\\\\\:%S}'[ine2];[ine2]scale=w=954:h=536[inn2];[x2][inn2]overlay=962:2[x3];[x3]null[out]";

        overlay滤镜的参数用于指明覆盖视频的位置,negate滤镜用于消除alpha分量

最后的效果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值