音视频开发36-7【FFmpeg】Filter 过滤器代码实现

查看ffmpeg内部支持哪些filter

ffmpeg -filters

ffmpeg 中 filter,filterchain,filtergraph 三者的概念和关系

FFmpeg中filter包含三个层次, filter->filterchain->filtergraph

filter的语法

1 [in_link_1]…[in_link_N]filter_name=parameters[out_link_1]…[out_link_M]

参数说明:
1. [in_link_N]、[out_link_N]:⽤来标识输⼊和输出的标签。in_link_N是标签名,标签名可以任意命
名,需使⽤⽅括号括起来。在filter_name的前⾯的标签⽤于标识输⼊,在filter_name后⾯的⽤于标识
输出。⼀个filter可以有多个输⼊和多个输出,没有输⼊的filter称为source filter,没有输出的filter称
为sink filter。对输⼊或输出打标签是可选的,打上标签是为了连接其他filter时使⽤。
2. filter_name:filter的名称。
3. “=parameters”:包含初始化filter的参数,是可选的。
“=parameters”有以下⼏种形式

3.1. 使⽤':'字符分隔的⼀个“键=值”对列表。如下所示。
ffmpeg -i input -vf scale=w=iw/2:h=ih/2 output
ffmpeg -i input -vf scale=h=ih/2:w=iw/2 output

3.2. 使⽤':'字符分割的“值”的列表。在这种情况下,键按照声明的顺序被假定为选项名。例如,scale filter
的前两个选项分别是w和h,当参数列表为“iw/2:ih/2”时,iw/2的值赋给w,ih/2的值赋给h。如下所
示。

ffmpeg -i input -vf scale=iw/2:ih/2 output


3.3. 使⽤':' 字符分隔混合“值”和“键=值”对的列表。“值”必须位于“键=值”对之前,并遵循与前⼀点相同的
约束顺序。之后的“键=值”对的顺序不受约束。如下所示。

ffmpeg -i input -vf scale=iw/2:h=ih/2 output


filter类定义了filter的特性以及输⼊和输出的数量,某个filter的使⽤⽅式可以通过以下命令获知。
ffmpeg -h filter=filter_name

filterchain 语法

⽤⼀个字符串描述filterchain的组成,形式如下
"filter1, filter2, ... filterN-1, filterN"

说明:
1. 由⼀个或多个filter的连接⽽成,filter之间以逗号“,”分隔。
2. 每个filter都连接到序列中的前⼀个filter,即前⼀个filter的输出是后⼀个filter的输⼊。
⽐如示例

ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT

示例说明:
1. crop、vflip在同⼀个filterchain中

filtergraph的语法

⽤⼀个字符串描述filtergraph的组成,形式如下
"filterchain1;filterchain2;...filterchainN-1;fiterchainN"

AVFilter主体框架流程

我们以 如下的命令为例子,说明流程

ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT

                [main]
input --> split ---------------------> overlay --> output
            |                             ^
            |[tmp]                  [flip]|
            +-----> crop --> vflip -------+

这个的意思是:当你将一个input 输入的时候,

会自动的split 分成两个,一个main,一个 temp,

对于temp进行处理,包括 crop(视频裁剪),vflip(垂直镜像翻转)后 会变成 flip,

再将 flip 和 main 结合起来,最终成为一个output  ,这里结合的过程 ffpemg 这里用了一个overlay的单词,这个overlay 是 覆盖的意思,是否意味着,用flip会覆盖main的一部分?,获取是我们多想了。

FFmpeg filter简介

FFmpeg filter提供了很多⾳视频特效处理的功能,⽐如视频缩放、截取、翻转、叠加等。
其中定义了很多的filter,例如以下常⽤的⼀些filter。

scale:视频/图像的缩放
overlay:视频/图像的叠加
crop:视频/图像的裁剪
trim:截取视频的⽚段
rotate:以任意⻆度旋转视频
⽀持的filter的列表可以通过以下命令获得。我们查看会发现 
ffmpeg -filters

一些基本概念:

先来看这个框架,

可以把AVFilter看做一些列Filter节点链组成,这个链由AVfilterGraph管理,每个AVFilter节点都会对数据处理,处理完成后交给下一个节点继续处理,直到最后一个节点处理完成。每个AVFilter节点都会有一个AVFilterContext上下文对其进行管理,第一个节点音视频名称为buffer/abuffer,最后一个节点名称为buffersink/abuffersink;内部各个节点链接方式可以自由灵活配置,前一个的输出配置在后一个的输入,可以多个节点进行过滤,也可以少数2个节点过滤

创建Filter链的管理者

创建AVFilterGraph

AVFilterGraph* filterGraph = avfilter_graph_alloc();

配置一个AVFilter节点

  1. 创建一个AVFilter、AVFilterContext,并初始化
AVFilterContext* bufferSrcCtx;
AVFilter *bufferSrc = avfilter_get_by_name("buffer");
ret = avfilter_graph_create_filter(&bufferSrcCtx, bufferSrc, "in", 
												args, nullptr,filterGraph);

