FFmpeg 滤镜AVFilter编程——简单滤镜

一、认识滤镜

ffmpeg里面的滤镜(也叫过滤器)十分重要,功能也十分强大,除了我们所谓的滤镜,还有贴图、伸缩变形、拆解合并等功能,这里放2个滤镜变形来证明他的强大。

ffmpeg里面用(AVFilterGraph)graph来作为管理一整套滤镜流程的容器,里面还有(AVFilter)filter作为过滤器和它的实例(AVFilterContext)filter_ctx,过滤器里还包含AVFilterPad作为过滤器的输入输出口,还有(AVFilterLink)link在过滤器之间做关联,这样前后串起来就是一个滤镜链了。

这里需要注意的是,滤镜链的前后需要名字专门叫做“buffer”的过滤器作为输入,也需要名字一定是“buffersink”的过滤器作为输出。

滤镜在FFmpeg里还分简单滤镜链和复杂滤镜链,一般的单入单出的为简单滤镜链,我们先讲简单的。

二、如何使用滤镜

使用滤镜有2种方式,我们就先讲很多demo里面都有的一种,就是使用avfilter_graph_parse_ptr。

FFmpeg的example里有个filtering_video.c,对照着滤镜部分看。

static int init_filters(const char *filters_descr)
{
    char args[512];
    int ret = 0;
    // "buffer"滤镜:缓冲视频帧,作为滤镜图的输入
    const AVFilter *buffersrc  = avfilter_get_by_name("buffer");
    // "buffersink"滤镜:缓冲视频帧,作为滤镜图的输出
    const AVFilter *buffersink = avfilter_get_by_name("buffersink");
    AVFilterInOut *outputs = avfilter_inout_alloc();
    AVFilterInOut *inputs  = avfilter_inout_alloc();
    AVRational time_base = fmt_ctx->streams[video_stream_index]->time_base;
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE };

    // 分配一个滤镜图filter_graph
    filter_graph = avfilter_graph_alloc();
    if (!outputs || !inputs || !filter_graph) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    /* buffer video source: the decoded frames from the decoder will be inserted here. */
    // args是buffersrc滤镜的参数
    snprintf(args, sizeof(args),
            "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
            dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
            time_base.num, time_base.den,
            dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
    // 为buffersrc滤镜创建滤镜实例buffersrc_ctx,命名为"in"
    // 将新创建的滤镜实例buffersrc_ctx添加到滤镜图filter_graph中
    ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                                       args, NULL, filter_graph);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");
        goto end;
    }

    /* buffer video sink: to terminate the filter chain. */
    // 为buffersink滤镜创建滤镜实例buffersink_ctx,命名为"out"
    // 将新创建的滤镜实例buffersink_ctx添加到滤镜图filter_graph中
    ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
                                       NULL, NULL, filter_graph);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
        goto end;
    }

    // 设置输出像素格式为pix_fmts[]中指定的格式(如果要用SDL显示,则这些格式应是SDL支持格式)
    ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
                              AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n");
        goto end;
    }

    /*
     * Set the endpoints for the filter graph. The filter_graph will
     * be linked to the graph described by filters_descr.
     */
    // 设置滤镜图的端点,包含此端点的滤镜图将会被连接到filters_descr
    // 描述的滤镜图中

    /*
     * The buffer source output must be connected to the input pad of
     * the first filter described by filters_descr; since the first
     * filter input label is not specified, it is set to "in" by
     * default.
     */
    // outputs变量意指buffersrc_ctx滤镜的输出引脚(output pad)
    // src缓冲区(buffersrc_ctx滤镜)的输出必须连到filters_descr中第一个
    // 滤镜的输入;filters_descr中第一个滤镜的输入标号未指定,故默认为
    // "in",此处将buffersrc_ctx的输出标号也设为"in",就实现了同标号相连
    outputs->name       = av_strdup("in");
    outputs->filter_ctx = buffersrc_ctx;
    outputs->pad_idx    = 0;
    outputs->next       = NULL;

    /*
     * The buffer sink input must be connected to the output pad of
     * the last filter described by filters_descr; since the last
     * filter output label is not specified, it is set to "out" by
     * default.
     */
    // inputs变量意指buffersink_ctx滤镜的输入引脚(input pad)
    // sink缓冲区(buffersink_ctx滤镜)的输入必须连到filters_descr中最后
    // 一个滤镜的输出;filters_descr中最后一个滤镜的输出标号未指定,故
    // 默认为"out",此处将buffersink_ctx的输出标号也设为"out",就实现了
    // 同标号相连
    inputs->name       = av_strdup("out");
    inputs->filter_ctx = buffersink_ctx;
    inputs->pad_idx    = 0;
    inputs->next       = NULL;

    // 将filters_descr描述的滤镜图添加到filter_graph滤镜图中
    // 在filters_descr字符串中,如果第一个滤镜未指定输入标号,则假定为"in";如果最后一个滤镜
    // 未指定输出标号,则假定为"out"。几个实参说明如下:
    // @filter_graph  将字符串描述的滤镜图连接到此滤镜图中
    // @filters_descr 描述滤镜的字符串,用于解析生成滤镜图 
    // @inputs        指向滤镜图中输入链表,调用返回时,更新为包含开放输入列表
    // @outputs       指向滤镜图的输出链表,调用返回时,更新为包含开放输出列表
    // 我的话:
    // 调用前:filter_graph包含两个滤镜buffersrc_ctx和buffersink_ctx
    // 调用后:filters_descr描述的滤镜图插入到filter_graph中,buffersrc_ctx连接到filters_descr
    //         的输入,filters_descr的输出连接到buffersink_ctx,filters_descr只进行了解析而不
    //         建立内部滤镜间的连接。filters_desc与filter_graph间的连接是利用AVFilterInOut inputs
    //         和AVFilterInOut inputs连接起来的,AVFilterInOut是一个链表,最终可用的连在一起的
    //         滤镜链/滤镜图就是通过这个链表串在一起的。
    if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
                                    &inputs, &outputs, NULL)) < 0)
        goto end;

    // 验证有效性并配置filtergraph中所有连接和格式
    if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
        goto end;

