* [FFmpeg-devel] [PATCH 2/5] lavfi/avfilter: track whether a filter has been initialized
2023-01-20 19:31 [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options() Anton Khirnov
@ 2023-01-20 19:31 ` Anton Khirnov
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 3/5] lavfi: add a new filtergraph parsing API Anton Khirnov
` (3 subsequent siblings)
4 siblings, 0 replies; 25+ messages in thread
From: Anton Khirnov @ 2023-01-20 19:31 UTC (permalink / raw)
To: ffmpeg-devel
Refuse to link uninitialized filters or initialize a filter twice.
---
libavfilter/avfilter.c | 12 ++++++++++++
libavfilter/internal.h | 4 ++++
2 files changed, 16 insertions(+)
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index 86b275dc4f..619b96f5ac 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -158,6 +158,11 @@ int avfilter_link(AVFilterContext *src, unsigned srcpad,
src->outputs[srcpad] || dst->inputs[dstpad])
return AVERROR(EINVAL);
+ if (!src->internal->initialized || !dst->internal->initialized) {
+ av_log(src, AV_LOG_ERROR, "Filters must be initialized before linking.\n");
+ return AVERROR(EINVAL);
+ }
+
if (src->output_pads[srcpad].type != dst->input_pads[dstpad].type) {
av_log(src, AV_LOG_ERROR,
"Media type mismatch between the '%s' filter output pad %d (%s) and the '%s' filter input pad %d (%s)\n",
@@ -872,6 +877,11 @@ int avfilter_init_dict(AVFilterContext *ctx, AVDictionary **options)
{
int ret = 0;
+ if (ctx->internal->initialized) {
+ av_log(ctx, AV_LOG_ERROR, "Filter already initialized\n");
+ return AVERROR(EINVAL);
+ }
+
ret = av_opt_set_dict2(ctx, options, AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
av_log(ctx, AV_LOG_ERROR, "Error applying generic filter options.\n");
@@ -898,6 +908,8 @@ int avfilter_init_dict(AVFilterContext *ctx, AVDictionary **options)
return ret;
}
+ ctx->internal->initialized = 1;
+
return 0;
}
diff --git a/libavfilter/internal.h b/libavfilter/internal.h
index 2ec41917f7..8b232a8d8f 100644
--- a/libavfilter/internal.h
+++ b/libavfilter/internal.h
@@ -137,6 +137,10 @@ struct AVFilterGraphInternal {
struct AVFilterInternal {
avfilter_execute_func *execute;
+
+ // 1 when avfilter_init_*() was successfully called on this filter
+ // 0 otherwise
+ int initialized;
};
static av_always_inline int ff_filter_execute(AVFilterContext *ctx, avfilter_action_func *func,
--
2.35.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* [FFmpeg-devel] [PATCH 3/5] lavfi: add a new filtergraph parsing API
2023-01-20 19:31 [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options() Anton Khirnov
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 2/5] lavfi/avfilter: track whether a filter has been initialized Anton Khirnov
@ 2023-01-20 19:31 ` Anton Khirnov
2023-01-30 12:59 ` Nicolas George
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API Anton Khirnov
` (2 subsequent siblings)
4 siblings, 1 reply; 25+ messages in thread
From: Anton Khirnov @ 2023-01-20 19:31 UTC (permalink / raw)
To: ffmpeg-devel
Callers currently have two ways of adding filters to a graph - they can
either
- create, initialize, and link them manually
- use one of the avfilter_graph_parse*() functions, which take a
(typically end-user-written) string, split it into individual filter
definitions+options, then create filters, apply options, initialize
filters, and finally link them - all based on information from this
string.
A major problem with the second approach is that it performs many
actions as a single atomic unit, leaving the caller no space to
intervene in between. Such intervention would be useful e.g. to
- modify filter options;
- supply hardware device contexts;
both of which typically must be done before the filter is initialized.
Callers who need such intervention are then forced to invent their own
filtergraph parsing, which is clearly suboptimal.
This commit aims to address this problem by adding a new modular
filtergraph parsing API. It adds a new avfilter_graph_segment_parse()
function to parse a string filtergraph description into an intermediate
tree-like representation (AVFilterGraphSegment and its children).
This intermediate form may then be applied step by step using further
new avfilter_graph_segment*() functions, with user intervention possible
between each step.
---
doc/APIchanges | 15 +
libavfilter/avfilter.h | 313 +++++++++++++++++
libavfilter/graphparser.c | 689 +++++++++++++++++++++++++++++++++++++-
3 files changed, 1010 insertions(+), 7 deletions(-)
diff --git a/doc/APIchanges b/doc/APIchanges
index a11acadecd..f8e936ba9a 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -14,6 +14,21 @@ libavutil: 2021-04-27
API changes, most recent first:
+2023-xx-xx - xxxxxxxxxx - lavfi 8.x.100 - avfilter.h
+ Add filtergraph segment parsing API.
+ New structs:
+ - AVFilterGraphSegment
+ - AVFilterChain
+ - AVFilterParams
+ - AVFilterPadParams
+ New functions:
+ - avfilter_graph_segment_parse()
+ - avfilter_graph_segment_create_filters()
+ - avfilter_graph_segment_apply_opts()
+ - avfilter_graph_segment_init()
+ - avfilter_graph_segment_link()
+ - avfilter_graph_segment_apply()
+
2023-01-13 - xxxxxxxxxx - lavu 57.44.100 - ambient_viewing_environment.h frame.h
Adds a new structure for holding H.274 Ambient Viewing Environment metadata,
AVAmbientViewingEnvironment.
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index c2ec7a4b5f..037a8bc490 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -1118,6 +1118,319 @@ int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
AVFilterInOut **inputs,
AVFilterInOut **outputs);
+/**
+ * Parameters of a filter's input or output pad.
+ *
+ * Created as a child of AVFilterParams by avfilter_graph_segment_parse().
+ * Freed in avfilter_graph_segment_free().
+ */
+typedef struct AVFilterPadParams {
+ /**
+ * An av_malloc()'ed string containing the pad label.
+ *
+ * May be av_free()'d and set to NULL by the caller, in which case this pad
+ * will be treated as unlabeled for linking.
+ * May also be replaced by another av_malloc()'ed string.
+ */
+ char *label;
+} AVFilterPadParams;
+
+/**
+ * Parameters describing a filter to be created in a filtergraph.
+ *
+ * Created as a child of AVFilterGraphSegment by avfilter_graph_segment_parse().
+ * Freed in avfilter_graph_segment_free().
+ */
+typedef struct AVFilterParams {
+ /**
+ * The filter context.
+ *
+ * Created by avfilter_graph_segment_create_filters() based on
+ * AVFilterParams.filter_name and instance_name.
+ *
+ * Callers may also create the filter context manually, then they should
+ * av_free() filter_name and set it to NULL. Such AVFilterParams instances
+ * are then skipped by avfilter_graph_segment_create_filters().
+ */
+ AVFilterContext *filter;
+
+ /**
+ * Name of the AVFilter to be used.
+ *
+ * An av_malloc()'ed string, set by avfilter_graph_segment_parse(). Will be
+ * passed to avfilter_get_by_name() by
+ * avfilter_graph_segment_create_filters().
+ *
+ * Callers may av_free() this string and replace it with another one or
+ * NULL. If the caller creates the filter instance manually, this string
+ * MUST be set to NULL.
+ *
+ * When both AVFilterParams.filter an AVFilterParams.filter_name are NULL,
+ * this AVFilterParams instance is skipped by avfilter_graph_segment_*()
+ * functions.
+ */
+ char *filter_name;
+ /**
+ * Name to be used for this filter instance.
+ *
+ * An av_malloc()'ed string, may be set by avfilter_graph_segment_parse() or
+ * left NULL. The caller may av_free() this string and replace with another
+ * one or NULL.
+ *
+ * Will be used by avfilter_graph_segment_create_filters() - passed as the
+ * third argument to avfilter_graph_alloc_filter(), then freed and set to
+ * NULL.
+ */
+ char *instance_name;
+
+ /**
+ * Options to be apllied to the filter.
+ *
+ * Filled by avfilter_graph_segment_parse(). Afterwards may be freely
+ * modified by the caller.
+ *
+ * Will be applied to the filter by avfilter_graph_segment_apply_opts()
+ * with an equivalent of av_opt_set_dict2(filter, &opts, * AV_OPT_SEARCH_CHILDREN),
+ * i.e. any unapplied options will be left in this dictionary.
+ */
+ AVDictionary *opts;
+
+ AVFilterPadParams **inputs;
+ unsigned nb_inputs;
+
+ AVFilterPadParams **outputs;
+ unsigned nb_outputs;
+} AVFilterParams;
+
+/**
+ * A filterchain is a list of filter specifications.
+ *
+ * Created as a child of AVFilterGraphSegment by avfilter_graph_segment_parse().
+ * Freed in avfilter_graph_segment_free().
+ */
+typedef struct AVFilterChain {
+ AVFilterParams **filters;
+ size_t nb_filters;
+} AVFilterChain;
+
+/**
+ * A parsed representation of a filtergraph segment.
+ *
+ * A filtergraph segment is conceptually a list of filterchains, with some
+ * supplementary information (e.g. format conversion flags).
+ *
+ * Created by avfilter_graph_segment_parse(). Must be freed with
+ * avfilter_graph_segment_free().
+ */
+typedef struct AVFilterGraphSegment {
+ /**
+ * The filtergraph this segment is associated with.
+ * Set by avfilter_graph_segment_parse().
+ */
+ AVFilterGraph *graph;
+
+ /**
+ * A list of filter chain contained in this segment..
+ * Set in avfilter_graph_segment_parse().
+ */
+ AVFilterChain **chains;
+ size_t nb_chains;
+
+ /**
+ * A string containing a colon-separated list of key=value options applied
+ * to all scale filters in this segment.
+ *
+ * May be set by avfilter_graph_segment_parse().
+ * The caller may free this string with av_free() and replace it with a
+ * different av_malloc()'ed string.
+ */
+ char *scale_sws_opts;
+} AVFilterGraphSegment;
+
+/**
+ * Parse a textual filtergraph description into an intermediate form.
+ *
+ * This intermediate representation is intended to be modified by the caller as
+ * described in the documentation of AVFilterGraphSegment and its children, and
+ * then applied to the graph either manually or with other
+ * avfilter_graph_segment_*() functions. See the documentation for
+ * avfilter_graph_segment_apply() for the canonical way to apply
+ * AVFilterGraphSegment.
+ *
+ * @param graph Filter graph the parsed segment is associated with. Will only be
+ * used for logging and similar auxiliary purposes. The graph will
+ * not be actually modified by this function - the parsing results
+ * are instead stored in seg for further processing.
+ * @param graph_str a string describing the filtergraph segment
+ * @param flags reserved for future use, caller must set to 0 for now
+ * @param seg A pointer to the newly-created AVFilterGraphSegment is written
+ * here on success. The graph segment is owned by the caller and must
+ * be freed with avfilter_graph_segment_free() before graph itself is
+ * freed.
+ *
+ * @return a non-negative number on success, a negative error code on failure
+ */
+int avfilter_graph_segment_parse(AVFilterGraph *graph, const char *graph_str,
+ int flags, AVFilterGraphSegment **seg);
+
+/**
+ * Create filters specified in a graph segment.
+ *
+ * Walk through the creation-pending AVFilterParams in the segment and create
+ * new filter instances for them.
+ * Creation-pending params are those where AVFilterParams.filter_name is
+ * non-NULL (and hence AVFilterParams.filter is NULL). All other AVFilterParams
+ * instances are ignored.
+ *
+ * For any filter created by this function, the corresponding
+ * AVFilterParams.filter is set to the newly-created filter context,
+ * AVFilterParams.filter_name and AVFilterParams.instance_name are freed and set
+ * to NULL.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ *
+ * @return
+ * - a non-negative number if all creation-pending filters were successfully
+ * created
+ * - AVERROR_FILTER_NOT_FOUND if some filter's name did not correspond to a
+ * known filter
+ * - another negative error code on other failures
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_create_filters(AVFilterGraphSegment *seg, int flags);
+
+/**
+ * Apply parsed options to filter instances in a graph segment.
+ *
+ * Walk through all filter instances in the graph segment that have option
+ * dictionaries associated with them and apply those options with
+ * av_opt_set_dict2(..., AV_OPT_SEARCH_CHILDREN). AVFilterParams.opts is
+ * replaced by the dictionary output by av_opt_set_dict2(), which should be
+ * empty (NULL) if all options were successfully applied.
+ *
+ * If any options could not be found, this function will continue processing all
+ * other filters and finally return AVERROR_OPTION_NOT_FOUND (unless another
+ * error happens). The calling program may then deal with unapplied options as
+ * it wishes.
+ *
+ * Any creation-pending filters (see avfilter_graph_segment_create_filters())
+ * present in the segment will cause this function to fail. AVFilterParams with
+ * no associated filter context are simply skipped.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ *
+ * @return
+ * - a non-negative number if all options were successfully applied
+ * - AVERROR_OPTION_NOT_FOUND if some options were not found in a filter
+ * - another negative error code on other failures
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_apply_opts(AVFilterGraphSegment *seg, int flags);
+
+/**
+ * Initialize all filter instances in a graph segment.
+ *
+ * Walk through all filter instances in the graph segment and call
+ * avfilter_init_dict(..., NULL) on those that have not been initialized yet.
+ *
+ * Any creation-pending filters (see avfilter_graph_segment_create_filters())
+ * present in the segment will cause this function to fail. AVFilterParams with
+ * no associated filter context or whose filter context is already initialized,
+ * are simply skipped.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ *
+ * @return
+ * - a non-negative number if all filter instances were successfully initialized
+ * - a negative error code on other failures
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_init(AVFilterGraphSegment *seg, int flags);
+
+/**
+ * Link filters in a graph segment.
+ *
+ * Walk through all filter instances in the graph segment and try to link all
+ * unlinked input and output pads. Any creation-pending filters (see
+ * avfilter_graph_segment_create_filters()) present in the segment will cause
+ * this function to fail. Disabled filters and already linked pads are skipped.
+ *
+ * Every filter output pad that has a corresponding AVFilterPadParams with a
+ * non-NULL label is
+ * - linked to the input with the matching label, if one exists;
+ * - exported in the outputs linked list otherwise, with the label preserved.
+ * Unlabeled outputs are
+ * - linked to the first unlinked unlabeled input in the next non-disabled
+ * filter in the chain, if one exists
+ * - exported in the ouputs linked list otherwise, with NULL label
+ *
+ * Similarly, unlinked input pads are exported in the inputs linked list.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ * @param[out] inputs a linked list of all free (unlinked) inputs of the
+ * filters in this graph segment will be returned here. It
+ * is to be freed by the caller using avfilter_inout_free().
+ * @param[out] outputs a linked list of all free (unlinked) outputs of the
+ * filters in this graph segment will be returned here. It
+ * is to be freed by the caller using avfilter_inout_free().
+ *
+ * @return
+ * - a non-negative number on success
+ * - a negative error code on failure
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_link(AVFilterGraphSegment *seg, int flags,
+ AVFilterInOut **inputs,
+ AVFilterInOut **outputs);
+
+/**
+ * Apply all filter/link descriptions from a graph segment to the associated filtergraph.
+ *
+ * This functions is currently equivalent to calling the following in sequence:
+ * - avfilter_graph_segment_create_filters();
+ * - avfilter_graph_segment_apply_opts();
+ * - avfilter_graph_segment_init();
+ * - avfilter_graph_segment_link();
+ * failing if any of them fails. This list may be extended in the future.
+ *
+ * Since the above functions are idempotent, the caller may call some of the
+ * manually, then do some custom processing on the filtergraph, then call this
+ * function to do the rest.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ * @param[out] inputs passed to avfilter_graph_segment_link()
+ * @param[out] outputs passed to avfilter_graph_segment_link()
+ * @return
+ * - a non-negative number on success
+ * - a negative error code on failure
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_apply(AVFilterGraphSegment *seg, int flags,
+ AVFilterInOut **inputs,
+ AVFilterInOut **outputs);
+
+/**
+ * Free the provided AVFilterGraphSegment and everything associated with it.
+ *
+ * @param seg double pointer to the AVFilterGraphSegment to be freed. NULL will
+ * be written to this pointer on exit from this function.
+ *
+ * @note
+ * The filter contexts (AVFilterParams.filter) are owned by AVFilterGraph rather
+ * than AVFilterGraphSegment, so they are not freed.
+ */
+void avfilter_graph_segment_free(AVFilterGraphSegment **seg);
+
/**
* Send a command to one or more filter instances.
*
diff --git a/libavfilter/graphparser.c b/libavfilter/graphparser.c
index 0759c39014..ac6171e0c4 100644
--- a/libavfilter/graphparser.c
+++ b/libavfilter/graphparser.c
@@ -24,10 +24,12 @@
#include <stdio.h>
#include "libavutil/avstring.h"
+#include "libavutil/dict.h"
#include "libavutil/mem.h"
#include "libavutil/opt.h"
#include "avfilter.h"
+#include "internal.h"
#define WHITESPACES " \n\t\r"
@@ -386,7 +388,7 @@ static int parse_outputs(const char **buf, AVFilterInOut **curr_inputs,
return pad;
}
-static int parse_sws_flags(const char **buf, AVFilterGraph *graph)
+static int parse_sws_flags(const char **buf, char **dst, void *log_ctx)
{
char *p = strchr(*buf, ';');
@@ -394,16 +396,16 @@ static int parse_sws_flags(const char **buf, AVFilterGraph *graph)
return 0;
if (!p) {
- av_log(graph, AV_LOG_ERROR, "sws_flags not terminated with ';'.\n");
+ av_log(log_ctx, AV_LOG_ERROR, "sws_flags not terminated with ';'.\n");
return AVERROR(EINVAL);
}
*buf += 4; // keep the 'flags=' part
- av_freep(&graph->scale_sws_opts);
- if (!(graph->scale_sws_opts = av_mallocz(p - *buf + 1)))
+ av_freep(dst);
+ if (!(*dst = av_mallocz(p - *buf + 1)))
return AVERROR(ENOMEM);
- av_strlcpy(graph->scale_sws_opts, *buf, p - *buf + 1);
+ av_strlcpy(*dst, *buf, p - *buf + 1);
*buf = p + 1;
return 0;
@@ -420,7 +422,7 @@ int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
filters += strspn(filters, WHITESPACES);
- if ((ret = parse_sws_flags(&filters, graph)) < 0)
+ if ((ret = parse_sws_flags(&filters, &graph->scale_sws_opts, graph)) < 0)
goto end;
do {
@@ -551,7 +553,7 @@ int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
AVFilterInOut *open_inputs = open_inputs_ptr ? *open_inputs_ptr : NULL;
AVFilterInOut *open_outputs = open_outputs_ptr ? *open_outputs_ptr : NULL;
- if ((ret = parse_sws_flags(&filters, graph)) < 0)
+ if ((ret = parse_sws_flags(&filters, &graph->scale_sws_opts, graph)) < 0)
goto end;
do {
@@ -623,3 +625,676 @@ end:
}
return ret;
}
+
+static void pad_params_free(AVFilterPadParams **pfpp)
+{
+ AVFilterPadParams *fpp = *pfpp;
+
+ if (!fpp)
+ return;
+
+ av_freep(&fpp->label);
+
+ av_freep(pfpp);
+}
+
+static void filter_params_free(AVFilterParams **pp)
+{
+ AVFilterParams *p = *pp;
+
+ if (!p)
+ return;
+
+ for (unsigned i = 0; i < p->nb_inputs; i++)
+ pad_params_free(&p->inputs[i]);
+ av_freep(&p->inputs);
+
+ for (unsigned i = 0; i < p->nb_outputs; i++)
+ pad_params_free(&p->outputs[i]);
+ av_freep(&p->outputs);
+
+ av_dict_free(&p->opts);
+
+ av_freep(&p->filter_name);
+ av_freep(&p->instance_name);
+
+ av_freep(pp);
+}
+
+static void chain_free(AVFilterChain **pch)
+{
+ AVFilterChain *ch = *pch;
+
+ if (!ch)
+ return;
+
+ for (size_t i = 0; i < ch->nb_filters; i++)
+ filter_params_free(&ch->filters[i]);
+ av_freep(&ch->filters);
+
+ av_freep(pch);
+}
+
+void avfilter_graph_segment_free(AVFilterGraphSegment **pseg)
+{
+ AVFilterGraphSegment *seg = *pseg;
+
+ if (!seg)
+ return;
+
+ for (size_t i = 0; i < seg->nb_chains; i++)
+ chain_free(&seg->chains[i]);
+ av_freep(&seg->chains);
+
+ av_freep(&seg->scale_sws_opts);
+
+ av_freep(pseg);
+}
+
+static int linklabels_parse(void *logctx, const char **linklabels,
+ AVFilterPadParams ***res, unsigned *nb_res)
+{
+ AVFilterPadParams **pp = NULL;
+ unsigned nb = 0;
+ int ret;
+
+ while (**linklabels == '[') {
+ char *label;
+ AVFilterPadParams *par, **tmp;
+
+ label = parse_link_name(linklabels, logctx);
+ if (!label) {
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ par = av_mallocz(sizeof(*par));
+ if (!par) {
+ av_freep(&label);
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ par->label = label;
+
+ tmp = av_realloc_array(pp, nb + 1, sizeof(*pp));
+ if (!tmp) {
+ pad_params_free(&par);
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ pp = tmp;
+ pp[nb++] = par;
+
+ *linklabels += strspn(*linklabels, WHITESPACES);
+ }
+
+ *res = pp;
+ *nb_res = nb;
+
+ return 0;
+fail:
+ for (unsigned i = 0; i < nb; i++)
+ pad_params_free(&pp[i]);
+ av_freep(&pp);
+ return ret;
+}
+
+static int filter_parse(void *logctx, const char **filter,
+ AVFilterParams **pp)
+{
+ AVFilterParams *p;
+ char *inst_name;
+ int ret;
+
+ p = av_mallocz(sizeof(*p));
+ if (!p)
+ return AVERROR(ENOMEM);
+
+ ret = linklabels_parse(logctx, filter, &p->inputs, &p->nb_inputs);
+ if (ret < 0)
+ goto fail;
+
+ p->filter_name = av_get_token(filter, "=,;[");
+ if (!p->filter_name) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ inst_name = strchr(p->filter_name, '@');
+ if (inst_name) {
+ *inst_name++ = 0;
+ p->instance_name = av_strdup(inst_name);
+ if (!p->instance_name) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ }
+
+ if (**filter == '=') {
+ const AVFilter *f = avfilter_get_by_name(p->filter_name);
+ char *opts;
+
+ (*filter)++;
+
+ opts = av_get_token(filter, "[],;");
+ if (!opts) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ ret = ff_filter_opt_parse(logctx, f ? f->priv_class : NULL,
+ &p->opts, opts);
+ av_freep(&opts);
+ if (ret < 0)
+ goto fail;
+ }
+
+ ret = linklabels_parse(logctx, filter, &p->outputs, &p->nb_outputs);
+ if (ret < 0)
+ goto fail;
+
+ *filter += strspn(*filter, WHITESPACES);
+
+ *pp = p;
+ return 0;
+fail:
+ av_log(logctx, AV_LOG_ERROR,
+ "Error parsing a filter description around: %s\n", *filter);
+ filter_params_free(&p);
+ return ret;
+}
+
+static int chain_parse(void *logctx, const char **pchain,
+ AVFilterChain **pch)
+{
+ const char *chain = *pchain;
+ AVFilterChain *ch;
+ int ret;
+
+ *pch = NULL;
+
+ ch = av_mallocz(sizeof(*ch));
+ if (!ch)
+ return AVERROR(ENOMEM);
+
+ while (*chain) {
+ AVFilterParams *p, **tmp;
+ char chr;
+
+ ret = filter_parse(logctx, &chain, &p);
+ if (ret < 0)
+ goto fail;
+
+ tmp = av_realloc_array(ch->filters, ch->nb_filters + 1, sizeof(*ch->filters));
+ if (!tmp) {
+ filter_params_free(&p);
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ ch->filters = tmp;
+ ch->filters[ch->nb_filters++] = p;
+
+ // a filter ends with one of: , ; end-of-string
+ chr = *chain;
+ if (chr && chr != ',' && chr != ';') {
+ av_log(logctx, AV_LOG_ERROR,
+ "Trailing garbage after a filter: %s\n", chain);
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ if (chr) {
+ chain++;
+ chain += strspn(chain, WHITESPACES);
+
+ if (chr == ';')
+ break;
+ }
+ }
+
+ *pchain = chain;
+ *pch = ch;
+
+ return 0;
+fail:
+ av_log(logctx, AV_LOG_ERROR,
+ "Error parsing filterchain '%s' around: %s\n", *pchain, chain);
+ chain_free(&ch);
+ return ret;
+}
+
+int avfilter_graph_segment_parse(AVFilterGraph *graph, const char *graph_str,
+ int flags, AVFilterGraphSegment **pseg)
+{
+ AVFilterGraphSegment *seg;
+ int ret;
+
+ *pseg = NULL;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ seg = av_mallocz(sizeof(*seg));
+ if (!seg)
+ return AVERROR(ENOMEM);
+
+ seg->graph = graph;
+
+ graph_str += strspn(graph_str, WHITESPACES);
+
+ ret = parse_sws_flags(&graph_str, &seg->scale_sws_opts, &graph);
+ if (ret < 0)
+ goto fail;
+
+ graph_str += strspn(graph_str, WHITESPACES);
+
+ while (*graph_str) {
+ AVFilterChain *ch, **tmp;
+
+ ret = chain_parse(graph, &graph_str, &ch);
+ if (ret < 0)
+ goto fail;
+
+ tmp = av_realloc_array(seg->chains, seg->nb_chains + 1, sizeof(*seg->chains));
+ if (!tmp) {
+ chain_free(&ch);
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ seg->chains = tmp;
+ seg->chains[seg->nb_chains++] = ch;
+
+ graph_str += strspn(graph_str, WHITESPACES);
+ }
+
+ if (!seg->nb_chains) {
+ av_log(graph, AV_LOG_ERROR, "No filters specified in the graph description\n");
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ *pseg = seg;
+
+ return 0;
+fail:
+ avfilter_graph_segment_free(&seg);
+ return ret;
+}
+
+int avfilter_graph_segment_create_filters(AVFilterGraphSegment *seg, int flags)
+{
+ size_t idx = 0;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ if (seg->scale_sws_opts) {
+ av_freep(&seg->graph->scale_sws_opts);
+ seg->graph->scale_sws_opts = av_strdup(seg->scale_sws_opts);
+ if (!seg->graph->scale_sws_opts)
+ return AVERROR(ENOMEM);
+ }
+
+ for (size_t i = 0; i < seg->nb_chains; i++) {
+ AVFilterChain *ch = seg->chains[i];
+
+ for (size_t j = 0; j < ch->nb_filters; j++) {
+ AVFilterParams *p = ch->filters[j];
+ const AVFilter *f = avfilter_get_by_name(p->filter_name);
+ char inst_name[30], *name = p->instance_name ? p->instance_name :
+ inst_name;
+
+ // skip already processed filters
+ if (p->filter || !p->filter_name)
+ continue;
+
+ if (!f) {
+ av_log(seg->graph, AV_LOG_ERROR,
+ "No such filter: '%s'\n", p->filter_name);
+ return AVERROR_FILTER_NOT_FOUND;
+ }
+
+ if (!p->instance_name)
+ snprintf(inst_name, sizeof(inst_name), "Parsed_%s_%zu", f->name, idx);
+
+ p->filter = avfilter_graph_alloc_filter(seg->graph, f, name);
+ if (!p->filter)
+ return AVERROR(ENOMEM);
+
+ if (!strcmp(f->name, "scale") && seg->graph->scale_sws_opts) {
+ int ret = av_set_options_string(p->filter, seg->graph->scale_sws_opts,
+ "=", ":");
+ if (ret < 0) {
+ avfilter_free(p->filter);
+ p->filter = NULL;
+ return ret;
+ }
+ }
+
+ av_freep(&p->filter_name);
+ av_freep(&p->instance_name);
+
+ idx++;
+ }
+ }
+
+ return 0;
+}
+
+static int fail_creation_pending(AVFilterGraphSegment *seg, const char *fn,
+ const char *func)
+{
+ av_log(seg->graph, AV_LOG_ERROR,
+ "A creation-pending filter '%s' present in the segment. All filters "
+ "must be created or disabled before calling %s().\n", fn, func);
+ return AVERROR(EINVAL);
+}
+
+int avfilter_graph_segment_apply_opts(AVFilterGraphSegment *seg, int flags)
+{
+ int ret, leftover_opts = 0;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ for (size_t i = 0; i < seg->nb_chains; i++) {
+ AVFilterChain *ch = seg->chains[i];
+
+ for (size_t j = 0; j < ch->nb_filters; j++) {
+ AVFilterParams *p = ch->filters[j];
+
+ if (p->filter_name)
+ return fail_creation_pending(seg, p->filter_name, __func__);
+ if (!p->filter || !p->opts)
+ continue;
+
+ ret = av_opt_set_dict2(p->filter, &p->opts, AV_OPT_SEARCH_CHILDREN);
+ if (ret < 0)
+ return ret;
+
+ if (av_dict_count(p->opts))
+ leftover_opts = 1;
+ }
+ }
+
+ return leftover_opts ? AVERROR_OPTION_NOT_FOUND : 0;
+}
+
+int avfilter_graph_segment_init(AVFilterGraphSegment *seg, int flags)
+{
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ for (size_t i = 0; i < seg->nb_chains; i++) {
+ AVFilterChain *ch = seg->chains[i];
+
+ for (size_t j = 0; j < ch->nb_filters; j++) {
+ AVFilterParams *p = ch->filters[j];
+ int ret;
+
+ if (p->filter_name)
+ return fail_creation_pending(seg, p->filter_name, __func__);
+ if (!p->filter || p->filter->internal->initialized)
+ continue;
+
+ ret = avfilter_init_dict(p->filter, NULL);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static unsigned
+find_linklabel(AVFilterGraphSegment *seg, const char *label,
+ int output, size_t idx_chain, size_t idx_filter,
+ AVFilterParams **pp)
+{
+ for (; idx_chain < seg->nb_chains; idx_chain++) {
+ AVFilterChain *ch = seg->chains[idx_chain];
+
+ for (; idx_filter < ch->nb_filters; idx_filter++) {
+ AVFilterParams *p = ch->filters[idx_filter];
+ AVFilterPadParams **io = output ? p->outputs : p->inputs;
+ unsigned nb_io = output ? p->nb_outputs : p->nb_inputs;
+ AVFilterLink **l;
+ unsigned nb_l;
+
+ if (!p->filter)
+ continue;
+
+ l = output ? p->filter->outputs : p->filter->inputs;
+ nb_l = output ? p->filter->nb_outputs : p->filter->nb_inputs;
+
+ for (unsigned i = 0; i < FFMIN(nb_io, nb_l); i++)
+ if (!l[i] && io[i]->label && !strcmp(io[i]->label, label)) {
+ *pp = p;
+ return i;
+ }
+ }
+
+ idx_filter = 0;
+ }
+
+ *pp = NULL;
+ return 0;
+}
+
+static int inout_add(AVFilterInOut **inouts, AVFilterContext *f, unsigned pad_idx,
+ const char *label)
+{
+ AVFilterInOut *io = av_mallocz(sizeof(*io));
+
+ if (!io)
+ return AVERROR(ENOMEM);
+
+ io->filter_ctx = f;
+ io->pad_idx = pad_idx;
+
+ if (label) {
+ io->name = av_strdup(label);
+ if (!io->name) {
+ avfilter_inout_free(&io);
+ return AVERROR(ENOMEM);
+ }
+ }
+
+ append_inout(inouts, &io);
+
+ return 0;
+}
+
+static int link_inputs(AVFilterGraphSegment *seg, size_t idx_chain,
+ size_t idx_filter, AVFilterInOut **inputs)
+{
+ AVFilterChain *ch = seg->chains[idx_chain];
+ AVFilterParams *p = ch->filters[idx_filter];
+ AVFilterContext *f = p->filter;
+
+ int ret;
+
+ if (f->nb_inputs < p->nb_inputs) {
+ av_log(seg->graph, AV_LOG_ERROR,
+ "More input link labels specified for filter '%s' than "
+ "it has inputs: %u > %d\n", f->filter->name,
+ p->nb_inputs, f->nb_inputs);
+ return AVERROR(EINVAL);
+ }
+
+ for (unsigned in = 0; in < f->nb_inputs; in++) {
+ const char *label = (in < p->nb_inputs) ? p->inputs[in]->label : NULL;
+
+ // skip already linked inputs
+ if (f->inputs[in])
+ continue;
+
+ if (label) {
+ AVFilterParams *po = NULL;
+ unsigned idx = find_linklabel(seg, label, 1, idx_chain, idx_filter, &po);
+
+ if (po) {
+ ret = avfilter_link(po->filter, idx, f, in);
+ if (ret < 0)
+ return ret;
+
+ continue;
+ }
+ }
+
+ ret = inout_add(inputs, f, in, label);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int link_outputs(AVFilterGraphSegment *seg, size_t idx_chain,
+ size_t idx_filter, AVFilterInOut **outputs)
+{
+ AVFilterChain *ch = seg->chains[idx_chain];
+ AVFilterParams *p = ch->filters[idx_filter];
+ AVFilterContext *f = p->filter;
+
+ int ret;
+
+ if (f->nb_outputs < p->nb_outputs) {
+ av_log(seg->graph, AV_LOG_ERROR,
+ "More output link labels specified for filter '%s' than "
+ "it has outputs: %u > %d\n", f->filter->name,
+ p->nb_outputs, f->nb_outputs);
+ return AVERROR(EINVAL);
+ }
+ for (unsigned out = 0; out < f->nb_outputs; out++) {
+ char *label = (out < p->nb_outputs) ? p->outputs[out]->label : NULL;
+
+ // skip already linked outputs
+ if (f->outputs[out])
+ continue;
+
+ if (label) {
+ AVFilterParams *po = NULL;
+ unsigned idx = find_linklabel(seg, label, 0, idx_chain, idx_filter, &po);
+
+ if (po) {
+ ret = avfilter_link(f, out, po->filter, idx);
+ if (ret < 0)
+ return ret;
+
+ continue;
+ }
+ }
+
+ // if this output is unlabeled, try linking it to an unlabeled
+ // input in the next non-disabled filter in the chain
+ for (size_t i = idx_filter + 1; i < ch->nb_filters && !label; i++) {
+ AVFilterParams *p_next = ch->filters[i];
+
+ if (!p_next->filter)
+ continue;
+
+ for (unsigned in = 0; in < p_next->filter->nb_inputs; in++) {
+ if (!p_next->filter->inputs[in] &&
+ (in >= p_next->nb_inputs || !p_next->inputs[in]->label)) {
+ ret = avfilter_link(f, out, p_next->filter, in);
+ if (ret < 0)
+ return ret;
+
+ goto cont;
+ }
+ }
+ break;
+ }
+
+ ret = inout_add(outputs, f, out, label);
+ if (ret < 0)
+ return ret;
+
+cont:;
+ }
+
+ return 0;
+}
+
+int avfilter_graph_segment_link(AVFilterGraphSegment *seg, int flags,
+ AVFilterInOut **inputs,
+ AVFilterInOut **outputs)
+{
+ int ret;
+
+ *inputs = NULL;
+ *outputs = NULL;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ for (size_t idx_chain = 0; idx_chain < seg->nb_chains; idx_chain++) {
+ AVFilterChain *ch = seg->chains[idx_chain];
+
+ for (size_t idx_filter = 0; idx_filter < ch->nb_filters; idx_filter++) {
+ AVFilterParams *p = ch->filters[idx_filter];
+
+ if (p->filter_name) {
+ ret = fail_creation_pending(seg, p->filter_name, __func__);
+ goto fail;
+ }
+
+ if (!p->filter)
+ continue;
+
+ ret = link_inputs(seg, idx_chain, idx_filter, inputs);
+ if (ret < 0)
+ goto fail;
+
+ ret = link_outputs(seg, idx_chain, idx_filter, outputs);
+ if (ret < 0)
+ goto fail;
+ }
+ }
+ return 0;
+fail:
+ avfilter_inout_free(inputs);
+ avfilter_inout_free(outputs);
+ return ret;
+}
+
+int avfilter_graph_segment_apply(AVFilterGraphSegment *seg, int flags,
+ AVFilterInOut **inputs,
+ AVFilterInOut **outputs)
+{
+ int ret;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ ret = avfilter_graph_segment_create_filters(seg, 0);
+ if (ret < 0) {
+ av_log(seg->graph, AV_LOG_ERROR, "Error creating filters\n");
+ return ret;
+ }
+
+ ret = avfilter_graph_segment_apply_opts(seg, 0);
+ if (ret < 0) {
+ av_log(seg->graph, AV_LOG_ERROR, "Error applying filter options\n");
+ return ret;
+ }
+
+ ret = avfilter_graph_segment_init(seg, 0);
+ if (ret < 0) {
+ av_log(seg->graph, AV_LOG_ERROR, "Error initializing filters\n");
+ return ret;
+ }
+
+ ret = avfilter_graph_segment_link(seg, 0, inputs, outputs);
+ if (ret < 0) {
+ av_log(seg->graph, AV_LOG_ERROR, "Error linking filters\n");
+ return ret;
+ }
+
+ return 0;
+}
--
2.35.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/5] lavfi: add a new filtergraph parsing API
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 3/5] lavfi: add a new filtergraph parsing API Anton Khirnov
@ 2023-01-30 12:59 ` Nicolas George
2023-02-01 7:02 ` Anton Khirnov
2023-02-03 9:14 ` [FFmpeg-devel] [PATCH] " Anton Khirnov
0 siblings, 2 replies; 25+ messages in thread
From: Nicolas George @ 2023-01-30 12:59 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Anton Khirnov (12023-01-20):
> Callers currently have two ways of adding filters to a graph - they can
> either
> - create, initialize, and link them manually
> - use one of the avfilter_graph_parse*() functions, which take a
> (typically end-user-written) string, split it into individual filter
> definitions+options, then create filters, apply options, initialize
> filters, and finally link them - all based on information from this
> string.
>
> A major problem with the second approach is that it performs many
> actions as a single atomic unit, leaving the caller no space to
> intervene in between. Such intervention would be useful e.g. to
> - modify filter options;
> - supply hardware device contexts;
> both of which typically must be done before the filter is initialized.
>
> Callers who need such intervention are then forced to invent their own
> filtergraph parsing, which is clearly suboptimal.
>
> This commit aims to address this problem by adding a new modular
> filtergraph parsing API. It adds a new avfilter_graph_segment_parse()
> function to parse a string filtergraph description into an intermediate
> tree-like representation (AVFilterGraphSegment and its children).
>
> This intermediate form may then be applied step by step using further
> new avfilter_graph_segment*() functions, with user intervention possible
> between each step.
> ---
> doc/APIchanges | 15 +
> libavfilter/avfilter.h | 313 +++++++++++++++++
> libavfilter/graphparser.c | 689 +++++++++++++++++++++++++++++++++++++-
> 3 files changed, 1010 insertions(+), 7 deletions(-)
The API seems ok on the whole, which was the biggest concern for a
change like that done in isolation.
>
> diff --git a/doc/APIchanges b/doc/APIchanges
> index a11acadecd..f8e936ba9a 100644
> --- a/doc/APIchanges
> +++ b/doc/APIchanges
> @@ -14,6 +14,21 @@ libavutil: 2021-04-27
>
> API changes, most recent first:
>
> +2023-xx-xx - xxxxxxxxxx - lavfi 8.x.100 - avfilter.h
> + Add filtergraph segment parsing API.
> + New structs:
> + - AVFilterGraphSegment
> + - AVFilterChain
> + - AVFilterParams
> + - AVFilterPadParams
> + New functions:
> + - avfilter_graph_segment_parse()
> + - avfilter_graph_segment_create_filters()
> + - avfilter_graph_segment_apply_opts()
> + - avfilter_graph_segment_init()
> + - avfilter_graph_segment_link()
> + - avfilter_graph_segment_apply()
> +
> 2023-01-13 - xxxxxxxxxx - lavu 57.44.100 - ambient_viewing_environment.h frame.h
> Adds a new structure for holding H.274 Ambient Viewing Environment metadata,
> AVAmbientViewingEnvironment.
> diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
> index c2ec7a4b5f..037a8bc490 100644
> --- a/libavfilter/avfilter.h
> +++ b/libavfilter/avfilter.h
> @@ -1118,6 +1118,319 @@ int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
> AVFilterInOut **inputs,
> AVFilterInOut **outputs);
>
> +/**
> + * Parameters of a filter's input or output pad.
> + *
> + * Created as a child of AVFilterParams by avfilter_graph_segment_parse().
> + * Freed in avfilter_graph_segment_free().
> + */
> +typedef struct AVFilterPadParams {
> + /**
> + * An av_malloc()'ed string containing the pad label.
> + *
> + * May be av_free()'d and set to NULL by the caller, in which case this pad
> + * will be treated as unlabeled for linking.
> + * May also be replaced by another av_malloc()'ed string.
> + */
> + char *label;
> +} AVFilterPadParams;
> +
> +/**
> + * Parameters describing a filter to be created in a filtergraph.
> + *
> + * Created as a child of AVFilterGraphSegment by avfilter_graph_segment_parse().
> + * Freed in avfilter_graph_segment_free().
> + */
> +typedef struct AVFilterParams {
> + /**
> + * The filter context.
> + *
> + * Created by avfilter_graph_segment_create_filters() based on
> + * AVFilterParams.filter_name and instance_name.
> + *
> + * Callers may also create the filter context manually, then they should
> + * av_free() filter_name and set it to NULL. Such AVFilterParams instances
> + * are then skipped by avfilter_graph_segment_create_filters().
> + */
> + AVFilterContext *filter;
> +
> + /**
> + * Name of the AVFilter to be used.
> + *
> + * An av_malloc()'ed string, set by avfilter_graph_segment_parse(). Will be
> + * passed to avfilter_get_by_name() by
> + * avfilter_graph_segment_create_filters().
> + *
> + * Callers may av_free() this string and replace it with another one or
> + * NULL. If the caller creates the filter instance manually, this string
> + * MUST be set to NULL.
> + *
> + * When both AVFilterParams.filter an AVFilterParams.filter_name are NULL,
> + * this AVFilterParams instance is skipped by avfilter_graph_segment_*()
> + * functions.
> + */
> + char *filter_name;
> + /**
> + * Name to be used for this filter instance.
> + *
> + * An av_malloc()'ed string, may be set by avfilter_graph_segment_parse() or
> + * left NULL. The caller may av_free() this string and replace with another
> + * one or NULL.
> + *
> + * Will be used by avfilter_graph_segment_create_filters() - passed as the
> + * third argument to avfilter_graph_alloc_filter(), then freed and set to
> + * NULL.
> + */
> + char *instance_name;
> +
> + /**
> + * Options to be apllied to the filter.
> + *
> + * Filled by avfilter_graph_segment_parse(). Afterwards may be freely
> + * modified by the caller.
> + *
> + * Will be applied to the filter by avfilter_graph_segment_apply_opts()
> + * with an equivalent of av_opt_set_dict2(filter, &opts, * AV_OPT_SEARCH_CHILDREN),
> + * i.e. any unapplied options will be left in this dictionary.
> + */
> + AVDictionary *opts;
> +
> + AVFilterPadParams **inputs;
> + unsigned nb_inputs;
> +
> + AVFilterPadParams **outputs;
> + unsigned nb_outputs;
> +} AVFilterParams;
> +
> +/**
> + * A filterchain is a list of filter specifications.
> + *
> + * Created as a child of AVFilterGraphSegment by avfilter_graph_segment_parse().
> + * Freed in avfilter_graph_segment_free().
> + */
> +typedef struct AVFilterChain {
> + AVFilterParams **filters;
> + size_t nb_filters;
> +} AVFilterChain;
> +
> +/**
> + * A parsed representation of a filtergraph segment.
> + *
> + * A filtergraph segment is conceptually a list of filterchains, with some
> + * supplementary information (e.g. format conversion flags).
> + *
> + * Created by avfilter_graph_segment_parse(). Must be freed with
> + * avfilter_graph_segment_free().
> + */
> +typedef struct AVFilterGraphSegment {
> + /**
> + * The filtergraph this segment is associated with.
> + * Set by avfilter_graph_segment_parse().
> + */
> + AVFilterGraph *graph;
> +
> + /**
> + * A list of filter chain contained in this segment..
> + * Set in avfilter_graph_segment_parse().
> + */
> + AVFilterChain **chains;
> + size_t nb_chains;
> +
> + /**
> + * A string containing a colon-separated list of key=value options applied
> + * to all scale filters in this segment.
> + *
> + * May be set by avfilter_graph_segment_parse().
> + * The caller may free this string with av_free() and replace it with a
> + * different av_malloc()'ed string.
> + */
> + char *scale_sws_opts;
> +} AVFilterGraphSegment;
> +
> +/**
> + * Parse a textual filtergraph description into an intermediate form.
> + *
> + * This intermediate representation is intended to be modified by the caller as
> + * described in the documentation of AVFilterGraphSegment and its children, and
> + * then applied to the graph either manually or with other
> + * avfilter_graph_segment_*() functions. See the documentation for
> + * avfilter_graph_segment_apply() for the canonical way to apply
> + * AVFilterGraphSegment.
> + *
> + * @param graph Filter graph the parsed segment is associated with. Will only be
> + * used for logging and similar auxiliary purposes. The graph will
> + * not be actually modified by this function - the parsing results
> + * are instead stored in seg for further processing.
> + * @param graph_str a string describing the filtergraph segment
> + * @param flags reserved for future use, caller must set to 0 for now
> + * @param seg A pointer to the newly-created AVFilterGraphSegment is written
> + * here on success. The graph segment is owned by the caller and must
> + * be freed with avfilter_graph_segment_free() before graph itself is
> + * freed.
> + *
> + * @return a non-negative number on success, a negative error code on failure
> + */
> +int avfilter_graph_segment_parse(AVFilterGraph *graph, const char *graph_str,
> + int flags, AVFilterGraphSegment **seg);
> +
> +/**
> + * Create filters specified in a graph segment.
> + *
> + * Walk through the creation-pending AVFilterParams in the segment and create
> + * new filter instances for them.
> + * Creation-pending params are those where AVFilterParams.filter_name is
> + * non-NULL (and hence AVFilterParams.filter is NULL). All other AVFilterParams
> + * instances are ignored.
> + *
> + * For any filter created by this function, the corresponding
> + * AVFilterParams.filter is set to the newly-created filter context,
> + * AVFilterParams.filter_name and AVFilterParams.instance_name are freed and set
> + * to NULL.
> + *
> + * @param seg the filtergraph segment to process
> + * @param flags reserved for future use, caller must set to 0 for now
> + *
> + * @return
> + * - a non-negative number if all creation-pending filters were successfully
> + * created
> + * - AVERROR_FILTER_NOT_FOUND if some filter's name did not correspond to a
> + * known filter
> + * - another negative error code on other failures
> + *
> + * @note Calling this function multiple times is safe, as it is idempotent.
> + */
> +int avfilter_graph_segment_create_filters(AVFilterGraphSegment *seg, int flags);
> +
> +/**
> + * Apply parsed options to filter instances in a graph segment.
> + *
> + * Walk through all filter instances in the graph segment that have option
> + * dictionaries associated with them and apply those options with
> + * av_opt_set_dict2(..., AV_OPT_SEARCH_CHILDREN). AVFilterParams.opts is
> + * replaced by the dictionary output by av_opt_set_dict2(), which should be
> + * empty (NULL) if all options were successfully applied.
> + *
> + * If any options could not be found, this function will continue processing all
> + * other filters and finally return AVERROR_OPTION_NOT_FOUND (unless another
> + * error happens). The calling program may then deal with unapplied options as
> + * it wishes.
> + *
> + * Any creation-pending filters (see avfilter_graph_segment_create_filters())
> + * present in the segment will cause this function to fail. AVFilterParams with
> + * no associated filter context are simply skipped.
> + *
> + * @param seg the filtergraph segment to process
> + * @param flags reserved for future use, caller must set to 0 for now
> + *
> + * @return
> + * - a non-negative number if all options were successfully applied
> + * - AVERROR_OPTION_NOT_FOUND if some options were not found in a filter
> + * - another negative error code on other failures
> + *
> + * @note Calling this function multiple times is safe, as it is idempotent.
> + */
> +int avfilter_graph_segment_apply_opts(AVFilterGraphSegment *seg, int flags);
> +
> +/**
> + * Initialize all filter instances in a graph segment.
> + *
> + * Walk through all filter instances in the graph segment and call
> + * avfilter_init_dict(..., NULL) on those that have not been initialized yet.
> + *
> + * Any creation-pending filters (see avfilter_graph_segment_create_filters())
> + * present in the segment will cause this function to fail. AVFilterParams with
> + * no associated filter context or whose filter context is already initialized,
> + * are simply skipped.
> + *
> + * @param seg the filtergraph segment to process
> + * @param flags reserved for future use, caller must set to 0 for now
> + *
> + * @return
> + * - a non-negative number if all filter instances were successfully initialized
> + * - a negative error code on other failures
> + *
> + * @note Calling this function multiple times is safe, as it is idempotent.
> + */
> +int avfilter_graph_segment_init(AVFilterGraphSegment *seg, int flags);
> +
> +/**
> + * Link filters in a graph segment.
> + *
> + * Walk through all filter instances in the graph segment and try to link all
> + * unlinked input and output pads. Any creation-pending filters (see
> + * avfilter_graph_segment_create_filters()) present in the segment will cause
> + * this function to fail. Disabled filters and already linked pads are skipped.
> + *
> + * Every filter output pad that has a corresponding AVFilterPadParams with a
> + * non-NULL label is
> + * - linked to the input with the matching label, if one exists;
> + * - exported in the outputs linked list otherwise, with the label preserved.
> + * Unlabeled outputs are
> + * - linked to the first unlinked unlabeled input in the next non-disabled
> + * filter in the chain, if one exists
> + * - exported in the ouputs linked list otherwise, with NULL label
> + *
> + * Similarly, unlinked input pads are exported in the inputs linked list.
> + *
> + * @param seg the filtergraph segment to process
> + * @param flags reserved for future use, caller must set to 0 for now
> + * @param[out] inputs a linked list of all free (unlinked) inputs of the
> + * filters in this graph segment will be returned here. It
> + * is to be freed by the caller using avfilter_inout_free().
> + * @param[out] outputs a linked list of all free (unlinked) outputs of the
> + * filters in this graph segment will be returned here. It
> + * is to be freed by the caller using avfilter_inout_free().
> + *
> + * @return
> + * - a non-negative number on success
> + * - a negative error code on failure
> + *
> + * @note Calling this function multiple times is safe, as it is idempotent.
> + */
> +int avfilter_graph_segment_link(AVFilterGraphSegment *seg, int flags,
> + AVFilterInOut **inputs,
> + AVFilterInOut **outputs);
> +
> +/**
> + * Apply all filter/link descriptions from a graph segment to the associated filtergraph.
> + *
> + * This functions is currently equivalent to calling the following in sequence:
> + * - avfilter_graph_segment_create_filters();
> + * - avfilter_graph_segment_apply_opts();
> + * - avfilter_graph_segment_init();
> + * - avfilter_graph_segment_link();
> + * failing if any of them fails. This list may be extended in the future.
> + *
> + * Since the above functions are idempotent, the caller may call some of the
> + * manually, then do some custom processing on the filtergraph, then call this
> + * function to do the rest.
> + *
> + * @param seg the filtergraph segment to process
> + * @param flags reserved for future use, caller must set to 0 for now
> + * @param[out] inputs passed to avfilter_graph_segment_link()
> + * @param[out] outputs passed to avfilter_graph_segment_link()
> + * @return
> + * - a non-negative number on success
> + * - a negative error code on failure
> + *
> + * @note Calling this function multiple times is safe, as it is idempotent.
> + */
> +int avfilter_graph_segment_apply(AVFilterGraphSegment *seg, int flags,
> + AVFilterInOut **inputs,
> + AVFilterInOut **outputs);
> +
> +/**
> + * Free the provided AVFilterGraphSegment and everything associated with it.
> + *
> + * @param seg double pointer to the AVFilterGraphSegment to be freed. NULL will
> + * be written to this pointer on exit from this function.
> + *
> + * @note
> + * The filter contexts (AVFilterParams.filter) are owned by AVFilterGraph rather
> + * than AVFilterGraphSegment, so they are not freed.
> + */
> +void avfilter_graph_segment_free(AVFilterGraphSegment **seg);
> +
> /**
> * Send a command to one or more filter instances.
> *
> diff --git a/libavfilter/graphparser.c b/libavfilter/graphparser.c
> index 0759c39014..ac6171e0c4 100644
> --- a/libavfilter/graphparser.c
> +++ b/libavfilter/graphparser.c
> @@ -24,10 +24,12 @@
> #include <stdio.h>
>
> #include "libavutil/avstring.h"
> +#include "libavutil/dict.h"
> #include "libavutil/mem.h"
> #include "libavutil/opt.h"
>
> #include "avfilter.h"
> +#include "internal.h"
>
> #define WHITESPACES " \n\t\r"
>
> @@ -386,7 +388,7 @@ static int parse_outputs(const char **buf, AVFilterInOut **curr_inputs,
> return pad;
> }
>
> -static int parse_sws_flags(const char **buf, AVFilterGraph *graph)
> +static int parse_sws_flags(const char **buf, char **dst, void *log_ctx)
> {
> char *p = strchr(*buf, ';');
>
> @@ -394,16 +396,16 @@ static int parse_sws_flags(const char **buf, AVFilterGraph *graph)
> return 0;
>
> if (!p) {
> - av_log(graph, AV_LOG_ERROR, "sws_flags not terminated with ';'.\n");
> + av_log(log_ctx, AV_LOG_ERROR, "sws_flags not terminated with ';'.\n");
> return AVERROR(EINVAL);
> }
>
> *buf += 4; // keep the 'flags=' part
>
> - av_freep(&graph->scale_sws_opts);
> - if (!(graph->scale_sws_opts = av_mallocz(p - *buf + 1)))
> + av_freep(dst);
> + if (!(*dst = av_mallocz(p - *buf + 1)))
> return AVERROR(ENOMEM);
> - av_strlcpy(graph->scale_sws_opts, *buf, p - *buf + 1);
> + av_strlcpy(*dst, *buf, p - *buf + 1);
>
> *buf = p + 1;
> return 0;
> @@ -420,7 +422,7 @@ int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
>
> filters += strspn(filters, WHITESPACES);
>
> - if ((ret = parse_sws_flags(&filters, graph)) < 0)
> + if ((ret = parse_sws_flags(&filters, &graph->scale_sws_opts, graph)) < 0)
> goto end;
>
> do {
> @@ -551,7 +553,7 @@ int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
> AVFilterInOut *open_inputs = open_inputs_ptr ? *open_inputs_ptr : NULL;
> AVFilterInOut *open_outputs = open_outputs_ptr ? *open_outputs_ptr : NULL;
>
> - if ((ret = parse_sws_flags(&filters, graph)) < 0)
> + if ((ret = parse_sws_flags(&filters, &graph->scale_sws_opts, graph)) < 0)
> goto end;
>
> do {
> @@ -623,3 +625,676 @@ end:
> }
> return ret;
> }
> +
> +static void pad_params_free(AVFilterPadParams **pfpp)
> +{
> + AVFilterPadParams *fpp = *pfpp;
> +
> + if (!fpp)
> + return;
> +
> + av_freep(&fpp->label);
> +
> + av_freep(pfpp);
> +}
> +
> +static void filter_params_free(AVFilterParams **pp)
> +{
> + AVFilterParams *p = *pp;
> +
> + if (!p)
> + return;
> +
> + for (unsigned i = 0; i < p->nb_inputs; i++)
> + pad_params_free(&p->inputs[i]);
> + av_freep(&p->inputs);
> +
> + for (unsigned i = 0; i < p->nb_outputs; i++)
> + pad_params_free(&p->outputs[i]);
> + av_freep(&p->outputs);
> +
> + av_dict_free(&p->opts);
> +
> + av_freep(&p->filter_name);
> + av_freep(&p->instance_name);
> +
> + av_freep(pp);
> +}
> +
> +static void chain_free(AVFilterChain **pch)
> +{
> + AVFilterChain *ch = *pch;
> +
> + if (!ch)
> + return;
> +
> + for (size_t i = 0; i < ch->nb_filters; i++)
> + filter_params_free(&ch->filters[i]);
> + av_freep(&ch->filters);
> +
> + av_freep(pch);
> +}
> +
> +void avfilter_graph_segment_free(AVFilterGraphSegment **pseg)
> +{
> + AVFilterGraphSegment *seg = *pseg;
> +
> + if (!seg)
> + return;
> +
> + for (size_t i = 0; i < seg->nb_chains; i++)
> + chain_free(&seg->chains[i]);
> + av_freep(&seg->chains);
> +
> + av_freep(&seg->scale_sws_opts);
> +
> + av_freep(pseg);
> +}
> +
> +static int linklabels_parse(void *logctx, const char **linklabels,
> + AVFilterPadParams ***res, unsigned *nb_res)
> +{
> + AVFilterPadParams **pp = NULL;
> + unsigned nb = 0;
> + int ret;
> +
> + while (**linklabels == '[') {
> + char *label;
> + AVFilterPadParams *par, **tmp;
> +
> + label = parse_link_name(linklabels, logctx);
> + if (!label) {
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + par = av_mallocz(sizeof(*par));
> + if (!par) {
> + av_freep(&label);
> + ret = AVERROR(ENOMEM);
> + goto fail;
> + }
> +
> + par->label = label;
> +
> + tmp = av_realloc_array(pp, nb + 1, sizeof(*pp));
> + if (!tmp) {
> + pad_params_free(&par);
> + ret = AVERROR(ENOMEM);
> + goto fail;
> + }
Use FF_DYNARRAY_ADD() or av_dynarray_add(). There are users with
stupidly large filter graphs, it should not be quadratic. Same at
several places below.
> +
> + pp = tmp;
> + pp[nb++] = par;
> +
> + *linklabels += strspn(*linklabels, WHITESPACES);
> + }
> +
> + *res = pp;
> + *nb_res = nb;
> +
> + return 0;
> +fail:
> + for (unsigned i = 0; i < nb; i++)
> + pad_params_free(&pp[i]);
> + av_freep(&pp);
> + return ret;
> +}
> +
> +static int filter_parse(void *logctx, const char **filter,
> + AVFilterParams **pp)
> +{
> + AVFilterParams *p;
> + char *inst_name;
> + int ret;
> +
> + p = av_mallocz(sizeof(*p));
> + if (!p)
> + return AVERROR(ENOMEM);
> +
> + ret = linklabels_parse(logctx, filter, &p->inputs, &p->nb_inputs);
> + if (ret < 0)
> + goto fail;
> +
> + p->filter_name = av_get_token(filter, "=,;[");
> + if (!p->filter_name) {
> + ret = AVERROR(ENOMEM);
> + goto fail;
> + }
> +
> + inst_name = strchr(p->filter_name, '@');
> + if (inst_name) {
> + *inst_name++ = 0;
> + p->instance_name = av_strdup(inst_name);
> + if (!p->instance_name) {
> + ret = AVERROR(ENOMEM);
> + goto fail;
> + }
> + }
> +
> + if (**filter == '=') {
> + const AVFilter *f = avfilter_get_by_name(p->filter_name);
> + char *opts;
> +
> + (*filter)++;
> +
> + opts = av_get_token(filter, "[],;");
> + if (!opts) {
> + ret = AVERROR(ENOMEM);
> + goto fail;
> + }
> +
> + ret = ff_filter_opt_parse(logctx, f ? f->priv_class : NULL,
> + &p->opts, opts);
> + av_freep(&opts);
> + if (ret < 0)
> + goto fail;
> + }
> +
> + ret = linklabels_parse(logctx, filter, &p->outputs, &p->nb_outputs);
> + if (ret < 0)
> + goto fail;
> +
> + *filter += strspn(*filter, WHITESPACES);
> +
> + *pp = p;
> + return 0;
> +fail:
> + av_log(logctx, AV_LOG_ERROR,
> + "Error parsing a filter description around: %s\n", *filter);
> + filter_params_free(&p);
> + return ret;
> +}
> +
> +static int chain_parse(void *logctx, const char **pchain,
> + AVFilterChain **pch)
> +{
> + const char *chain = *pchain;
> + AVFilterChain *ch;
> + int ret;
> +
> + *pch = NULL;
> +
> + ch = av_mallocz(sizeof(*ch));
> + if (!ch)
> + return AVERROR(ENOMEM);
> +
> + while (*chain) {
> + AVFilterParams *p, **tmp;
> + char chr;
> +
> + ret = filter_parse(logctx, &chain, &p);
> + if (ret < 0)
> + goto fail;
> +
> + tmp = av_realloc_array(ch->filters, ch->nb_filters + 1, sizeof(*ch->filters));
> + if (!tmp) {
> + filter_params_free(&p);
> + ret = AVERROR(ENOMEM);
> + goto fail;
> + }
> +
> + ch->filters = tmp;
> + ch->filters[ch->nb_filters++] = p;
> +
> + // a filter ends with one of: , ; end-of-string
> + chr = *chain;
> + if (chr && chr != ',' && chr != ';') {
> + av_log(logctx, AV_LOG_ERROR,
> + "Trailing garbage after a filter: %s\n", chain);
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + if (chr) {
> + chain++;
> + chain += strspn(chain, WHITESPACES);
> +
> + if (chr == ';')
> + break;
> + }
> + }
> +
> + *pchain = chain;
> + *pch = ch;
> +
> + return 0;
> +fail:
> + av_log(logctx, AV_LOG_ERROR,
> + "Error parsing filterchain '%s' around: %s\n", *pchain, chain);
> + chain_free(&ch);
> + return ret;
> +}
> +
> +int avfilter_graph_segment_parse(AVFilterGraph *graph, const char *graph_str,
> + int flags, AVFilterGraphSegment **pseg)
> +{
> + AVFilterGraphSegment *seg;
> + int ret;
> +
> + *pseg = NULL;
> +
> + if (flags)
> + return AVERROR(ENOSYS);
> +
> + seg = av_mallocz(sizeof(*seg));
> + if (!seg)
> + return AVERROR(ENOMEM);
> +
> + seg->graph = graph;
> +
> + graph_str += strspn(graph_str, WHITESPACES);
> +
> + ret = parse_sws_flags(&graph_str, &seg->scale_sws_opts, &graph);
> + if (ret < 0)
> + goto fail;
> +
> + graph_str += strspn(graph_str, WHITESPACES);
> +
> + while (*graph_str) {
> + AVFilterChain *ch, **tmp;
> +
> + ret = chain_parse(graph, &graph_str, &ch);
> + if (ret < 0)
> + goto fail;
> +
> + tmp = av_realloc_array(seg->chains, seg->nb_chains + 1, sizeof(*seg->chains));
> + if (!tmp) {
> + chain_free(&ch);
> + ret = AVERROR(ENOMEM);
> + goto fail;
> + }
> +
> + seg->chains = tmp;
> + seg->chains[seg->nb_chains++] = ch;
> +
> + graph_str += strspn(graph_str, WHITESPACES);
> + }
> +
> + if (!seg->nb_chains) {
> + av_log(graph, AV_LOG_ERROR, "No filters specified in the graph description\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + *pseg = seg;
> +
> + return 0;
> +fail:
> + avfilter_graph_segment_free(&seg);
> + return ret;
> +}
> +
> +int avfilter_graph_segment_create_filters(AVFilterGraphSegment *seg, int flags)
> +{
> + size_t idx = 0;
> +
> + if (flags)
> + return AVERROR(ENOSYS);
> +
> + if (seg->scale_sws_opts) {
> + av_freep(&seg->graph->scale_sws_opts);
> + seg->graph->scale_sws_opts = av_strdup(seg->scale_sws_opts);
> + if (!seg->graph->scale_sws_opts)
> + return AVERROR(ENOMEM);
> + }
> +
> + for (size_t i = 0; i < seg->nb_chains; i++) {
> + AVFilterChain *ch = seg->chains[i];
> +
> + for (size_t j = 0; j < ch->nb_filters; j++) {
> + AVFilterParams *p = ch->filters[j];
> + const AVFilter *f = avfilter_get_by_name(p->filter_name);
> + char inst_name[30], *name = p->instance_name ? p->instance_name :
> + inst_name;
> +
> + // skip already processed filters
> + if (p->filter || !p->filter_name)
> + continue;
> +
> + if (!f) {
> + av_log(seg->graph, AV_LOG_ERROR,
> + "No such filter: '%s'\n", p->filter_name);
> + return AVERROR_FILTER_NOT_FOUND;
> + }
> +
> + if (!p->instance_name)
> + snprintf(inst_name, sizeof(inst_name), "Parsed_%s_%zu", f->name, idx);
> +
> + p->filter = avfilter_graph_alloc_filter(seg->graph, f, name);
> + if (!p->filter)
> + return AVERROR(ENOMEM);
> +
> + if (!strcmp(f->name, "scale") && seg->graph->scale_sws_opts) {
> + int ret = av_set_options_string(p->filter, seg->graph->scale_sws_opts,
> + "=", ":");
> + if (ret < 0) {
> + avfilter_free(p->filter);
> + p->filter = NULL;
> + return ret;
> + }
> + }
> +
> + av_freep(&p->filter_name);
> + av_freep(&p->instance_name);
> +
> + idx++;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int fail_creation_pending(AVFilterGraphSegment *seg, const char *fn,
> + const char *func)
> +{
> + av_log(seg->graph, AV_LOG_ERROR,
> + "A creation-pending filter '%s' present in the segment. All filters "
> + "must be created or disabled before calling %s().\n", fn, func);
> + return AVERROR(EINVAL);
> +}
> +
> +int avfilter_graph_segment_apply_opts(AVFilterGraphSegment *seg, int flags)
> +{
> + int ret, leftover_opts = 0;
> +
> + if (flags)
> + return AVERROR(ENOSYS);
> +
> + for (size_t i = 0; i < seg->nb_chains; i++) {
> + AVFilterChain *ch = seg->chains[i];
> +
> + for (size_t j = 0; j < ch->nb_filters; j++) {
> + AVFilterParams *p = ch->filters[j];
> +
> + if (p->filter_name)
> + return fail_creation_pending(seg, p->filter_name, __func__);
> + if (!p->filter || !p->opts)
> + continue;
> +
> + ret = av_opt_set_dict2(p->filter, &p->opts, AV_OPT_SEARCH_CHILDREN);
> + if (ret < 0)
> + return ret;
> +
> + if (av_dict_count(p->opts))
> + leftover_opts = 1;
> + }
> + }
> +
> + return leftover_opts ? AVERROR_OPTION_NOT_FOUND : 0;
> +}
> +
> +int avfilter_graph_segment_init(AVFilterGraphSegment *seg, int flags)
> +{
> + if (flags)
> + return AVERROR(ENOSYS);
> +
> + for (size_t i = 0; i < seg->nb_chains; i++) {
> + AVFilterChain *ch = seg->chains[i];
> +
> + for (size_t j = 0; j < ch->nb_filters; j++) {
> + AVFilterParams *p = ch->filters[j];
> + int ret;
> +
> + if (p->filter_name)
> + return fail_creation_pending(seg, p->filter_name, __func__);
> + if (!p->filter || p->filter->internal->initialized)
> + continue;
> +
> + ret = avfilter_init_dict(p->filter, NULL);
> + if (ret < 0)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static unsigned
> +find_linklabel(AVFilterGraphSegment *seg, const char *label,
> + int output, size_t idx_chain, size_t idx_filter,
> + AVFilterParams **pp)
> +{
> + for (; idx_chain < seg->nb_chains; idx_chain++) {
> + AVFilterChain *ch = seg->chains[idx_chain];
> +
> + for (; idx_filter < ch->nb_filters; idx_filter++) {
> + AVFilterParams *p = ch->filters[idx_filter];
> + AVFilterPadParams **io = output ? p->outputs : p->inputs;
> + unsigned nb_io = output ? p->nb_outputs : p->nb_inputs;
> + AVFilterLink **l;
> + unsigned nb_l;
> +
> + if (!p->filter)
> + continue;
> +
> + l = output ? p->filter->outputs : p->filter->inputs;
> + nb_l = output ? p->filter->nb_outputs : p->filter->nb_inputs;
> +
> + for (unsigned i = 0; i < FFMIN(nb_io, nb_l); i++)
> + if (!l[i] && io[i]->label && !strcmp(io[i]->label, label)) {
> + *pp = p;
> + return i;
> + }
> + }
> +
> + idx_filter = 0;
> + }
> +
> + *pp = NULL;
> + return 0;
> +}
If I read this code correctly, it will only find link labels that are
defined later in the chain or in a later chain. But referring to earlier
link labels is supported. Even cycles are meant to be supported at
parsing level (format negotiation will likely fail, though, but being
able to make cycles was a goal from the start).
> +
> +static int inout_add(AVFilterInOut **inouts, AVFilterContext *f, unsigned pad_idx,
> + const char *label)
> +{
> + AVFilterInOut *io = av_mallocz(sizeof(*io));
> +
> + if (!io)
> + return AVERROR(ENOMEM);
> +
> + io->filter_ctx = f;
> + io->pad_idx = pad_idx;
> +
> + if (label) {
> + io->name = av_strdup(label);
> + if (!io->name) {
> + avfilter_inout_free(&io);
> + return AVERROR(ENOMEM);
> + }
> + }
> +
> + append_inout(inouts, &io);
> +
> + return 0;
> +}
> +
> +static int link_inputs(AVFilterGraphSegment *seg, size_t idx_chain,
> + size_t idx_filter, AVFilterInOut **inputs)
> +{
> + AVFilterChain *ch = seg->chains[idx_chain];
> + AVFilterParams *p = ch->filters[idx_filter];
> + AVFilterContext *f = p->filter;
> +
> + int ret;
> +
> + if (f->nb_inputs < p->nb_inputs) {
> + av_log(seg->graph, AV_LOG_ERROR,
> + "More input link labels specified for filter '%s' than "
> + "it has inputs: %u > %d\n", f->filter->name,
> + p->nb_inputs, f->nb_inputs);
> + return AVERROR(EINVAL);
> + }
> +
> + for (unsigned in = 0; in < f->nb_inputs; in++) {
> + const char *label = (in < p->nb_inputs) ? p->inputs[in]->label : NULL;
> +
> + // skip already linked inputs
> + if (f->inputs[in])
> + continue;
> +
> + if (label) {
> + AVFilterParams *po = NULL;
> + unsigned idx = find_linklabel(seg, label, 1, idx_chain, idx_filter, &po);
> +
> + if (po) {
> + ret = avfilter_link(po->filter, idx, f, in);
> + if (ret < 0)
> + return ret;
> +
> + continue;
> + }
> + }
> +
> + ret = inout_add(inputs, f, in, label);
> + if (ret < 0)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int link_outputs(AVFilterGraphSegment *seg, size_t idx_chain,
> + size_t idx_filter, AVFilterInOut **outputs)
> +{
> + AVFilterChain *ch = seg->chains[idx_chain];
> + AVFilterParams *p = ch->filters[idx_filter];
> + AVFilterContext *f = p->filter;
> +
> + int ret;
> +
> + if (f->nb_outputs < p->nb_outputs) {
> + av_log(seg->graph, AV_LOG_ERROR,
> + "More output link labels specified for filter '%s' than "
> + "it has outputs: %u > %d\n", f->filter->name,
> + p->nb_outputs, f->nb_outputs);
> + return AVERROR(EINVAL);
> + }
> + for (unsigned out = 0; out < f->nb_outputs; out++) {
> + char *label = (out < p->nb_outputs) ? p->outputs[out]->label : NULL;
> +
> + // skip already linked outputs
> + if (f->outputs[out])
> + continue;
> +
> + if (label) {
> + AVFilterParams *po = NULL;
> + unsigned idx = find_linklabel(seg, label, 0, idx_chain, idx_filter, &po);
> +
> + if (po) {
> + ret = avfilter_link(f, out, po->filter, idx);
> + if (ret < 0)
> + return ret;
> +
> + continue;
> + }
> + }
> +
> + // if this output is unlabeled, try linking it to an unlabeled
> + // input in the next non-disabled filter in the chain
> + for (size_t i = idx_filter + 1; i < ch->nb_filters && !label; i++) {
> + AVFilterParams *p_next = ch->filters[i];
> +
> + if (!p_next->filter)
> + continue;
> +
> + for (unsigned in = 0; in < p_next->filter->nb_inputs; in++) {
> + if (!p_next->filter->inputs[in] &&
> + (in >= p_next->nb_inputs || !p_next->inputs[in]->label)) {
> + ret = avfilter_link(f, out, p_next->filter, in);
> + if (ret < 0)
> + return ret;
> +
> + goto cont;
> + }
> + }
> + break;
> + }
> +
> + ret = inout_add(outputs, f, out, label);
> + if (ret < 0)
> + return ret;
> +
> +cont:;
> + }
> +
> + return 0;
> +}
> +
> +int avfilter_graph_segment_link(AVFilterGraphSegment *seg, int flags,
> + AVFilterInOut **inputs,
> + AVFilterInOut **outputs)
> +{
> + int ret;
> +
> + *inputs = NULL;
> + *outputs = NULL;
> +
> + if (flags)
> + return AVERROR(ENOSYS);
> +
> + for (size_t idx_chain = 0; idx_chain < seg->nb_chains; idx_chain++) {
> + AVFilterChain *ch = seg->chains[idx_chain];
> +
> + for (size_t idx_filter = 0; idx_filter < ch->nb_filters; idx_filter++) {
> + AVFilterParams *p = ch->filters[idx_filter];
> +
> + if (p->filter_name) {
> + ret = fail_creation_pending(seg, p->filter_name, __func__);
> + goto fail;
> + }
> +
> + if (!p->filter)
> + continue;
> +
> + ret = link_inputs(seg, idx_chain, idx_filter, inputs);
> + if (ret < 0)
> + goto fail;
> +
> + ret = link_outputs(seg, idx_chain, idx_filter, outputs);
> + if (ret < 0)
> + goto fail;
> + }
> + }
> + return 0;
> +fail:
> + avfilter_inout_free(inputs);
> + avfilter_inout_free(outputs);
> + return ret;
> +}
> +
> +int avfilter_graph_segment_apply(AVFilterGraphSegment *seg, int flags,
> + AVFilterInOut **inputs,
> + AVFilterInOut **outputs)
> +{
> + int ret;
> +
> + if (flags)
> + return AVERROR(ENOSYS);
> +
> + ret = avfilter_graph_segment_create_filters(seg, 0);
> + if (ret < 0) {
> + av_log(seg->graph, AV_LOG_ERROR, "Error creating filters\n");
> + return ret;
> + }
> +
> + ret = avfilter_graph_segment_apply_opts(seg, 0);
> + if (ret < 0) {
> + av_log(seg->graph, AV_LOG_ERROR, "Error applying filter options\n");
> + return ret;
> + }
> +
> + ret = avfilter_graph_segment_init(seg, 0);
> + if (ret < 0) {
> + av_log(seg->graph, AV_LOG_ERROR, "Error initializing filters\n");
> + return ret;
> + }
> +
> + ret = avfilter_graph_segment_link(seg, 0, inputs, outputs);
> + if (ret < 0) {
> + av_log(seg->graph, AV_LOG_ERROR, "Error linking filters\n");
> + return ret;
> + }
> +
> + return 0;
> +}
This is all I have found for now, but I still need to look at the tricky
parts of the code more carefully.
--
Nicolas George
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/5] lavfi: add a new filtergraph parsing API
2023-01-30 12:59 ` Nicolas George
@ 2023-02-01 7:02 ` Anton Khirnov
2023-02-03 9:14 ` [FFmpeg-devel] [PATCH] " Anton Khirnov
1 sibling, 0 replies; 25+ messages in thread
From: Anton Khirnov @ 2023-02-01 7:02 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Quoting Nicolas George (2023-01-30 13:59:44)
> > +static unsigned
> > +find_linklabel(AVFilterGraphSegment *seg, const char *label,
> > + int output, size_t idx_chain, size_t idx_filter,
> > + AVFilterParams **pp)
> > +{
> > + for (; idx_chain < seg->nb_chains; idx_chain++) {
> > + AVFilterChain *ch = seg->chains[idx_chain];
> > +
> > + for (; idx_filter < ch->nb_filters; idx_filter++) {
> > + AVFilterParams *p = ch->filters[idx_filter];
> > + AVFilterPadParams **io = output ? p->outputs : p->inputs;
> > + unsigned nb_io = output ? p->nb_outputs : p->nb_inputs;
> > + AVFilterLink **l;
> > + unsigned nb_l;
> > +
> > + if (!p->filter)
> > + continue;
> > +
> > + l = output ? p->filter->outputs : p->filter->inputs;
> > + nb_l = output ? p->filter->nb_outputs : p->filter->nb_inputs;
> > +
> > + for (unsigned i = 0; i < FFMIN(nb_io, nb_l); i++)
> > + if (!l[i] && io[i]->label && !strcmp(io[i]->label, label)) {
> > + *pp = p;
> > + return i;
> > + }
> > + }
> > +
> > + idx_filter = 0;
> > + }
> > +
> > + *pp = NULL;
> > + return 0;
> > +}
>
> If I read this code correctly, it will only find link labels that are
> defined later in the chain or in a later chain. But referring to earlier
> link labels is supported. Even cycles are meant to be supported at
> parsing level (format negotiation will likely fail, though, but being
> able to make cycles was a goal from the start).
The earlier filters have already been processed at this point, so if an
earlier filter had a link label that would match this one, then it's
already been linked. So there's no point in considering earlier filters.
--
Anton Khirnov
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* [FFmpeg-devel] [PATCH] lavfi: add a new filtergraph parsing API
2023-01-30 12:59 ` Nicolas George
2023-02-01 7:02 ` Anton Khirnov
@ 2023-02-03 9:14 ` Anton Khirnov
2023-02-10 9:49 ` Anton Khirnov
1 sibling, 1 reply; 25+ messages in thread
From: Anton Khirnov @ 2023-02-03 9:14 UTC (permalink / raw)
To: ffmpeg-devel
Callers currently have two ways of adding filters to a graph - they can
either
- create, initialize, and link them manually
- use one of the avfilter_graph_parse*() functions, which take a
(typically end-user-written) string, split it into individual filter
definitions+options, then create filters, apply options, initialize
filters, and finally link them - all based on information from this
string.
A major problem with the second approach is that it performs many
actions as a single atomic unit, leaving the caller no space to
intervene in between. Such intervention would be useful e.g. to
- modify filter options;
- supply hardware device contexts;
both of which typically must be done before the filter is initialized.
Callers who need such intervention are then forced to invent their own
filtergraph parsing, which is clearly suboptimal.
This commit aims to address this problem by adding a new modular
filtergraph parsing API. It adds a new avfilter_graph_segment_parse()
function to parse a string filtergraph description into an intermediate
tree-like representation (AVFilterGraphSegment and its children).
This intermediate form may then be applied step by step using further
new avfilter_graph_segment*() functions, with user intervention possible
between each step.
---
doc/APIchanges | 15 +
libavfilter/avfilter.h | 311 +++++++++++++++++
libavfilter/graphparser.c | 679 +++++++++++++++++++++++++++++++++++++-
3 files changed, 998 insertions(+), 7 deletions(-)
diff --git a/doc/APIchanges b/doc/APIchanges
index bc52a07964..37cf9efebe 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -14,6 +14,21 @@ libavutil: 2021-04-27
API changes, most recent first:
+2023-xx-xx - xxxxxxxxxx - lavfi 8.x.100 - avfilter.h
+ Add filtergraph segment parsing API.
+ New structs:
+ - AVFilterGraphSegment
+ - AVFilterChain
+ - AVFilterParams
+ - AVFilterPadParams
+ New functions:
+ - avfilter_graph_segment_parse()
+ - avfilter_graph_segment_create_filters()
+ - avfilter_graph_segment_apply_opts()
+ - avfilter_graph_segment_init()
+ - avfilter_graph_segment_link()
+ - avfilter_graph_segment_apply()
+
2023-01-29 - xxxxxxxxxx - lavc 59.59.100 - avcodec.h
Add AV_CODEC_FLAG_COPY_OPAQUE and AV_CODEC_FLAG_FRAME_DURATION.
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index c2ec7a4b5f..04a7f00ffe 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -1118,6 +1118,317 @@ int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
AVFilterInOut **inputs,
AVFilterInOut **outputs);
+/**
+ * Parameters of a filter's input or output pad.
+ *
+ * Created as a child of AVFilterParams by avfilter_graph_segment_parse().
+ * Freed in avfilter_graph_segment_free().
+ */
+typedef struct AVFilterPadParams {
+ /**
+ * An av_malloc()'ed string containing the pad label.
+ *
+ * May be av_free()'d and set to NULL by the caller, in which case this pad
+ * will be treated as unlabeled for linking.
+ * May also be replaced by another av_malloc()'ed string.
+ */
+ char *label;
+} AVFilterPadParams;
+
+/**
+ * Parameters describing a filter to be created in a filtergraph.
+ *
+ * Created as a child of AVFilterGraphSegment by avfilter_graph_segment_parse().
+ * Freed in avfilter_graph_segment_free().
+ */
+typedef struct AVFilterParams {
+ /**
+ * The filter context.
+ *
+ * Created by avfilter_graph_segment_create_filters() based on
+ * AVFilterParams.filter_name and instance_name.
+ *
+ * Callers may also create the filter context manually, then they should
+ * av_free() filter_name and set it to NULL. Such AVFilterParams instances
+ * are then skipped by avfilter_graph_segment_create_filters().
+ */
+ AVFilterContext *filter;
+
+ /**
+ * Name of the AVFilter to be used.
+ *
+ * An av_malloc()'ed string, set by avfilter_graph_segment_parse(). Will be
+ * passed to avfilter_get_by_name() by
+ * avfilter_graph_segment_create_filters().
+ *
+ * Callers may av_free() this string and replace it with another one or
+ * NULL. If the caller creates the filter instance manually, this string
+ * MUST be set to NULL.
+ *
+ * When both AVFilterParams.filter an AVFilterParams.filter_name are NULL,
+ * this AVFilterParams instance is skipped by avfilter_graph_segment_*()
+ * functions.
+ */
+ char *filter_name;
+ /**
+ * Name to be used for this filter instance.
+ *
+ * An av_malloc()'ed string, may be set by avfilter_graph_segment_parse() or
+ * left NULL. The caller may av_free() this string and replace with another
+ * one or NULL.
+ *
+ * Will be used by avfilter_graph_segment_create_filters() - passed as the
+ * third argument to avfilter_graph_alloc_filter(), then freed and set to
+ * NULL.
+ */
+ char *instance_name;
+
+ /**
+ * Options to be apllied to the filter.
+ *
+ * Filled by avfilter_graph_segment_parse(). Afterwards may be freely
+ * modified by the caller.
+ *
+ * Will be applied to the filter by avfilter_graph_segment_apply_opts()
+ * with an equivalent of av_opt_set_dict2(filter, &opts, * AV_OPT_SEARCH_CHILDREN),
+ * i.e. any unapplied options will be left in this dictionary.
+ */
+ AVDictionary *opts;
+
+ AVFilterPadParams **inputs;
+ unsigned nb_inputs;
+
+ AVFilterPadParams **outputs;
+ unsigned nb_outputs;
+} AVFilterParams;
+
+/**
+ * A filterchain is a list of filter specifications.
+ *
+ * Created as a child of AVFilterGraphSegment by avfilter_graph_segment_parse().
+ * Freed in avfilter_graph_segment_free().
+ */
+typedef struct AVFilterChain {
+ AVFilterParams **filters;
+ size_t nb_filters;
+} AVFilterChain;
+
+/**
+ * A parsed representation of a filtergraph segment.
+ *
+ * A filtergraph segment is conceptually a list of filterchains, with some
+ * supplementary information (e.g. format conversion flags).
+ *
+ * Created by avfilter_graph_segment_parse(). Must be freed with
+ * avfilter_graph_segment_free().
+ */
+typedef struct AVFilterGraphSegment {
+ /**
+ * The filtergraph this segment is associated with.
+ * Set by avfilter_graph_segment_parse().
+ */
+ AVFilterGraph *graph;
+
+ /**
+ * A list of filter chain contained in this segment..
+ * Set in avfilter_graph_segment_parse().
+ */
+ AVFilterChain **chains;
+ size_t nb_chains;
+
+ /**
+ * A string containing a colon-separated list of key=value options applied
+ * to all scale filters in this segment.
+ *
+ * May be set by avfilter_graph_segment_parse().
+ * The caller may free this string with av_free() and replace it with a
+ * different av_malloc()'ed string.
+ */
+ char *scale_sws_opts;
+} AVFilterGraphSegment;
+
+/**
+ * Parse a textual filtergraph description into an intermediate form.
+ *
+ * This intermediate representation is intended to be modified by the caller as
+ * described in the documentation of AVFilterGraphSegment and its children, and
+ * then applied to the graph either manually or with other
+ * avfilter_graph_segment_*() functions. See the documentation for
+ * avfilter_graph_segment_apply() for the canonical way to apply
+ * AVFilterGraphSegment.
+ *
+ * @param graph Filter graph the parsed segment is associated with. Will only be
+ * used for logging and similar auxiliary purposes. The graph will
+ * not be actually modified by this function - the parsing results
+ * are instead stored in seg for further processing.
+ * @param graph_str a string describing the filtergraph segment
+ * @param flags reserved for future use, caller must set to 0 for now
+ * @param seg A pointer to the newly-created AVFilterGraphSegment is written
+ * here on success. The graph segment is owned by the caller and must
+ * be freed with avfilter_graph_segment_free() before graph itself is
+ * freed.
+ *
+ * @retval "non-negative number" success
+ * @retval "negative error code" failure
+ */
+int avfilter_graph_segment_parse(AVFilterGraph *graph, const char *graph_str,
+ int flags, AVFilterGraphSegment **seg);
+
+/**
+ * Create filters specified in a graph segment.
+ *
+ * Walk through the creation-pending AVFilterParams in the segment and create
+ * new filter instances for them.
+ * Creation-pending params are those where AVFilterParams.filter_name is
+ * non-NULL (and hence AVFilterParams.filter is NULL). All other AVFilterParams
+ * instances are ignored.
+ *
+ * For any filter created by this function, the corresponding
+ * AVFilterParams.filter is set to the newly-created filter context,
+ * AVFilterParams.filter_name and AVFilterParams.instance_name are freed and set
+ * to NULL.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ *
+ * @retval "non-negative number" Success, all creation-pending filters were
+ * successfully created
+ * @retval AVERROR_FILTER_NOT_FOUND some filter's name did not correspond to a
+ * known filter
+ * @retval "another negative error code" other failures
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_create_filters(AVFilterGraphSegment *seg, int flags);
+
+/**
+ * Apply parsed options to filter instances in a graph segment.
+ *
+ * Walk through all filter instances in the graph segment that have option
+ * dictionaries associated with them and apply those options with
+ * av_opt_set_dict2(..., AV_OPT_SEARCH_CHILDREN). AVFilterParams.opts is
+ * replaced by the dictionary output by av_opt_set_dict2(), which should be
+ * empty (NULL) if all options were successfully applied.
+ *
+ * If any options could not be found, this function will continue processing all
+ * other filters and finally return AVERROR_OPTION_NOT_FOUND (unless another
+ * error happens). The calling program may then deal with unapplied options as
+ * it wishes.
+ *
+ * Any creation-pending filters (see avfilter_graph_segment_create_filters())
+ * present in the segment will cause this function to fail. AVFilterParams with
+ * no associated filter context are simply skipped.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ *
+ * @retval "non-negative number" Success, all options were successfully applied.
+ * @retval AVERROR_OPTION_NOT_FOUND some options were not found in a filter
+ * @retval "another negative error code" other failures
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_apply_opts(AVFilterGraphSegment *seg, int flags);
+
+/**
+ * Initialize all filter instances in a graph segment.
+ *
+ * Walk through all filter instances in the graph segment and call
+ * avfilter_init_dict(..., NULL) on those that have not been initialized yet.
+ *
+ * Any creation-pending filters (see avfilter_graph_segment_create_filters())
+ * present in the segment will cause this function to fail. AVFilterParams with
+ * no associated filter context or whose filter context is already initialized,
+ * are simply skipped.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ *
+ * @retval "non-negative number" Success, all filter instances were successfully
+ * initialized
+ * @retval "negative error code" failure
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_init(AVFilterGraphSegment *seg, int flags);
+
+/**
+ * Link filters in a graph segment.
+ *
+ * Walk through all filter instances in the graph segment and try to link all
+ * unlinked input and output pads. Any creation-pending filters (see
+ * avfilter_graph_segment_create_filters()) present in the segment will cause
+ * this function to fail. Disabled filters and already linked pads are skipped.
+ *
+ * Every filter output pad that has a corresponding AVFilterPadParams with a
+ * non-NULL label is
+ * - linked to the input with the matching label, if one exists;
+ * - exported in the outputs linked list otherwise, with the label preserved.
+ * Unlabeled outputs are
+ * - linked to the first unlinked unlabeled input in the next non-disabled
+ * filter in the chain, if one exists
+ * - exported in the ouputs linked list otherwise, with NULL label
+ *
+ * Similarly, unlinked input pads are exported in the inputs linked list.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ * @param[out] inputs a linked list of all free (unlinked) inputs of the
+ * filters in this graph segment will be returned here. It
+ * is to be freed by the caller using avfilter_inout_free().
+ * @param[out] outputs a linked list of all free (unlinked) outputs of the
+ * filters in this graph segment will be returned here. It
+ * is to be freed by the caller using avfilter_inout_free().
+ *
+ * @retval "non-negative number" success
+ * @retval "negative error code" failure
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_link(AVFilterGraphSegment *seg, int flags,
+ AVFilterInOut **inputs,
+ AVFilterInOut **outputs);
+
+/**
+ * Apply all filter/link descriptions from a graph segment to the associated filtergraph.
+ *
+ * This functions is currently equivalent to calling the following in sequence:
+ * - avfilter_graph_segment_create_filters();
+ * - avfilter_graph_segment_apply_opts();
+ * - avfilter_graph_segment_init();
+ * - avfilter_graph_segment_link();
+ * failing if any of them fails. This list may be extended in the future.
+ *
+ * Since the above functions are idempotent, the caller may call some of them
+ * manually, then do some custom processing on the filtergraph, then call this
+ * function to do the rest.
+ *
+ * @param seg the filtergraph segment to process
+ * @param flags reserved for future use, caller must set to 0 for now
+ * @param[out] inputs passed to avfilter_graph_segment_link()
+ * @param[out] outputs passed to avfilter_graph_segment_link()
+ *
+ * @retval "non-negative number" success
+ * @retval "negative error code" failure
+ *
+ * @note Calling this function multiple times is safe, as it is idempotent.
+ */
+int avfilter_graph_segment_apply(AVFilterGraphSegment *seg, int flags,
+ AVFilterInOut **inputs,
+ AVFilterInOut **outputs);
+
+/**
+ * Free the provided AVFilterGraphSegment and everything associated with it.
+ *
+ * @param seg double pointer to the AVFilterGraphSegment to be freed. NULL will
+ * be written to this pointer on exit from this function.
+ *
+ * @note
+ * The filter contexts (AVFilterParams.filter) are owned by AVFilterGraph rather
+ * than AVFilterGraphSegment, so they are not freed.
+ */
+void avfilter_graph_segment_free(AVFilterGraphSegment **seg);
+
/**
* Send a command to one or more filter instances.
*
diff --git a/libavfilter/graphparser.c b/libavfilter/graphparser.c
index 0759c39014..84d86a6441 100644
--- a/libavfilter/graphparser.c
+++ b/libavfilter/graphparser.c
@@ -24,10 +24,12 @@
#include <stdio.h>
#include "libavutil/avstring.h"
+#include "libavutil/dict.h"
#include "libavutil/mem.h"
#include "libavutil/opt.h"
#include "avfilter.h"
+#include "internal.h"
#define WHITESPACES " \n\t\r"
@@ -386,7 +388,7 @@ static int parse_outputs(const char **buf, AVFilterInOut **curr_inputs,
return pad;
}
-static int parse_sws_flags(const char **buf, AVFilterGraph *graph)
+static int parse_sws_flags(const char **buf, char **dst, void *log_ctx)
{
char *p = strchr(*buf, ';');
@@ -394,16 +396,16 @@ static int parse_sws_flags(const char **buf, AVFilterGraph *graph)
return 0;
if (!p) {
- av_log(graph, AV_LOG_ERROR, "sws_flags not terminated with ';'.\n");
+ av_log(log_ctx, AV_LOG_ERROR, "sws_flags not terminated with ';'.\n");
return AVERROR(EINVAL);
}
*buf += 4; // keep the 'flags=' part
- av_freep(&graph->scale_sws_opts);
- if (!(graph->scale_sws_opts = av_mallocz(p - *buf + 1)))
+ av_freep(dst);
+ if (!(*dst = av_mallocz(p - *buf + 1)))
return AVERROR(ENOMEM);
- av_strlcpy(graph->scale_sws_opts, *buf, p - *buf + 1);
+ av_strlcpy(*dst, *buf, p - *buf + 1);
*buf = p + 1;
return 0;
@@ -420,7 +422,7 @@ int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
filters += strspn(filters, WHITESPACES);
- if ((ret = parse_sws_flags(&filters, graph)) < 0)
+ if ((ret = parse_sws_flags(&filters, &graph->scale_sws_opts, graph)) < 0)
goto end;
do {
@@ -551,7 +553,7 @@ int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
AVFilterInOut *open_inputs = open_inputs_ptr ? *open_inputs_ptr : NULL;
AVFilterInOut *open_outputs = open_outputs_ptr ? *open_outputs_ptr : NULL;
- if ((ret = parse_sws_flags(&filters, graph)) < 0)
+ if ((ret = parse_sws_flags(&filters, &graph->scale_sws_opts, graph)) < 0)
goto end;
do {
@@ -623,3 +625,666 @@ end:
}
return ret;
}
+
+static void pad_params_free(AVFilterPadParams **pfpp)
+{
+ AVFilterPadParams *fpp = *pfpp;
+
+ if (!fpp)
+ return;
+
+ av_freep(&fpp->label);
+
+ av_freep(pfpp);
+}
+
+static void filter_params_free(AVFilterParams **pp)
+{
+ AVFilterParams *p = *pp;
+
+ if (!p)
+ return;
+
+ for (unsigned i = 0; i < p->nb_inputs; i++)
+ pad_params_free(&p->inputs[i]);
+ av_freep(&p->inputs);
+
+ for (unsigned i = 0; i < p->nb_outputs; i++)
+ pad_params_free(&p->outputs[i]);
+ av_freep(&p->outputs);
+
+ av_dict_free(&p->opts);
+
+ av_freep(&p->filter_name);
+ av_freep(&p->instance_name);
+
+ av_freep(pp);
+}
+
+static void chain_free(AVFilterChain **pch)
+{
+ AVFilterChain *ch = *pch;
+
+ if (!ch)
+ return;
+
+ for (size_t i = 0; i < ch->nb_filters; i++)
+ filter_params_free(&ch->filters[i]);
+ av_freep(&ch->filters);
+
+ av_freep(pch);
+}
+
+void avfilter_graph_segment_free(AVFilterGraphSegment **pseg)
+{
+ AVFilterGraphSegment *seg = *pseg;
+
+ if (!seg)
+ return;
+
+ for (size_t i = 0; i < seg->nb_chains; i++)
+ chain_free(&seg->chains[i]);
+ av_freep(&seg->chains);
+
+ av_freep(&seg->scale_sws_opts);
+
+ av_freep(pseg);
+}
+
+static int linklabels_parse(void *logctx, const char **linklabels,
+ AVFilterPadParams ***res, unsigned *nb_res)
+{
+ AVFilterPadParams **pp = NULL;
+ int nb = 0;
+ int ret;
+
+ while (**linklabels == '[') {
+ char *label;
+ AVFilterPadParams *par;
+
+ label = parse_link_name(linklabels, logctx);
+ if (!label) {
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ par = av_mallocz(sizeof(*par));
+ if (!par) {
+ av_freep(&label);
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ par->label = label;
+
+ ret = av_dynarray_add_nofree(&pp, &nb, par);
+ if (ret < 0) {
+ pad_params_free(&par);
+ goto fail;
+ }
+
+ *linklabels += strspn(*linklabels, WHITESPACES);
+ }
+
+ *res = pp;
+ *nb_res = nb;
+
+ return 0;
+fail:
+ for (unsigned i = 0; i < nb; i++)
+ pad_params_free(&pp[i]);
+ av_freep(&pp);
+ return ret;
+}
+
+static int filter_parse(void *logctx, const char **filter,
+ AVFilterParams **pp)
+{
+ AVFilterParams *p;
+ char *inst_name;
+ int ret;
+
+ p = av_mallocz(sizeof(*p));
+ if (!p)
+ return AVERROR(ENOMEM);
+
+ ret = linklabels_parse(logctx, filter, &p->inputs, &p->nb_inputs);
+ if (ret < 0)
+ goto fail;
+
+ p->filter_name = av_get_token(filter, "=,;[");
+ if (!p->filter_name) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ inst_name = strchr(p->filter_name, '@');
+ if (inst_name) {
+ *inst_name++ = 0;
+ p->instance_name = av_strdup(inst_name);
+ if (!p->instance_name) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ }
+
+ if (**filter == '=') {
+ const AVFilter *f = avfilter_get_by_name(p->filter_name);
+ char *opts;
+
+ (*filter)++;
+
+ opts = av_get_token(filter, "[],;");
+ if (!opts) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ ret = ff_filter_opt_parse(logctx, f ? f->priv_class : NULL,
+ &p->opts, opts);
+ av_freep(&opts);
+ if (ret < 0)
+ goto fail;
+ }
+
+ ret = linklabels_parse(logctx, filter, &p->outputs, &p->nb_outputs);
+ if (ret < 0)
+ goto fail;
+
+ *filter += strspn(*filter, WHITESPACES);
+
+ *pp = p;
+ return 0;
+fail:
+ av_log(logctx, AV_LOG_ERROR,
+ "Error parsing a filter description around: %s\n", *filter);
+ filter_params_free(&p);
+ return ret;
+}
+
+static int chain_parse(void *logctx, const char **pchain,
+ AVFilterChain **pch)
+{
+ const char *chain = *pchain;
+ AVFilterChain *ch;
+ int ret, nb_filters = 0;
+
+ *pch = NULL;
+
+ ch = av_mallocz(sizeof(*ch));
+ if (!ch)
+ return AVERROR(ENOMEM);
+
+ while (*chain) {
+ AVFilterParams *p;
+ char chr;
+
+ ret = filter_parse(logctx, &chain, &p);
+ if (ret < 0)
+ goto fail;
+
+ ret = av_dynarray_add_nofree(&ch->filters, &nb_filters, p);
+ if (ret < 0) {
+ filter_params_free(&p);
+ goto fail;
+ }
+ ch->nb_filters = nb_filters;
+
+ // a filter ends with one of: , ; end-of-string
+ chr = *chain;
+ if (chr && chr != ',' && chr != ';') {
+ av_log(logctx, AV_LOG_ERROR,
+ "Trailing garbage after a filter: %s\n", chain);
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ if (chr) {
+ chain++;
+ chain += strspn(chain, WHITESPACES);
+
+ if (chr == ';')
+ break;
+ }
+ }
+
+ *pchain = chain;
+ *pch = ch;
+
+ return 0;
+fail:
+ av_log(logctx, AV_LOG_ERROR,
+ "Error parsing filterchain '%s' around: %s\n", *pchain, chain);
+ chain_free(&ch);
+ return ret;
+}
+
+int avfilter_graph_segment_parse(AVFilterGraph *graph, const char *graph_str,
+ int flags, AVFilterGraphSegment **pseg)
+{
+ AVFilterGraphSegment *seg;
+ int ret, nb_chains = 0;
+
+ *pseg = NULL;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ seg = av_mallocz(sizeof(*seg));
+ if (!seg)
+ return AVERROR(ENOMEM);
+
+ seg->graph = graph;
+
+ graph_str += strspn(graph_str, WHITESPACES);
+
+ ret = parse_sws_flags(&graph_str, &seg->scale_sws_opts, &graph);
+ if (ret < 0)
+ goto fail;
+
+ graph_str += strspn(graph_str, WHITESPACES);
+
+ while (*graph_str) {
+ AVFilterChain *ch;
+
+ ret = chain_parse(graph, &graph_str, &ch);
+ if (ret < 0)
+ goto fail;
+
+ ret = av_dynarray_add_nofree(&seg->chains, &nb_chains, ch);
+ if (ret < 0) {
+ chain_free(&ch);
+ goto fail;
+ }
+ seg->nb_chains = nb_chains;
+
+ graph_str += strspn(graph_str, WHITESPACES);
+ }
+
+ if (!seg->nb_chains) {
+ av_log(graph, AV_LOG_ERROR, "No filters specified in the graph description\n");
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+
+ *pseg = seg;
+
+ return 0;
+fail:
+ avfilter_graph_segment_free(&seg);
+ return ret;
+}
+
+int avfilter_graph_segment_create_filters(AVFilterGraphSegment *seg, int flags)
+{
+ size_t idx = 0;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ if (seg->scale_sws_opts) {
+ av_freep(&seg->graph->scale_sws_opts);
+ seg->graph->scale_sws_opts = av_strdup(seg->scale_sws_opts);
+ if (!seg->graph->scale_sws_opts)
+ return AVERROR(ENOMEM);
+ }
+
+ for (size_t i = 0; i < seg->nb_chains; i++) {
+ AVFilterChain *ch = seg->chains[i];
+
+ for (size_t j = 0; j < ch->nb_filters; j++) {
+ AVFilterParams *p = ch->filters[j];
+ const AVFilter *f = avfilter_get_by_name(p->filter_name);
+ char inst_name[30], *name = p->instance_name ? p->instance_name :
+ inst_name;
+
+ // skip already processed filters
+ if (p->filter || !p->filter_name)
+ continue;
+
+ if (!f) {
+ av_log(seg->graph, AV_LOG_ERROR,
+ "No such filter: '%s'\n", p->filter_name);
+ return AVERROR_FILTER_NOT_FOUND;
+ }
+
+ if (!p->instance_name)
+ snprintf(inst_name, sizeof(inst_name), "Parsed_%s_%zu", f->name, idx);
+
+ p->filter = avfilter_graph_alloc_filter(seg->graph, f, name);
+ if (!p->filter)
+ return AVERROR(ENOMEM);
+
+ if (!strcmp(f->name, "scale") && seg->graph->scale_sws_opts) {
+ int ret = av_set_options_string(p->filter, seg->graph->scale_sws_opts,
+ "=", ":");
+ if (ret < 0) {
+ avfilter_free(p->filter);
+ p->filter = NULL;
+ return ret;
+ }
+ }
+
+ av_freep(&p->filter_name);
+ av_freep(&p->instance_name);
+
+ idx++;
+ }
+ }
+
+ return 0;
+}
+
+static int fail_creation_pending(AVFilterGraphSegment *seg, const char *fn,
+ const char *func)
+{
+ av_log(seg->graph, AV_LOG_ERROR,
+ "A creation-pending filter '%s' present in the segment. All filters "
+ "must be created or disabled before calling %s().\n", fn, func);
+ return AVERROR(EINVAL);
+}
+
+int avfilter_graph_segment_apply_opts(AVFilterGraphSegment *seg, int flags)
+{
+ int ret, leftover_opts = 0;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ for (size_t i = 0; i < seg->nb_chains; i++) {
+ AVFilterChain *ch = seg->chains[i];
+
+ for (size_t j = 0; j < ch->nb_filters; j++) {
+ AVFilterParams *p = ch->filters[j];
+
+ if (p->filter_name)
+ return fail_creation_pending(seg, p->filter_name, __func__);
+ if (!p->filter || !p->opts)
+ continue;
+
+ ret = av_opt_set_dict2(p->filter, &p->opts, AV_OPT_SEARCH_CHILDREN);
+ if (ret < 0)
+ return ret;
+
+ if (av_dict_count(p->opts))
+ leftover_opts = 1;
+ }
+ }
+
+ return leftover_opts ? AVERROR_OPTION_NOT_FOUND : 0;
+}
+
+int avfilter_graph_segment_init(AVFilterGraphSegment *seg, int flags)
+{
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ for (size_t i = 0; i < seg->nb_chains; i++) {
+ AVFilterChain *ch = seg->chains[i];
+
+ for (size_t j = 0; j < ch->nb_filters; j++) {
+ AVFilterParams *p = ch->filters[j];
+ int ret;
+
+ if (p->filter_name)
+ return fail_creation_pending(seg, p->filter_name, __func__);
+ if (!p->filter || p->filter->internal->initialized)
+ continue;
+
+ ret = avfilter_init_dict(p->filter, NULL);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static unsigned
+find_linklabel(AVFilterGraphSegment *seg, const char *label,
+ int output, size_t idx_chain, size_t idx_filter,
+ AVFilterParams **pp)
+{
+ for (; idx_chain < seg->nb_chains; idx_chain++) {
+ AVFilterChain *ch = seg->chains[idx_chain];
+
+ for (; idx_filter < ch->nb_filters; idx_filter++) {
+ AVFilterParams *p = ch->filters[idx_filter];
+ AVFilterPadParams **io = output ? p->outputs : p->inputs;
+ unsigned nb_io = output ? p->nb_outputs : p->nb_inputs;
+ AVFilterLink **l;
+ unsigned nb_l;
+
+ if (!p->filter)
+ continue;
+
+ l = output ? p->filter->outputs : p->filter->inputs;
+ nb_l = output ? p->filter->nb_outputs : p->filter->nb_inputs;
+
+ for (unsigned i = 0; i < FFMIN(nb_io, nb_l); i++)
+ if (!l[i] && io[i]->label && !strcmp(io[i]->label, label)) {
+ *pp = p;
+ return i;
+ }
+ }
+
+ idx_filter = 0;
+ }
+
+ *pp = NULL;
+ return 0;
+}
+
+static int inout_add(AVFilterInOut **inouts, AVFilterContext *f, unsigned pad_idx,
+ const char *label)
+{
+ AVFilterInOut *io = av_mallocz(sizeof(*io));
+
+ if (!io)
+ return AVERROR(ENOMEM);
+
+ io->filter_ctx = f;
+ io->pad_idx = pad_idx;
+
+ if (label) {
+ io->name = av_strdup(label);
+ if (!io->name) {
+ avfilter_inout_free(&io);
+ return AVERROR(ENOMEM);
+ }
+ }
+
+ append_inout(inouts, &io);
+
+ return 0;
+}
+
+static int link_inputs(AVFilterGraphSegment *seg, size_t idx_chain,
+ size_t idx_filter, AVFilterInOut **inputs)
+{
+ AVFilterChain *ch = seg->chains[idx_chain];
+ AVFilterParams *p = ch->filters[idx_filter];
+ AVFilterContext *f = p->filter;
+
+ int ret;
+
+ if (f->nb_inputs < p->nb_inputs) {
+ av_log(seg->graph, AV_LOG_ERROR,
+ "More input link labels specified for filter '%s' than "
+ "it has inputs: %u > %d\n", f->filter->name,
+ p->nb_inputs, f->nb_inputs);
+ return AVERROR(EINVAL);
+ }
+
+ for (unsigned in = 0; in < f->nb_inputs; in++) {
+ const char *label = (in < p->nb_inputs) ? p->inputs[in]->label : NULL;
+
+ // skip already linked inputs
+ if (f->inputs[in])
+ continue;
+
+ if (label) {
+ AVFilterParams *po = NULL;
+ unsigned idx = find_linklabel(seg, label, 1, idx_chain, idx_filter, &po);
+
+ if (po) {
+ ret = avfilter_link(po->filter, idx, f, in);
+ if (ret < 0)
+ return ret;
+
+ continue;
+ }
+ }
+
+ ret = inout_add(inputs, f, in, label);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int link_outputs(AVFilterGraphSegment *seg, size_t idx_chain,
+ size_t idx_filter, AVFilterInOut **outputs)
+{
+ AVFilterChain *ch = seg->chains[idx_chain];
+ AVFilterParams *p = ch->filters[idx_filter];
+ AVFilterContext *f = p->filter;
+
+ int ret;
+
+ if (f->nb_outputs < p->nb_outputs) {
+ av_log(seg->graph, AV_LOG_ERROR,
+ "More output link labels specified for filter '%s' than "
+ "it has outputs: %u > %d\n", f->filter->name,
+ p->nb_outputs, f->nb_outputs);
+ return AVERROR(EINVAL);
+ }
+ for (unsigned out = 0; out < f->nb_outputs; out++) {
+ char *label = (out < p->nb_outputs) ? p->outputs[out]->label : NULL;
+
+ // skip already linked outputs
+ if (f->outputs[out])
+ continue;
+
+ if (label) {
+ AVFilterParams *po = NULL;
+ unsigned idx = find_linklabel(seg, label, 0, idx_chain, idx_filter, &po);
+
+ if (po) {
+ ret = avfilter_link(f, out, po->filter, idx);
+ if (ret < 0)
+ return ret;
+
+ continue;
+ }
+ }
+
+ // if this output is unlabeled, try linking it to an unlabeled
+ // input in the next non-disabled filter in the chain
+ for (size_t i = idx_filter + 1; i < ch->nb_filters && !label; i++) {
+ AVFilterParams *p_next = ch->filters[i];
+
+ if (!p_next->filter)
+ continue;
+
+ for (unsigned in = 0; in < p_next->filter->nb_inputs; in++) {
+ if (!p_next->filter->inputs[in] &&
+ (in >= p_next->nb_inputs || !p_next->inputs[in]->label)) {
+ ret = avfilter_link(f, out, p_next->filter, in);
+ if (ret < 0)
+ return ret;
+
+ goto cont;
+ }
+ }
+ break;
+ }
+
+ ret = inout_add(outputs, f, out, label);
+ if (ret < 0)
+ return ret;
+
+cont:;
+ }
+
+ return 0;
+}
+
+int avfilter_graph_segment_link(AVFilterGraphSegment *seg, int flags,
+ AVFilterInOut **inputs,
+ AVFilterInOut **outputs)
+{
+ int ret;
+
+ *inputs = NULL;
+ *outputs = NULL;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ for (size_t idx_chain = 0; idx_chain < seg->nb_chains; idx_chain++) {
+ AVFilterChain *ch = seg->chains[idx_chain];
+
+ for (size_t idx_filter = 0; idx_filter < ch->nb_filters; idx_filter++) {
+ AVFilterParams *p = ch->filters[idx_filter];
+
+ if (p->filter_name) {
+ ret = fail_creation_pending(seg, p->filter_name, __func__);
+ goto fail;
+ }
+
+ if (!p->filter)
+ continue;
+
+ ret = link_inputs(seg, idx_chain, idx_filter, inputs);
+ if (ret < 0)
+ goto fail;
+
+ ret = link_outputs(seg, idx_chain, idx_filter, outputs);
+ if (ret < 0)
+ goto fail;
+ }
+ }
+ return 0;
+fail:
+ avfilter_inout_free(inputs);
+ avfilter_inout_free(outputs);
+ return ret;
+}
+
+int avfilter_graph_segment_apply(AVFilterGraphSegment *seg, int flags,
+ AVFilterInOut **inputs,
+ AVFilterInOut **outputs)
+{
+ int ret;
+
+ if (flags)
+ return AVERROR(ENOSYS);
+
+ ret = avfilter_graph_segment_create_filters(seg, 0);
+ if (ret < 0) {
+ av_log(seg->graph, AV_LOG_ERROR, "Error creating filters\n");
+ return ret;
+ }
+
+ ret = avfilter_graph_segment_apply_opts(seg, 0);
+ if (ret < 0) {
+ av_log(seg->graph, AV_LOG_ERROR, "Error applying filter options\n");
+ return ret;
+ }
+
+ ret = avfilter_graph_segment_init(seg, 0);
+ if (ret < 0) {
+ av_log(seg->graph, AV_LOG_ERROR, "Error initializing filters\n");
+ return ret;
+ }
+
+ ret = avfilter_graph_segment_link(seg, 0, inputs, outputs);
+ if (ret < 0) {
+ av_log(seg->graph, AV_LOG_ERROR, "Error linking filters\n");
+ return ret;
+ }
+
+ return 0;
+}
--
2.35.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-20 19:31 [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options() Anton Khirnov
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 2/5] lavfi/avfilter: track whether a filter has been initialized Anton Khirnov
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 3/5] lavfi: add a new filtergraph parsing API Anton Khirnov
@ 2023-01-20 19:31 ` Anton Khirnov
2023-01-20 20:47 ` Nicolas George
2023-05-30 21:09 ` Marton Balint
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 5/5] fftools/ffmpeg: add special syntax for loading filter options from files Anton Khirnov
2023-01-27 16:47 ` [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options() Anton Khirnov
4 siblings, 2 replies; 25+ messages in thread
From: Anton Khirnov @ 2023-01-20 19:31 UTC (permalink / raw)
To: ffmpeg-devel
---
libavfilter/graphparser.c | 537 +++++++++-----------------------------
1 file changed, 126 insertions(+), 411 deletions(-)
diff --git a/libavfilter/graphparser.c b/libavfilter/graphparser.c
index ac6171e0c4..5db8b95a0c 100644
--- a/libavfilter/graphparser.c
+++ b/libavfilter/graphparser.c
@@ -33,26 +33,6 @@
#define WHITESPACES " \n\t\r"
-/**
- * Link two filters together.
- *
- * @see avfilter_link()
- */
-static int link_filter(AVFilterContext *src, int srcpad,
- AVFilterContext *dst, int dstpad,
- void *log_ctx)
-{
- int ret;
- if ((ret = avfilter_link(src, srcpad, dst, dstpad))) {
- av_log(log_ctx, AV_LOG_ERROR,
- "Cannot create the link %s:%d -> %s:%d\n",
- src->filter->name, srcpad, dst->filter->name, dstpad);
- return ret;
- }
-
- return 0;
-}
-
/**
* Parse the name of a link, which has the format "[linkname]".
*
@@ -87,119 +67,6 @@ static char *parse_link_name(const char **buf, void *log_ctx)
return name;
}
-/**
- * Create an instance of a filter, initialize and insert it in the
- * filtergraph in *ctx.
- *
- * @param filt_ctx put here a filter context in case of successful creation and configuration, NULL otherwise.
- * @param ctx the filtergraph context
- * @param index an index which is supposed to be unique for each filter instance added to the filtergraph
- * @param name the name of the filter to create, can be filter name or filter_name\@id as instance name
- * @param args the arguments provided to the filter during its initialization
- * @param log_ctx the log context to use
- * @return >= 0 in case of success, a negative AVERROR code otherwise
- */
-static int create_filter(AVFilterContext **filt_ctx, AVFilterGraph *ctx, int index,
- const char *name, const char *args, void *log_ctx)
-{
- const AVFilter *filt;
- char name2[30];
- const char *inst_name = NULL, *filt_name = NULL;
- int ret, k;
-
- av_strlcpy(name2, name, sizeof(name2));
-
- for (k = 0; name2[k]; k++) {
- if (name2[k] == '@' && name[k+1]) {
- name2[k] = 0;
- inst_name = name;
- filt_name = name2;
- break;
- }
- }
-
- if (!inst_name) {
- snprintf(name2, sizeof(name2), "Parsed_%s_%d", name, index);
- inst_name = name2;
- filt_name = name;
- }
-
- filt = avfilter_get_by_name(filt_name);
-
- if (!filt) {
- av_log(log_ctx, AV_LOG_ERROR,
- "No such filter: '%s'\n", filt_name);
- return AVERROR(EINVAL);
- }
-
- *filt_ctx = avfilter_graph_alloc_filter(ctx, filt, inst_name);
- if (!*filt_ctx) {
- av_log(log_ctx, AV_LOG_ERROR,
- "Error creating filter '%s'\n", filt_name);
- return AVERROR(ENOMEM);
- }
-
- if (!strcmp(filt_name, "scale") && ctx->scale_sws_opts) {
- ret = av_set_options_string(*filt_ctx, ctx->scale_sws_opts, "=", ":");
- if (ret < 0)
- return ret;
- }
-
- ret = avfilter_init_str(*filt_ctx, args);
- if (ret < 0) {
- av_log(log_ctx, AV_LOG_ERROR,
- "Error initializing filter '%s'", filt_name);
- if (args)
- av_log(log_ctx, AV_LOG_ERROR, " with args '%s'", args);
- av_log(log_ctx, AV_LOG_ERROR, "\n");
- avfilter_free(*filt_ctx);
- *filt_ctx = NULL;
- }
-
- return ret;
-}
-
-/**
- * Parse a string of the form FILTER_NAME[=PARAMS], and create a
- * corresponding filter instance which is added to graph with
- * create_filter().
- *
- * @param filt_ctx Pointer that is set to the created and configured filter
- * context on success, set to NULL on failure.
- * @param filt_ctx put here a pointer to the created filter context on
- * success, NULL otherwise
- * @param buf pointer to the buffer to parse, *buf will be updated to
- * point to the char next after the parsed string
- * @param index an index which is assigned to the created filter
- * instance, and which is supposed to be unique for each filter
- * instance added to the filtergraph
- * @return >= 0 in case of success, a negative AVERROR code otherwise
- */
-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, "=,;[");
- int ret;
-
- if (!name)
- return AVERROR(ENOMEM);
-
- if (**buf == '=') {
- (*buf)++;
- opts = av_get_token(buf, "[],;");
- if (!opts) {
- av_free(name);
- return AVERROR(ENOMEM);
- }
- }
-
- ret = create_filter(filt_ctx, graph, index, name, opts, log_ctx);
- av_free(name);
- av_free(opts);
- return ret;
-}
-
AVFilterInOut *avfilter_inout_alloc(void)
{
return av_mallocz(sizeof(AVFilterInOut));
@@ -232,12 +99,6 @@ static AVFilterInOut *extract_inout(const char *label, AVFilterInOut **links)
return ret;
}
-static void insert_inout(AVFilterInOut **inouts, AVFilterInOut *element)
-{
- element->next = *inouts;
- *inouts = element;
-}
-
static void append_inout(AVFilterInOut **inouts, AVFilterInOut **element)
{
while (*inouts && (*inouts)->next)
@@ -250,144 +111,6 @@ static void append_inout(AVFilterInOut **inouts, AVFilterInOut **element)
*element = NULL;
}
-static int link_filter_inouts(AVFilterContext *filt_ctx,
- AVFilterInOut **curr_inputs,
- AVFilterInOut **open_inputs, void *log_ctx)
-{
- int pad, ret;
-
- for (pad = 0; pad < filt_ctx->nb_inputs; pad++) {
- AVFilterInOut *p = *curr_inputs;
-
- if (p) {
- *curr_inputs = (*curr_inputs)->next;
- p->next = NULL;
- } else if (!(p = av_mallocz(sizeof(*p))))
- return AVERROR(ENOMEM);
-
- if (p->filter_ctx) {
- ret = link_filter(p->filter_ctx, p->pad_idx, filt_ctx, pad, log_ctx);
- av_freep(&p->name);
- av_freep(&p);
- if (ret < 0)
- return ret;
- } else {
- p->filter_ctx = filt_ctx;
- p->pad_idx = pad;
- append_inout(open_inputs, &p);
- }
- }
-
- if (*curr_inputs) {
- av_log(log_ctx, AV_LOG_ERROR,
- "Too many inputs specified for the \"%s\" filter.\n",
- filt_ctx->filter->name);
- return AVERROR(EINVAL);
- }
-
- pad = filt_ctx->nb_outputs;
- while (pad--) {
- AVFilterInOut *currlinkn = av_mallocz(sizeof(AVFilterInOut));
- if (!currlinkn)
- return AVERROR(ENOMEM);
- currlinkn->filter_ctx = filt_ctx;
- currlinkn->pad_idx = pad;
- insert_inout(curr_inputs, currlinkn);
- }
-
- return 0;
-}
-
-static int parse_inputs(const char **buf, AVFilterInOut **curr_inputs,
- AVFilterInOut **open_outputs, void *log_ctx)
-{
- AVFilterInOut *parsed_inputs = NULL;
- int pad = 0;
-
- while (**buf == '[') {
- char *name = parse_link_name(buf, log_ctx);
- AVFilterInOut *match;
-
- if (!name) {
- avfilter_inout_free(&parsed_inputs);
- return AVERROR(EINVAL);
- }
-
- /* First check if the label is not in the open_outputs list */
- match = extract_inout(name, open_outputs);
-
- if (match) {
- av_free(name);
- } else {
- /* Not in the list, so add it as an input */
- if (!(match = av_mallocz(sizeof(AVFilterInOut)))) {
- avfilter_inout_free(&parsed_inputs);
- av_free(name);
- return AVERROR(ENOMEM);
- }
- match->name = name;
- match->pad_idx = pad;
- }
-
- append_inout(&parsed_inputs, &match);
-
- *buf += strspn(*buf, WHITESPACES);
- pad++;
- }
-
- append_inout(&parsed_inputs, curr_inputs);
- *curr_inputs = parsed_inputs;
-
- return pad;
-}
-
-static int parse_outputs(const char **buf, AVFilterInOut **curr_inputs,
- AVFilterInOut **open_inputs,
- AVFilterInOut **open_outputs, void *log_ctx)
-{
- int ret, pad = 0;
-
- while (**buf == '[') {
- char *name = parse_link_name(buf, log_ctx);
- AVFilterInOut *match;
-
- AVFilterInOut *input = *curr_inputs;
-
- if (!name)
- return AVERROR(EINVAL);
-
- if (!input) {
- av_log(log_ctx, AV_LOG_ERROR,
- "No output pad can be associated to link label '%s'.\n", name);
- av_free(name);
- return AVERROR(EINVAL);
- }
- *curr_inputs = (*curr_inputs)->next;
-
- /* First check if the label is not in the open_inputs list */
- match = extract_inout(name, open_inputs);
-
- if (match) {
- ret = link_filter(input->filter_ctx, input->pad_idx,
- match->filter_ctx, match->pad_idx, log_ctx);
- av_freep(&match->name);
- av_freep(&name);
- av_freep(&match);
- av_freep(&input);
- if (ret < 0)
- return ret;
- } else {
- /* Not in the list, so add the first input as an open_output */
- input->name = name;
- insert_inout(open_outputs, input);
- }
- *buf += strspn(*buf, WHITESPACES);
- pad++;
- }
-
- return pad;
-}
-
static int parse_sws_flags(const char **buf, char **dst, void *log_ctx)
{
char *p = strchr(*buf, ';');
@@ -415,66 +138,24 @@ int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
AVFilterInOut **inputs,
AVFilterInOut **outputs)
{
- int index = 0, ret = 0;
- char chr = 0;
-
- AVFilterInOut *curr_inputs = NULL, *open_inputs = NULL, *open_outputs = NULL;
-
- filters += strspn(filters, WHITESPACES);
-
- if ((ret = parse_sws_flags(&filters, &graph->scale_sws_opts, graph)) < 0)
- goto end;
-
- do {
- AVFilterContext *filter;
- filters += strspn(filters, WHITESPACES);
-
- if ((ret = parse_inputs(&filters, &curr_inputs, &open_outputs, graph)) < 0)
- goto end;
- if ((ret = parse_filter(&filter, &filters, graph, index, graph)) < 0)
- goto end;
-
-
- if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, graph)) < 0)
- goto end;
-
- if ((ret = parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs,
- graph)) < 0)
- goto end;
-
- filters += strspn(filters, WHITESPACES);
- chr = *filters++;
+ AVFilterGraphSegment *seg;
+ int ret;
- if (chr == ';' && curr_inputs)
- append_inout(&open_outputs, &curr_inputs);
- index++;
- } while (chr == ',' || chr == ';');
+ ret = avfilter_graph_segment_parse(graph, filters, 0, &seg);
+ if (ret < 0)
+ return ret;
- if (chr) {
- av_log(graph, AV_LOG_ERROR,
- "Unable to parse graph description substring: \"%s\"\n",
- filters - 1);
- ret = AVERROR(EINVAL);
+ ret = avfilter_graph_segment_apply(seg, 0, inputs, outputs);
+ avfilter_graph_segment_free(&seg);
+ if (ret < 0)
goto end;
- }
-
- append_inout(&open_outputs, &curr_inputs);
-
- *inputs = open_inputs;
- *outputs = open_outputs;
return 0;
end:
while (graph->nb_filters)
avfilter_free(graph->filters[0]);
av_freep(&graph->filters);
- avfilter_inout_free(&open_inputs);
- avfilter_inout_free(&open_outputs);
- avfilter_inout_free(&curr_inputs);
-
- *inputs = NULL;
- *outputs = NULL;
return ret;
}
@@ -542,90 +223,6 @@ int avfilter_graph_parse(AVFilterGraph *graph, const char *filters,
return ret;
}
-int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
- AVFilterInOut **open_inputs_ptr, AVFilterInOut **open_outputs_ptr,
- void *log_ctx)
-{
- int index = 0, ret = 0;
- char chr = 0;
-
- AVFilterInOut *curr_inputs = NULL;
- AVFilterInOut *open_inputs = open_inputs_ptr ? *open_inputs_ptr : NULL;
- AVFilterInOut *open_outputs = open_outputs_ptr ? *open_outputs_ptr : NULL;
-
- if ((ret = parse_sws_flags(&filters, &graph->scale_sws_opts, graph)) < 0)
- goto end;
-
- do {
- AVFilterContext *filter;
- const char *filterchain = filters;
- filters += strspn(filters, WHITESPACES);
-
- if ((ret = parse_inputs(&filters, &curr_inputs, &open_outputs, log_ctx)) < 0)
- goto end;
-
- if ((ret = parse_filter(&filter, &filters, graph, index, log_ctx)) < 0)
- goto end;
-
- if (filter->nb_inputs == 1 && !curr_inputs && !index) {
- /* First input pad, assume it is "[in]" if not specified */
- const char *tmp = "[in]";
- if ((ret = parse_inputs(&tmp, &curr_inputs, &open_outputs, log_ctx)) < 0)
- goto end;
- }
-
- if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, log_ctx)) < 0)
- goto end;
-
- if ((ret = parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs,
- log_ctx)) < 0)
- goto end;
-
- filters += strspn(filters, WHITESPACES);
- chr = *filters++;
-
- if (chr == ';' && curr_inputs) {
- av_log(log_ctx, AV_LOG_ERROR,
- "Invalid filterchain containing an unlabelled output pad: \"%s\"\n",
- filterchain);
- ret = AVERROR(EINVAL);
- goto end;
- }
- index++;
- } while (chr == ',' || chr == ';');
-
- if (chr) {
- av_log(log_ctx, AV_LOG_ERROR,
- "Unable to parse graph description substring: \"%s\"\n",
- filters - 1);
- ret = AVERROR(EINVAL);
- goto end;
- }
-
- if (curr_inputs) {
- /* Last output pad, assume it is "[out]" if not specified */
- const char *tmp = "[out]";
- if ((ret = parse_outputs(&tmp, &curr_inputs, &open_inputs, &open_outputs,
- log_ctx)) < 0)
- goto end;
- }
-
-end:
- /* clear open_in/outputs only if not passed as parameters */
- if (open_inputs_ptr) *open_inputs_ptr = open_inputs;
- else avfilter_inout_free(&open_inputs);
- if (open_outputs_ptr) *open_outputs_ptr = open_outputs;
- else avfilter_inout_free(&open_outputs);
- avfilter_inout_free(&curr_inputs);
-
- if (ret < 0) {
- while (graph->nb_filters)
- avfilter_free(graph->filters[0]);
- av_freep(&graph->filters);
- }
- return ret;
-}
-
static void pad_params_free(AVFilterPadParams **pfpp)
{
AVFilterPadParams *fpp = *pfpp;
@@ -1298,3 +895,121 @@ int avfilter_graph_segment_apply(AVFilterGraphSegment *seg, int flags,
return 0;
}
+
+int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
+ AVFilterInOut **open_inputs_ptr, AVFilterInOut **open_outputs_ptr,
+ void *log_ctx)
+{
+ AVFilterInOut *user_inputs = open_inputs_ptr ? *open_inputs_ptr : NULL;
+ AVFilterInOut *user_outputs = open_outputs_ptr ? *open_outputs_ptr : NULL;
+
+ AVFilterInOut *inputs = NULL, *outputs = NULL;
+ AVFilterGraphSegment *seg = NULL;
+ AVFilterChain *ch;
+ AVFilterParams *p;
+ int ret;
+
+ ret = avfilter_graph_segment_parse(graph, filters, 0, &seg);
+ if (ret < 0)
+ goto end;
+
+ ret = avfilter_graph_segment_create_filters(seg, 0);
+ if (ret < 0)
+ goto end;
+
+ ret = avfilter_graph_segment_apply_opts(seg, 0);
+ if (ret < 0)
+ goto end;
+
+ ret = avfilter_graph_segment_init(seg, 0);
+ if (ret < 0)
+ goto end;
+
+ /* First input pad, assume it is "[in]" if not specified */
+ p = seg->chains[0]->filters[0];
+ if (p->filter->nb_inputs == 1 && !p->inputs) {
+ const char *tmp = "[in]";
+
+ ret = linklabels_parse(graph, &tmp, &p->inputs, &p->nb_inputs);
+ if (ret < 0)
+ goto end;
+ }
+
+ /* Last output pad, assume it is "[out]" if not specified */
+ ch = seg->chains[seg->nb_chains - 1];
+ p = ch->filters[ch->nb_filters - 1];
+ if (p->filter->nb_outputs == 1 && !p->outputs) {
+ const char *tmp = "[out]";
+
+ ret = linklabels_parse(graph, &tmp, &p->outputs, &p->nb_outputs);
+ if (ret < 0)
+ goto end;
+ }
+
+ ret = avfilter_graph_segment_apply(seg, 0, &inputs, &outputs);
+ avfilter_graph_segment_free(&seg);
+ if (ret < 0)
+ goto end;
+
+ // process user-supplied inputs/outputs
+ while (inputs) {
+ AVFilterInOut *cur, *match = NULL;
+
+ cur = inputs;
+ inputs = cur->next;
+ cur->next = NULL;
+
+ if (cur->name)
+ match = extract_inout(cur->name, &user_outputs);
+
+ if (match) {
+ ret = avfilter_link(match->filter_ctx, match->pad_idx,
+ cur->filter_ctx, cur->pad_idx);
+ avfilter_inout_free(&match);
+ avfilter_inout_free(&cur);
+ if (ret < 0)
+ goto end;
+ } else
+ append_inout(&user_inputs, &cur);
+ }
+ while (outputs) {
+ AVFilterInOut *cur, *match = NULL;
+
+ cur = outputs;
+ outputs = cur->next;
+ cur->next = NULL;
+
+ if (cur->name)
+ match = extract_inout(cur->name, &user_inputs);
+
+ if (match) {
+ ret = avfilter_link(cur->filter_ctx, cur->pad_idx,
+ match->filter_ctx, match->pad_idx);
+ avfilter_inout_free(&match);
+ avfilter_inout_free(&cur);
+ if (ret < 0)
+ goto end;
+ } else
+ append_inout(&user_outputs, &cur);
+ }
+
+end:
+ avfilter_graph_segment_free(&seg);
+
+ if (ret < 0) {
+ while (graph->nb_filters)
+ avfilter_free(graph->filters[0]);
+ av_freep(&graph->filters);
+ }
+
+ /* clear open_in/outputs only if not passed as parameters */
+ if (open_inputs_ptr) *open_inputs_ptr = user_inputs;
+ else avfilter_inout_free(&user_inputs);
+ if (open_outputs_ptr) *open_outputs_ptr = user_outputs;
+ else avfilter_inout_free(&user_outputs);
+
+ avfilter_inout_free(&inputs);
+ avfilter_inout_free(&outputs);
+
+ return ret;
+}
--
2.35.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API Anton Khirnov
@ 2023-01-20 20:47 ` Nicolas George
2023-01-20 21:11 ` Jean-Baptiste Kempf
2023-01-20 22:36 ` Anton Khirnov
2023-05-30 21:09 ` Marton Balint
1 sibling, 2 replies; 25+ messages in thread
From: Nicolas George @ 2023-01-20 20:47 UTC (permalink / raw)
To: FFmpeg development discussions and patches
[-- Attachment #1.1: Type: text/plain, Size: 508 bytes --]
Anton Khirnov (12023-01-20):
> ---
> libavfilter/graphparser.c | 537 +++++++++-----------------------------
> 1 file changed, 126 insertions(+), 411 deletions(-)
I notice you removed the "RFC" from the subject line, but I probably
have comments. I need some time, my time is very limited these days.
By the way, I am baffled that you would write a 30k+ patch on code you
do not maintain and do not know well without consulting the maintainer
who knows it.
Regards,
--
Nicolas George
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
[-- Attachment #2: Type: text/plain, Size: 251 bytes --]
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-20 20:47 ` Nicolas George
@ 2023-01-20 21:11 ` Jean-Baptiste Kempf
2023-01-20 21:32 ` Nicolas George
2023-01-20 22:36 ` Anton Khirnov
1 sibling, 1 reply; 25+ messages in thread
From: Jean-Baptiste Kempf @ 2023-01-20 21:11 UTC (permalink / raw)
To: ffmpeg-devel
On Fri, 20 Jan 2023, at 21:47, Nicolas George wrote:
> have comments. I need some time, my time is very limited these days.
>
> By the way, I am baffled that you would write a 30k+ patch on code you
> do not maintain and do not know well without consulting the maintainer
> who knows it.
First, maintainer does not mean owner, so of course, it's normal that people work on what they want, without consulting before. Maintainer can reject with explanations, of course, but that does not forbid anyone to work on what they want...
Then, also, in the same email you say "I have no time" and "you should consult with me before". Those are contradictory.
jb
--
Jean-Baptiste Kempf - President
+33 672 704 734
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-20 21:11 ` Jean-Baptiste Kempf
@ 2023-01-20 21:32 ` Nicolas George
2023-01-20 21:39 ` Nicolas George
0 siblings, 1 reply; 25+ messages in thread
From: Nicolas George @ 2023-01-20 21:32 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Jean-Baptiste Kempf (12023-01-20):
> First, maintainer does not mean owner
No. But maintainer means person who knows the code very well.
> Then, also, in the same email you say "I have no time" and "you should consult with me before". Those are contradictory.
This is not contradictory. I have no time to reply in days, but this
patch took more than days in the making, I would have ample chance to
take the time.
Writing this patch alone, Anton, who has contributed to libavfilter a
lot at this time, incurs the risk of neglecting issues that might
require rewriting a significant part of it, or even voiding it entirely.
Somebody who knows the code and has given a lot of thought to future
developments (me for example) could have pointed to those possible
issues early, saving a lot of time to Anton.
This is not specific to this issue: Why would ANYBODY spend a lot of
time and effort writing a far-reaching patch to a part of the code they
do not know without consulting their fellow developers who know this
part better, the maintainer in particular?
I insist, it is a very specific combinations of criteria:
- Writing a small patch for a part you do not know well without
consulting is fine, because the possible wasted effort is small and
the likelihood of severe mistakes is small. It is quadratic.
- Writing a large patch for a part of the code you know well or for a
new feature is fine, of course.
But the combination (1) code that you do not know well, (2) patch that
took effort writing, (3) patch that goes far and this is at risk of hard
to see issues, then not consulting with people who know the code best
seems really absurd.
For lack of a better explanation, I perceive cases like that when they
happen as a lack of team-spirit that is detrimental to this project.
I mean, it would not have taken a lot of time: "Hey guys, I am
considering redesigning the graph parsing API in lavfi. Here are my main
ideas: ... Do you have comments before I really start?", and it could
have made the result a lot better from the get go.
Also, it would have been the POLITE thing to do. It would have been the
thing to do if one considers me as an equal.
--
Nicolas George
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-20 21:32 ` Nicolas George
@ 2023-01-20 21:39 ` Nicolas George
2023-01-23 12:29 ` James Almer
2023-01-24 21:51 ` Paul B Mahol
0 siblings, 2 replies; 25+ messages in thread
From: Nicolas George @ 2023-01-20 21:39 UTC (permalink / raw)
To: FFmpeg development discussions and patches
[-- Attachment #1.1: Type: text/plain, Size: 440 bytes --]
Nicolas George (12023-01-20):
> - Writing a large patch for a part of the code you know well or for a
> new feature is fine, of course.
In fact, even for code we know well, we should consult, as long as we
are not the only one who knows it well: as long as other developers
might also have plans that we would be interfering with, sharing intents
early is the only correct thing to do towards our peers.
--
Nicolas George
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
[-- Attachment #2: Type: text/plain, Size: 251 bytes --]
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-20 21:39 ` Nicolas George
@ 2023-01-23 12:29 ` James Almer
2023-01-30 13:04 ` Nicolas George
2023-01-24 21:51 ` Paul B Mahol
1 sibling, 1 reply; 25+ messages in thread
From: James Almer @ 2023-01-23 12:29 UTC (permalink / raw)
To: ffmpeg-devel
On 1/20/2023 6:39 PM, Nicolas George wrote:
> Nicolas George (12023-01-20):
>> - Writing a large patch for a part of the code you know well or for a
>> new feature is fine, of course.
>
> In fact, even for code we know well, we should consult, as long as we
> are not the only one who knows it well: as long as other developers
> might also have plans that we would be interfering with, sharing intents
> early is the only correct thing to do towards our peers.
Nobody is disrespecting you for writing a big patch that implements or
rewrites some API from a part of the code you consider yourself
knowledgeable about without consulting you personally beforehand. He in
fact sent a Request for Comment initial set precisely because he wanted
input on the overall approach before sending a version that's ready for
a proper review.
What's the difference between a RFC and a "I have this idea, what do you
think of this mock up?" email? That if suggestions are made to improve
or change said approach, then at worst what happens is that the writer
wasted /his/ time writing 30k lines of which a fifth might need to be
redone.
In all cases, you lost nothing, and the author at worst lost time. I
have no idea how you come to the conclusion that you were disrespected,
or not considered a peer.
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-23 12:29 ` James Almer
@ 2023-01-30 13:04 ` Nicolas George
0 siblings, 0 replies; 25+ messages in thread
From: Nicolas George @ 2023-01-30 13:04 UTC (permalink / raw)
To: FFmpeg development discussions and patches
James Almer (12023-01-23):
> What's the difference between a RFC and a "I have this idea, what do you
> think of this mock up?" email? That if suggestions are made to improve or
> change said approach, then at worst what happens is that the writer wasted
> /his/ time writing 30k lines of which a fifth might need to be redone.
You are assuming that somebody who already wrote 30K of code (not even
lines) will be as ready to make changes that require rewriting 27K than
somebody who did not already write the code.
We had the exact problem with channel layouts: the structure should have
been refcounted, but since the code was written without consulting
people who would have to use it and neglected necessary features, it was
not done, and since the code was already written the change was not
seriously considered.
Furthermore, communicating early would save time to somebody who might
be at the early stages of working on the same thing.
Regards,
--
Nicolas George
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-20 21:39 ` Nicolas George
2023-01-23 12:29 ` James Almer
@ 2023-01-24 21:51 ` Paul B Mahol
1 sibling, 0 replies; 25+ messages in thread
From: Paul B Mahol @ 2023-01-24 21:51 UTC (permalink / raw)
To: FFmpeg development discussions and patches
On 1/20/23, Nicolas George <george@nsup.org> wrote:
> Nicolas George (12023-01-20):
>> - Writing a large patch for a part of the code you know well or for a
>> new feature is fine, of course.
>
> In fact, even for code we know well, we should consult, as long as we
> are not the only one who knows it well: as long as other developers
> might also have plans that we would be interfering with, sharing intents
> early is the only correct thing to do towards our peers.
>
Please be polite, technical and respectful to contributors, thanks.
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-20 20:47 ` Nicolas George
2023-01-20 21:11 ` Jean-Baptiste Kempf
@ 2023-01-20 22:36 ` Anton Khirnov
1 sibling, 0 replies; 25+ messages in thread
From: Anton Khirnov @ 2023-01-20 22:36 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Quoting Nicolas George (2023-01-20 21:47:16)
> Anton Khirnov (12023-01-20):
> > ---
> > libavfilter/graphparser.c | 537 +++++++++-----------------------------
> > 1 file changed, 126 insertions(+), 411 deletions(-)
>
> I notice you removed the "RFC" from the subject line,
That means I now consider the code to be ready for a full review
rather than just comments on the overall approach.
> By the way, I am baffled that you would write a 30k+ patch on code you
> do not maintain
Does the graphparser have a maintainer? I did not notice.
$ grep graphparser MAINTAINERS
$
> and do not know well without consulting the maintainer
> who knows it.
Maybe it's the person who authored the most commits touching that file
recently
$ git shortlog -ns --since=2012 --no-merges origin/master libavfilter/graphparser.c
16 Anton Khirnov
8 Michael Niedermayer
4 Andreas Rheinhardt
Or maybe we should consider avfilter.h too, since I'm adding new API
$ git shortlog -ns --since=2012 --no-merges origin/master libavfilter/graphparser.c libavfilter/avfilter.h
73 Anton Khirnov
32 Nicolas George
23 Stefano Sabatini
Hmm, now who could possibly be the person who knows that code? Any
guesses?
--
Anton Khirnov
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API Anton Khirnov
2023-01-20 20:47 ` Nicolas George
@ 2023-05-30 21:09 ` Marton Balint
2023-05-31 14:39 ` Paul B Mahol
2023-06-03 10:02 ` Anton Khirnov
1 sibling, 2 replies; 25+ messages in thread
From: Marton Balint @ 2023-05-30 21:09 UTC (permalink / raw)
To: FFmpeg development discussions and patches
On Fri, 20 Jan 2023, Anton Khirnov wrote:
> ---
> libavfilter/graphparser.c | 537 +++++++++-----------------------------
> 1 file changed, 126 insertions(+), 411 deletions(-)
This change makes error messages for parse failures less useful or
completely missing. E.g.:
ffmpeg -f lavfi -i testsrc -vf yadif,scale=dummy=boo -f null none
Before:
[Parsed_scale_0 @ 0x555c19335b00] Option 'dummy' not found
[AVFilterGraph @ 0x555c19333e00] Error initializing filter 'scale' with args 'dummy=boo:flags=bicubic'
Error reinitializing filters!
After:
[AVFilterGraph @ 0x3c5a100] Error applying filter options
Error reinitializing filters!
ffplay -f lavfi -i testsrc -vf yadif,scale=dummy=boo
Before:
[Parsed_scale_1 @ 0x55fa2b935080] Option 'dummy' not found
Error initializing filter 'scale' with args 'dummy=boo:flags=bicubic'
After:
(no error message)
Regards,
Marton
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-05-30 21:09 ` Marton Balint
@ 2023-05-31 14:39 ` Paul B Mahol
2023-06-03 10:02 ` Anton Khirnov
1 sibling, 0 replies; 25+ messages in thread
From: Paul B Mahol @ 2023-05-31 14:39 UTC (permalink / raw)
To: FFmpeg development discussions and patches
On Tue, May 30, 2023 at 11:10 PM Marton Balint <cus@passwd.hu> wrote:
>
>
> On Fri, 20 Jan 2023, Anton Khirnov wrote:
>
> > ---
> > libavfilter/graphparser.c | 537 +++++++++-----------------------------
> > 1 file changed, 126 insertions(+), 411 deletions(-)
>
> This change makes error messages for parse failures less useful or
> completely missing. E.g.:
>
> ffmpeg -f lavfi -i testsrc -vf yadif,scale=dummy=boo -f null none
>
> Before:
>
> [Parsed_scale_0 @ 0x555c19335b00] Option 'dummy' not found
> [AVFilterGraph @ 0x555c19333e00] Error initializing filter 'scale' with
> args 'dummy=boo:flags=bicubic'
> Error reinitializing filters!
>
> After:
>
> [AVFilterGraph @ 0x3c5a100] Error applying filter options
> Error reinitializing filters!
>
>
> ffplay -f lavfi -i testsrc -vf yadif,scale=dummy=boo
>
> Before:
>
> [Parsed_scale_1 @ 0x55fa2b935080] Option 'dummy' not found
> Error initializing filter 'scale' with args 'dummy=boo:flags=bicubic'
>
> After:
>
> (no error message)
>
Yes, I was hit by this bug, multiple times recently.
It makes my pain bigger.
> Regards,
> Marton
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API
2023-05-30 21:09 ` Marton Balint
2023-05-31 14:39 ` Paul B Mahol
@ 2023-06-03 10:02 ` Anton Khirnov
1 sibling, 0 replies; 25+ messages in thread
From: Anton Khirnov @ 2023-06-03 10:02 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Quoting Marton Balint (2023-05-30 23:09:50)
>
>
> On Fri, 20 Jan 2023, Anton Khirnov wrote:
>
> > ---
> > libavfilter/graphparser.c | 537 +++++++++-----------------------------
> > 1 file changed, 126 insertions(+), 411 deletions(-)
>
> This change makes error messages for parse failures less useful or
> completely missing. E.g.:
>
> ffmpeg -f lavfi -i testsrc -vf yadif,scale=dummy=boo -f null none
>
> Before:
>
> [Parsed_scale_0 @ 0x555c19335b00] Option 'dummy' not found
> [AVFilterGraph @ 0x555c19333e00] Error initializing filter 'scale' with args 'dummy=boo:flags=bicubic'
> Error reinitializing filters!
>
> After:
>
> [AVFilterGraph @ 0x3c5a100] Error applying filter options
> Error reinitializing filters!
Cannot reproduce, for me current git master says:
Input #0, lavfi, from 'testsrc':
Duration: N/A, start: 0.000000, bitrate: N/A
Stream #0:0: Video: wrapped_avframe, rgb24, 320x240 [SAR 1:1 DAR 4:3], 25 fps, 25 tbr, 25 tbn
Error applying option 'dummy' to filter 'scale': Option not found
Option not found
Which could be improved, but it does mention the unknown option name.
> ffplay -f lavfi -i testsrc -vf yadif,scale=dummy=boo
>
> Before:
>
> [Parsed_scale_1 @ 0x55fa2b935080] Option 'dummy' not found
> Error initializing filter 'scale' with args 'dummy=boo:flags=bicubic'
>
> After:
>
> (no error message)
Should be addressed by the patch I just sent.
--
Anton Khirnov
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* [FFmpeg-devel] [PATCH 5/5] fftools/ffmpeg: add special syntax for loading filter options from files
2023-01-20 19:31 [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options() Anton Khirnov
` (2 preceding siblings ...)
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 4/5] lavfi/graphparser: reimplement avfilter_graph_parse* using new API Anton Khirnov
@ 2023-01-20 19:31 ` Anton Khirnov
2023-01-23 11:34 ` Anton Khirnov
2023-01-27 16:47 ` [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options() Anton Khirnov
4 siblings, 1 reply; 25+ messages in thread
From: Anton Khirnov @ 2023-01-20 19:31 UTC (permalink / raw)
To: ffmpeg-devel
Many filters accept user-provided data that is cumbersome to provide as
text strings - e.g. binary files or very long text. For that reason such
filters typically provide a option whose value is the path from which
the filter loads the actual data.
However, filters doing their own IO internally is a layering violation
that the callers may not expect, and is thus best avoided. With the
recently introduced graph segment parsing API, loading option values
from files can now be handled by the caller.
This commit makes use of the new API in ffmpeg CLI. Any option name in
the filtergraph syntax can now be prefixed with a slash '/'. This will
cause ffmpeg to interpret the value as the path to load the actual value
from.
---
Changelog | 2 +
doc/filters.texi | 11 +++
fftools/ffmpeg_filter.c | 154 +++++++++++++++++++++++++++++++++++++++-
3 files changed, 165 insertions(+), 2 deletions(-)
diff --git a/Changelog b/Changelog
index 5c01e8365e..2d1ea9540e 100644
--- a/Changelog
+++ b/Changelog
@@ -29,6 +29,8 @@ version <next>:
- corr video filter
- adrc audio filter
- afdelaysrc audio filter
+- filtergraph syntax in ffmpeg CLI now supports passing file contents
+ as option values, by prefixing option name with '/'
version 5.1:
diff --git a/doc/filters.texi b/doc/filters.texi
index be70a2396b..7ff9948239 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -171,6 +171,17 @@ within the quoted text; otherwise the argument string is considered
terminated when the next special character (belonging to the set
@samp{[]=;,}) is encountered.
+A special syntax implemented in the @command{ffmpeg} CLI tool allows loading
+option values from files. This is done be prepending a slash '/' to the option
+name, then the supplied value is interpreted as a path from which the actual
+value is loaded. E.g.
+@example
+ffmpeg -i <INPUT> -vf drawtext=/text=/tmp/some_text <OUTPUT>
+@end example
+will load the text to be drawn from @file{/tmp/some_text}. API users wishing to
+implement a similar feature should use the @code{avfilter_graph_segment_*()}
+functions together with custom IO code.
+
The name and arguments of the filter are optionally preceded and
followed by a list of link labels.
A link label allows one to name a link and associate it to a filter output
diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c
index 7eb656dbe5..3f79133f75 100644
--- a/fftools/ffmpeg_filter.c
+++ b/fftools/ffmpeg_filter.c
@@ -314,6 +314,156 @@ static void init_input_filter(FilterGraph *fg, AVFilterInOut *in)
ist->filters[ist->nb_filters - 1] = ifilter;
}
+static int read_binary(const char *path, uint8_t **data, int *len)
+{
+ AVIOContext *io = NULL;
+ int64_t fsize;
+ int ret;
+
+ *data = NULL;
+ *len = 0;
+
+ ret = avio_open2(&io, path, AVIO_FLAG_READ, &int_cb, NULL);
+ if (ret < 0) {
+ av_log(NULL, AV_LOG_ERROR, "Cannot open file '%s': %s\n",
+ path, av_err2str(ret));
+ return ret;
+ }
+
+ fsize = avio_size(io);
+ if (fsize < 0 || fsize > INT_MAX) {
+ av_log(NULL, AV_LOG_ERROR, "Cannot obtain size of file %s\n", path);
+ ret = AVERROR(EIO);
+ goto fail;
+ }
+
+ *data = av_malloc(fsize);
+ if (!*data) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ ret = avio_read(io, *data, fsize);
+ if (ret != fsize) {
+ av_log(NULL, AV_LOG_ERROR, "Error reading file %s\n", path);
+ ret = ret < 0 ? ret : AVERROR(EIO);
+ goto fail;
+ }
+
+ *len = fsize;
+
+ return 0;
+fail:
+ avio_close(io);
+ av_freep(data);
+ *len = 0;
+ return ret;
+}
+
+static int filter_opt_apply(AVFilterContext *f, const char *key, const char *val)
+{
+ const AVOption *o;
+ int ret;
+
+ ret = av_opt_set(f, key, val, AV_OPT_SEARCH_CHILDREN);
+ if (ret >= 0)
+ return 0;
+
+ if (ret == AVERROR_OPTION_NOT_FOUND && key[0] == '/')
+ o = av_opt_find(f, key + 1, NULL, 0, AV_OPT_SEARCH_CHILDREN);
+ if (!o)
+ goto err_apply;
+
+ // key is a valid option name prefixed with '/'
+ // interpret value as a path from which to load the actual option value
+ key++;
+
+ if (o->type == AV_OPT_TYPE_BINARY) {
+ uint8_t *data;
+ int len;
+
+ ret = read_binary(val, &data, &len);
+ if (ret < 0)
+ goto err_load;
+
+ ret = av_opt_set_bin(f, key, data, len, AV_OPT_SEARCH_CHILDREN);
+ av_freep(&data);
+ } else {
+ char *data = file_read(val);
+ if (!val) {
+ ret = AVERROR(EIO);
+ goto err_load;
+ }
+
+ ret = av_opt_set(f, key, data, AV_OPT_SEARCH_CHILDREN);
+ av_freep(&data);
+ }
+ if (ret < 0)
+ goto err_apply;
+
+ return 0;
+
+err_apply:
+ av_log(NULL, AV_LOG_ERROR,
+ "Error applying option '%s' to filter '%s': %s\n",
+ key, f->filter->name, av_err2str(ret));
+ return ret;
+err_load:
+ av_log(NULL, AV_LOG_ERROR,
+ "Error loading value for option '%s' from file '%s'\n",
+ key, val);
+ return ret;
+}
+
+static int graph_opts_apply(AVFilterGraphSegment *seg)
+{
+ for (size_t i = 0; i < seg->nb_chains; i++) {
+ AVFilterChain *ch = seg->chains[i];
+
+ for (size_t j = 0; j < ch->nb_filters; j++) {
+ AVFilterParams *p = ch->filters[j];
+ const AVDictionaryEntry *e = NULL;
+
+ av_assert0(p->filter);
+
+ while ((e = av_dict_iterate(p->opts, e))) {
+ int ret = filter_opt_apply(p->filter, e->key, e->value);
+ if (ret < 0)
+ return ret;
+ }
+
+ av_dict_free(&p->opts);
+ }
+ }
+
+ return 0;
+}
+
+static int graph_parse(AVFilterGraph *graph, const char *desc,
+ AVFilterInOut **inputs, AVFilterInOut **outputs)
+{
+ AVFilterGraphSegment *seg;
+ int ret;
+
+ ret = avfilter_graph_segment_parse(graph, desc, 0, &seg);
+ if (ret < 0)
+ return ret;
+
+ ret = avfilter_graph_segment_create_filters(seg, 0);
+ if (ret < 0)
+ goto fail;
+
+ ret = graph_opts_apply(seg);
+ if (ret < 0)
+ goto fail;
+
+ ret = avfilter_graph_segment_apply(seg, 0, inputs, outputs);
+
+fail:
+ avfilter_graph_segment_free(&seg);
+ return ret;
+}
+
int init_complex_filtergraph(FilterGraph *fg)
{
AVFilterInOut *inputs, *outputs, *cur;
@@ -327,7 +477,7 @@ int init_complex_filtergraph(FilterGraph *fg)
return AVERROR(ENOMEM);
graph->nb_threads = 1;
- ret = avfilter_graph_parse2(graph, fg->graph_desc, &inputs, &outputs);
+ ret = graph_parse(graph, fg->graph_desc, &inputs, &outputs);
if (ret < 0)
goto fail;
@@ -1004,7 +1154,7 @@ int configure_filtergraph(FilterGraph *fg)
fg->graph->nb_threads = filter_complex_nbthreads;
}
- if ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0)
+ if ((ret = graph_parse(fg->graph, graph_desc, &inputs, &outputs)) < 0)
goto fail;
ret = hw_device_setup_for_filter(fg);
--
2.35.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 5/5] fftools/ffmpeg: add special syntax for loading filter options from files
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 5/5] fftools/ffmpeg: add special syntax for loading filter options from files Anton Khirnov
@ 2023-01-23 11:34 ` Anton Khirnov
0 siblings, 0 replies; 25+ messages in thread
From: Anton Khirnov @ 2023-01-23 11:34 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Quoting Anton Khirnov (2023-01-20 20:31:32)
> +static int filter_opt_apply(AVFilterContext *f, const char *key, const char *val)
> +{
> + const AVOption *o;
> + int ret;
> +
> + ret = av_opt_set(f, key, val, AV_OPT_SEARCH_CHILDREN);
> + if (ret >= 0)
> + return 0;
> +
> + if (ret == AVERROR_OPTION_NOT_FOUND && key[0] == '/')
> + o = av_opt_find(f, key + 1, NULL, 0, AV_OPT_SEARCH_CHILDREN);
> + if (!o)
> + goto err_apply;
> +
> + // key is a valid option name prefixed with '/'
> + // interpret value as a path from which to load the actual option value
> + key++;
> +
> + if (o->type == AV_OPT_TYPE_BINARY) {
> + uint8_t *data;
> + int len;
> +
> + ret = read_binary(val, &data, &len);
> + if (ret < 0)
> + goto err_load;
> +
> + ret = av_opt_set_bin(f, key, data, len, AV_OPT_SEARCH_CHILDREN);
> + av_freep(&data);
> + } else {
> + char *data = file_read(val);
> + if (!val) {
should be s/val/data/
fixed locally
--
Anton Khirnov
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options()
2023-01-20 19:31 [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options() Anton Khirnov
` (3 preceding siblings ...)
2023-01-20 19:31 ` [FFmpeg-devel] [PATCH 5/5] fftools/ffmpeg: add special syntax for loading filter options from files Anton Khirnov
@ 2023-01-27 16:47 ` Anton Khirnov
2023-01-27 16:48 ` Nicolas George
4 siblings, 1 reply; 25+ messages in thread
From: Anton Khirnov @ 2023-01-27 16:47 UTC (permalink / raw)
To: FFmpeg development discussions and patches
If nobody has further comments, I would like to push this set next week.
--
Anton Khirnov
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options()
2023-01-27 16:47 ` [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options() Anton Khirnov
@ 2023-01-27 16:48 ` Nicolas George
2023-01-27 16:51 ` Anton Khirnov
0 siblings, 1 reply; 25+ messages in thread
From: Nicolas George @ 2023-01-27 16:48 UTC (permalink / raw)
To: FFmpeg development discussions and patches
[-- Attachment #1.1: Type: text/plain, Size: 177 bytes --]
Anton Khirnov (12023-01-27):
> If nobody has further comments, I would like to push this set next week.
I have already said I would have comments.
--
Nicolas George
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
[-- Attachment #2: Type: text/plain, Size: 251 bytes --]
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options()
2023-01-27 16:48 ` Nicolas George
@ 2023-01-27 16:51 ` Anton Khirnov
2023-01-27 16:53 ` Nicolas George
0 siblings, 1 reply; 25+ messages in thread
From: Anton Khirnov @ 2023-01-27 16:51 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Quoting Nicolas George (2023-01-27 17:48:02)
> Anton Khirnov (12023-01-27):
> > If nobody has further comments, I would like to push this set next week.
>
> I have already said I would have comments.
Send them then.
It's been almost two weeks since my initial RFC, and the code is quite
straightforward.
--
Anton Khirnov
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/5] lavfi/avfilter: export process_options()
2023-01-27 16:51 ` Anton Khirnov
@ 2023-01-27 16:53 ` Nicolas George
0 siblings, 0 replies; 25+ messages in thread
From: Nicolas George @ 2023-01-27 16:53 UTC (permalink / raw)
To: FFmpeg development discussions and patches
[-- Attachment #1.1: Type: text/plain, Size: 349 bytes --]
Anton Khirnov (12023-01-27):
> Send them then.
> It's been almost two weeks since my initial RFC,
And if you have to wait ten more weeks for a proper review, you will
wait ten more weeks.
> and the code is quite
> straightforward.
No, it is not. I have seen likely flaws, but I need time to make sure.
--
Nicolas George
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
[-- Attachment #2: Type: text/plain, Size: 251 bytes --]
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 25+ messages in thread