2. 滤镜配置和滤镜使用。FFmpeg中的滤镜使用分为两个步骤:滤镜配置滤镜使用滤镜配置目的是创建一个滤镜图并为其创建两个特殊的滤镜作为该滤镜图的输入端和输出端(视频:buffer滤镜和buffersink滤镜;音频:abuffer滤镜和abuffersink滤镜)

    //第四步,创建 输入 AVFilterContext, 和 输出 AVFilterContext,使用的方法是 avfilter_graph_create_filter
    //4.1 创建 输入 AVFilterContext,这里要非常清楚 avfilter_graph_create_filter 方法的各个参数
    /*
     * filt_ctx: 指向指针的指针,用于返回创建的滤镜实例的 AVFilterContext 结构体指针。
     * filt: 要创建的滤镜的 AVFilter 结构体指针。
     * name: 滤镜实例的名称。这个名字是我们随便取的,叫啥不重要,重要的是要唯一
     * args: 滤镜实例的参数,可以是滤镜实例初始化时需要的参数字符串。args则是这个AVFilter的参数, 注意仅仅是这个AVFilter的参数,不是整个graph的参数。再拿Fade举例,args就可以是t=in:st=3:d=3。


     * opaque: 不透明指针,可以传递给滤镜的初始化函数。
     * graph_ctx: 滤镜图的上下文 AVFilterGraph 结构体指针,表示滤镜实例将要被添加到的滤镜图。
     * 该函数返回一个整数值,表示操作是否成功。如果成功创建滤镜实例并将其添加到滤镜图中,则返回0;如果发生错误,则返回负值。



// 1. 创建滤镜图
AVFilterGraph *filter_graph = avfilter_graph_alloc();
if(! filter_graph) {
    RLOG_E("alloc filter graph failed.");
    return -1;
}
// 2. 配置滤镜图的输入端,即创建buffer滤镜,然后创建其滤镜实例并命名为"in",
//    并将该滤镜实例添加到之前创建的滤镜图中。需要注意的是,在创建buffer滤镜实例
//    AVFilterContext时,要传入创建所需参数args
char args[512];
snprintf(args, sizeof(args),
         "video_size=%d%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
         decode_ctx->width, decode_ctx->height, decode_ctx->pix_fmt,
         time_base.num, time_base.den, decode_ctx->sample_aspect_ratio.num,
         decode_ctx->sample_aspect_ratio.den);
AVFilterContext *buffer_ctx;
const AVFilter *buffer_filter = avfilter_get_by_name("buffer");
int ret = avfilter_graph_create_filter(&buffer_ctx,   // buffer滤镜的实例
                                       buffer_filter, // buffer滤镜
                                       "in",          // 滤镜实例命名
                                       args,        // 创建buffer滤镜实例所需参数
                                       NULL,
                                       filter_graph); // 被添加的滤镜图
if(ret < 0) {
    RLOG_E_("avfilter_graph_create_filter failed,err=%d", ret);
    return ret;
}
// 3. 配置滤镜图的输出端,即创建buffersink滤镜,然后创建其滤镜实例并命名为"out",
// 同时将该滤镜实例添加到之前创建的滤镜图中。另外,buffersink滤镜输出有一个输出参数
// 即"pix_fmt",表示输出像素格式,假如后面视频的视频帧由sws_scale进行转换可以不设置
AVFilterContext *buffersink_ctx;
const AVFilter *buffersink_filter = avfilter_get_by_name("buffersink");
ret =  avfilter_graph_create_filter(&buffersink_ctx,  // 被创建的滤镜实例
                                    buffersink_filter,// buffersink滤镜
                                    "out",            // 滤镜实例命名
                                    NULL,
                                    NULL,
                                    filter_graph);    // 被添加的滤镜图
if(ret < 0) {
    RLOG_E_("avfilter_graph_create_filter failed,err=%d", ret);
    return ret;
}
enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_NONE};
ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
                          AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
if(ret < 0) {
    RLOG_E_("set output pixel format failed,err=%d", ret);
    return ret;
}

这里重点讲下在buffer滤镜和buffersink滤镜在创建滤镜实例时,为什么需要分别传入args参数信息和设定输出像素格式,以及如何知道具体需要传入什么参数?我们在命令行中分别输入ffmpeg -h filter=bufferffmpeg -h filter=buffersink命令,得到的信息如下:

# buffer滤镜帮助信息
$ ffmpeg -h filter=buffer
ffmpeg version 4.0.2 Copyright (c) 2000-2018 the FFmpeg developers
Filter buffer
Buffer video frames, and make them accessible to the filterchain.
	Inputs:
		none (source filter)
	Outputs:
		#0: default (video)
buffer AVOptions:
width         <int>        ..FV..... (from 0 to INT_MAX) (default 0)
video_size    <image_size> ..FV.....
height        <int>        ..FV..... (from 0 to INT_MAX) (default 0)
pix_fmt       <pix_fmt>    ..FV..... (default none)
sar           <rational>   ..FV... sample aspect ratio (from 0 to DBL_MAX) (default 0/1)
pixel_aspect  <rational>   ..FV... sample aspect ratio (from 0 to DBL_MAX) (default 0/1)
time_base     <rational>   ..FV..... (from 0 to DBL_MAX) (default 0/1)
frame_rate    <rational>   ..FV..... (from 0 to DBL_MAX) (default 0/1)
sws_param     <string>     ..FV.....

# buffer滤镜帮助信息  
 $  ffmpeg -h filter=buffersink
 ffmpeg version 4.0.2 Copyright (c) 2000-2018 the FFmpeg developers
 Filter buffersink
 Buffer video frames, and make them available to the end of the filter graph.
 	Inputs:
 		#0: default (video)
 	Outputs:
 		none (sink filter)
 buffersink AVOptions:
 pix_fmts          <binary>     ..FV..... set the supported pixel formats

1.AVFilter-定义filter本身的能⼒

在 libavfilter/avfilter.h 中定义

AVFilter 是 FFmpeg 中用于实现音视频滤镜的基本单位。它代表了一个特定的音视频处理功能,比如色彩转换、尺寸调整、去噪等。AVFilter 可以单独使用。

例如ffmepg提供的scale滤镜,就是一个AVFilter。

AVFilter 结构的作用主要是定义了一个滤镜的基本属性和行为,以及提供了滤镜初始化、销毁、格式查询等相关的回调函数接口。通过这些接口,用户可以对滤镜进行初始化配置,并将其应用到音视频流数据上,实现各种音视频处理效果。

数据结构
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;

    /**
     * A class for the private data, used to declare filter private AVOptions.
     * This field is NULL for filters that do not declare any options.
     *
     * If this field is non-NULL, the first member of the filter private data
     * must be a pointer to AVClass, which will be set by libavfilter generic
     * code to this class.
     */ //用于指定滤镜的私有类,定义了滤镜的参数和选项。
    const AVClass *priv_class;

    /**
     * A combination of AVFILTER_FLAG_*
     */
    int flags;

    /*****************************************************************
     * All fields below this line are not part of the public API. They
     * may not be used outside of libavfilter and can be changed and
     * removed at will.
     * New public fields should be added right above.
     *****************************************************************
     */

    /**
     * The number of entries in the list of inputs.
     */
    uint8_t nb_inputs;

    /**
     * The number of entries in the list of outputs.
     */
    uint8_t nb_outputs;

    /**
     * This field determines the state of the formats union.
     * It is an enum FilterFormatsState value.
     */
    uint8_t formats_state;

    /**
     * Filter pre-initialization function
     *
     * This callback will be called immediately after the filter context is
     * allocated, to allow allocating and initing sub-objects.
     *
     * If this callback is not NULL, the uninit callback will be called on
     * allocation failure.
     *
     * @return 0 on success,
     *         AVERROR code on failure (but the code will be
     *           dropped and treated as ENOMEM by the calling code)
     */
    int (*preinit)(AVFilterContext *ctx);

    /**
     * Filter initialization function.
     *
     * This callback will be called only once during the filter lifetime, after
     * all the options have been set, but before links between filters are
     * established and format negotiation is done.
     *
     * Basic filter initialization should be done here. Filters with dynamic
     * inputs and/or outputs should create those inputs/outputs here based on
     * provided options. No more changes to this filter's inputs/outputs can be
     * done after this callback.
     *
     * This callback must not assume that the filter links exist or frame
     * parameters are known.
     *
     * @ref AVFilter.uninit "uninit" is guaranteed to be called even if
     * initialization fails, so this callback does not have to clean up on
     * failure.
     *
     * @return 0 on success, a negative AVERROR on failure
     *///用于初始化滤镜的回调函数,可以在滤镜被创建时执行一些初始化操作。
    int (*init)(AVFilterContext *ctx);

    /**
     * Filter uninitialization function.
     *
     * Called only once right before the filter is freed. Should deallocate any
     * memory held by the filter, release any buffer references, etc. It does
     * not need to deallocate the AVFilterContext.priv memory itself.
     *
     * This callback may be called even if @ref AVFilter.init "init" was not
     * called or failed, so it must be prepared to handle such a situation.
     *///用于销毁滤镜的回调函数,在滤镜不再需要时执行清理操作。
    void (*uninit)(AVFilterContext *ctx);

    /**
     * The state of the following union is determined by formats_state.
     * See the documentation of enum FilterFormatsState in internal.h.
     */
    union {
        /**
         * Query formats supported by the filter on its inputs and outputs.
         *
         * This callback is called after the filter is initialized (so the inputs
         * and outputs are fixed), shortly before the format negotiation. This
         * callback may be called more than once.
         *
         * This callback must set ::AVFilterLink's
         * @ref AVFilterFormatsConfig.formats "outcfg.formats"
         * on every input link and
         * @ref AVFilterFormatsConfig.formats "incfg.formats"
         * on every output link to a list of pixel/sample formats that the filter
         * supports on that link.
         * For audio links, this filter must also set
         * @ref AVFilterFormatsConfig.samplerates "incfg.samplerates"
         *  /
         * @ref AVFilterFormatsConfig.samplerates "outcfg.samplerates"
         * and @ref AVFilterFormatsConfig.channel_layouts "incfg.channel_layouts"
         *  /
         * @ref AVFilterFormatsConfig.channel_layouts "outcfg.channel_layouts"
         * analogously.
         *
         * This callback must never be NULL if the union is in this state.
         *
         * @return zero on success, a negative value corresponding to an
         * AVERROR code otherwise
         *///用于查询支持的输入输出格式的回调函数。
        int (*query_func)(AVFilterContext *);
        /**
         * A pointer to an array of admissible pixel formats delimited
         * by AV_PIX_FMT_NONE. The generic code will use this list
         * to indicate that this filter supports each of these pixel formats,
         * provided that all inputs and outputs use the same pixel format.
         *
         * This list must never be NULL if the union is in this state.
         * The type of all inputs and outputs of filters using this must
         * be AVMEDIA_TYPE_VIDEO.
         */
        const enum AVPixelFormat *pixels_list;
        /**
         * Analogous to pixels, but delimited by AV_SAMPLE_FMT_NONE
         * and restricted to filters that only have AVMEDIA_TYPE_AUDIO
         * inputs and outputs.
         *
         * In addition to that the generic code will mark all inputs
         * and all outputs as supporting all sample rates and every
         * channel count and channel layout, as long as all inputs
         * and outputs use the same sample rate and channel count/layout.
         */
        const enum AVSampleFormat *samples_list;
        /**
         * Equivalent to { pix_fmt, AV_PIX_FMT_NONE } as pixels_list.
         */
        enum AVPixelFormat  pix_fmt;
        /**
         * Equivalent to { sample_fmt, AV_SAMPLE_FMT_NONE } as samples_list.
         */
        enum AVSampleFormat sample_fmt;
    } formats;