end:
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);

    return ret;
}

1、用avfilter_get_by_name创建一个AVFilter对象,如buffersrc

2、用avfilter_graph_alloc分配一个AVFilterGraph对象,如filter_graph

3、用avfilter_graph_create_filter实例化一个AVFilterContext对象,如buffersrc_ctx,期间对“buffer”的过滤器要设置一些args

4、然后对AVFilterInOut进行inputs和outputs的对应创建和设置

5、通过avfilter_graph_parse_ptr将filters_descr所描述的滤镜内容插入到前面的inputs、outputs之间,串起来设置到filter_graph中

6、通过avfilter_graph_config验证有效性并配置filter_graph中所有连接和格式

主要函数和流程就是这些,照着例子搬就行了,你只需要改动filters_descr,也就是你想要的滤镜效果即可

7、解码后的帧送入av_buffersrc_add_frame_flags(buffersrc_ctx, frame, 0);

8、过滤后的帧从里面取出av_buffersink_get_frame(buffersink_ctx, filt_frame);

三、用例子分析流程

我这里用命令行来分析一下流程

ffmpeg.exe -re -y -rtsp_transport tcp -i "rtsp://xxx" -an -vf "scale=352x288" -vcodec libx264 -f flv 1.flv

我们可以使用char *graph_str = avfilter_graph_dump(filter_graph, NULL);将整个滤镜链打印出来,打印出来如下图:

我们可以看到,ffmpeg里面给我们的scale过滤器命名为"Parsed_scale_0"。我们知道过滤器一定有个输入有个输出,可以看到输入的buffer过滤器命名为了"graph 0 input from stream 0:0",里面设置了如video_size的args。然后还看到了format,ffmpeg自动给我们加了色彩空间转换的过滤器。可以看到滤镜链是从  输入过滤器=》scale过滤器=》format过滤器=》输出过滤器

