FFmpeg filter浅析--中篇

2022-11-19 10:13:51 浏览数 (2)

本文主要讲解FFmpeg filter相关API和走读官方examples/filtering_video.c示例

过滤器主要结构体

AVFilterGraph

过滤器图,统筹管理所有filter实例

代码语言:javascript复制
typedef struct AVFilterGraph {
    AVFilterContext **filters;
    unsigned nb_filters;
    // .... 略
} AVFilterGraph;

AVFilter

用于定义一个filter

代码语言:javascript复制
/**
 * Filter definition. This defines the pads a filter contains, and all the
 * callback functions used to interact with the filter.
 */
typedef struct AVFilter {
    /**
     * Filter name. Must be non-NULL and unique among filters.
     */
    const char *name;

    /**
     * A description of the filter. May be NULL.
     *
     * You should use the NULL_IF_CONFIG_SMALL() macro to define it.
     */
    const char *description;

    /**
     * List of static inputs.
     *
     * NULL if there are no (static) inputs. Instances of filters with
     * AVFILTER_FLAG_DYNAMIC_INPUTS set may have more inputs than present in
     * this list.
     */
    const AVFilterPad *inputs;

    /**
     * List of static outputs.
     *
     * NULL if there are no (static) outputs. Instances of filters with
     * AVFILTER_FLAG_DYNAMIC_OUTPUTS set may have more outputs than present in
     * this list.
     */
    const AVFilterPad *outputs;

    // ...略
} AVFilter;

FFmpeg的所有滤镜定义在如下路径中

libavfilter/filter_list.c

libavfilter/allfilters.c

以之前文章我们举例的drawgrid filter为例,它定义在

libavfilter/vf_drawbox.c

AVFilterContext

表示一个filter的实例,即使是同一个过滤器,在进行处理时由于输入参数的不同也会导致有不同的滤波效果输出,AVFilterContext就是在实际进行滤波时用于维护过滤器相关信息的实体

代码语言:javascript复制
/** An instance of a filter */
struct AVFilterContext {
    const AVFilter *filter; ///< the AVFilter of which this is an instance
    AVFilterPad *input_pads; ///< array of input pads
    AVFilterLink **inputs; ///< array of pointers to input links
    unsigned    nb_inputs; ///< number of input pads

    AVFilterPad *output_pads; ///< array of output pads
    AVFilterLink **outputs; ///< array of pointers to output links
    unsigned    nb_outputs; ///< number of output pads

    struct AVFilterGraph *graph;    ///< filtergraph this filter belongs to

    // ...略
};

AVFilterLink

过滤器链,用于链接相邻的两个AVFilterContext形成管道

代码语言:javascript复制
/**
 * A link between two filters. This contains pointers to the source and
 * destination filters between which this link exists, and the indexes of
 * the pads involved. In addition, this link also contains the parameters
 * which have been negotiated and agreed upon between the filter, such as
 * image dimensions, format, etc.
 *
 * Applications must not normally access the link structure directly.
 * Use the buffersrc and buffersink API instead.
 * In the future, access to the header may be reserved for filters
 * implementation.
 */
struct AVFilterLink {
    AVFilterContext *src; ///< source filter
    AVFilterPad *srcpad; ///< output pad on the source filter

    AVFilterContext *dst; ///< dest filter
    AVFilterPad *dstpad; ///< input pad on the dest filter

    /**
     * Graph the filter belongs to.
     */
    struct AVFilterGraph *graph;

    // ...略
};

从标红的文档注释可以看到一般我们通过"buffersrc"和"buffersink" API来替代

AVFilterPad

过滤器的输入输出端口,一个过滤器可以有多个输入以及多个输出端口

AVFilterInOut

过滤器输入/输出的链接列表

代码语言:javascript复制
/**
 * A linked-list of the inputs/outputs of the filter chain.
 *
 * This is mainly useful for avfilter_graph_parse() / avfilter_graph_parse2(),
 * where it is used to communicate open (unlinked) inputs and outputs from and
 * to the caller.
 * This struct specifies, per each not connected pad contained in the graph, the
 * filter context and the pad index required for establishing a link.
 */
typedef struct AVFilterInOut {
    /** unique name for this input/output in the list */
    char *name;

    /** filter context associated to this input/output */
    AVFilterContext *filter_ctx;

    /** index of the filt_ctx pad to use for linking */
    int pad_idx;

    /** next input/input in the list, NULL if this is the last */
    struct AVFilterInOut *next;
} AVFilterInOut;