//滤镜私有数据的大小,用于分配存储私有数据的内存空间。
    int priv_size;      ///< size of private data to allocate for the filter

    int flags_internal; ///< Additional flags for avfilter internal use only.

    /**
     * Make the filter instance process a command.
     *
     * @param cmd    the command to process, for handling simplicity all commands must be alphanumeric only
     * @param arg    the argument for the command
     * @param res    a buffer with size res_size where the filter(s) can return a response. This must not change when the command is not supported.
     * @param flags  if AVFILTER_CMD_FLAG_FAST is set and the command would be
     *               time consuming then a filter should treat it like an unsupported command
     *
     * @returns >=0 on success otherwise an error code.
     *          AVERROR(ENOSYS) on unsupported commands
     */
    int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags);

    /**
     * Filter activation function.
     *
     * Called when any processing is needed from the filter, instead of any
     * filter_frame and request_frame on pads.
     *
     * The function must examine inlinks and outlinks and perform a single
     * step of processing. If there is nothing to do, the function must do
     * nothing and not return an error. If more steps are or may be
     * possible, it must use ff_filter_set_ready() to schedule another
     * activation.
     */
    int (*activate)(AVFilterContext *ctx);
} AVFilter;

核心为:

const char *name; // overlay
const AVFilterPad inputs;
const AVFilterPad outputs;

在使用 AVFilter 时,通常的步骤包括:

创建 AVFilterContext 对象,表示一个具体的 AVFilter 实例。
设置滤镜参数和选项,可以通过 priv_class定义的私有类进行配置。
连接输入输出端口,将 AVFilterContext 对象连接到其他滤镜或音视频流中。
将音视频帧数据送入滤镜进行处理,得到处理后的输出数据。

2.AVFilterContext-filter实例,管理filter与外部的联系

AVFilterContext 是 FFmpeg 中用于表示一个特定 AVFilter 实例的数据结构。它包含了一个 AVFilter 的所有信息和状态,以便在音视频处理中对该滤镜进行配置、连接和处理。
AVFilterContext 结构的作用是管理一个 AVFilter 实例的输入输出端口,通过输入端口接收数据并经过滤镜处理后输出到输出端口。它还负责与滤镜图(AVFilterGraph)进行交互,将滤镜上下文与其他滤镜连接起来以实现复杂的音视频处理链。

在使用 AVFilterContext 时,通常的步骤包括:

创建 AVFilterContext 对象,可以通过 avfilter_alloc_context() 函数创建。
配置 AVFilterContext 的输入输出端口,可以通过 avfilter_link() 函数连接输入输出端口。
设置滤镜参数和选项,可以通过 AVFilterContext 的属性进行配置。
将音视频帧数据送入 AVFilterContext 进行处理,得到处理后的输出数据

数据结构

/** An instance of a filter */
struct AVFilterContext {
    const AVClass *av_class;        ///< needed for av_log() and filters common options

    const AVFilter *filter;         ///< the AVFilter of which this is an instance

    char *name;                     ///< name of this filter 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

    void *priv;                     ///< private data for use by the filter

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

    /**
     * Type of multithreading being allowed/used. A combination of
     * AVFILTER_THREAD_* flags.
     *
     * May be set by the caller before initializing the filter to forbid some
     * or all kinds of multithreading for this filter. The default is allowing
     * everything.
     *
     * When the filter is initialized, this field is combined using bit AND with
     * AVFilterGraph.thread_type to get the final mask used for determining
     * allowed threading types. I.e. a threading type needs to be set in both
     * to be allowed.
     *
     * After the filter is initialized, libavfilter sets this field to the
     * threading type that is actually used (0 for no multithreading).
     */
    int thread_type;

    /**
     * An opaque struct for libavfilter internal use.
     */
    AVFilterInternal *internal;

    struct AVFilterCommand *command_queue;

    char *enable_str;               ///< enable expression string
    void *enable;                   ///< parsed expression (AVExpr*)
    double *var_values;             ///< variable values for the enable expression
    int is_disabled;                ///< the enabled state from the last expression evaluation

    /**
     * For filters which will create hardware frames, sets the device the
     * filter should create them in.  All other filters will ignore this field:
     * in particular, a filter which consumes or processes hardware frames will
     * instead use the hw_frames_ctx field in AVFilterLink to carry the
     * hardware context information.
     */
    AVBufferRef *hw_device_ctx;

    /**
     * Max number of threads allowed in this filter instance.
     * If <= 0, its value is ignored.
     * Overrides global number of threads set per filter graph.
     */
    int nb_threads;

    /**
     * Ready status of the filter.
     * A non-0 value means that the filter needs activating;
     * a higher value suggests a more urgent activation.
     */
    unsigned ready;

    /**
     * Sets the number of extra hardware frames which the filter will
     * allocate on its output links for use in following filters or by
     * the caller.
     *
     * Some hardware filters require all frames that they will use for
     * output to be defined in advance before filtering starts.  For such
     * filters, any hardware frame pools used for output must therefore be
     * of fixed size.  The extra frames set here are on top of any number
     * that the filter needs internally in order to operate normally.
     *
     * This field must be set before the graph containing this filter is
     * configured.
     */
    int extra_hw_frames;
};

重点是 

struct AVFilterContext
{
const AVFilter *filter; ///< the AVFilter of which this is an instance
char *name;   ///< name of this filter 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; // 从属于哪个AVFilterGraph
}

怎么理解呢?

 3.AVFilterLink-定义两个filters之间的联接

AVFilterLink用于连接滤镜图中两个滤镜之间的输入和输出。它包含了连接两个滤镜所需的所有信息,例如输入和输出的样本格式、缓冲区、时间戳等等。AVFilterLink还包含了有关滤镜链中帧的信息,如PTS(Presentation Time Stamp,显示时间戳)和DTS(Decoding Time Stamp,解码时间戳)。通过AVFilterLink,不同的滤镜可以在滤镜图中相互连接,从而实现视频和音频处理的各种功能。

数据结构

/**
 * 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

    enum AVMediaType type;      ///< filter media type

    /* These parameters apply only to video */
    int w;                      ///< agreed upon image width
    int h;                      ///< agreed upon image height
    AVRational sample_aspect_ratio; ///< agreed upon sample aspect ratio
    /* These parameters apply only to audio */
#if FF_API_OLD_CHANNEL_LAYOUT
    /**
     * channel layout of current buffer (see libavutil/channel_layout.h)
     * @deprecated use ch_layout
     */
    attribute_deprecated
    uint64_t channel_layout;
