X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavfilter%2Favfiltergraph.c;h=1c126e956700be1c68999b7a1a35b8ab7d8030bf;hb=9958f07a81479fd57af8da5d230a3163f57e3b0b;hp=4e5bfd2f1014cc11ad0e611e585340ec2033a44d;hpb=ffcaef348580ba32c57a85e115c0809e5b857d95;p=ffmpeg diff --git a/libavfilter/avfiltergraph.c b/libavfilter/avfiltergraph.c index 4e5bfd2f101..1c126e95670 100644 --- a/libavfilter/avfiltergraph.c +++ b/libavfilter/avfiltergraph.c @@ -1,5 +1,5 @@ /* - * Filter graphs + * filter graphs * copyright (c) 2007 Bobby Bingham * * This file is part of FFmpeg. @@ -22,35 +22,655 @@ #include "avfilter.h" #include "avfiltergraph.h" -struct AVFilterGraph { +#include "allfilters.h" + +typedef struct AVFilterGraph { unsigned filter_count; AVFilterContext **filters; + + /** fake filters to handle links to internal filters */ + AVFilterContext *link_filter_in; + AVFilterContext *link_filter_out; +} GraphContext; + +typedef struct { + AVFilterContext *graph; +} GraphLinkContext; + +static int link_init(AVFilterContext *ctx, const char *args, void *opaque) +{ + GraphLinkContext *linkctx = ctx->priv; + linkctx->graph = opaque; + return !opaque; +} + +/** + * Given the link between the dummy filter and an internal filter whose input + * is being exported outside the graph, this returns the externally visible + * link. + */ +static inline AVFilterLink *get_extern_input_link(AVFilterLink *link) +{ + GraphLinkContext *lctx = link->src->priv; + return lctx->graph->inputs[link->srcpad]; +} + +/** request a frame from a filter providing input to the graph */ +static int link_in_request_frame(AVFilterLink *link) +{ + AVFilterLink *link2 = get_extern_input_link(link); + + if(!link2) + return -1; + return avfilter_request_frame(link2); +} + + +static int link_in_poll_frame(AVFilterLink *link) +{ + AVFilterLink *link2 = get_extern_input_link(link); + if(!link2) + return -1; + return avfilter_poll_frame(link2); +} + +static int link_in_config_props(AVFilterLink *link) +{ + AVFilterLink *link2 = get_extern_input_link(link); + int (*config_props)(AVFilterLink *); + int ret; + + if(!link2) + return -1; + if(!(config_props = link2->src->output_pads[link2->srcpad].config_props)) + config_props = avfilter_default_config_output_link; + ret = config_props(link2); + + link->w = link2->w; + link->h = link2->h; + + return ret; +} + +/** + * Given the link between the dummy filter and an internal filter whose input + * is being exported outside the graph, this returns the externally visible + * link. + */ +static inline AVFilterLink *get_extern_output_link(AVFilterLink *link) +{ + GraphLinkContext *lctx = link->dst->priv; + return lctx->graph->outputs[link->dstpad]; +} + +static int link_out_config_props(AVFilterLink *link) +{ + AVFilterLink *link2 = get_extern_output_link(link); + + if(!link2) + return 0; + + link2->w = link->w; + link2->h = link->h; + + return 0; +} + +static void link_out_start_frame(AVFilterLink *link, AVFilterPicRef *picref) +{ + AVFilterLink *link2 = get_extern_output_link(link); + + if(!link2) + avfilter_unref_pic(picref); + else + avfilter_start_frame(link2, picref); +} + +static void link_out_end_frame(AVFilterLink *link) +{ + AVFilterLink *link2 = get_extern_output_link(link); + + if(link2) + avfilter_end_frame(link2); +} + +static AVFilterPicRef *link_out_get_video_buffer(AVFilterLink *link, int perms) +{ + AVFilterLink *link2 = get_extern_output_link(link); + + if(!link2) + return NULL; + else + return avfilter_get_video_buffer(link2, perms); +} + +static void link_out_draw_slice(AVFilterLink *link, int y, int height) +{ + AVFilterLink *link2 = get_extern_output_link(link); + + if(link2) + avfilter_draw_slice(link2, y, height); +} + +/** dummy filter used to help export filters pads outside the graph */ +static AVFilter vf_graph_dummy = +{ + .name = "graph_dummy", + + .priv_size = sizeof(GraphLinkContext), + + .init = link_init, + + .inputs = (AVFilterPad[]) {{ .name = NULL, }}, + .outputs = (AVFilterPad[]) {{ .name = NULL, }}, }; -AVFilterGraph *avfilter_create_graph(void) +static AVFilterLink *get_intern_input_link(AVFilterLink *link) { - return av_mallocz(sizeof(AVFilterGraph)); + GraphContext *graph = link->dst->priv; + return graph->link_filter_in->outputs[link->dstpad]; } -static void destroy_graph_filters(AVFilterGraph *graph) +static void graph_in_start_frame(AVFilterLink *link, AVFilterPicRef *picref) { - unsigned i; + AVFilterLink *link2 = get_intern_input_link(link); + if(link2) + avfilter_start_frame(link2, picref); +} - for(i = 0; i < graph->filter_count; i ++) - avfilter_destroy(graph->filters[i]); - av_freep(&graph->filters); +static void graph_in_end_frame(AVFilterLink *link) +{ + AVFilterLink *link2 = get_intern_input_link(link); + if(link2) + avfilter_end_frame(link2); +} + +static AVFilterPicRef *graph_in_get_video_buffer(AVFilterLink *link, int perms) +{ + AVFilterLink *link2 = get_intern_input_link(link); + if(link2) + return avfilter_get_video_buffer(link2, perms); + return NULL; +} + +static void graph_in_draw_slice(AVFilterLink *link, int y, int height) +{ + AVFilterLink *link2 = get_intern_input_link(link); + if(link2) + avfilter_draw_slice(link2, y, height); +} + +static int graph_in_config_props(AVFilterLink *link) +{ + AVFilterLink *link2 = get_intern_input_link(link); + int (*config_props)(AVFilterLink *); + int ret; + + if(!link2) + return -1; + + /* copy link properties over to the dummy internal link */ + link2->w = link->w; + link2->h = link->h; + link2->format = link->format; + + if(!(config_props = link2->dst->input_pads[link2->dstpad].config_props)) + return 0; /* FIXME? */ + //config_props = avfilter_default_config_input_link; + if(!(ret = config_props(link2))) + link2->init_state = AVLINK_INIT; + else + link2->init_state = AVLINK_STARTINIT; + + return ret; } -void avfilter_destroy_graph(AVFilterGraph *graph) +static AVFilterLink *get_intern_output_link(AVFilterLink *link) { - destroy_graph_filters(graph); - av_free(graph); + GraphContext *graph = link->src->priv; + return graph->link_filter_out->inputs[link->srcpad]; } -void avfilter_graph_add_filter(AVFilterGraph *graph, AVFilterContext *filter) +static int graph_out_request_frame(AVFilterLink *link) { + AVFilterLink *link2 = get_intern_output_link(link); + + if(link2) + return avfilter_request_frame(link2); + return -1; +} + +static int graph_out_poll_frame(AVFilterLink *link) +{ + AVFilterLink *link2 = get_intern_output_link(link); + + if(!link2) + return -1; + + return avfilter_poll_frame(link2); +} + +static int graph_out_config_props(AVFilterLink *link) +{ + GraphContext *graph = link->src->priv; + AVFilterLink *link2 = graph->link_filter_out->inputs[link->srcpad]; + int ret; + + if((ret = avfilter_config_links(graph->link_filter_out))) + return ret; + + if(!link2) + return 0; + + link->w = link2->w; + link->h = link2->h; + link->format = link2->format; + + return 0; +} + +static int add_graph_input(AVFilterContext *gctx, AVFilterContext *filt, unsigned idx, + char *name) +{ + GraphContext *graph = gctx->priv; + + AVFilterPad graph_inpad = + { + .name = name, + .type = CODEC_TYPE_VIDEO, + .start_frame = graph_in_start_frame, + .end_frame = graph_in_end_frame, + .get_video_buffer = graph_in_get_video_buffer, + .draw_slice = graph_in_draw_slice, + .config_props = graph_in_config_props, + /* XXX */ + }; + AVFilterPad dummy_outpad = + { + .name = NULL, /* FIXME? */ + .type = CODEC_TYPE_VIDEO, + .request_frame = link_in_request_frame, + .poll_frame = link_in_poll_frame, + .config_props = link_in_config_props, + }; + + avfilter_insert_inpad (gctx, gctx->input_count, &graph_inpad); + avfilter_insert_outpad(graph->link_filter_in, graph->link_filter_in->output_count, + &dummy_outpad); + return avfilter_link(graph->link_filter_in, + graph->link_filter_in->output_count-1, filt, idx); +} + +static int add_graph_output(AVFilterContext *gctx, AVFilterContext *filt, unsigned idx, + char *name) +{ + GraphContext *graph = gctx->priv; + + AVFilterPad graph_outpad = + { + .name = name, + .type = CODEC_TYPE_VIDEO, + .request_frame = graph_out_request_frame, + .poll_frame = graph_out_poll_frame, + .config_props = graph_out_config_props, + }; + AVFilterPad dummy_inpad = + { + .name = NULL, /* FIXME? */ + .type = CODEC_TYPE_VIDEO, + .start_frame = link_out_start_frame, + .end_frame = link_out_end_frame, + .draw_slice = link_out_draw_slice, + .get_video_buffer = link_out_get_video_buffer, + .config_props = link_out_config_props, + }; + + avfilter_insert_outpad(gctx, gctx->output_count, &graph_outpad); + avfilter_insert_inpad (graph->link_filter_out, graph->link_filter_out->input_count, + &dummy_inpad); + return avfilter_link(filt, idx, graph->link_filter_out, + graph->link_filter_out->input_count-1); +} + +static void uninit(AVFilterContext *ctx) +{ + GraphContext *graph = ctx->priv; + + if(graph->link_filter_in) { + avfilter_destroy(graph->link_filter_in); + graph->link_filter_in = NULL; + } + if(graph->link_filter_out) { + avfilter_destroy(graph->link_filter_out); + graph->link_filter_out = NULL; + } + for(; graph->filter_count > 0; graph->filter_count --) + avfilter_destroy(graph->filters[graph->filter_count - 1]); + av_freep(&graph->filters); +} + +/* TODO: insert in sorted order */ +void avfilter_graph_add_filter(AVFilterContext *graphctx, AVFilterContext *filter) +{ + GraphContext *graph = graphctx->priv; + graph->filters = av_realloc(graph->filters, sizeof(AVFilterContext*) * ++graph->filter_count); graph->filters[graph->filter_count - 1] = filter; } +/* search intelligently, once we insert in order */ +AVFilterContext *avfilter_graph_get_filter(AVFilterContext *ctx, char *name) +{ + GraphContext *graph = ctx->priv; + int i; + + if(!name) + return NULL; + + for(i = 0; i < graph->filter_count; i ++) + if(graph->filters[i]->name && !strcmp(name, graph->filters[i]->name)) + return graph->filters[i]; + + return NULL; +} + +static int query_formats(AVFilterContext *graphctx) +{ + GraphContext *graph = graphctx->priv; + AVFilterContext *linkfiltin = graph->link_filter_in; + AVFilterContext *linkfiltout = graph->link_filter_out; + int i, j; + + /* ask all the sub-filters for their supported colorspaces */ + 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]); + } + + /* use these formats on our exported links */ + for(i = 0; i < linkfiltout->input_count; i ++) { + avfilter_formats_ref( linkfiltout->inputs[i]->in_formats, + &linkfiltout->inputs[i]->out_formats); + + if(graphctx->outputs[i]) + avfilter_formats_ref(linkfiltout->inputs[i]->in_formats, + &graphctx->outputs[i]->in_formats); + } + for(i = 0; i < linkfiltin->output_count; i ++) { + avfilter_formats_ref( linkfiltin->outputs[i]->out_formats, + &linkfiltin->outputs[i]->in_formats); + + if(graphctx->inputs[i]) + avfilter_formats_ref(linkfiltin->outputs[i]->out_formats, + &graphctx-> inputs[i]->out_formats); + } + + /* go through and merge as many format lists as possible */ + for(i = 0; i < graph->filter_count; i ++) { + AVFilterContext *filter = graph->filters[i]; + + for(j = 0; j < filter->input_count; j ++) { + AVFilterLink *link; + if(!(link = filter->inputs[j])) + continue; + if(link->in_formats != link->out_formats) { + if(!avfilter_merge_formats(link->in_formats, + link->out_formats)) { + /* couldn't merge format lists. auto-insert scale filter */ + AVFilterContext *scale; + + if(!(scale = avfilter_open(&avfilter_vf_scale, NULL))) + return -1; + if(scale->filter->init(scale, NULL, NULL) || + avfilter_insert_filter(link, scale, 0, 0)) { + avfilter_destroy(scale); + return -1; + } + + avfilter_graph_add_filter(graphctx, scale); + scale->filter->query_formats(scale); + if(!avfilter_merge_formats(scale-> inputs[0]->in_formats, + scale-> inputs[0]->out_formats) || + !avfilter_merge_formats(scale->outputs[0]->in_formats, + scale->outputs[0]->out_formats)) + return -1; + } + } + } + } + + return 0; +} + +static void pick_format(AVFilterLink *link) +{ + if(!link || !link->in_formats) + return; + + link->in_formats->format_count = 1; + link->format = link->in_formats->formats[0]; + + avfilter_formats_unref(&link->in_formats); + avfilter_formats_unref(&link->out_formats); +} + +static void pick_formats(GraphContext *graph) +{ + int i, j; + + for(i = 0; i < graph->filter_count; i ++) { + AVFilterContext *filter = graph->filters[i]; + + if(filter->filter == &avfilter_vf_graph || + filter->filter == &avfilter_vf_graphfile || + filter->filter == &avfilter_vf_graphdesc) + pick_formats(filter->priv); + + for(j = 0; j < filter->input_count; j ++) + pick_format(filter->inputs[j]); + for(j = 0; j < filter->output_count; j ++) + pick_format(filter->outputs[j]); + } +} + +int avfilter_graph_config_formats(AVFilterContext *graphctx) +{ + GraphContext *graph = graphctx->priv; + + /* find supported formats from sub-filters, and merge along links */ + if(query_formats(graphctx)) + return -1; + + /* Once everything is merged, it's possible that we'll still have + * multiple valid choices of colorspace. We pick the first one. */ + pick_formats(graph); + + return 0; +} + +static int graph_load_from_desc(AVFilterContext *ctx, AVFilterGraphDesc *desc) +{ + AVFilterGraphDescFilter *curfilt; + AVFilterGraphDescLink *curlink; + AVFilterGraphDescExport *curpad; + AVFilterContext *filt, *filtb; + + AVFilter *filterdef; + + /* create all filters */ + for(curfilt = desc->filters; curfilt; curfilt = curfilt->next) { + if(!(filterdef = avfilter_get_by_name(curfilt->filter)) || + !(filt = avfilter_open(filterdef, curfilt->name))) { + av_log(ctx, AV_LOG_ERROR, + "error creating filter '%s'\n", curfilt->name); + goto fail; + } + avfilter_graph_add_filter(ctx, filt); + if(avfilter_init_filter(filt, curfilt->args, NULL)) { + av_log(ctx, AV_LOG_ERROR, + "error initializing filter '%s'\n", curfilt->name); + goto fail; + } + } + + /* create all links */ + for(curlink = desc->links; curlink; curlink = curlink->next) { + if(!(filt = avfilter_graph_get_filter(ctx, curlink->src))) { + av_log(ctx, AV_LOG_ERROR, "link source does not exist in graph\n"); + goto fail; + } + if(!(filtb = avfilter_graph_get_filter(ctx, curlink->dst))) { + av_log(ctx, AV_LOG_ERROR, "link destination does not exist in graph\n"); + goto fail; + } + if(avfilter_link(filt, curlink->srcpad, filtb, curlink->dstpad)) { + av_log(ctx, AV_LOG_ERROR, "cannot create link between source and destination filters\n"); + goto fail; + } + } + + /* export all input pads */ + for(curpad = desc->inputs; curpad; curpad = curpad->next) { + if(!(filt = avfilter_graph_get_filter(ctx, curpad->filter))) { + av_log(ctx, AV_LOG_ERROR, "filter owning exported pad does not exist\n"); + goto fail; + } + add_graph_input(ctx, filt, curpad->pad, curpad->name); + } + + /* export all output pads */ + for(curpad = desc->outputs; curpad; curpad = curpad->next) { + if(!(filt = avfilter_graph_get_filter(ctx, curpad->filter))) { + av_log(ctx, AV_LOG_ERROR, "filter owning exported pad does not exist\n"); + goto fail; + } + add_graph_output(ctx, filt, curpad->pad, curpad->name); + } + + return 0; + +fail: + uninit(ctx); + return -1; +} + +static int init(AVFilterContext *ctx, const char *args, void *opaque) +{ + GraphContext *gctx = ctx->priv; + AVFilterGraphDesc *desc; + int ret; + + if(!(gctx->link_filter_in = avfilter_open(&vf_graph_dummy, NULL))) + return -1; + if(avfilter_init_filter(gctx->link_filter_in, NULL, ctx)) + goto fail; + if(!(gctx->link_filter_out = avfilter_open(&vf_graph_dummy, NULL))) + goto fail; + if(avfilter_init_filter(gctx->link_filter_out, NULL, ctx)) + goto fail; + + if(!args) + return 0; + + if(!(desc = avfilter_graph_parse_chain(args))) + goto fail; + + ret = graph_load_from_desc(ctx, desc); + avfilter_graph_free_desc(desc); + return ret; + +fail: + avfilter_destroy(gctx->link_filter_in); + if(gctx->link_filter_out) + avfilter_destroy(gctx->link_filter_out); + return -1; +} + +AVFilter avfilter_vf_graph = +{ + .name = "graph", + + .priv_size = sizeof(GraphContext), + + .init = init, + .uninit = uninit, + + .query_formats = query_formats, + + .inputs = (AVFilterPad[]) {{ .name = NULL, }}, + .outputs = (AVFilterPad[]) {{ .name = NULL, }}, +}; + +static int init_desc(AVFilterContext *ctx, const char *args, void *opaque) +{ + GraphContext *gctx = ctx->priv; + + if(!opaque) + return -1; + + if(!(gctx->link_filter_in = avfilter_open(&vf_graph_dummy, NULL))) + return -1; + if(avfilter_init_filter(gctx->link_filter_in, NULL, ctx)) + goto fail; + if(!(gctx->link_filter_out = avfilter_open(&vf_graph_dummy, NULL))) + goto fail; + if(avfilter_init_filter(gctx->link_filter_out, NULL, ctx)) + goto fail; + + return graph_load_from_desc(ctx, opaque); + +fail: + avfilter_destroy(gctx->link_filter_in); + if(gctx->link_filter_out) + avfilter_destroy(gctx->link_filter_out); + return -1; +} + +AVFilter avfilter_vf_graphdesc = +{ + .name = "graph_desc", + + .priv_size = sizeof(GraphContext), + + .init = init_desc, + .uninit = uninit, + + .query_formats = query_formats, + + .inputs = (AVFilterPad[]) {{ .name = NULL, }}, + .outputs = (AVFilterPad[]) {{ .name = NULL, }}, +}; + +static int init_file(AVFilterContext *ctx, const char *args, void *opaque) +{ + AVFilterGraphDesc *desc; + int ret; + + if(!args) + return -1; + if(!(desc = avfilter_graph_load_desc(args))) + return -1; + + ret = init_desc(ctx, NULL, desc); + avfilter_graph_free_desc(desc); + return ret; +} + +AVFilter avfilter_vf_graphfile = +{ + .name = "graph_file", + + .priv_size = sizeof(GraphContext), + + .init = init_file, + .uninit = uninit, + + .query_formats = query_formats, + + .inputs = (AVFilterPad[]) {{ .name = NULL, }}, + .outputs = (AVFilterPad[]) {{ .name = NULL, }}, +}; +