本文為作者原創,轉載請注明出處:https://www.cnblogs.com/leisure_chn/p/10429145.html
在 FFmpeg 中,濾鏡(filter)處理的是未壓縮的原始音視頻數據(RGB/YUV視頻幀,PCM音頻幀等)。一個濾鏡的輸出可以連接到另一個濾鏡的輸入,多個濾鏡可以連接起來,構成濾鏡鏈/濾鏡圖,各種濾鏡的組合為 FFmpeg 提供了豐富的音視頻處理功能。
比較常用的濾鏡有:scale、trim、overlay、rotate、movie、yadif。scale 濾鏡用於縮放,trim 濾鏡用於幀級剪切,overlay 濾鏡用於視頻疊加,rotate 濾鏡實現旋轉,movie 濾鏡可以加載第三方的視頻,yadif 濾鏡可以去隔行。
本文將通過實例詳細介紹濾鏡 API 的使用方法。
1. 濾鏡的構成及命令行用法
參考 “FFmpeg使用基礎” 第 4 節 “濾鏡”。
2. 濾鏡數據結構與API簡介
待補充
2.1 struct AVFilter
/**
* Filter definition. This defines the pads a filter contains, and all the
* callback functions used to interact with the filter.
*/
typedef struct AVFilter {
const char *name;
const char *description;
const AVFilterPad *inputs;
const AVFilterPad *outputs;
const AVClass *priv_class;
int flags;
// private API
......
} AVFilter;
2.2 struct 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
......
};
2.3 struct AVFilterGraph
typedef struct AVFilterGraph {
const AVClass *av_class;
AVFilterContext **filters;
unsigned nb_filters;
......
} AVFilterGraph;
2.4 struct 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
......
}
2.5 struct AVFilterInOut
/**
* 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;
2.6 avfilter_graph_create_filter()
/**
* Create and add a filter instance into an existing graph.
* The filter instance is created from the filter filt and inited
* with the parameters args and opaque.
*
* In case of success put in *filt_ctx the pointer to the created
* filter instance, otherwise set *filt_ctx to NULL.
*
* @param name the instance name to give to the created filter instance
* @param graph_ctx the filter graph
* @return a negative AVERROR error code in case of failure, a non
* negative value otherwise
*/
int avfilter_graph_create_filter(AVFilterContext **filt_ctx, const AVFilter *filt,
const char *name, const char *args, void *opaque,
AVFilterGraph *graph_ctx);
2.7 avfilter_graph_parse_ptr()
/**
* Add a graph described by a string to a graph.
*
* In the graph filters description, if the input label of the first
* filter is not specified, "in" is assumed; if the output label of
* the last filter is not specified, "out" is assumed.
*
* @param graph the filter graph where to link the parsed graph context
* @param filters string to be parsed
* @param inputs pointer to a linked list to the inputs of the graph, may be NULL.
* If non-NULL, *inputs is updated to contain the list of open inputs
* after the parsing, should be freed with avfilter_inout_free().
* @param outputs pointer to a linked list to the outputs of the graph, may be NULL.
* If non-NULL, *outputs is updated to contain the list of open outputs
* after the parsing, should be freed with avfilter_inout_free().
* @return non negative on success, a negative AVERROR code on error
*/
int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
AVFilterInOut **inputs, AVFilterInOut **outputs,
void *log_ctx);
2.8 avfilter_graph_config()
/**
* Check validity and configure all the links and formats in the graph.
*
* @param graphctx the filter graph
* @param log_ctx context used for logging
* @return >= 0 in case of success, a negative AVERROR code otherwise
*/
int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx);
2.9 av_buffersrc_add_frame_flags()
/**
* Add a frame to the buffer source.
*
* By default, if the frame is reference-counted, this function will take
* ownership of the reference(s) and reset the frame. This can be controlled
* using the flags.
*
* If this function returns an error, the input frame is not touched.
*
* @param buffer_src pointer to a buffer source context
* @param frame a frame, or NULL to mark EOF
* @param flags a combination of AV_BUFFERSRC_FLAG_*
* @return >= 0 in case of success, a negative AVERROR code
* in case of failure
*/
av_warn_unused_result
int av_buffersrc_add_frame_flags(AVFilterContext *buffer_src,
AVFrame *frame, int flags);
2.10 av_buffersink_get_frame()
/**
* Get a frame with filtered data from sink and put it in frame.
*
* @param ctx pointer to a context of a buffersink or abuffersink AVFilter.
* @param frame pointer to an allocated frame that will be filled with data.
* The data must be freed using av_frame_unref() / av_frame_free()
*
* @return
* - >= 0 if a frame was successfully returned.
* - AVERROR(EAGAIN) if no frames are available at this point; more
* input frames must be added to the filtergraph to get more output.
* - AVERROR_EOF if there will be no more output frames on this sink.
* - A different negative AVERROR code in other failure cases.
*/
int av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame);
3. 濾鏡API使用方法
在代碼中使用濾鏡,主要分為兩個步驟:
[1]. 濾鏡的初始化配置:根據濾鏡參數,配置生成濾鏡圖,此濾鏡圖供下一步驟使用
[2]. 使用濾鏡處理原始音視頻幀:向濾鏡圖提供輸入幀(AVFrame),從濾鏡圖取出經處理后的輸出幀(AVFrame)
1. init_filters() // 配置生成可用的濾鏡圖,由用戶編寫
2. av_buffersrc_add_frame_flags() // 向濾鏡圖提供輸入幀,API函數
3. av_buffersink_get_frame() // 從濾鏡圖取出處理后的輸出幀,API函數
3.1 濾鏡配置
在代碼中,濾鏡配置比濾鏡使用復雜,濾鏡配置代碼如下:
// 功能:創建配置一個濾鏡圖,在后續濾鏡處理中,可以往此濾鏡圖輸入數據並從濾鏡圖獲得輸出數據
// filters_descr:輸入參數,形如“transpose=cclock,pad=iw+80:ih:40”
// @vfmt:輸入參數,描述提供給待生成濾鏡圖的視頻幀和格式
// @fctx:輸出參數,返回生成濾鏡圖的信息,供調用者使用
int init_video_filters(const char *filters_descr, const input_vfmt_t *vfmt, filter_ctx_t *fctx)
{
int ret = 0;
// 1. 配置濾鏡圖輸入端和輸出端
// 分配一個濾鏡圖filter_graph
fctx->filter_graph = avfilter_graph_alloc();
if (!fctx->filter_graph)
{
ret = AVERROR(ENOMEM);
goto end;
}
char args[512];
char *p_args = NULL;
if (vfmt != NULL)
{
/* 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",
vfmt->width, vfmt->height, vfmt->pix_fmt,
vfmt->time_base.num, vfmt->time_base.den,
vfmt->sar.num, vfmt->sar.den);
p_args = args;
}
// buffer濾鏡:緩沖視頻幀,作為濾鏡圖的輸入
const AVFilter *bufsrc = avfilter_get_by_name("buffer");
// 創建濾鏡實例fctx->bufsrc_ctx,此濾鏡實例從bufsrc中創建,並使用參數p_args進行初始化
// 新創建的濾鏡實例命名為"in",並被添加到濾鏡圖fctx->filter_graph中
ret = avfilter_graph_create_filter(&fctx->bufsrc_ctx, bufsrc, "in",
p_args, NULL, fctx->filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");
goto end;
}
// buffersink濾鏡:緩沖視頻幀,作為濾鏡圖的輸出
const AVFilter *bufsink = avfilter_get_by_name("buffersink");
/* buffer video sink: to terminate the filter chain. */
// 創建濾鏡實例buffersink_ctx,此濾鏡實例從bufsink中創建
// 新創建的濾鏡實例命名為"out",並被添加到濾鏡圖fctx->filter_graph中
ret = avfilter_graph_create_filter(&fctx->bufsink_ctx, bufsink, "out",
NULL, NULL, fctx->filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
goto end;
}
#if 0 // 因為后面顯示視頻幀時有sws_scale()進行圖像格式轉換,故此處不設置濾鏡輸出格式也可
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_NONE };
// 設置輸出像素格式為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;
}
#endif
// 1. end
// 2. 將filters_descr描述的濾鏡圖添加到fctx->filter_graph濾鏡圖中
/*
* Set the endpoints for the filter graph. The filter_graph will
* be linked to the graph described by filters_descr.
*/
// 設置濾鏡圖的端點,將filters_descr描述的濾鏡圖連接到此濾鏡圖,
// 兩個濾鏡圖的連接是通過端點連接(AVFilterInOut)完成的
/*
* 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",就實現了同標號相連
AVFilterInOut *outputs = avfilter_inout_alloc();
outputs->name = av_strdup("in");
outputs->filter_ctx = fctx->bufsrc_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",就實現了
// 同標號相連
AVFilterInOut *inputs = avfilter_inout_alloc();
inputs->name = av_strdup("out");
inputs->filter_ctx = fctx->bufsink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
// 將filters_descr描述的濾鏡圖添加到fctx->filter_graph濾鏡圖中
// 調用前:fctx->filter_graph包含兩個濾鏡fctx->bufsrc_ctx和fctx->bufsink_ctx
// 調用后:filters_descr描述的濾鏡圖插入到fctx->filter_graph中,fctx->bufsrc_ctx連接到filters_descr
// 的輸入,filters_descr的輸出連接到fctx->bufsink_ctx,filters_descr只進行了解析而不
// 建立內部濾鏡間的連接。filters_desc與fctx->filter_graph間的連接是利用AVFilterInOut inputs
// 和AVFilterInOut outputs連接起來的,AVFilterInOut是一個鏈表,最終可用的連在一起的
// 濾鏡鏈/濾鏡圖就是通過這個鏈表串在一起的。
ret = avfilter_graph_parse_ptr(fctx->filter_graph, filters_descr,
&inputs, &outputs, NULL);
if (ret < 0)
{
goto end;
}
// 2. end
// 3. 配置filtergraph濾鏡圖,建立濾鏡間的連接
// 驗證有效性並配置filtergraph中所有連接和格式
ret = avfilter_graph_config(fctx->filter_graph, NULL);
if (ret < 0)
{
goto end;
}
// 3. end
end:
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
函數參數說明:
-
輸入參數
const char *filters_descr
以字符串形式提供濾鏡選項,例如參數為 "transpose=cclock,pad=iw+80:ih:40" 時,表示將視頻幀逆時針旋轉 90 度,然后在視頻左右各填充 40 像素的黑邊。 -
輸入參數
input_vfmt_t *vfmt
用於描述提供給濾鏡圖的視頻幀和格式,在配置濾鏡圖中的第一個濾鏡 buffer 時需要為濾鏡提供參數,就是從 vfmt 參數轉換得到。
input_vfmt_t 為自定義數據結構,定義如下:
typedef struct {
int width;
int height;
enum AVPixelFormat pix_fmt;
AVRational time_base;
AVRational sar;
AVRational frame_rate;
} input_vfmt_t;
- 輸出參數
filter_ctx_t *fctx
用於返回生成濾鏡圖的信息,供調用者使用。
filter_ctx_t 為自定義數據結構,定義如下:
typedef struct {
AVFilterContext *bufsink_ctx;
AVFilterContext *bufsrc_ctx;
AVFilterGraph *filter_graph;
} filter_ctx_t;
此結構中三個成員:bufsrc_ctx 用於濾鏡圖的輸入,bufsink_ctx 用於濾鏡圖的輸出,filter_graph 指向濾鏡圖。
TODO: 一個濾鏡圖可能含多個濾鏡鏈,即可能有多個輸入節點(bufsrc_ctx)或多個輸出節點(bufsink_ctx),此數據結構應改進為支持多輸入和多輸出
init_video_filters() 函數實現的幾個步驟如下:
3.1.1 配置濾鏡圖輸入端和輸出端
buffer 濾鏡和 buffersink 濾鏡是兩個特殊的視頻濾鏡,分別用於視頻濾鏡鏈的輸入端和輸出端。與之相似,abuffer 濾鏡和 abuffersink 濾鏡是兩個特殊的音頻濾鏡,分別用於音頻濾鏡鏈的輸入端和輸出端。
一個濾鏡圖可能由多個濾鏡鏈構成,每個濾鏡鏈的輸入節點就是 buffer 濾鏡,輸出節點是 buffersink 濾鏡,因此一個濾鏡圖可能有多個 buffer 濾鏡,也可能有多個 buffersink 濾鏡。應用程序通過訪問 buffer 濾鏡和 buffersink 濾鏡實現和濾鏡圖的數據交互。
buffer 濾鏡
在命令行中輸入 ffmpeg -h filter=buffer 查看 buffer 濾鏡的幫助信息,如下:
$ ffmpeg -h filter=buffer
ffmpeg version 4.1 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 濾鏡用作濾鏡鏈的輸入節點。buffer 濾鏡緩沖視頻幀,濾鏡鏈可以從 buffer 濾鏡中取得視頻幀數據。
在上述幫助信息中,Inputs 和 Outputs 指濾鏡的輸入引腳和輸出引腳。buffer 濾鏡是濾鏡鏈中的第一個濾鏡,因此只有輸出引腳而無輸入引腳。
濾鏡(AVFilter)需要通過濾鏡實例(AVFilterContext)引用,為 buffer 濾鏡創建的濾鏡實例是 fctx->bufsrc_ctx,用戶通過往 fctx->bufsrc_ctx 填入視頻幀來為濾鏡鏈提供輸入。
為 buffer 濾鏡創建濾鏡實例時需要提供參數,buffer 濾鏡需要的參數在幫助信息中的 “buffer AVOptions” 部分列出,由 vfmt 輸入參數提供,代碼如下:
char args[512];
char *p_args = NULL;
if (vfmt != NULL)
{
/* 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",
vfmt->width, vfmt->height, vfmt->pix_fmt,
vfmt->time_base.num, vfmt->time_base.den,
vfmt->sar.num, vfmt->sar.den);
p_args = args;
}
// buffer濾鏡:緩沖視頻幀,作為濾鏡圖的輸入
const AVFilter *bufsrc = avfilter_get_by_name("buffer");
// 創建濾鏡實例fctx->bufsrc_ctx,此濾鏡實例從bufsrc中創建,並使用參數p_args進行初始化
// 新創建的濾鏡實例命名為"in",並被添加到濾鏡圖fctx->filter_graph中
ret = avfilter_graph_create_filter(&fctx->bufsrc_ctx, bufsrc, "in",
p_args, NULL, fctx->filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");
goto end;
}
buffersink 濾鏡
在命令行中輸入 ffmpeg -h filter=buffersink 查看 buffersink 濾鏡的幫助信息,如下:
$ ffmpeg -h filter=buffersink
ffmpeg version 4.1 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
buffersink 濾鏡用作濾鏡鏈的輸出節點。濾鏡鏈處理后的視頻幀可以緩存到 buffersink 濾鏡中。buffersink 濾鏡是濾鏡鏈中的最后一個濾鏡,因此只有輸入引腳而無輸出引腳。
為 buffersink 濾鏡創建的濾鏡實例是 fctx->bufsink_ctx,用戶可以從 fctx->bufsink_ctx 中讀視頻幀來獲得濾鏡鏈的輸出。
通過幫助信息可以看到,buffersink 濾鏡參數只有一個 “pix_fmt”,用於設置濾鏡鏈輸出幀的像素格式列表,這個像素格式有多種,以限制輸出幀格式不超過指定的范圍。
// buffersink濾鏡:緩沖視頻幀,作為濾鏡圖的輸出
const AVFilter *bufsink = avfilter_get_by_name("buffersink");
// 為buffersink濾鏡創建濾鏡實例buffersink_ctx,命名為"out"
// 將新創建的濾鏡實例buffersink_ctx添加到濾鏡圖filter_graph中
ret = avfilter_graph_create_filter(&fctx->bufsink_ctx, bufsink, "out",
NULL, NULL, fctx->filter_graph);
#if 0 // 因為后面顯示視頻幀時有sws_scale()進行圖像格式轉換,故此處不設置濾鏡輸出格式也可
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_NONE };
// 設置輸出像素格式為pix_fmts[]中指定的格式(如果要用SDL顯示,則這些格式應是SDL支持格式)
ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
#endif
將 buffer 濾鏡和 buffsink 濾鏡添加進濾鏡圖中后,如下圖所示:

3.1.2 將 filters_descr 描述的濾鏡插入濾鏡圖中
解析濾鏡選項(filters_descr),將解析得到的濾鏡插入第 1 步構造的濾鏡圖中,並與濾鏡圖輸入端和輸出端連接起來
// 設置濾鏡圖的端點,將filters_descr描述的濾鏡圖連接到此濾鏡圖
// 兩個濾鏡圖的連接是通過端點(AVFilterInOut)連接完成的
// 端點數據結構AVFilterInOut主要用於avfilter_graph_parse()系列函數
// outputs變量意指buffersrc_ctx濾鏡的輸出引腳(output pad)
// src緩沖區(buffersrc_ctx濾鏡)的輸出必須連到filters_descr中第一個
// 濾鏡的輸入;filters_descr中第一個濾鏡的輸入標號未指定,故默認為
// "in",此處將buffersrc_ctx的輸出標號也設為"in",就實現了同標號相連
AVFilterInOut *outputs = avfilter_inout_alloc();
outputs->name = av_strdup("in");
outputs->filter_ctx = fctx->bufsrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
// inputs變量意指buffersink_ctx濾鏡的輸入引腳(input pad)
// sink緩沖區(buffersink_ctx濾鏡)的輸入必須連到filters_descr中最后
// 一個濾鏡的輸出;filters_descr中最后一個濾鏡的輸出標號未指定,故
// 默認為"out",此處將buffersink_ctx的輸出標號也設為"out",就實現了
// 同標號相連
AVFilterInOut *inputs = avfilter_inout_alloc();
inputs->name = av_strdup("out");
inputs->filter_ctx = fctx->bufsink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
// 將filters_descr描述的濾鏡圖添加到filter_graph濾鏡圖中
// 調用前: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 outputs連接起來的,AVFilterInOut是一個鏈表,最終可用的連在一起的
// 濾鏡鏈/濾鏡圖就是通過這個鏈表串在一起的。
ret = avfilter_graph_parse_ptr(fctx->filter_graph, filters_descr,
&inputs, &outputs, NULL);
filters_descr 描述的濾鏡如下圖所示:

調用 avfilter_graph_parse_ptr() 后,濾鏡圖如下所示:

3.1.3. 建立濾鏡連接
調用 avfilter_graph_config() 將上一步得到的濾鏡圖進行配置,建立濾鏡間的連接,此步完成后即生了一個可用的濾鏡圖,如下圖所示:

3.2 使用濾鏡處理原始幀
配置好濾鏡后,可在音視頻處理過程中使用濾鏡。使用濾鏡比配置濾鏡簡單很多,主要調用如下兩個 API 函數:
- 調用 av_buffersrc_add_frame_flags() 將音視頻幀發送給濾鏡
- 調用 av_buffersink_get_frame() 取得經濾鏡處理后的音視頻幀
4. 濾鏡 API 應用實例分析
濾鏡接收原始音視頻幀,經過各種效果的濾鏡處理后輸出的仍然是原始音視頻幀。在濾鏡 API 應用實例中,核心內容是 “濾鏡配置” 和 “濾鏡使用” 兩個部分,濾鏡接收什么樣的輸入源不重要,對濾鏡的輸出做什么處理也不重要。不同的輸入源,及不同的輸出處理方式僅僅是為了加深對濾鏡 API 使用的理解,以及方便觀察濾鏡的處理效果。
濾鏡的輸入可以是解碼器的輸出、原始 YUV 文件及測試圖。本文三個示例只針對視頻濾鏡:
示例 1:編碼器的輸出作為濾鏡的輸入,濾鏡的輸出簡單處理,無法觀察濾鏡效果。
示例 2:編碼器的輸出作為濾鏡的輸入,濾鏡的輸出可以播放,可直觀觀察濾鏡效果。
示例 3:測試圖作為濾鏡的輸入(而測試圖本身也是由特殊濾鏡生成),濾鏡的輸出可以播放,可直接觀察濾鏡效果。
示例 1 源碼下載:https://github.com/FFmpeg/FFmpeg/blob/n4.1/doc/examples/filtering_video.c
示例 2 與示例 3 源碼下載(shell中運行如下命令):
svn checkout https://github.com/leichn/exercises/trunk/source/ffmpeg/ffmpeg_vfilter/
4.1 示例 1:官方例程
官方例程實現的功能是:打開一個視頻文件,解碼后經過濾鏡處理,然后以簡單灰度模式在命令窗口中播放視頻幀。
例程中使用的濾鏡選項是 "scale=78:24,transpose=cclock",表示先用 scale 濾鏡將視頻幀縮放到 78x24 像素,再用 transpose 濾鏡將視頻幀逆時針旋轉 90 度。
簡述一下例程的步驟:
- 打開視頻文件,調用 open_input_file() 實現
- 初始化濾鏡,調用 init_filters() 實現
- 解碼得到視頻幀,調用 avcodec_send_packet() 和 avcodec_receive_frame() 獲得解碼后的原始視頻幀
- 將視頻幀發給濾鏡,調用 av_buffersrc_add_frame_flags() 實現
- 從濾鏡輸出端取視頻幀,調用 av_buffersink_get_frame() 實現
- 播放視頻幀,調用 display_frame() 實現
例程核心是濾鏡相關的代碼,因此視頻幀播放部分做了簡化處理。
4.2 示例 2:可播放版本
官方例程主要演示濾鏡 API 的使用方法,代碼量較少,簡化了視頻播放部分,這樣使得濾鏡的處理效果無法直觀觀察。示例 2 針對此問題,在官方代碼基礎上增加了正常的視頻播放效果。
4.2.1 代碼
下載代碼后,源碼目錄下有如下幾個文件,說明如下:
vfilter_filesrc.c 用於示例2:輸入源為視頻文件,經濾鏡處理后播放
vfilter_testsrc.c 用於示例3:輸入源為測試圖,經濾鏡處理后播放
video_filter.c 濾鏡處理功能
video_play.c 視頻播放功能
Makefile
video_filter.c 封裝了濾鏡處理相關代碼,詳參本文第 3 節。
video_play.c 實現了視頻播放功能,本例無需過多關注,實現原理可參考如下兩篇文章:
“FFmpeg簡易播放器的實現-視頻播放”
“ffplay源碼分析5-圖像格式轉換”
vfilter_filesrc.c 是示例 2 的主程序,實現了打開視頻文件、解碼、濾鏡處理、播放主流程
4.2.2 編譯
進入源碼目錄,編譯生成 vf_file 可執行文件:
make vf_file
4.2.3 測試
在命令行運行:
./vf_file ./ring.flv -vf crop=iw/2:ih:0:0,pad=iw*2:ih`
濾鏡選項 "-vf crop=iw/2:ih:0:0,pad=iw*2:ih" 表示先將視頻裁剪為一半寬度,再填充為二倍寬度,預期結果為視頻的右半部分為黑邊。
測試文件下載(右鍵另存為):ring.flv
未經濾鏡處理和經過濾鏡處理的視頻效果對比如下兩圖所示:


4.3 示例 3:測試圖作輸入源
示例 3 使用測試圖(test pattern)作為濾鏡的輸入,測試圖(test pattern)是由 FFmpeg 內部產生的測試圖案,用於測試非常方便。
因測試圖直接輸出原始視頻幀,不需解碼器,因此示例 3 中用到 AVFilter 庫,不需要用到 AVFormat 庫。
4.3.1 代碼
4.2 節下載得到的源碼中的 vfilter_testsrc.c 就是示例 3 的主程序,實現了構建測試源,濾鏡處理,播放的主流程。除濾鏡輸入源的獲取方式與示例 2 不同之外,其他過程並無不同。
示例 3 增加的關鍵內容是構造測試源,參考 vfilter_testsrc.c 中如下函數:
// @filter [i] 產生測試圖案的filter
// @vfmt [i] @filter的參數
// @fctx [o] 用戶定義的數據類型,輸出供調用者使用
static int open_testsrc(const char *filter, const input_vfmt_t *vfmt, filter_ctx_t *fctx)
{
int ret = 0;
// 分配一個濾鏡圖filter_graph
fctx->filter_graph = avfilter_graph_alloc();
if (!fctx->filter_graph)
{
return AVERROR(ENOMEM);
}
// source濾鏡:合法值有"testsrc"/"smptebars"/"color"/...
const AVFilter *bufsrc = avfilter_get_by_name(filter);
// 為buffersrc濾鏡創建濾鏡實例buffersrc_ctx,命名為"in"
// 將新創建的濾鏡實例buffersrc_ctx添加到濾鏡圖filter_graph中
ret = avfilter_graph_create_filter(&fctx->bufsrc_ctx, bufsrc, "in",
NULL, NULL, fctx->filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create filter testsrc\n");
goto end;
}
// "buffersink"濾鏡:緩沖視頻幀,作為濾鏡圖的輸出
const AVFilter *bufsink = avfilter_get_by_name("buffersink");
/* buffer video sink: to terminate the filter chain. */
// 為buffersink濾鏡創建濾鏡實例buffersink_ctx,命名為"out"
// 將新創建的濾鏡實例buffersink_ctx添加到濾鏡圖filter_graph中
ret = avfilter_graph_create_filter(&fctx->bufsink_ctx, bufsink, "out",
NULL, NULL, fctx->filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create filter buffersink\n");
goto end;
}
if ((ret = avfilter_link(fctx->bufsrc_ctx, 0, fctx->bufsink_ctx, 0)) < 0)
{
goto end;
}
// 驗證有效性並配置filtergraph中所有連接和格式
ret = avfilter_graph_config(fctx->filter_graph, NULL);
if (ret < 0)
{
goto end;
}
vfmt->pix_fmt = av_buffersink_get_format(fctx->bufsink_ctx);
vfmt->width = av_buffersink_get_w(fctx->bufsink_ctx);
vfmt->height = av_buffersink_get_h(fctx->bufsink_ctx);
vfmt->sar = av_buffersink_get_sample_aspect_ratio(fctx->bufsink_ctx);
vfmt->time_base = av_buffersink_get_time_base(fctx->bufsink_ctx);
vfmt->frame_rate = av_buffersink_get_frame_rate(fctx->bufsink_ctx);
av_log(NULL, AV_LOG_INFO, "probe video format: "
"%dx%d, pix_fmt %d, SAR %d/%d, tb %d/%d, rate %d/%d\n",
vfmt->width, vfmt->height, vfmt->pix_fmt,
vfmt->sar.num, vfmt->sar.den,
vfmt->time_base.num, vfmt->time_base.den,
vfmt->frame_rate.num, vfmt->frame_rate.den);
return 0;
end:
avfilter_graph_free(&fctx->filter_graph);
return ret;
}
測試源的本質是使用 FFmpeg 提供的用於產生測試圖案的濾鏡來生成視頻數據。具體到代碼實現層面,將 testsrc/smptebars 等濾鏡代替常用的 buffer 濾鏡作為源濾鏡,然后直接與 buffersink 濾鏡相連,以輸出測試圖案,如下圖:

4.3.2 編譯
進入源碼目錄,編譯生成 vf_test 可執行文件:
make vf_test
4.3.3 測試
濾鏡選項 "-vf transpose=cclock,pad=iw+80:ih:40" 表示先將視頻逆時針旋轉 90 度,然后將視頻左右兩邊各增加 40 像素寬度的黑邊
使用“testsrc”測試圖作輸入源
運行如下命令:
ffplay -f lavfi -i testsrc
無濾鏡處理的效果如圖所示:

運行帶濾鏡選項的 ffplay 命令:
ffplay -f lavfi -i testsrc -vf transpose=cclock,pad=iw+80:ih:40
運行帶濾鏡選項的測試程序(效果等同於上述 ffplay 命令):
./vf_test testsrc -vf transpose=cclock,pad=iw+80:ih:40
經濾鏡處理的效果如圖所示:

使用“smptebars”測試圖作輸入源
運行如下命令:
ffplay -f lavfi -i smptebars
無濾鏡處理的效果如圖所示:

運行帶濾鏡選項的ffplay命令:
ffplay -f lavfi -i smptebars -vf transpose=cclock,pad=iw+80:ih:40
運行帶濾鏡選項的測試程序(效果等同於上述ffplay命令):
./vf_test smptebars -vf transpose=cclock,pad=iw+80:ih:40
經濾鏡處理的效果如圖所示:

5. 遺留問題
[1] 不支持多輸入多輸出的復雜濾鏡圖,待改進驗證
[2] 如何使用 API 以類似打開普通輸入文件的方法來獲取測試圖的格式,即ffprobe -f lavfi -i testsrc的內部原理是什么?
think@linux-1phi:~> ffprobe -f lavfi -i testsrc
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, lavfi, from 'testsrc':
Duration: N/A, start: 0.000000, bitrate: N/A
Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 320x240 [SAR 1:1 DAR 4:3], 25 tbr, 25 tbn, 25 tbc
6. 參考資料
[1] 劉歧,FFmpeg Filter深度應用,https://yq.aliyun.com/articles/628153?utm_content=m_1000014065
7. 修改記錄
2019-02-24 V1.0 初稿