#endif
    int sample_rate;            ///< samples per second

    int format;                 ///< agreed upon media format

    /**
     * Define the time base used by the PTS of the frames/samples
     * which will pass through this link.
     * During the configuration stage, each filter is supposed to
     * change only the output timebase, while the timebase of the
     * input link is assumed to be an unchangeable property.
     */
    AVRational time_base;

    AVChannelLayout ch_layout;  ///< channel layout of current buffer (see libavutil/channel_layout.h)

    /*****************************************************************
     * All fields below this line are not part of the public API. They
     * may not be used outside of libavfilter and can be changed and
     * removed at will.
     * New public fields should be added right above.
     *****************************************************************
     */

    /**
     * Lists of supported formats / etc. supported by the input filter.
     */
    AVFilterFormatsConfig incfg;

    /**
     * Lists of supported formats / etc. supported by the output filter.
     */
    AVFilterFormatsConfig outcfg;

    /** stage of the initialization of the link properties (dimensions, etc) */
    enum {
        AVLINK_UNINIT = 0,      ///< not started
        AVLINK_STARTINIT,       ///< started, but incomplete
        AVLINK_INIT             ///< complete
    } init_state;

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

    /**
     * Current timestamp of the link, as defined by the most recent
     * frame(s), in link time_base units.
     */
    int64_t current_pts;

    /**
     * Current timestamp of the link, as defined by the most recent
     * frame(s), in AV_TIME_BASE units.
     */
    int64_t current_pts_us;

    /**
     * Index in the age array.
     */
    int age_index;

    /**
     * Frame rate of the stream on the link, or 1/0 if unknown or variable;
     * if left to 0/0, will be automatically copied from the first input
     * of the source filter if it exists.
     *
     * Sources should set it to the best estimation of the real frame rate.
     * If the source frame rate is unknown or variable, set this to 1/0.
     * Filters should update it if necessary depending on their function.
     * Sinks can use it to set a default output frame rate.
     * It is similar to the r_frame_rate field in AVStream.
     */
    AVRational frame_rate;

    /**
     * Minimum number of samples to filter at once. If filter_frame() is
     * called with fewer samples, it will accumulate them in fifo.
     * This field and the related ones must not be changed after filtering
     * has started.
     * If 0, all related fields are ignored.
     */
    int min_samples;

    /**
     * Maximum number of samples to filter at once. If filter_frame() is
     * called with more samples, it will split them.
     */
    int max_samples;

    /**
     * Number of past frames sent through the link.
     */
    int64_t frame_count_in, frame_count_out;

    /**
     * Number of past samples sent through the link.
     */
    int64_t sample_count_in, sample_count_out;

    /**
     * A pointer to a FFFramePool struct.
     */
    void *frame_pool;

    /**
     * True if a frame is currently wanted on the output of this filter.
     * Set when ff_request_frame() is called by the output,
     * cleared when a frame is filtered.
     */
    int frame_wanted_out;

    /**
     * For hwaccel pixel formats, this should be a reference to the
     * AVHWFramesContext describing the frames.
     */
    AVBufferRef *hw_frames_ctx;

#ifndef FF_INTERNAL_FIELDS

    /**
     * Internal structure members.
     * The fields below this limit are internal for libavfilter's use
     * and must in no way be accessed by applications.
     */
    char reserved[0xF000];

#else /* FF_INTERNAL_FIELDS */

    /**
     * Queue of frames waiting to be filtered.
     */
    FFFrameQueue fifo;

    /**
     * If set, the source filter can not generate a frame as is.
     * The goal is to avoid repeatedly calling the request_frame() method on
     * the same link.
     */
    int frame_blocked_in;

    /**
     * Link input status.
     * If not zero, all attempts of filter_frame will fail with the
     * corresponding code.
     */
    int status_in;

    /**
     * Timestamp of the input status change.
     */
    int64_t status_in_pts;

    /**
     * Link output status.
     * If not zero, all attempts of request_frame will fail with the
     * corresponding code.
     */
    int status_out;

#endif /* FF_INTERNAL_FIELDS */

};

重点:
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
struct AVFilterGraph graph;    /*** Graph the filter belongs to.*/ 属于那个 AVFilterGraph 
}

4. AVFilterPad-定义filter的输⼊/输出接⼝

AVFilterPad是FFmpeg中的结构,用于描述滤镜的输入或输出端口。滤镜可以有多个输入和输出端口,每个端口都由一个AVFilterPad结构表示。该结构包含了端口的名称、类型(输入或输出)、滤镜所支持的样本格式、是否支持多通道等信息。
在滤镜图中,滤镜之间的连接通过连接它们的输入和输出端口来实现。因此,AVFilterPad在滤镜之间建立连接时起到了关键的作用。通过AVFilterPad,FFmpeg能够正确地识别滤镜之间的连接,从而实现音视频数据的流动和处理。

/**
 * A filter pad used for either input or output.
 */
struct AVFilterPad {
    /**
     * Pad name. The name is unique among inputs and among outputs, but an
     * input may have the same name as an output. This may be NULL if this
     * pad has no need to ever be referenced by name.
     */
    const char *name;

    /**
     * AVFilterPad type.
     */
    enum AVMediaType type;

    /**
     * The filter expects writable frames from its input link,
     * duplicating data buffers if needed.
     *
     * input pads only.
     */
#define AVFILTERPAD_FLAG_NEEDS_WRITABLE                  (1 << 0)

    /**
     * The pad's name is allocated and should be freed generically.
     */
#define AVFILTERPAD_FLAG_FREE_NAME                       (1 << 1)

    /**
     * A combination of AVFILTERPAD_FLAG_* flags.
     */
    int flags;

    /**
     * Callback functions to get a video/audio buffers. If NULL,
     * the filter system will use ff_default_get_video_buffer() for video
     * and ff_default_get_audio_buffer() for audio.
     *
     * The state of the union is determined by type.
     *
     * Input pads only.
     */
    union {
        AVFrame *(*video)(AVFilterLink *link, int w, int h);
        AVFrame *(*audio)(AVFilterLink *link, int nb_samples);
    } get_buffer;

    /**
     * Filtering callback. This is where a filter receives a frame with
     * audio/video data and should do its processing.
     *
     * Input pads only.
     *
     * @return >= 0 on success, a negative AVERROR on error. This function
     * must ensure that frame is properly unreferenced on error if it
     * hasn't been passed on to another filter.
     */
    int (*filter_frame)(AVFilterLink *link, AVFrame *frame);

    /**
     * Frame request callback. A call to this should result in some progress
     * towards producing output over the given link. This should return zero
     * on success, and another value on error.
     *
     * Output pads only.
     */
    int (*request_frame)(AVFilterLink *link);

    /**
     * Link configuration callback.
     *
     * For output pads, this should set the link properties such as
     * width/height. This should NOT set the format property - that is
     * negotiated between filters by the filter system using the
     * query_formats() callback before this function is called.
     *
     * For input pads, this should check the properties of the link, and update
     * the filter's internal state as necessary.
     *
     * For both input and output filters, this should return zero on success,
     * and another value on error.
     */
    int (*config_props)(AVFilterLink *link);
};

**重点
struct AVFilterPad
{
const char *name;
AVFrame *(*get_video_buffer)(AVFilterLink *link, int w, int h);
AVFrame *(*get_audio_buffer)(AVFilterLink *link, int nb_samples);
int (*filter_frame)(AVFilterLink *link, AVFrame *frame);
int (request_frame)(AVFilterLink link);
}

ffmpeg的filter 通过avfilter_link,将各个创建好的filter按⾃⼰想要的次序链接到⼀起,然后avfilter_graph_config之后,就可以正常使⽤。
⽐较常⽤的滤镜有:scale、trim、overlay、rotate、movie、yadif。

        scale 滤镜⽤于缩放,

        trim 滤镜⽤于帧级剪切,

        overlay 滤镜⽤于视频叠加,

        rotate 滤镜实现旋转,

        movie 滤镜可以加载第三⽅的视频,

        yadif 滤镜可以去隔⾏

5 AVFilterInOut-过滤器链输⼊/输出的链接列表

在AVFilter模块中定义了AVFilter结构,每个AVFilter都是具有独⽴功能的节点,如scale filter的作⽤就是进⾏图像尺⼨变换,overlay filter的作⽤就是进⾏图像的叠加。
 

这⾥需要重点提的是两个特别的filter,⼀个是buffer,⼀个是buffersink,

  • 滤波器buffer代表filter graph中的源头,原始数据就往这个filter节点输⼊的;
  • ⽽滤波器buffersink代表filter graph中的输出节点,处理完成的数据从这个filter节点输出。

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;

6 AVFilterGraph-对filters系统的整体管理

我们可以认为 AVFilterGraph 是一个管家婆,

AVFilterGraph是FFmpeg中用于管理音视频滤镜的数据结构。它表示一个完整的滤镜图,可以包含多个输入输出,并通过连接不同的滤镜节点来实现各种音视频处理操作。

AVFilterGraph提供了创建、配置和管理滤镜的接口,允许用户构建复杂的滤镜拓扑结构,以实现音视频的处理和编辑。通AVFilterGraph,用户可以添加各种滤镜(如变换、剪切、合并、调节等)并将它们连接起来,最终实现所需的音视频处理效果。

在使用AVFilterGraph时,通常的流程包括创建滤镜图、添加输入输出流、添加滤镜节点、连接滤镜节点、设置参数等。一旦完成了滤镜图的构建,就可以将音视频帧送入滤镜图进行处理,最终得到处理后的输出。

