由于input_filter是个source,所以只为它分配output pad,并且只有一个pad.
static AVFilter input_filter = { .name = "ffplay_input", .priv_size = sizeof(FilterPriv), .init = input_init, .uninit = input_uninit, .query_formats = input_query_formats, .inputs = (AVFilterPad[]) {{ .name = NULL }}, .outputs = (AVFilterPad[]) {{ .name = "default", .type = AVMEDIA_TYPE_VIDEO, .request_frame = input_request_frame, .config_props = input_config_props, }, { .name = NULL }}, };再实现AVFilter的回调函数们:init()和uninit()--用于初始化/销毁所用到的资源.
看一下ffplay.c中的实现:
static int input_init(AVFilterContext *ctx, const char *args, void *opaque) { FilterPriv *priv = ctx->priv; AVCodecContext *codec; if(!opaque) return -1; priv->is = opaque; codec = priv->is->video_st->codec; codec->opaque = ctx; if((codec->codec->capabilities & CODEC_CAP_DR1)) { av_assert0(codec->flags & CODEC_FLAG_EMU_EDGE); priv->use_dr1 = 1; codec->get_buffer = input_get_buffer; codec->release_buffer = input_release_buffer; codec->reget_buffer = input_reget_buffer; codec->thread_safe_callbacks = 1; } priv->frame = avcodec_alloc_frame(); return 0; }FilterPriv是ffplay实现的filter(也就是input_filter)的私有数据结构.主要的工作是分配了一个AVFrame,用于保存从设备取得的帧.uninit()更简单,就不用看了.
还需实现output pad的request_frame(),才能使input_filter后面的filter获取到帧
static int input_request_frame(AVFilterLink *link) { FilterPriv *priv = link->src->priv; AVFilterBufferRef *picref; int64_t pts = 0; AVPacket pkt; int ret; while (!(ret = get_video_frame(priv->is, priv->frame, &pts, &pkt))) av_free_packet(&pkt); if (ret < 0) return -1; if(priv->use_dr1 && priv->frame->opaque) { picref = avfilter_ref_buffer(priv->frame->opaque, ~0); } else { picref = avfilter_get_video_buffer(link, AV_PERM_WRITE, link->w, link->h); av_image_copy(picref->data, picref->linesize, priv->frame->data, priv->frame->linesize, picref->format, link->w, link->h); } av_free_packet(&pkt); avfilter_copy_frame_props(picref, priv->frame); picref->pts = pts; avfilter_start_frame(link, picref); avfilter_draw_slice(link, 0, link->h, 1); avfilter_end_frame(link); return 0; }调用者从sink中获取处理后的帧:
av_buffersink_get_buffer_ref(filt_out, &picref, 0);
获取后的帧保存在picref中.这个函数会引起graph中的filter从后向前依次调用上一个filter的outpad的request_frame(),最后调用到source的request_frame(),也就是input_request_frame(),input_request_frame()调用get_video_frame()(见ffplay.c)从设备获取一帧(可能需要解码),然后将这帧数据复制到picref中,filter们处理的帧是用AVFilterBufferRef表示的.然后将帧的一些属性也复制到picref中,最后调用avfilter_start_frame(link, picref);avfilter_draw_slice(link, 0, link->h, 1);avfilter_end_frame(link);来处理这一帧.这三个函数对应着pad上的三个函数指针:start_frame,draw_slice,end_frame.以start_frame为例,其调用过程是这样的:首先是source的start_frame被调用,做一些必要的处理后,再调用连接到source之后的filter的start_frame.每个filter的output pad都负责在这个函数中向下传递这个调用.当sink调用完start_frame()时再一层层返回到source的output pad.当这三个函数都被source的output pad调用完成后,这一帧的最终结果就出来了.于是可以用sink上获得.
与DShow比较起来,avfilter没有那些推模式,拉模式的概念,没有在source的output pad上实现线程,整个graph的运转都是由调用者驱动.