前一篇文章《为FFmpeg添加自定义滤镜》详细讲述了FFmpeg的滤镜添加步骤,并给出了代码实例。
本文将以FFmpeg自带的deinterlace滤镜”yadif –
yet another deinterlace filter”为例分析
FFmpeg滤镜的代码级实现机制。
总的来说,FFmpeg的滤镜机制和MicroSoft
Directshow机制基本相同——不知道谁学了谁的,还是程
序员所见略同:
构建机制上:FFmpeg自己实现了统一的对象模型,DShow是COM的实现;
框架实现上:都使用filter
graph来管理滤镜;
连接方式上:FFmpeg使用Pad结构体,DShow使用Pin,两者都是分配器的机制;
下面是以层级来分析代码:
FFmpeg转码层的伪码如下所示,红色粗体是和filter相关的函数:
点击(此处)折叠或打开
int transcode(AVFormatContext **output_files,
int nb_output_files,
AVInputFile *input_files,
int nb_input_files,
AVStreamMap *stream_maps,
int nb_stream_maps)
{
…
解码参数计算;
#if CONFIG_AVFILTER
if (configure_video_filters(ist,
ost)) {
fprintf(stderr, "Error opening
filters!\n");
ret = AVERROR(EINVAL);
goto fail;
}
#endif
编码参数计算;
output_packet(ist, ist_index,
ost_table,
nb_ostreams,
&pkt, &errorflag);
……
return ret;
}
函数configure_video_filters是用来初始化filter graph
和filter本身的初始化;
函数output_packet是FFmpeg整个解码,滤镜处理,编码调用的实现。
1.
先来看configure_video_filters的实现:
int configure_video_filters(AVInputStream *ist,
AVOutputStream *ost)
//
类似于directshow一样初始化filter graph等;
ost->graph = avfilter_graph_alloc();
// 初始化filter
graph和filter
avfilter_graph_parse(ost->graph,
ost->avfilter,
&inputs,
&outputs, NULL)
//
检查filter支持的媒体类型
avfilter_graph_config(ost->graph,
NULL);
。。。
return 0;
filter
graph和filter的初始化:
int avfilter_graph_parse(AVFilterGraph *graph,
const char *filters,
AVFilterInOut **open_inputs,
AVFilterInOut **open_outputs,
void *log_ctx)
do {
parse_inputs(&filters, &curr_inputs, open_outputs, log_ctx);
parse_filter(&filter,
&filters, graph, index, log_ctx);
if (filter->input_count == 1 && !curr_inputs && !index) {
/* First
input can be omitted if it is "[in]" */
const char *tmp = "[in]";
if ((ret = parse_inputs(&tmp, &curr_inputs, open_outputs,
log_ctx)) < 0)
goto
fail;
}
link_filter_inouts(filter, &curr_inputs, open_inputs, log_ctx);
parse_outputs(&filters, &curr_inputs, open_inputs,
open_outputs, log_ctx);
filters += strspn(filters, WHITESPACES);
chr = *filters++;
index++;
} while (chr == ‘,‘ || chr == ‘;‘);
if (open_inputs && *open_inputs &&
!strcmp((*open_inputs)->name, ""out"") && curr_inputs) {
/* Last output can be
omitted if it is "[out]" */
const char *tmp = "[out]";
if ((ret = parse_outputs(&tmp, &curr_inputs, open_inputs,
open_outputs, log_ctx)) < 0)
goto
}
return 0;
static int parse_filter(AVFilterContext **filt_ctx,
const char **buf, AVFilterGraph *graph,"
int index, void *log_ctx)
char *opts = NULL;
char *name = av_get_token(buf, "=,;[\n");
int ret;
if (**buf == ‘=‘) {
(*buf)++;
opts = av_get_token(buf, "[],;\n");
}
ret = create_filter(filt_ctx, graph, index, name,
opts, log_ctx);
av_free(name);
av_free(opts);
return ret;
到了真正的Filter
graph初始化,这部分和DShow很相似:
int create_filter(AVFilterContext **filt_ctx,
AVFilterGraph *ctx, int index,
const char *filt_name, const char *args,
void *log_ctx)
AVFilter *filt;
char inst_name[30];
char tmp_args[256];
int ret;
//
注册当前filter到静态数组中
filt = avfilter_get_by_name(filt_name);
分配并初始化输入输出Pad
ret = avfilter_open(filt_ctx, filt, inst_name);
将当前filter添加到filter graph中
ret = avfilter_graph_add_filter(ctx, *filt_ctx);
通过函数指针,调用当前filter的初始化函数
ret = avfilter_init_filter(*filt_ctx,
args, NULL);
int avfilter_init_filter(AVFilterContext *filter,
const char *args,
void *opaque)
int ret=0;
if (filter->filter->init)
ret = filter->filter->init(filter, args, opaque);
return ret;
static av_cold int init(AVFilterContext *ctx,
const char *args,
void *opaque)
YADIFContext *yadif = ctx->priv;
av_unused int cpu_flags = av_get_cpu_flags();
yadif->mode = 0;
yadif->parity = -1;
yadif->csp = NULL;
滤镜函数指针初始化赋值
yadif->filter_line = filter_line_c;
2.
接着来看两个filter的Pad连接时的媒体类型的协商机制
int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx)
//
检查filter的权限
if ((ret = ff_avfilter_graph_check_validity(graphctx, log_ctx)))
return ret;
//
检查Pad支持的媒体类型
if ((ret = ff_avfilter_graph_config_formats(graphctx,
log_ctx)))
//
if ((ret = ff_avfilter_graph_config_links(graphctx, log_ctx)))
检查Pad支持的媒体类型 :
int ff_avfilter_graph_config_formats(AVFilterGraph *graph,
AVClass *log_ctx)
/* find
supported formats from sub-filters, and merge along links */
if ((ret = query_formats(graph,
log_ctx)) < 0)
/* Once
everything is merged, it‘s possible that we‘ll still have
*
multiple valid media format choices. We pick the first one. */
pick_formats(graph);
static int query_formats(AVFilterGraph *graph,
AVClass *log_ctx)
int i, j, ret;
int scaler_count = 0;
/* ask all
the sub-filters for their supported media formats */
for (i = 0; i < graph->filter_count; i++) {
if (graph->filters[i]->filter->query_formats)
graph->filters[i]->filter->query_formats(graph->filters[i]);
else
avfilter_default_query_formats(graph->filters[i]);
/* go through and merge as many
format lists as possible */
…
/* 检查连接用Pad支持的媒体类型 */
static int query_formats(AVFilterContext *ctx)
static const enum PixelFormat
pix_fmts[] = {
PIX_FMT_YUV420P,
PIX_FMT_YUV422P,
PIX_FMT_YUV444P,
PIX_FMT_YUV410P,
PIX_FMT_YUV411P,
PIX_FMT_GRAY8,
PIX_FMT_YUVJ420P,
PIX_FMT_YUVJ422P,
PIX_FMT_YUVJ444P,
AV_NE( PIX_FMT_GRAY16BE, PIX_FMT_GRAY16LE ),
PIX_FMT_YUV440P,
PIX_FMT_YUVJ440P,
AV_NE( PIX_FMT_YUV420P16BE, PIX_FMT_YUV420P16LE ),
AV_NE( PIX_FMT_YUV422P16BE, PIX_FMT_YUV422P16LE ),
AV_NE( PIX_FMT_YUV444P16BE, PIX_FMT_YUV444P16LE ),
PIX_FMT_NONE
};
avfilter_set_common_pixel_formats(ctx,
avfilter_make_format_list(pix_fmts));
3. 最后来看滤镜的连接和对每帧图像处理时的调用
int output_packet(AVInputStream *ist, int ist_index,
AVOutputStream **ost_table,
int nb_ostreams,
const AVPacket *pkt,
int *errorflag)
/* decode the packet if needed */
if (ist->decoding_needed) {
解码;
#if CONFIG_AVFILTER
if(ist->st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
if (start_time == 0 || ist->pts >= start_time) {
for(i=0;i<nb_ostreams;i++) {
ost = ost_table[i];
if (ost->input_video_filter && ost->source_index == ist_index) {
if (!picture.sample_aspect_ratio.num)
picture.sample_aspect_ratio = ist->st->sample_aspect_ratio;
picture.pts = ist->pts;
av_vsrc_buffer_add_frame(ost->input_video_filter,
&picture, AV_VSRC_BUF_FLAG_OVERWRITE);
}
#endif
frame_available = ist->st->codec->codec_type
!= AVMEDIA_TYPE_VIDEO ||
!ost->output_video_filter ||
avfilter_poll_frame(ost->output_video_filter->inputs[0]);
while (frame_available) {
if (ist->st->codec->codec_type == AVMEDIA_TYPE_VIDEO &&
ost->output_video_filter) {
AVRational
ist_pts_tb = ost->output_video_filter->inputs[0]->time_base;
if (av_vsink_buffer_get_video_buffer_ref(ost->output_video_filter,
&ost->picref, 0) < 0)
goto
cont;
if (ost->picref) {
AVRational
tempVar = {1, AV_TIME_BASE};
avfilter_fill_frame_from_video_buffer_ref(&picture, ost->picref);
//ist->pts = av_rescale_q(ost->picref->pts, ist_pts_tb, AV_TIME_BASE_Q);
ist->pts = av_rescale_q(ost->picref->pts, ist_pts_tb, tempVar);//modify
by chenrui
编码;
return 0;
fail_decode:
return -1;
}
先来看滤镜的连接:
int av_vsrc_buffer_add_frame(AVFilterContext *buffer_src,
const AVFrame *frame, int flags)
// 从帧列表中得到当前帧
AVFilterBufferRef *picref =
avfilter_get_video_buffer_ref_from_frame(frame,
AV_PERM_WRITE);
if (!picref)
return AVERROR(ENOMEM);
ret =
av_vsrc_buffer_add_video_buffer_ref(buffer_src,
picref, flags);
picref->buf->data[0] = NULL;
avfilter_unref_buffer(picref);
int av_vsrc_buffer_add_video_buffer_ref(AVFilterContext
*buffer_filter,
AVFilterBufferRef *picref, int flags)"
智能插入相应的filter;
c->picref =
avfilter_get_video_buffer(outlink, AV_PERM_WRITE,
picref->video->w,
picref->video->h);
av_image_copy(c->picref->data,
c->picref->linesize,
picref->data, picref->linesize,
picref->format, picref->video->w, picref->video->h);
avfilter_copy_buffer_ref_props(c->picref, picref);
return 0;
AVFilterBufferRef *avfilter_get_video_buffer(AVFilterLink *link,
int perms, int
w, int h)
AVFilterBufferRef *ret = NULL;
if (link->dstpad->get_video_buffer)
ret =
link->dstpad->get_video_buffer(link, perms, w, h);
if (!ret)
avfilter_default_get_video_buffer(link, perms, w, h);
if (ret)
ret->type = AVMEDIA_TYPE_VIDEO;
AVFilterBufferRef *get_video_buffer(AVFilterLink *link, int
perms,
int w, int h)
AVFilterBufferRef *picref;
int width = FFALIGN(w, 32);
int height= FFALIGN(h+2, 32);
int i;
picref = avfilter_default_get_video_buffer(link, perms,
width, height);
picref->video->w = w;
picref->video->h = h;
for (i = 0; i < 3; i++)
picref->data[i] +=
picref->linesize[i];
return picref;
最后来看图像处理函数的实现:
int avfilter_poll_frame(AVFilterLink *link)
int i, min = INT_MAX;
if (link->srcpad->poll_frame)
return link->srcpad->poll_frame(link);
for (i = 0; i < link->src->input_count; i++) {
int val;
if (!link->src->inputs[i])
return -1;
val = avfilter_poll_frame(link->src->inputs[i]);
min = FFMIN(min, val);
return min;
int poll_frame(AVFilterLink *link)
YADIFContext *yadif = link->src->priv;
int ret, val;
if (yadif->frame_pending)
return 1;
val = avfilter_poll_frame(link->src->inputs[0]);
if (val==1 && !yadif->next) {
//FIXME change
API to not requre this red tape
if ((ret = avfilter_request_frame(link->src->inputs[0])) < 0)
val = avfilter_poll_frame(link->src->inputs[0]);
assert(yadif->next || !val);
return
val * ((yadif->mode&1)+1);
int avfilter_request_frame(AVFilterLink *link)
if (link->srcpad->request_frame)
return link->srcpad->request_frame(link);
else if (link->src->inputs[0])
return
avfilter_request_frame(link->src->inputs[0]);
else return -1;
static int request_frame(AVFilterLink *link)
BufferSourceContext *c = link->src->priv;
if (!c->picref) {
av_log(link->src, AV_LOG_WARNING,
"request_frame()
called with no available frame!\n");
AVERROR(EINVAL);
图像处理函数的真正实现
avfilter_start_frame(link, avfilter_ref_buffer(c->picref, ~0));
avfilter_draw_slice(link, 0, link->h, 1);
avfilter_end_frame(link);
avfilter_unref_buffer(c->picref);
c->picref = NULL;
完结。