**重点
struct AVFilterGraph
{
AVFilterContext filters;
unsigned nb_filters;
}

二 、函数使用

FFmpeg库常用函数介绍(三)
打工战士
打工战士
​关注
1 av_opt_set_bin
作用:将二进制数据作为值设置给对象。

函数原型:

int av_opt_set_bin(void *obj, const char *name, const uint8_t *val, int size, int search_flags);
obj:要设置选项的对象;

name:选项名称;

val:要设置的值;

size:值的大小;

search_flags:搜索选项的方式。例如,AV_OPT_SEARCH_CHILDREN指的是首先搜索给定对象的子对象。

返回值:负值表示失败。

2 av_opt_set_int_list
作用:将整数数组作为选项值设置给对象。

函数原型:

#define av_opt_set_int_list(obj, name, val, term, flags) \
(av_int_list_length(val, term) > INT_MAX / sizeof(*(val)) ? \ AVERROR(EINVAL) : \ av_opt_set_bin(obj, name, (const uint8_t *)(val), \
av_int_list_length(val, term) * sizeof(*(val)), flags))
obj:要设置选项的对象;

name:选项名称;

val:要设置的值,整型数组;

term:val所代表数组中的最后一个元素,其值通常为0或者-1;假设val数组为enum AVPixelFormat val[] = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_NONE},那么term的值就是AV_PIX_FMT_NONE,AV_PIX_FMT_NONE值为-1;

flags:搜索选项的方式。例如,AV_OPT_SEARCH_CHILDREN指的是首先搜索给定对象的子对象。

宏定义运行的结果:负值表示失败。

注意:从函数原型上可以看出,该宏定义其实就是调用的av_opt_set_bin。

3 av_get_default_channel_layout
作用:根据通道数返回默认的声道布局。

函数原型:

int64_t av_get_default_channel_layout(int nb_channels);
nb_channels:通道数;

返回值:声道布局。

4 av_get_sample_fmt_name
作用:根据采样格式返回采样格式的名称。

函数原型:

const char *av_get_sample_fmt_name(AVSampleFormat sample_fmt);
sample_fmt:采样格式;

返回值:成功返回采样格式的名称,失败返回NULL。

5 av_strdup
作用:拷贝一个字符串。

函数原型:

char *av_strdup(const char *s);
s:待拷贝的字符串;

返回值:成功返回拷贝的字符串,失败返回NULL。

6 avfilter_graph_parse_ptr
作用:将字符串描述的滤镜图添加到现有的滤镜图中。

函数原型:

int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters, AVFilterInOut **inputs, AVFilterInOut **outputs, void *log_ctx);
graph:现有的滤镜图;

filters:待解析的字符串描述的滤镜图;

inputs:buffersink滤镜的输入引脚;

outputs:buffer滤镜的输出引进;

log_ctx:为NULL;

返回值:负值表示失败。

7 avfilter_graph_config
作用:为滤镜图中的滤镜建立连接。

函数原型:

int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx);
graphctx:滤镜图;

log_ctx:为NULL;

返回值:负值表示失败。

8 av_frame_alloc
作用:分配一个AVFrame。

函数原型:

AVFrame *av_frame_alloc();
返回值:成功返回分配的AVFrame,失败返回NULL。

注意:用完后必须使用av_frame_free释放。

9 av_frame_free
作用:释放分配的AVFrame。

函数原型:

void av_frame_free(AVFrame **frame);
frame:待释放的AVFrame;

10 avcodec_send_packet
作用:将编码的音视频帧数据送入解码器进行解码。

函数原型:

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
avctx:解码器上下文;

avpkt:编码的音视频帧数据;

返回值: < 0表示失败。

注意:AVCodecContext必须先使用avcodec_open2打开。

11 avcodec_receive_frame
作用:从解码器中获取解码的音视频帧数据。

函数原型:

int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
avctx:解码器上下文;

frame:解码的音视频帧数据;

返回值:

0:表示成功;

AVERROR(EAGAIN):当前状态无法获取输出,必须发送新的数据到解码器;

AVERROR_EOF:解码器已完全清空;

AVERROR(EINVAL):解码器没有打开,或者它是一个编码器;

其他负值:解码失败;

12 avcodec_open2
作用:使用编解码器初始化AVCodecContext。

函数原型:

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
avctx:待初始化的AVCodecContext;

codec:编解码器;

options:一般为NULL;

返回值:负值表示失败。

13 av_buffersrc_add_frame_flags
作用:将解码后的音视频帧送入滤镜处理。

函数原型:

int av_buffersrc_add_frame_flags(AVFilterContext *buffer_src, AVFrame *frame, int flags);
buffer_src:buffer或abuffer滤镜实例;

frame:解码后的音视频帧;

flags:一般为0;

返回值:负值表示失败。

14 av_buffersink_get_frame
作用:从滤镜中获取过滤后的音视频帧。

函数原型:

int av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame);
ctx:buffersink或abuffersink滤镜实例;

frame:用于存储过滤后的音视频帧数据;

返回值:>= 0表示成功,AVERROR(EAGAIN)表示当前情况无法获取输出帧(正常状态),AVERROR_EOF表示无法获取更多输出帧,其它负值表示失败。

15 avcodec_send_frame
作用:将原始的音视频帧送入编码器进行编码。

函数原型:

int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
avctx:编码器上下文;

frame:待编码的原始音视频帧;

返回值:负值表示失败。

16 avcodec_receive_packet
作用:从编码器中获取编码的音视频帧。

函数原型:

int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
avctx:编码器上下文;

avpkt:编码的音视频帧;

返回值:

0:表示成功;

AVERROR(EAGAIN):当前状态无法获取输出,必须发送新的数据到编码器;

AVERROR_EOF:编码器已完全清空;

AVERROR(EINVAL):编码器没有打开,或者它是一个解码器;

其他负值:编码失败;

17 avcodec_alloc_context3
作用:分配一个AVCodecContext,并将其字段设为默认值。

函数原型:

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
codec:用于初始化的编解码器;

返回值:成功返回分配的AVCodecContext,失败返回NULL。

18 avcodec_free_context
作用:释放AVCodecContext。

函数原型:

void avcodec_free_context(AVCodecContext **avctx);
avctx:待释放的AVCodecContext;

19 avcodec_parameters_to_context
作用:将AVCodecParameters里的参数拷贝给AVCodecContext里的对应字段。

函数原型:

int avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par);
codec:dst AVCodecContext;

par:src AVCodecParameters;

返回值:负值表示失败。

20 av_guess_frame_rate
作用:根据容器和编解码器信息猜测帧率。

函数原型:

AVRational av_guess_frame_rate(AVFormatContext *ctx, AVStream *stream, AVFrame *frame);
ctx:stream所属的AVFormatContext;

stream:frame所属的stream;

frame:需要确定帧率的帧,一般为NULL;

返回值:猜测的帧率。

21 av_inv_q
作用:将AVRational q转换为1 / q。

函数原型:

extern "C" static inline AVRational av_inv_q(AVRational q);
q:待转换AVRational;

返回值:1 / q。

22 av_channel_layout_copy
作用:拷贝通道布局。

函数原型:

int av_channel_layout_copy(AVChannelLayout *dst, const AVChannelLayout *src);
dst:目的通道布局;

stc:源通道布局;

返回值:负值表示失败。

23 avcodec_parameters_from_context
作用:将AVCodecContext里的参数拷贝给AVCodecParameters里的对应字段。

函数原型:

int avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec);
par:dst AVCodecParameters;

codec:src AVCodecContext;

返回值:负值表示失败。

24 av_channel_layout_default
作用:获取给定通道数量的默认通道布局。

函数原型:

void av_channel_layout_default(AVChannelLayout *ch_layout, int nb_channels);
ch_layout:待初始化的通道布局;

nb_channels:通道数量;

25 av_channel_layout_describe
作用:获取描述通道布局属性的字符串,该字符串与av_channel_layout_from_string的参数格式相同,可以用来重建相同的通道布局。

函数原型:

int av_channel_layout_describe(const AVChannelLayout *channel_layout, char *buf, size_t buf_size);
channel_layout:待描述的通道布局;

buf:存储生成字符串的缓冲区;

buf_size:缓冲区的大小;

返回值:成功返回生成字符串的字节数,失败返回负值。