在AVFilter模块中有两个特别的filter:buffer和buffersink

buffer:filter graph中的源头,原始数据向此filter节点输入。在创建该过滤器实例的时候需要提供一些关于输入帧格式的必要参数(如:time_base,fmt,w/h等)

buffersink:filter graph中的输出节点,经过滤波的数据从此filter节点输出。创建该过滤器实例的时候需要指定pix_fmts

过滤器使用流程

FFmpeg AVFilter使用流程图如下:

(图片来自参考1)

使用流程主要分为三个部分:过滤器graph构建,filter处理和资源释放

  • 通过avfilter_graph_alloc创建graph
  • 通过avfilter_graph_create_filter创建filter context,然后通过parse,link等操作构建filter处理链路
  • 通过av_buffersrc_add_frame将原始AVFrame添加到过滤器graph的fifo队列中
  • 通过av_buffersink_get_frame_flags来获取处理完成的数据帧
  • 最后则是释放相关资源

官方example讲解

此处以video filter为例

filtering_video.c有四个函数

从命名中可以很容易猜测func的用途,调用顺序依次是

入口main -> 打开输入文件open_input_file -> 初始化过滤器init_filters -> 显示视频帧display_frame

入口main函数

代码语言:javascript复制
int main(int argc, char **argv) {
    // ...初始化相关变量,包括原始frame,过滤器处理结果filt_frame,packet等
    
    // 打开输入file,查找视频stream,打开解码器等
    if ((ret = open_input_file(argv[1])) < 0)
        goto end;

    // 初始化过滤器相关
    if ((ret = init_filters(filter_descr)) < 0)
        goto end;

    while (1) {
        // 读包
        av_read_frame(...);
        // 解码
        avcodec_send_packet(...);
        avcodec_receive_frame(...);
        /* 将解码后的原始frame push到filtergraph */
        av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF)
        /* 从filtergraph pull经过滤波处理的frame */
        av_buffersink_get_frame(buffersink_ctx, filt_frame);
        // 显示
        display_frame(filt_frame, buffersink_ctx->inputs[0]->time_base);
        // ... 略
    }
end:
    // 主要是相关资源释放
    avfilter_graph_free(&filter_graph);
    // ... 略
}

打开输入文件open_input_file

代码语言:javascript复制
static int open_input_file(const char *filename) {
    // 打开文件
    avformat_open_input(&fmt_ctx, filename, NULL, NULL));

    // 查找video stream
    avformat_find_stream_info(fmt_ctx, NULL));
    av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);

    // 创建和初始化解码器context
    dec_ctx = avcodec_alloc_context3(dec);
    avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[video_stream_index]->codecpar);
    
    // 打开解码器
    avcodec_open2(dec_ctx, dec, NULL);
}

初始化过滤器init_filters

代码语言:javascript复制
static int init_filters(const char *filters_descr) {
    char args[512];
    int ret = 0;

    // 获取buffer & buffersink filter
    const AVFilter *buffersrc = avfilter_get_by_name("buffer");
    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 = avfilter_graph_alloc();
    
    /* buffer video source: the decoded frames from the decoder will be inserted here. */
    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);

    // 创建过滤器图的输入端,即buffer过滤器并配置相关参数
    ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
                                       args, NULL, filter_graph);

    // 创建过滤器图的输出端,即buffersink过滤器并配置相关参数
    ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
                                       NULL, NULL, filter_graph);

    ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
                              AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);

    /*
     * Set the endpoints for the filter graph. The filter_graph will
     * be linked to the graph described by 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->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->name = av_strdup("out");
    inputs->filter_ctx = buffersink_ctx;
    inputs->pad_idx = 0;
    inputs->next = NULL;

    // 将filter_descr字符串描述的过滤器链添加到filter grapha中
    // const char *filter_descr = "scale=78:24,transpose=cclock";
    // filter_descr表示的是一个过滤器链,scale transpose
    if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,
                                    &inputs, &outputs, NULL)) < 0)
        goto end;

    // 为filter graph中所有过滤器建立链接
    if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
        goto end;

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

    return ret;
}

总结一下主要流程:

  • 创建并初始化buffer & buffersink滤镜
  • 创建filtergraph
  • 将描述具体过滤器链的filter_descr加入到filtergraph做parse
  • filtergraph为所有过滤器建立链接

显示视频帧display_frame

显示逻辑比较简单,此处就不贴代码了,感兴趣的同学可以编译整个example目录,实际运行下filtering_video.c

参考

1.【FFmpeg开发之AVFilter使用流程总结】

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

0 人点赞