/*
- * Filter graphs
- * copyright (c) 2007 Bobby Bingham
+ * filter graphs
+ * Copyright (c) 2008 Vitor Sessak
+ * Copyright (c) 2007 Bobby Bingham
*
- * This file is part of FFmpeg.
+ * This file is part of Libav.
*
- * FFmpeg is free software; you can redistribute it and/or
+ * Libav is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
- * FFmpeg is distributed in the hope that it will be useful,
+ * Libav is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
+ * License along with Libav; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include <ctype.h>
#include <string.h>
-#include <stddef.h>
-#include "avstring.h"
#include "avfilter.h"
#include "avfiltergraph.h"
+#include "formats.h"
+#include "internal.h"
-#include "allfilters.h"
+#include "libavutil/audioconvert.h"
+#include "libavutil/log.h"
-typedef struct AVFilterGraph {
- unsigned filter_count;
- AVFilterContext **filters;
-
- /** fake filter to handle links to internal filters */
- AVFilterContext *link_filter;
-} GraphContext;
-
-typedef struct {
- AVFilterContext *graph;
-} GraphLinkContext;
+static const AVClass filtergraph_class = {
+ .class_name = "AVFilterGraph",
+ .item_name = av_default_item_name,
+ .version = LIBAVUTIL_VERSION_INT,
+};
-static int link_init(AVFilterContext *ctx, const char *args, void *opaque)
+AVFilterGraph *avfilter_graph_alloc(void)
{
- GraphLinkContext *linkctx = ctx->priv;
- linkctx->graph = opaque;
- return !opaque;
+ AVFilterGraph *ret = av_mallocz(sizeof(AVFilterGraph));
+ if (!ret)
+ return NULL;
+#if FF_API_GRAPH_AVCLASS
+ ret->av_class = &filtergraph_class;
+#endif
+ 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_input_link(AVFilterLink *link)
+void avfilter_graph_free(AVFilterGraph **graph)
{
- GraphLinkContext *lctx = link->src->priv;
- return lctx->graph->inputs[link->srcpad];
+ if (!*graph)
+ return;
+ for (; (*graph)->filter_count > 0; (*graph)->filter_count--)
+ avfilter_free((*graph)->filters[(*graph)->filter_count - 1]);
+ av_freep(&(*graph)->scale_sws_opts);
+ av_freep(&(*graph)->filters);
+ av_freep(graph);
}
-/** request a frame from a filter providing input to the graph */
-static int link_in_request_frame(AVFilterLink *link)
+int avfilter_graph_add_filter(AVFilterGraph *graph, AVFilterContext *filter)
{
- AVFilterLink *link2 = get_extern_input_link(link);
+ AVFilterContext **filters = av_realloc(graph->filters,
+ sizeof(AVFilterContext*) * (graph->filter_count+1));
+ if (!filters)
+ return AVERROR(ENOMEM);
+
+ graph->filters = filters;
+ graph->filters[graph->filter_count++] = filter;
- if(!link2)
- return -1;
- return avfilter_request_frame(link2);
+ return 0;
}
-static int link_in_config_props(AVFilterLink *link)
+int avfilter_graph_create_filter(AVFilterContext **filt_ctx, AVFilter *filt,
+ const char *name, const char *args, void *opaque,
+ AVFilterGraph *graph_ctx)
{
- 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;
+ if ((ret = avfilter_open(filt_ctx, filt, name)) < 0)
+ goto fail;
+ if ((ret = avfilter_init_filter(*filt_ctx, args, opaque)) < 0)
+ goto fail;
+ if ((ret = avfilter_graph_add_filter(graph_ctx, *filt_ctx)) < 0)
+ goto fail;
+ return 0;
+fail:
+ if (*filt_ctx)
+ avfilter_free(*filt_ctx);
+ *filt_ctx = NULL;
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
+ * Check for the validity of graph.
+ *
+ * A graph is considered valid if all its input and output pads are
+ * connected.
+ *
+ * @return 0 in case of success, a negative value otherwise
*/
-static inline AVFilterLink *get_extern_output_link(AVFilterLink *link)
+static int graph_check_validity(AVFilterGraph *graph, AVClass *log_ctx)
{
- GraphLinkContext *lctx = link->dst->priv;
- return lctx->graph->outputs[link->dstpad];
-}
+ AVFilterContext *filt;
+ int i, j;
-static int link_out_config_props(AVFilterLink *link)
-{
- AVFilterLink *link2 = get_extern_output_link(link);
- int (*config_props)(AVFilterLink *);
+ for (i = 0; i < graph->filter_count; i++) {
+ filt = graph->filters[i];
- if(!link2)
- return 0;
+ for (j = 0; j < filt->input_count; j++) {
+ if (!filt->inputs[j] || !filt->inputs[j]->src) {
+ av_log(log_ctx, AV_LOG_ERROR,
+ "Input pad \"%s\" for the filter \"%s\" of type \"%s\" not connected to any source\n",
+ filt->input_pads[j].name, filt->name, filt->filter->name);
+ return AVERROR(EINVAL);
+ }
+ }
- link2->w = link->w;
- link2->h = link->h;
+ for (j = 0; j < filt->output_count; j++) {
+ if (!filt->outputs[j] || !filt->outputs[j]->dst) {
+ av_log(log_ctx, AV_LOG_ERROR,
+ "Output pad \"%s\" for the filter \"%s\" of type \"%s\" not connected to any destination\n",
+ filt->output_pads[j].name, filt->name, filt->filter->name);
+ return AVERROR(EINVAL);
+ }
+ }
+ }
- if(!(config_props = link2->dst->input_pads[link2->dstpad].config_props))
- config_props = avfilter_default_config_input_link;
- return config_props(link2);
+ return 0;
}
-static void link_out_start_frame(AVFilterLink *link, AVFilterPicRef *picref)
+/**
+ * Configure all the links of graphctx.
+ *
+ * @return 0 in case of success, a negative value otherwise
+ */
+static int graph_config_links(AVFilterGraph *graph, AVClass *log_ctx)
{
- AVFilterLink *link2 = get_extern_output_link(link);
+ AVFilterContext *filt;
+ int i, ret;
- if(!link2)
- avfilter_unref_pic(picref);
- else
- avfilter_start_frame(link2, picref);
-}
+ for (i=0; i < graph->filter_count; i++) {
+ filt = graph->filters[i];
-static void link_out_end_frame(AVFilterLink *link)
-{
- AVFilterLink *link2 = get_extern_output_link(link);
+ if (!filt->output_count) {
+ if ((ret = avfilter_config_links(filt)))
+ return ret;
+ }
+ }
- if(link2)
- avfilter_end_frame(link2);
+ return 0;
}
-static AVFilterPicRef *link_out_get_video_buffer(AVFilterLink *link, int perms)
+AVFilterContext *avfilter_graph_get_filter(AVFilterGraph *graph, char *name)
{
- AVFilterLink *link2 = get_extern_output_link(link);
+ int i;
- if(!link2)
- return NULL;
- else
- return avfilter_get_video_buffer(link2, perms);
+ 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 void link_out_draw_slice(AVFilterLink *link, int y, int height)
+static int query_formats(AVFilterGraph *graph, AVClass *log_ctx)
{
- AVFilterLink *link2 = get_extern_output_link(link);
+ int i, j, ret;
+ int scaler_count = 0, resampler_count = 0;
- if(link2)
- avfilter_draw_slice(link2, y, height);
-}
+ /* 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
+ ff_default_query_formats(graph->filters[i]);
+ }
-/** dummy filter used to help export filters pads outside the graph */
-static AVFilter vf_graph_dummy =
-{
- .name = "graph_dummy",
- .author = "Bobby Bingham",
+ /* go through and merge as many format lists as possible */
+ for (i = 0; i < graph->filter_count; i++) {
+ AVFilterContext *filter = graph->filters[i];
- .priv_size = sizeof(GraphLinkContext),
+ for (j = 0; j < filter->input_count; j++) {
+ AVFilterLink *link = filter->inputs[j];
+ int convert_needed = 0;
- .init = link_init,
+ if (!link)
+ continue;
- .inputs = (AVFilterPad[]) {{ .name = NULL, }},
- .outputs = (AVFilterPad[]) {{ .name = NULL, }},
-};
+ if (link->in_formats != link->out_formats &&
+ !ff_merge_formats(link->in_formats,
+ link->out_formats))
+ convert_needed = 1;
+ if (link->type == AVMEDIA_TYPE_AUDIO) {
+ if (link->in_channel_layouts != link->out_channel_layouts &&
+ !ff_merge_channel_layouts(link->in_channel_layouts,
+ link->out_channel_layouts))
+ convert_needed = 1;
+ if (link->in_samplerates != link->out_samplerates &&
+ !ff_merge_samplerates(link->in_samplerates,
+ link->out_samplerates))
+ convert_needed = 1;
+ }
-static AVFilterLink *get_intern_input_link(AVFilterLink *link)
-{
- GraphContext *graph = link->dst->priv;
- return graph->link_filter->outputs[link->dstpad];
-}
+ if (convert_needed) {
+ AVFilterContext *convert;
+ AVFilter *filter;
+ AVFilterLink *inlink, *outlink;
+ char scale_args[256];
+ char inst_name[30];
+
+ /* couldn't merge format lists. auto-insert conversion filter */
+ switch (link->type) {
+ case AVMEDIA_TYPE_VIDEO:
+ snprintf(inst_name, sizeof(inst_name), "auto-inserted scaler %d",
+ scaler_count++);
+ snprintf(scale_args, sizeof(scale_args), "0:0:%s", graph->scale_sws_opts);
+ if ((ret = avfilter_graph_create_filter(&convert,
+ avfilter_get_by_name("scale"),
+ inst_name, scale_args, NULL,
+ graph)) < 0)
+ return ret;
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ if (!(filter = avfilter_get_by_name("resample"))) {
+ av_log(log_ctx, AV_LOG_ERROR, "'resample' filter "
+ "not present, cannot convert audio formats.\n");
+ return AVERROR(EINVAL);
+ }
-static void graph_in_start_frame(AVFilterLink *link, AVFilterPicRef *picref)
-{
- AVFilterLink *link2 = get_intern_input_link(link);
- if(link2)
- avfilter_start_frame(link2, picref);
-}
+ snprintf(inst_name, sizeof(inst_name), "auto-inserted resampler %d",
+ resampler_count++);
+ if ((ret = avfilter_graph_create_filter(&convert,
+ avfilter_get_by_name("resample"),
+ inst_name, NULL, NULL, graph)) < 0)
+ return ret;
+ break;
+ default:
+ return AVERROR(EINVAL);
+ }
-static void graph_in_end_frame(AVFilterLink *link)
-{
- AVFilterLink *link2 = get_intern_input_link(link);
- if(link2)
- avfilter_end_frame(link2);
-}
+ if ((ret = avfilter_insert_filter(link, convert, 0, 0)) < 0)
+ return ret;
+
+ convert->filter->query_formats(convert);
+ inlink = convert->inputs[0];
+ outlink = convert->outputs[0];
+ if (!ff_merge_formats( inlink->in_formats, inlink->out_formats) ||
+ !ff_merge_formats(outlink->in_formats, outlink->out_formats))
+ ret |= AVERROR(ENOSYS);
+ if (inlink->type == AVMEDIA_TYPE_AUDIO &&
+ (!ff_merge_samplerates(inlink->in_samplerates,
+ inlink->out_samplerates) ||
+ !ff_merge_channel_layouts(inlink->in_channel_layouts,
+ inlink->out_channel_layouts)))
+ ret |= AVERROR(ENOSYS);
+ if (outlink->type == AVMEDIA_TYPE_AUDIO &&
+ (!ff_merge_samplerates(outlink->in_samplerates,
+ outlink->out_samplerates) ||
+ !ff_merge_channel_layouts(outlink->in_channel_layouts,
+ outlink->out_channel_layouts)))
+ ret |= AVERROR(ENOSYS);
+
+ if (ret < 0) {
+ av_log(log_ctx, AV_LOG_ERROR,
+ "Impossible to convert between the formats supported by the filter "
+ "'%s' and the filter '%s'\n", link->src->name, link->dst->name);
+ return ret;
+ }
+ }
+ }
+ }
-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;
+ return 0;
}
-static void graph_in_draw_slice(AVFilterLink *link, int y, int height)
+static int pick_format(AVFilterLink *link)
{
- AVFilterLink *link2 = get_intern_input_link(link);
- if(link2)
- avfilter_draw_slice(link2, y, height);
-}
+ if (!link || !link->in_formats)
+ return 0;
-static int graph_in_config_props(AVFilterLink *link)
-{
- AVFilterLink *link2 = get_intern_input_link(link);
- int (*config_props)(AVFilterLink *);
+ link->in_formats->format_count = 1;
+ link->format = link->in_formats->formats[0];
- if(!link2)
- return -1;
+ if (link->type == AVMEDIA_TYPE_AUDIO) {
+ if (!link->in_samplerates->format_count) {
+ av_log(link->src, AV_LOG_ERROR, "Cannot select sample rate for"
+ " the link between filters %s and %s.\n", link->src->name,
+ link->dst->name);
+ return AVERROR(EINVAL);
+ }
+ link->in_samplerates->format_count = 1;
+ link->sample_rate = link->in_samplerates->formats[0];
+
+ if (!link->in_channel_layouts->nb_channel_layouts) {
+ av_log(link->src, AV_LOG_ERROR, "Cannot select channel layout for"
+ "the link between filters %s and %s.\n", link->src->name,
+ link->dst->name);
+ return AVERROR(EINVAL);
+ }
+ link->in_channel_layouts->nb_channel_layouts = 1;
+ link->channel_layout = link->in_channel_layouts->channel_layouts[0];
+ }
- /* copy link properties over to the dummy internal link */
- link2->w = link->w;
- link2->h = link->h;
- link2->format = link->format;
+ ff_formats_unref(&link->in_formats);
+ ff_formats_unref(&link->out_formats);
+ ff_formats_unref(&link->in_samplerates);
+ ff_formats_unref(&link->out_samplerates);
+ ff_channel_layouts_unref(&link->in_channel_layouts);
+ ff_channel_layouts_unref(&link->out_channel_layouts);
- if(!(config_props = link2->dst->input_pads[link2->dstpad].config_props))
- return 0; /* FIXME? */
- //config_props = avfilter_default_config_input_link;
- return config_props(link2);
+ return 0;
}
-static AVFilterLink *get_intern_output_link(AVFilterLink *link)
-{
- GraphContext *graph = link->src->priv;
- return graph->link_filter->inputs[link->srcpad];
+#define REDUCE_FORMATS(fmt_type, list_type, list, var, nb, add_format) \
+do { \
+ for (i = 0; i < filter->input_count; i++) { \
+ AVFilterLink *link = filter->inputs[i]; \
+ fmt_type fmt; \
+ \
+ if (!link->out_ ## list || link->out_ ## list->nb != 1) \
+ continue; \
+ fmt = link->out_ ## list->var[0]; \
+ \
+ for (j = 0; j < filter->output_count; j++) { \
+ AVFilterLink *out_link = filter->outputs[j]; \
+ list_type *fmts; \
+ \
+ if (link->type != out_link->type || \
+ out_link->in_ ## list->nb == 1) \
+ continue; \
+ fmts = out_link->in_ ## list; \
+ \
+ if (!out_link->in_ ## list->nb) { \
+ add_format(&out_link->in_ ##list, fmt); \
+ break; \
+ } \
+ \
+ for (k = 0; k < out_link->in_ ## list->nb; k++) \
+ if (fmts->var[k] == fmt) { \
+ fmts->var[0] = fmt; \
+ fmts->nb = 1; \
+ ret = 1; \
+ break; \
+ } \
+ } \
+ } \
+} while (0)
+
+static int reduce_formats_on_filter(AVFilterContext *filter)
+{
+ int i, j, k, ret = 0;
+
+ REDUCE_FORMATS(int, AVFilterFormats, formats, formats,
+ format_count, ff_add_format);
+ REDUCE_FORMATS(int, AVFilterFormats, samplerates, formats,
+ format_count, ff_add_format);
+ REDUCE_FORMATS(uint64_t, AVFilterChannelLayouts, channel_layouts,
+ channel_layouts, nb_channel_layouts, ff_add_channel_layout);
+
+ return ret;
}
-static int graph_out_request_frame(AVFilterLink *link)
+static void reduce_formats(AVFilterGraph *graph)
{
- AVFilterLink *link2 = get_intern_output_link(link);
+ int i, reduced;
- if(link2)
- return avfilter_request_frame(link2);
- return -1;
+ do {
+ reduced = 0;
+
+ for (i = 0; i < graph->filter_count; i++)
+ reduced |= reduce_formats_on_filter(graph->filters[i]);
+ } while (reduced);
}
-static int graph_out_config_props(AVFilterLink *link)
+static void swap_samplerates_on_filter(AVFilterContext *filter)
{
- AVFilterLink *link2 = get_intern_output_link(link);
- int (*config_props)(AVFilterLink *);
- int ret;
-
- if(!link2)
- return 0;
-
- link2->w = link->w;
- link2->h = link->h;
- link2->format = link->format;
+ AVFilterLink *link = NULL;
+ int sample_rate;
+ int i, j;
- if(!(config_props = link2->src->output_pads[link2->srcpad].config_props))
- config_props = avfilter_default_config_output_link;
- ret = config_props(link2);
+ for (i = 0; i < filter->input_count; i++) {
+ link = filter->inputs[i];
- link->w = link2->w;
- link->h = link2->h;
- link->format = link2->format;
+ if (link->type == AVMEDIA_TYPE_AUDIO &&
+ link->out_samplerates->format_count == 1)
+ break;
+ }
+ if (i == filter->input_count)
+ return;
- return ret;
-}
+ sample_rate = link->out_samplerates->formats[0];
-static int add_graph_input(AVFilterContext *gctx, AVFilterContext *filt, unsigned idx,
- char *name)
-{
- GraphContext *graph = gctx->priv;
-
- AVFilterPad graph_inpad =
- {
- .name = name,
- .type = AV_PAD_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 = AV_PAD_VIDEO,
- .request_frame = link_in_request_frame,
- .config_props = link_in_config_props,
- };
-
- avfilter_insert_inpad (gctx, gctx->input_count, &graph_inpad);
- avfilter_insert_outpad(graph->link_filter, graph->link_filter->output_count,
- &dummy_outpad);
- return avfilter_link(graph->link_filter,
- graph->link_filter->output_count-1, filt, idx);
-}
+ for (i = 0; i < filter->output_count; i++) {
+ AVFilterLink *outlink = filter->outputs[i];
+ int best_idx, best_diff = INT_MAX;
-static int add_graph_output(AVFilterContext *gctx, AVFilterContext *filt, unsigned idx,
- char *name)
-{
- GraphContext *graph = gctx->priv;
-
- AVFilterPad graph_outpad =
- {
- .name = name,
- .type = AV_PAD_VIDEO,
- .request_frame = graph_out_request_frame,
- .config_props = graph_out_config_props,
- };
- AVFilterPad dummy_inpad =
- {
- .name = NULL, /* FIXME? */
- .type = AV_PAD_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, graph->link_filter->input_count,
- &dummy_inpad);
- return avfilter_link(filt, idx, graph->link_filter,
- graph->link_filter->input_count-1);
-}
+ if (outlink->type != AVMEDIA_TYPE_AUDIO ||
+ outlink->in_samplerates->format_count < 2)
+ continue;
-static void uninit(AVFilterContext *ctx)
-{
- GraphContext *graph = ctx->priv;
+ for (j = 0; j < outlink->in_samplerates->format_count; j++) {
+ int diff = abs(sample_rate - outlink->in_samplerates->formats[j]);
- if(graph->link_filter) {
- avfilter_destroy(graph->link_filter);
- graph->link_filter = NULL;
+ if (diff < best_diff) {
+ best_diff = diff;
+ best_idx = j;
+ }
+ }
+ FFSWAP(int, outlink->in_samplerates->formats[0],
+ outlink->in_samplerates->formats[best_idx]);
}
- 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)
+static void swap_samplerates(AVFilterGraph *graph)
{
- 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;
+ for (i = 0; i < graph->filter_count; i++)
+ swap_samplerates_on_filter(graph->filters[i]);
}
-static int query_formats(AVFilterContext *graphctx)
+static void swap_channel_layouts_on_filter(AVFilterContext *filter)
{
- GraphContext *graph = graphctx->priv;
- AVFilterContext *linkfilt = graph->link_filter;
+ AVFilterLink *link = NULL;
+ uint64_t chlayout;
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 < linkfilt->input_count; i ++) {
- avfilter_formats_ref( linkfilt->inputs[i]->in_formats,
- &linkfilt->inputs[i]->out_formats);
+ for (i = 0; i < filter->input_count; i++) {
+ link = filter->inputs[i];
- if(graphctx->outputs[i])
- avfilter_formats_ref( linkfilt-> inputs[i]->in_formats,
- &graphctx->outputs[i]->in_formats);
+ if (link->type == AVMEDIA_TYPE_AUDIO &&
+ link->out_channel_layouts->nb_channel_layouts == 1)
+ break;
}
- for(i = 0; i < linkfilt->output_count; i ++) {
- avfilter_formats_ref( linkfilt->outputs[i]->out_formats,
- &linkfilt->outputs[i]->in_formats);
+ if (i == filter->input_count)
+ return;
- if(graphctx->inputs[i])
- avfilter_formats_ref( linkfilt->outputs[i]->out_formats,
- &graphctx-> inputs[i]->out_formats);
- }
+ chlayout = link->out_channel_layouts->channel_layouts[0];
- /* go through and merge as many format lists as possible */
- for(i = 0; i < graph->filter_count; i ++) {
- AVFilterContext *filter = graph->filters[i];
+ for (i = 0; i < filter->output_count; i++) {
+ AVFilterLink *outlink = filter->outputs[i];
+ int best_idx, best_score = INT_MIN;
- 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;
- }
+ if (outlink->type != AVMEDIA_TYPE_AUDIO ||
+ outlink->in_channel_layouts->nb_channel_layouts < 2)
+ continue;
- 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;
- }
+ for (j = 0; j < outlink->in_channel_layouts->nb_channel_layouts; j++) {
+ uint64_t out_chlayout = outlink->in_channel_layouts->channel_layouts[j];
+ int matched_channels = av_get_channel_layout_nb_channels(chlayout &
+ out_chlayout);
+ int extra_channels = av_get_channel_layout_nb_channels(out_chlayout &
+ (~chlayout));
+ int score = matched_channels - extra_channels;
+
+ if (score > best_score) {
+ best_score = score;
+ best_idx = j;
}
}
+ FFSWAP(uint64_t, outlink->in_channel_layouts->channel_layouts[0],
+ outlink->in_channel_layouts->channel_layouts[best_idx]);
}
- return 0;
}
-static void pick_format(AVFilterLink *link)
+static void swap_channel_layouts(AVFilterGraph *graph)
{
- if(!link || !link->in_formats)
- return;
-
- link->in_formats->format_count = 1;
- link->format = link->in_formats->formats[0];
+ int i;
- avfilter_formats_unref(&link->in_formats);
- avfilter_formats_unref(&link->out_formats);
+ for (i = 0; i < graph->filter_count; i++)
+ swap_channel_layouts_on_filter(graph->filters[i]);
}
-static void pick_formats(GraphContext *graph)
+static void swap_sample_fmts_on_filter(AVFilterContext *filter)
{
+ AVFilterLink *link = NULL;
+ int format, bps;
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 (i = 0; i < filter->input_count; i++) {
+ link = filter->inputs[i];
- 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]);
+ if (link->type == AVMEDIA_TYPE_AUDIO &&
+ link->out_formats->format_count == 1)
+ break;
}
-}
+ if (i == filter->input_count)
+ return;
-int avfilter_graph_config_formats(AVFilterContext *graphctx)
-{
- GraphContext *graph = graphctx->priv;
+ format = link->out_formats->formats[0];
+ bps = av_get_bytes_per_sample(format);
- /* Find supported formats from sub-filters, and merge along links */
- if(query_formats(graphctx))
- return -1;
+ for (i = 0; i < filter->output_count; i++) {
+ AVFilterLink *outlink = filter->outputs[i];
+ int best_idx, best_score = INT_MIN;
- /* 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);
+ if (outlink->type != AVMEDIA_TYPE_AUDIO ||
+ outlink->in_formats->format_count < 2)
+ continue;
- return 0;
-}
+ for (j = 0; j < outlink->in_formats->format_count; j++) {
+ int out_format = outlink->in_formats->formats[j];
+ int out_bps = av_get_bytes_per_sample(out_format);
+ int score;
-int avfilter_graph_config_links(AVFilterContext *graphctx)
-{
- GraphContext *graph = graphctx->priv;
- int i, j;
-
- for(i = 0; i < graph->filter_count; i ++) {
- for(j = 0; j < graph->filters[i]->input_count; j ++) {
- /* ensure that graphs contained within graphs are configured */
- if((graph->filters[i]->filter == &avfilter_vf_graph ||
- graph->filters[i]->filter == &avfilter_vf_graphfile ||
- graph->filters[i]->filter == &avfilter_vf_graphdesc) &&
- avfilter_graph_config_links(graph->filters[i]))
- return -1;
- if(avfilter_config_link(graph->filters[i]->inputs[j]))
- return -1;
- }
- }
-
- return 0;
-}
+ if (av_get_packed_sample_fmt(out_format) == format ||
+ av_get_planar_sample_fmt(out_format) == format) {
+ best_idx = j;
+ break;
+ }
-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\n");
- 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\n");
- goto fail;
- }
- }
+ /* for s32 and float prefer double to prevent loss of information */
+ if (bps == 4 && out_bps == 8) {
+ best_idx = j;
+ break;
+ }
- /* 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;
- }
- }
+ /* prefer closest higher or equal bps */
+ score = -abs(out_bps - bps);
+ if (out_bps >= bps)
+ score += INT_MAX/2;
- /* 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;
+ if (score > best_score) {
+ best_score = score;
+ best_idx = j;
+ }
}
- add_graph_output(ctx, filt, curpad->pad, curpad->name);
+ FFSWAP(int, outlink->in_formats->formats[0],
+ outlink->in_formats->formats[best_idx]);
}
-
- return 0;
-
-fail:
- uninit(ctx);
- return -1;
}
-static int init(AVFilterContext *ctx, const char *args, void *opaque)
+static void swap_sample_fmts(AVFilterGraph *graph)
{
- GraphContext *gctx = ctx->priv;
- AVFilterGraphDesc *desc;
- int ret;
-
- if(!(gctx->link_filter = avfilter_open(&vf_graph_dummy, NULL)))
- return -1;
- if(avfilter_init_filter(gctx->link_filter, NULL, ctx))
- goto fail;
-
- if(!args)
- return 0;
-
- if(!(desc = avfilter_graph_parse_chain(args)))
- goto fail;
+ int i;
- ret = graph_load_from_desc(ctx, desc);
- avfilter_graph_free_desc(desc);
- return ret;
+ for (i = 0; i < graph->filter_count; i++)
+ swap_sample_fmts_on_filter(graph->filters[i]);
-fail:
- avfilter_destroy(gctx->link_filter);
- return -1;
}
-AVFilter avfilter_vf_graph =
-{
- .name = "graph",
- .author = "Bobby Bingham",
-
- .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)
+static int pick_formats(AVFilterGraph *graph)
{
- GraphContext *gctx = ctx->priv;
-
- if(!opaque)
- return -1;
+ int i, j, ret;
- if(!(gctx->link_filter = avfilter_open(&vf_graph_dummy, NULL)))
- return -1;
- if(avfilter_init_filter(gctx->link_filter, NULL, ctx))
- goto fail;
-
- return graph_load_from_desc(ctx, opaque);
+ for (i = 0; i < graph->filter_count; i++) {
+ AVFilterContext *filter = graph->filters[i];
-fail:
- avfilter_destroy(gctx->link_filter);
- return -1;
+ for (j = 0; j < filter->input_count; j++)
+ if ((ret = pick_format(filter->inputs[j])) < 0)
+ return ret;
+ for (j = 0; j < filter->output_count; j++)
+ if ((ret = pick_format(filter->outputs[j])) < 0)
+ return ret;
+ }
+ return 0;
}
-AVFilter avfilter_vf_graphdesc =
+/**
+ * Configure the formats of all the links in the graph.
+ */
+static int graph_config_formats(AVFilterGraph *graph, AVClass *log_ctx)
{
- .name = "graph_desc",
- .author = "Bobby Bingham",
-
- .priv_size = sizeof(GraphContext),
-
- .init = init_desc,
- .uninit = uninit,
+ int ret;
- .query_formats = query_formats,
+ /* find supported formats from sub-filters, and merge along links */
+ if ((ret = query_formats(graph, log_ctx)) < 0)
+ return ret;
- .inputs = (AVFilterPad[]) {{ .name = NULL, }},
- .outputs = (AVFilterPad[]) {{ .name = NULL, }},
-};
+ /* Once everything is merged, it's possible that we'll still have
+ * multiple valid media format choices. We try to minimize the amount
+ * of format conversion inside filters */
+ reduce_formats(graph);
-static int init_file(AVFilterContext *ctx, const char *args, void *opaque)
-{
- AVFilterGraphDesc *desc;
- int ret;
+ /* for audio filters, ensure the best format, sample rate and channel layout
+ * is selected */
+ swap_sample_fmts(graph);
+ swap_samplerates(graph);
+ swap_channel_layouts(graph);
- if(!args)
- return -1;
- if(!(desc = avfilter_graph_load_desc(args)))
- return -1;
+ if ((ret = pick_formats(graph)) < 0)
+ return ret;
- ret = init_desc(ctx, NULL, desc);
- avfilter_graph_free_desc(desc);
- return ret;
+ return 0;
}
-AVFilter avfilter_vf_graphfile =
+int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx)
{
- .name = "graph_file",
- .author = "Bobby Bingham",
-
- .priv_size = sizeof(GraphContext),
-
- .init = init_file,
- .uninit = uninit,
-
- .query_formats = query_formats,
+ int ret;
- .inputs = (AVFilterPad[]) {{ .name = NULL, }},
- .outputs = (AVFilterPad[]) {{ .name = NULL, }},
-};
+ if ((ret = graph_check_validity(graphctx, log_ctx)))
+ return ret;
+ if ((ret = graph_config_formats(graphctx, log_ctx)))
+ return ret;
+ if ((ret = graph_config_links(graphctx, log_ctx)))
+ return ret;
+ return 0;
+}