26 av_packet_alloc
作用:分配一个AVPacket,并将其字段设为默认值。

函数原型:

AVPacket *av_packet_alloc();
返回值:成功返回分配的AVPacket,失败返回NULL。

27 av_buffersink_get_time_base
作用:获取buffersink滤镜实例的时间基。

函数原型:

AVRational av_buffersink_get_time_base(const AVFilterContext *ctx);
ctx:buffersink滤镜实例;

返回值:buffersink滤镜实例的时间基。

28 av_packet_rescale_ts
作用:将AVPacket中的以tb_src时间基表示的计时字段(时间戳、持续时间)转换为以tb_dst时间基表示。

函数原型:

void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
pkt:待转换的AVPacket;

tb_src:源时间基,转换前的AVPacket中的计时字段以该时间基表示;

tb_dst:目的时间基,转换后的AVPacket中的计时字段以该时间基表示;

2.0 avfilter_register_all --进行滤波注册,在ffmpeg 中6.0该方法已经被弃用

2.1 获取指定名称的滤镜–avfilter_get_by_name

// 获取FFmpeg中定义的filter,调⽤该⽅法前需要先调⽤avfilter_register_all();进⾏滤波器注册
AVFilter avfilter_get_by_name(const char name);
如果找到了指定名称的滤镜,该函数将返回一个指向对应AVFilter结构体的指针。如果未找到匹配的滤镜,函数将返回NULL。

2.2 向FFmpeg中的缓冲源滤镜(Buffer Source Filter)添加视频帧或音频帧–av_buffersrc_add_frame

/*
 *参数ctx是指向Buffer Source Filter的AVFilterContext结构体指针,通过它可以访问和控制Buffer Source Filter的属性和状态。
 *参数frame是要添加到Buffer Source Filter中的AVFrame结构体指针,它包含了要添加的视频帧或音频帧的数据和相关信息。
 *函数返回一个整数值,表示操作是否成功。如果成功添加帧数据,则返回0;如果发生错    误,则返回负值。
 */
int av_buffersrc_add_frame(AVFilterContext ctx, AVFrame frame);

通过调用av_buffersrc_add_frame函数,你可以将视频帧或音频帧添加到Buffer Source Filter中,从而使得该帧数据成为滤镜链的输入。

2.3 从缓冲汇滤镜(Buffer Sink Filter)中获取输出帧数据–

av_buffersink_get_frame

/* 
 *参数 ctx 是指向 Buffer Sink Filter 的 AVFilterContext 结构体指针,通过它可以访问和控制 Buffer Sink Filter 的属性和状态。
 *函数返回一个指向 AVFrame 结构体的指针,该结构体包含了从 Buffer Sink Filter 中获取的输出帧数据。如果没有可用的输出帧数据,函数将返回 NULL。
 */
int av_buffersink_get_frame(AVFilterContext ctx, AVFrame frame);

2.4 分配并初始化一个空的滤镜图–avfilter_graph_alloc

//分配并初始化一个空的滤镜图`在这里插入代码片`
AVFilterGraph *avfilter_graph_alloc(void);

在使用滤镜图进行音视频处理时,首先需要通过 avfilter_graph_alloc 分配一个滤镜图,然后在该滤镜图上添加各种滤镜节点,构建滤镜链,最后配置和链接这些滤镜节点,从而完成音视频处理任务。

2.5 在滤镜图中创建一个新的滤镜– avfilter_graph_create_filter

/* 
 * filt_ctx: 指向指针的指针,用于返回创建的滤镜实例的 AVFilterContext 结构体指针。
 * filt: 要创建的滤镜的 AVFilter 结构体指针。
 * name: 滤镜实例的名称。这个名字是我们随便取的,叫啥不重要,重要的是要唯一
 * args: 滤镜实例的参数,可以是滤镜实例初始化时需要的参数字符串。args则是这个AVFilter的参数, 注意仅仅是这个AVFilter的参数,不是整个graph的参数。再拿Fade举例,args就可以是t=in:st=3:d=3。


 * opaque: 不透明指针,可以传递给滤镜的初始化函数。
 * graph_ctx: 滤镜图的上下文 AVFilterGraph 结构体指针,表示滤镜实例将要被添加到的滤镜图。
 * 该函数返回一个整数值,表示操作是否成功。如果成功创建滤镜实例并将其添加到滤镜图中,则返回0;如果发生错误,则返回负值。
 */
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, 
								const AVFilter *filt,const char name, 
								const char args, void *opaque,
								AVFilterGraph *graph_ctx);

avfilter_graph_create_filter函数用于在滤镜图中创建一个新的滤镜实例,并将其添加到滤镜链中。通过调用 avfilter_graph_create_filter 函数,可以在滤镜图中创建一个指定类型的滤镜实例,并配置其参数。

2.6 在滤镜图中连接两个滤镜–avfilter_link

 
/*
 * src: 源滤镜的 AVFilterContext 结构体指针,表示连接的起始滤镜。
 * srcpad: 源滤镜的输出端口索引,表示连接的起始滤镜的输出端口。
 * dst: 目标滤镜的 AVFilterContext 结构体指针,表示连接的目标滤镜。
 * dstpad: 目标滤镜的输入端口索引,表示连接的目标滤镜的输入端口。
 * 该函数返回一个整数值,表示连接是否成功。如果成功连接两个滤镜,则返回0;如果发生错误,则返回负值。
 */ 
int avfilter_link(AVFilterContext *src, unsigned srcpad,AVFilterContext *dst, unsigned dstpad);

通过调用 avfilter_link 函数,可以在滤镜图中连接两个滤镜,从而构建滤镜链。连接的源滤镜的输出端口将与目标滤镜的输入端口相连。连接后,数据将从源滤镜流向目标滤镜,进行进一步的处理。

三  AVFilter主体框架流程

在利⽤AVFilter进⾏⾳视频数据处理前先将在进⾏的处理流程绘制出来,现在以FFmpeg filter官⽅⽂档中的⼀个例⼦为例进⾏说明。

这个例⼦的处理流程如上所示,⾸先使⽤split滤波器将input流分成两路流(main和tmp),然后分别对两路流进⾏处理。对于tmp流,先经过crop滤波器进⾏裁剪处理,再经过flip滤波器进⾏垂直⽅向上的翻转操作,输出的结果命名为flip流。再将main流和flip流输⼊到overlay滤波器进⾏合成操作。上图的input就是上⾯提过的buffer源滤波器,output就是上⾯的提过的buffersink滤波器。上图中每个节点都是⼀个AVFilterContext,每个连线就是AVFliterLink。所有这些信息都统⼀由AVFilterGraph来管理。

四 示例代码

我们的代码 是将一个 yuv420p_1920_800_orig.yuv  视频,先将宽裁剪为三分之一,高不变,从0,0的位置开始裁剪;;下来再将 视频 逆时针转90度,得到最终的视频。

1.使用ffmpeg 命令得到想要的数据,用来和我们的代码进行对比

先从 2_audio_track_5s.mp4 视频中将 原始的yuv数据弄出来。裁剪之前需要知道我们裁剪出来的yuv数据的格式是啥,可以使用 ffprobe 工具获得,这样我们在裁剪或者播放的yuv的时候就可以写确定的参数了。
ffprobe 2_audio_track_5s.mp4

ffmpeg -i 2_audio_track_5s.mp4  -pix_fmt yuv420p yuv420p_1920_800_orig.yuv
ffplay -pixel_format yuv420p -video_size 1920x800 -framerate 24  yuv420p_1920_800_orig.yuv



ffmpeg -pixel_format yuv420p -video_size 1920x800 -framerate 24 -i yuv420p_1920_800_orig.yuv -vf crop=iw/3:ih:0:0  out1.yuv
ffplay -pixel_format yuv420p -video_size 640x800 -framerate 24  out1.yuv




ffmpeg -pixel_format yuv420p -video_size 1920x800 -framerate 24 -i yuv420p_1920_800_orig.yuv -vf crop=iw/3:ih:0:0,transpose=cclock  out2.yuv
ffplay -pixel_format yuv420p -video_size 800x640 -framerate 24  out2.yuv