[Parsed_scale_0 @ 0x5555559178c0] Setting 'w' to value '352x288'
[Parsed_scale_0 @ 0x5555559178c0] Setting 'flags' to value 'bicubic'
[Parsed_scale_0 @ 0x5555559178c0] w:352 h:288 flags:'bicubic' interl:0
[graph 0 input from stream 0:0 @ 0x55555581b180] Setting 'video_size' to value '1920x1080'
[graph 0 input from stream 0:0 @ 0x55555581b180] Setting 'pix_fmt' to value '12'
[graph 0 input from stream 0:0 @ 0x55555581b180] Setting 'time_base' to value '1/90000'
[graph 0 input from stream 0:0 @ 0x55555581b180] Setting 'pixel_aspect' to value '0/1'
[graph 0 input from stream 0:0 @ 0x55555581b180] Setting 'sws_param' to value 'flags=2'
[graph 0 input from stream 0:0 @ 0x55555581b180] Setting 'frame_rate' to value '25/1'
[graph 0 input from stream 0:0 @ 0x55555581b180] w:1920 h:1080 pixfmt:yuvj420p tb:1/90000 fr:25/1 sar:0/1 sws_param:flags=2
[format @ 0x5555557f7940] Setting 'pix_fmts' to value 'yuv420p|yuvj420p|yuv422p|yuvj422p|yuv444p|yuvj444p|nv12|nv16|nv21|yuv420p10le|yuv422p10le|yuv444p10le|nv20le|gray|gray10le'
avfilter_graph_dump:                                                           
                                                           +----------------+
graph 0 input from stream 0:0:default--[0x0 0:0 ?]--default| Parsed_scale_0 |default--[0x0 0:0 ?]--format:default
                                                           |    (scale)     |
                                                           +----------------+

+-------------------------------+
| graph 0 input from stream 0:0 |default--[0x0 0:0 ?]--Parsed_scale_0:default
|           (buffer)            |
+-------------------------------+

                                    +--------------+
format:default--[0x0 0:0 ?]--default|   out_0_0    |
                                    | (buffersink) |
                                    +--------------+

                                            +----------+
Parsed_scale_0:default--[0x0 0:0 ?]--default|  format  |default--[0x0 0:0 ?]--out_0_0:default
                                            | (format) |
                                            +----------+

经过上面代码流程的讲解和实例的展示,看下面的流程图应该也不会难了。

后续会讲关于第二种过滤器编程的方法和复杂过滤器的编程使用,记得关注哦~

参考:

https://www.cnblogs.com/vczf/p/13638051.html

https://www.cnblogs.com/renhui/p/14663071.html

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
滤镜FFmpeg中的一个重要概念,它可以对音视频进行各种处理,例如裁剪、旋转、缩放、调整亮度、对比度等等。FFmpeg提供了丰富的滤镜库,可以通过API在代码中使用滤镜。下面是一个简单的使用FFmpeg API添加滤镜的例子: ```c #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> AVFilterContext *buffersink_ctx; AVFilterContext *buffersrc_ctx; AVFilterGraph *filter_graph; int video_stream_index = -1; // 初始化滤镜 int init_filters(const char *filters_descr) { char args[512]; int ret = 0; AVFilter *buffersrc = avfilter_get_by_name("buffer"); AVFilter *buffersink = avfilter_get_by_name("buffersink"); AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE }; filter_graph = avfilter_graph_alloc(); if (!outputs || !inputs || !filter_graph) { ret = AVERROR(ENOMEM); goto end; } // 设置输入参数 snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", buffersrc_ctx->inputs[0]->width, buffersrc_ctx->inputs[0]->height, buffersrc_ctx->inputs[0]->format, buffersrc_ctx->inputs[0]->time_base.num, buffersrc_ctx->inputs[0]->time_base.den, buffersrc_ctx->inputs[0]->sample_aspect_ratio.num, buffersrc_ctx->inputs[0]->sample_aspect_ratio.den); // 添加输入滤镜 ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n"); goto end; } // 添加输出滤镜 ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\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) { av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n"); goto end; } // 添加滤镜 outputs->name = av_strdup("in"); outputs->filter_ctx = buffersrc_ctx; outputs->pad_idx = 0; outputs->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, &inputs, &outputs, NULL)) < 0) goto end; if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) goto end; end: avfilter_inout_free(&inputs); avfilter_inout_free(&outputs); return ret; } // 处理滤镜 int filter_encode_write_frame(AVFrame *frame, AVCodecContext *enc_ctx, AVFormatContext *fmt_ctx) { int ret; AVFrame *filt_frame = av_frame_alloc(); if (!filt_frame) { ret = AVERROR(ENOMEM); goto end; } // 添加滤镜 if (av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) { av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n"); goto end; } // 获取输出帧 while (1) { ret = av_buffersink_get_frame(buffersink_ctx, filt_frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) goto end; // 编码并写入文件 filt_frame->pict_type = AV_PICTURE_TYPE_NONE; ret = encode_write_frame(filt_frame, enc_ctx, fmt_ctx); av_frame_unref(filt_frame); if (ret < 0) goto end; } end: av_frame_free(&filt_frame); return ret; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值