第一步:先从 2_audio_track_5s.mp4 视频中将 原始的yuv数据弄出来。裁剪之前需要知道我们裁剪出来的yuv数据的格式是啥,可以使用 ffprobe 工具获得,这样我们在裁剪或者播放的yuv的时候就可以写确定的参数了。


ffprobe 2_audio_track_5s.mp4

可以得到 yuv的值,yuv420p(tv, bt709, progressive), 1920x800,24 fps

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '2_audio_track_5s.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.29.100
  Duration: 00:00:05.02, start: 0.000000, bitrate: 2178 kb/s
  Stream #0:0[0x1](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 183 kb/s (default)
    Metadata:
      handler_name    : 粤语
      vendor_id       : [0][0][0][0]
  Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 184 kb/s (default)
    Metadata:
      handler_name    : 国语
      vendor_id       : [0][0][0][0]
  Stream #0:2[0x3](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x800, 1847 kb/s, SAR 1:1 DAR 12:5, 24 fps, 24 tbr, 90k tbn (default)

第二步 ,使用ffmpeg 命令从 mp4文件中,得到我们想要的 原始的 yuv数据,我们给这个yuv命名为yuv420p_1920_800_orig.yuv

ffmpeg -i 2_audio_track_5s.mp4  -pix_fmt yuv420p yuv420p_1920_800_orig.yuv


ffplay -pixel_format yuv420p -video_size 1920x800 -framerate 24  yuv420p_1920_800_orig.yuv
 

第三步:我们就可以将这个 yuv数据 做filiter了,我们的目的是fliter两步,第一步是裁剪是将原来的宽变成三分之一,高不变。第二步是将裁剪完成后的数据 逆时针旋转90度。

我们用一条命令就可以完成

ffmpeg -pixel_format yuv420p -video_size 1920x800 -framerate 24 -i yuv420p_1920_800_orig.yuv -vf crop=iw/3:ih:0:0,transpose=cclock  out2.yuv

播放测试
ffplay -pixel_format yuv420p -video_size 800x640 -framerate 24  out2.yuv

注意的是:这其实是中间的一步,为了方便观察,我们将裁剪的命令单独拿出来了,目的是为了在播放的时候和原始的图片以及最终的图片进行对比(只是为了方便我们理解)

ffmpeg -pixel_format yuv420p -video_size 1920x800 -framerate 24 -i yuv420p_1920_800_orig.yuv -vf crop=iw/3:ih:0:0  out1.yuv


ffplay -pixel_format yuv420p -video_size 640x800 -framerate 24  out1.yuv

注意红色部分的 video_size,都是不同的,原始的数据是 1920x800 ,这好理解

当 裁剪第一步后,宽变成了原始的 三分之一,高不变,因此 变成了 640x800

最后还要逆时针翻转,因此变成了 800x640.

我们将三次播放的最后一帧图片列出来对比,以方便的观察。

2.代码实现

ffmpeg -i 2_audio_track_5s.mp4  -pix_fmt yuv420p yuv420p_1920_800_orig.yuv

ffplay -pixel_format yuv420p -video_size 1920x800 -framerate 24  yuv420p_1920_800_orig.yuv

ffmpeg -i 2_audio_track_5s.mp4


ffmpeg -pixel_format yuv420p -video_size 1920x800 -framerate 24 -i yuv420p_1920_800_orig.yuv -vf crop=iw/3:ih:0:0  out1.yuv
ffplay -pixel_format yuv420p -video_size 640x800 -framerate 24  out1.yuv




ffmpeg -pixel_format yuv420p -video_size 1920x800 -framerate 24 -i yuv420p_1920_800_orig.yuv -vf crop=iw/3:ih:0:0,transpose=cclock  out2.yuv
ffplay -pixel_format yuv420p -video_size 800x640 -framerate 24  out2.yuv

ffplay -pixel_format yuv420p -video_size 800x640 -framerate 24  aaaout.yuv





#include <iostream>

using namespace std;
///我们这里测试的目的是将一个mp4 文件中的 视频文件 加上一些filter,存储成yuv格式,然后播放测试



extern "C" {
#include "libavfilter/buffersrc.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libavutil/opt.h"
#include "libavfilter/buffersink.h"

}

AVFormatContext *avformatContext = nullptr;
const AVCodec * avcodec = nullptr;
AVCodecContext * avcodecContext = nullptr;

int video_stream_index = -1;
//我们对于视频的操作命令如下: crop 的意思是裁剪,就是对于原先的视频裁剪, width为原先的三分之一,height不变
//transpose=cclock的意思是 逆时针旋转90度。
char *filter_descr = "crop=iw/3:ih:0:0,transpose=cclock";


AVFilterGraph *filter_graph;
AVFilterContext *buffersrc_ctx;
AVFilterContext *buffersink_ctx;
FILE *outfile = NULL;
const char *outfilename;


int openInputfile(char * filename){
    int ret =0;
    outfile = fopen(outfilename, "wb");
    avformatContext = avformat_alloc_context();
    if(avformatContext == nullptr){
        ret = -1;
        av_log(NULL, AV_LOG_ERROR, "avformat_alloc_context fail \n");
        return ret;
    }

    ret = avformat_open_input(&avformatContext,filename,nullptr,nullptr);

    if(ret < 0 ){
        av_log(NULL, AV_LOG_ERROR, "avformat_open_input fail \n");
        return ret;
    }


    ret = avformat_find_stream_info(avformatContext,nullptr);
    if(ret < 0 ){
        av_log(NULL, AV_LOG_ERROR, "avformat_find_stream_info fail \n");
        return ret;
    }


    //通过这个可以自动的找到 解码器
    ret = av_find_best_stream(avformatContext,AVMEDIA_TYPE_VIDEO,-1,-1,&avcodec,0);
    if(ret < 0 ){
        av_log(NULL, AV_LOG_ERROR, "av_find_best_stream fail \n");
        return ret;
    }
    video_stream_index = ret;


    avcodecContext = avcodec_alloc_context3(avcodec);
    if(avcodecContext == nullptr){
        ret = -1;
        av_log(NULL, AV_LOG_ERROR, "avcodec_alloc_context3 fail \n");
        return ret;
    }


    ret = avcodec_parameters_to_context(avcodecContext,avformatContext->streams[video_stream_index]->codecpar);
    if(ret < 0 ){
        av_log(NULL, AV_LOG_ERROR, "avcodec_parameters_to_context fail \n");
        return ret;
    }


    ret = avcodec_open2(avcodecContext,avcodec,nullptr);
    if(ret < 0 ){
        av_log(NULL, AV_LOG_ERROR, "avcodec_open2 fail \n");
        return ret;
    }

    return ret;
}


int init_filters(char *filter_descr){

    int ret =0;
    char args[512];
    AVRational time_base = avformatContext->streams[video_stream_index]->time_base;
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };

    //过滤器链输⼊/输出的链接列表
    AVFilterInOut *outputs = avfilter_inout_alloc();
    AVFilterInOut *inputs  = avfilter_inout_alloc();

    //第一步 ,在ffmpeg 中调用 avfilter_register_all 方法,在ffmpeg 6.0中已经废弃,不再使用。
    //avfilter_register_all();


    //第二步,创建 AVFilterGraph,(创建滤波器图),滤波器图可以看成是整个 AVFilter过程的管家婆
    filter_graph = avfilter_graph_alloc();
    if(filter_graph == nullptr){
        cout<<"avfilter_graph_alloc error "<<endl;
        int ret = -1;
        return ret;
    }

    //第三步,创建输入AVFilter,输出AVFilter
    const AVFilter* bufferSrc = avfilter_get_by_name("buffer");   // AVFilterGraph的输入源,其中"buffer"是固定的,
    if(bufferSrc == nullptr){
        cout<<"avfilter_get_by_name buffer error "<<endl;
        int ret = -1;
        return ret;
    }

    const AVFilter* bufferSink = avfilter_get_by_name("buffersink");   // AVFilterGraph的输出源,其中"buffersink"是固定的,
    if(bufferSink == nullptr){
        cout<<"avfilter_get_by_name buffersink error "<<endl;
        int ret = -1;
        return ret;
    }

    //第四步,创建 输入 AVFilterContext, 和 输出 AVFilterContext,使用的方法是 avfilter_graph_create_filter
    //4.1 创建 输入 AVFilterContext,这里要非常清楚 avfilter_graph_create_filter 方法的各个参数
    /*
     * filt_ctx: 指向指针的指针,用于返回创建的滤镜实例的 AVFilterContext 结构体指针。
     * filt: 要创建的滤镜的 AVFilter 结构体指针。
     * name: 滤镜实例的名称。这个名字是我们随便取的,叫啥不重要,重要的是要唯一
     * args: 滤镜实例的参数,可以是滤镜实例初始化时需要的参数字符串。args则是这个AVFilter的参数, 注意仅仅是这个AVFilter的参数,不是整个graph的参数。再拿Fade举例,args就可以是t=in:st=3:d=3。


     * opaque: 不透明指针,可以传递给滤镜的初始化函数。
     * graph_ctx: 滤镜图的上下文 AVFilterGraph 结构体指针,表示滤镜实例将要被添加到的滤镜图。
     * 该函数返回一个整数值,表示操作是否成功。如果成功创建滤镜实例并将其添加到滤镜图中,则返回0;如果发生错误,则返回负值。

    int avfilter_graph_create_filter(AVFilterContext **filt_ctx,
                                    const AVFilter *filt,const char name,
                                    const char args, void *opaque,
                                    AVFilterGraph *graph_ctx);
    参数args,从结果来看,是 video_size=1920x800:pix_fmt=0:time_base=1/90000:pixel_aspect=0/1
    对应的就是 1920x800,pix_fmt是 AV_PIX_FMT_YUV420P,timebase是 1/90000
    */
    snprintf(args, sizeof(args),
             "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
             avcodecContext->width, avcodecContext->height, avcodecContext->pix_fmt,
             time_base.num, time_base.den,
             avcodecContext->sample_aspect_ratio.num, avcodecContext->sample_aspect_ratio.den);
    cout<<"args = "<< args << endl;
    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;
    }

    //4.2 创建 输出 AVFilterContext,这里要非常清楚 avfilter_graph_create_filter 方法的各个参数
    //输出 AVFilterContext 是 filter chain的最后一环,
    /* buffer video sink: to terminate the filter chain. */
    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_ctx \n");
        goto end;
    }

    /**
    #define av_opt_set_int_list(obj, name, val, term, flags)
    作用:将整数数组作为选项值设置给对象。
    obj:要设置选项的对象;
    name:选项名称;
    val:要设置的值,整型数组;
    term:val所代表数组中的最后一个元素,其值通常为0或者-1;假设val数组为enum AVPixelFormat val[] = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_NONE},那么term的值就是AV_PIX_FMT_NONE,AV_PIX_FMT_NONE值为-1;
    flags:搜索选项的方式。例如,AV_OPT_SEARCH_CHILDREN指的是首先搜索给定对象的子对象。
    宏定义运行的结果:负值表示失败。
    注意:从函数原型上可以看出,该宏定义其实就是调用的av_opt_set_bin。
    **/
    ///为什么要设置这个呢?
    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.
     * 设置过滤器图的端点。filter_graph将链接到filters_desc描述的图。
     */

    /*
     * 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;

    ///作用:将字符串描述的 滤镜图 添加到现有的滤镜图 中。这个api实际上很重要。
    /// 目的是将 将 你要处理的ffmpeg 命令的字符串filters 添加到整个 滤镜图中,以及将 过滤器链接 的最开始的输入和最终的输出 都链接到一块。

    ///
    /// int avfilter_graph_parse_ptr(AVFilterGraph *graph,
    ///                             const char *filters,
    ///                             AVFilterInOut **inputs,
    ///                             AVFilterInOut **outputs,
    ///                             void *log_ctx);
    /// graph:现有的滤镜图;
    /// filters:待解析的字符串描述的滤镜图;也就是对应我们的 ffmpeg的命令
    /// inputs:buffersink滤镜的输入引脚;
    /// outputs:buffer滤镜的输出引进;
    /// log_ctx:为NULL;
    /// 返回值:负值表示失败。

    if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_descr,
                                        &inputs, &outputs, NULL)) < 0)
        goto end;

    /// 在没有看源码之前,我们可以想象一下,ffmpeg大致是怎么实现这个api的,肯定是需要 拆分字符串,用逗号分隔,然后 使用avfilter_link将每一个都链接起来,组成 责任链。
    if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
        goto end;


    cout<<"init_filters debug"<<endl;

end:

    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);

    return ret;
}


int main()
{
    cout << "Hello World!" << endl;
    AVPacket *avpacket = nullptr;
    AVFrame *avframe = nullptr;
    AVFrame *avfilterframe = nullptr;

    int ret =0;
    char * sourcename = "D:/AllInformation/qtworkspacenew/0902videofilter/2_audio_track_5s.mp4";
    outfilename = "D:/AllInformation/qtworkspacenew/0902videofilter/aaaout.yuv";

    ret = openInputfile(sourcename);
    if(ret <0){
        goto mainend;
    }

    ret = init_filters(filter_descr);
    if(ret <0){
        goto mainend;
    }

    avpacket = av_packet_alloc();
    avframe = av_frame_alloc();
    avfilterframe = av_frame_alloc();
    //开始读取数据
    while(1){
        ret = av_read_frame(avformatContext,avpacket);
        if(ret < 0){
            break;
        }

        //判断当是 video stream index的时候
        if(avpacket->stream_index == video_stream_index){
            //将数据从avpacket 发送到 avcodecContext,目的是
            ret = avcodec_send_packet(avcodecContext,avpacket);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");
                break;
            }
            while (ret >= 0){
                ret = avcodec_receive_frame(avcodecContext, avframe);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    //AVERROR_EOF 的意思是已经读取到文件的最后了,EAGAIN 的意思是重新试一下
                    break;
                } else if(ret < 0){
                    //这行是真的出现问题了
                    av_log(NULL, AV_LOG_ERROR, "Error while receiving a frame from the decoder\n");
                    goto mainend;
                }


                //best_effort_timestamp 的是解码器根据算法算出来 pts
                avframe->pts = avframe->best_effort_timestamp;


                /* push the decoded frame into the filtergraph */
                if (av_buffersrc_add_frame_flags(buffersrc_ctx, avframe, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) {
                    av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
                    break;
                }

                /* pull filtered frames from the filtergraph */
                while (1) {
                    ret = av_buffersink_get_frame(buffersink_ctx, avfilterframe);
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                        break;
                    if (ret < 0)
                        goto mainend;
                    /// ffmpeg 源码中示例中,这块是直接display frame,我们这里是直接将数据存储到一个 codeaaa.yuv中
                    ///                    display_frame(avfilterframe, buffersink_ctx->inputs[0]->time_base);
                    printf("video frame data = %f \n", (avfilterframe->width) * (avfilterframe->height) * 1.5);
                    printf("frame->line[0] = %d \n",avfilterframe->linesize[0]);
                    printf("frame->line[1] = %d \n",avfilterframe->linesize[1]);
                    printf("frame->line[2] = %d \n",avfilterframe->linesize[2]);
                    printf("frame->pkt_size = %d \n",avfilterframe->pkt_size);
                    printf("avfilterframe->width = %d \n",avfilterframe->width);
                    printf("avfilterframe->height = %d \n",avfilterframe->height);


                    for(int j=0; j<avfilterframe->height; j++)
                        fwrite(avfilterframe->data[0] + j * avfilterframe->linesize[0], 1, avfilterframe->width, outfile);
                    for(int j=0; j<avfilterframe->height/2; j++)
                        fwrite(avfilterframe->data[1] + j * avfilterframe->linesize[1], 1, avfilterframe->width/2, outfile);
                    for(int j=0; j<avfilterframe->height/2; j++)
                        fwrite(avfilterframe->data[2] + j * avfilterframe->linesize[2], 1, avfilterframe->width/2, outfile);
                    av_frame_unref(avfilterframe);
                }
                av_frame_unref(avframe);
            }
        }
    }

    cout<<"main func debug end"<<endl;

mainend:
    if(avformatContext){
        avformat_close_input(&avformatContext);
    }
    if(avpacket){
        av_packet_free(&avpacket);
    }

    if(avframe){
        av_frame_free(&avframe);
    }

    if(avfilterframe){
        av_frame_free(&avfilterframe);
    }






    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值