From 666f6d7d31972e9fd3377691a02711f4d8baa366 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 14 Mar 2025 10:10:18 +0100 Subject: [PATCH 1/3] filter-graph: make the filter-graph ports dynamic When parsing the graph, parse the input and output port names into a separate string array. This was we can keep them around when setting up the graph. Instead of setting up the graph right after loading it, do the graph setup when we activate the graph. This makes it possible to pass the input channels to the filter-graph and let it create the right amount of plugins and ouput channels. When setting up the graphs in the audioconverter, pass the current number of channels as the input to the graph and keep track of the channels that each filter produces. This way we can also load a custom upmix or downmix graph, for example. --- spa/include/spa/filter-graph/filter-graph.h | 1 + spa/plugins/audioconvert/audioconvert.c | 36 ++++-- spa/plugins/filter-graph/filter-graph.c | 124 +++++++++++++++----- 3 files changed, 122 insertions(+), 39 deletions(-) diff --git a/spa/include/spa/filter-graph/filter-graph.h b/spa/include/spa/filter-graph/filter-graph.h index 05904c7f3..b9c5ea426 100644 --- a/spa/include/spa/filter-graph/filter-graph.h +++ b/spa/include/spa/filter-graph/filter-graph.h @@ -46,6 +46,7 @@ struct spa_filter_graph_info { #define SPA_FILTER_GRAPH_CHANGE_MASK_FLAGS (1u<<0) #define SPA_FILTER_GRAPH_CHANGE_MASK_PROPS (1u<<1) +#define SPA_FILTER_GRAPH_CHANGE_MASK_PORTS (1u<<2) uint64_t change_mask; uint64_t flags; diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 9cb580d7d..1b1f5faf8 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -995,10 +995,10 @@ struct spa_filter_graph_events graph_events = { .props_changed = graph_props_changed, }; -static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph) +static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph, uint32_t channels) { int res; - char rate_str[64]; + char rate_str[64], in_ports[64]; struct dir *dir; if (graph == NULL) @@ -1006,11 +1006,15 @@ static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph) dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; snprintf(rate_str, sizeof(rate_str), "%d", dir->format.info.raw.rate); + if (channels) + snprintf(in_ports, sizeof(in_ports), "%d", channels); spa_filter_graph_deactivate(graph); res = spa_filter_graph_activate(graph, &SPA_DICT_ITEMS( - SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str))); + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str), + SPA_DICT_ITEM("filter-graph.n_inputs", channels ? in_ports : NULL))); + return res; } @@ -1104,7 +1108,7 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order) goto error; /* prepare new filter and swap it */ - res = setup_filter_graph(impl, iface); + res = setup_filter_graph(impl, iface, 0); if (res < 0) goto error; pending->graph = iface; @@ -1909,7 +1913,7 @@ static char *format_position(char *str, size_t len, uint32_t channels, uint32_t return str; } -static int setup_channelmix(struct impl *this) +static int setup_channelmix(struct impl *this, uint32_t channels) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; @@ -1918,7 +1922,7 @@ static int setup_channelmix(struct impl *this) char str[1024]; int res; - src_chan = in->format.info.raw.channels; + src_chan = channels; dst_chan = out->format.info.raw.channels; for (i = 0, src_mask = 0; i < src_chan; i++) { @@ -1929,6 +1933,13 @@ static int setup_channelmix(struct impl *this) p = out->format.info.raw.position[i]; dst_mask |= 1ULL << (p < 64 ? p : 0); } + + /* if we needed a channel conversion but we already did one before this + * stage, assume we are now with the dst layout */ + if ((out->format.info.raw.channels != in->format.info.raw.channels) && + channels != in->format.info.raw.channels) + src_mask = dst_mask; + spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), src_chan, in->format.info.raw.position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), @@ -2222,7 +2233,7 @@ static inline bool resample_is_passthrough(struct impl *this) static int setup_convert(struct impl *this) { struct dir *in, *out; - uint32_t i, rate, maxsize, maxports, duration; + uint32_t i, rate, maxsize, maxports, duration, channels; struct port *p; int res; @@ -2269,16 +2280,19 @@ static int setup_convert(struct impl *this) if (in->format.info.raw.channels == 0 || out->format.info.raw.channels == 0) return -EINVAL; + channels = in->format.info.raw.channels; + if ((res = setup_in_convert(this)) < 0) return res; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &this->filter_graph[i]; + for (i = 0; i < this->n_graph; i++) { + struct filter_graph *g = &this->filter_graph[this->graph_index[i]]; if (!g->active) continue; - if ((res = setup_filter_graph(this, g->graph)) < 0) + if ((res = setup_filter_graph(this, g->graph, channels)) < 0) return res; + channels = g->n_outputs; } - if ((res = setup_channelmix(this)) < 0) + if ((res = setup_channelmix(this, channels)) < 0) return res; if ((res = setup_resample(this)) < 0) return res; diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 91afd62a5..5a7410db7 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -175,9 +175,16 @@ struct graph { uint32_t n_control; struct port **control_port; + uint32_t n_input_names; + char **input_names; + + uint32_t n_output_names; + char **output_names; + struct volume volume[2]; unsigned activated:1; + unsigned setup:1; }; struct impl { @@ -1422,6 +1429,8 @@ static int impl_deactivate(void *object) return 0; } +static int setup_graph(struct graph *graph); + static int impl_activate(void *object, const struct spa_dict *props) { struct impl *impl = object; @@ -1432,10 +1441,10 @@ static int impl_activate(void *object, const struct spa_dict *props) struct descriptor *desc; const struct spa_fga_descriptor *d; const struct spa_fga_plugin *p; - uint32_t i, j, max_samples = impl->quantum_limit; + uint32_t i, j, max_samples = impl->quantum_limit, n_ports; int res; float *sd, *dd, *data; - const char *rate; + const char *rate, *str; if (graph->activated) return 0; @@ -1445,6 +1454,31 @@ static int impl_activate(void *object, const struct spa_dict *props) rate = spa_dict_lookup(props, SPA_KEY_AUDIO_RATE); impl->rate = rate ? atoi(rate) : DEFAULT_RATE; + if ((str = spa_dict_lookup(props, "filter-graph.n_inputs")) != NULL) { + if (spa_atou32(str, &n_ports, 0) && + n_ports != impl->info.n_inputs) { + impl->info.n_inputs = n_ports; + impl->info.n_outputs = 0; + impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PORTS; + graph->setup = false; + } + } + if ((str = spa_dict_lookup(props, "filter-graph.n_outputs")) != NULL) { + if (spa_atou32(str, &n_ports, 0) && + n_ports != impl->info.n_outputs) { + impl->info.n_outputs = n_ports; + impl->info.n_inputs = 0; + impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PORTS; + graph->setup = false; + } + } + if (!graph->setup) { + if ((res = setup_graph(graph)) < 0) + return res; + graph->setup = true; + spa_filter_graph_emit_info(&impl->hooks, &impl->info); + } + /* first make instances */ spa_list_for_each(node, &graph->node_list, link) { node_cleanup(node); @@ -1569,7 +1603,19 @@ static struct node *find_next_node(struct graph *graph) return NULL; } -static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_json *outputs) +static void unsetup_graph(struct graph *graph) +{ + free(graph->input); + graph->input = NULL; + free(graph->output); + graph->output = NULL; + free(graph->hndl); + graph->hndl = NULL; + free(graph->control_port); + graph->control_port = NULL; + +} +static int setup_graph(struct graph *graph) { struct impl *impl = graph->impl; struct node *node, *first, *last; @@ -1577,33 +1623,35 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ struct link *link; struct graph_port *gp; struct graph_hndl *gh; - uint32_t i, j, n_nodes, n_input, n_output, n_control, n_hndl = 0; + uint32_t i, j, n, n_nodes, n_input, n_output, n_control, n_hndl = 0; int res; struct descriptor *desc; const struct spa_fga_descriptor *d; - char v[256]; + char *pname; bool allow_unused; + unsetup_graph(graph); + first = spa_list_first(&graph->node_list, struct node, link); last = spa_list_last(&graph->node_list, struct node, link); /* calculate the number of inputs and outputs into the graph. - * If we have a list of inputs/outputs, just count them. Otherwise + * If we have a list of inputs/outputs, just use them. Otherwise * we count all input ports of the first node and all output * ports of the last node */ - if (inputs != NULL) - n_input = count_array(inputs); + if (graph->n_input_names != 0) + n_input = graph->n_input_names; else n_input = first->desc->n_input; - if (outputs != NULL) - n_output = count_array(outputs); + if (graph->n_output_names != 0) + n_output = graph->n_output_names; else n_output = last->desc->n_output; /* we allow unconnected ports when not explicitly given and the nodes support * NULL data */ - allow_unused = inputs == NULL && outputs == NULL && + allow_unused = graph->n_input_names == 0 && graph->n_output_names == 0 && SPA_FLAG_IS_SET(first->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA) && SPA_FLAG_IS_SET(last->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA); @@ -1675,7 +1723,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ /* now collect all input and output ports for all the handles. */ for (i = 0; i < n_hndl; i++) { - if (inputs == NULL) { + if (graph->n_input_names == 0) { desc = first->desc; d = desc->desc; for (j = 0; j < desc->n_input; j++) { @@ -1687,15 +1735,15 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ gp->port = desc->input[j]; } } else { - struct spa_json it = *inputs; - while (spa_json_get_string(&it, v, sizeof(v)) > 0) { - if (spa_streq(v, "null")) { + for (n = 0; n < graph->n_input_names; n++) { + pname = graph->input_names[n]; + if (spa_streq(pname, "null")) { gp = &graph->input[graph->n_input++]; gp->desc = NULL; spa_log_info(impl->log, "ignore input port %d", graph->n_input); - } else if ((port = find_port(first, v, SPA_FGA_PORT_INPUT)) == NULL) { + } else if ((port = find_port(first, pname, SPA_FGA_PORT_INPUT)) == NULL) { res = -ENOENT; - spa_log_error(impl->log, "input port %s not found", v); + spa_log_error(impl->log, "input port %s not found", pname); goto error; } else { bool disabled = false; @@ -1754,7 +1802,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ } } } - if (outputs == NULL) { + if (graph->n_output_names == 0) { desc = last->desc; d = desc->desc; for (j = 0; j < desc->n_output; j++) { @@ -1766,15 +1814,15 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ gp->port = desc->output[j]; } } else { - struct spa_json it = *outputs; - while (spa_json_get_string(&it, v, sizeof(v)) > 0) { + for (n = 0; n < graph->n_output_names; n++) { + pname = graph->output_names[n]; gp = &graph->output[graph->n_output]; - if (spa_streq(v, "null")) { + if (spa_streq(pname, "null")) { gp->desc = NULL; spa_log_info(impl->log, "silence output port %d", graph->n_output); - } else if ((port = find_port(last, v, SPA_FGA_PORT_OUTPUT)) == NULL) { + } else if ((port = find_port(last, pname, SPA_FGA_PORT_OUTPUT)) == NULL) { res = -ENOENT; - spa_log_error(impl->log, "output port %s not found", v); + spa_log_error(impl->log, "output port %s not found", pname); goto error; } else { desc = port->node->desc; @@ -1979,21 +2027,41 @@ static int load_graph(struct graph *graph, const struct spa_dict *props) return res; } } - return setup_graph(graph, pinputs, poutputs); + if (pinputs != NULL) { + graph->n_input_names = count_array(pinputs); + graph->input_names = calloc(graph->n_input_names, sizeof(char *)); + graph->n_input_names = 0; + while (spa_json_get_string(pinputs, key, sizeof(key)) > 0) + graph->input_names[graph->n_input_names++] = strdup(key); + } + if (poutputs != NULL) { + graph->n_output_names = count_array(poutputs); + graph->output_names = calloc(graph->n_output_names, sizeof(char *)); + graph->n_output_names = 0; + while (spa_json_get_string(poutputs, key, sizeof(key)) > 0) + graph->output_names[graph->n_output_names++] = strdup(key); + } + return 0; } static void graph_free(struct graph *graph) { struct link *link; struct node *node; + uint32_t i; + + unsetup_graph(graph); + spa_list_consume(link, &graph->link_list, link) link_free(link); spa_list_consume(node, &graph->node_list, link) node_free(node); - free(graph->input); - free(graph->output); - free(graph->hndl); - free(graph->control_port); + for (i = 0; i < graph->n_input_names; i++) + free(graph->input_names[i]); + free(graph->input_names); + for (i = 0; i < graph->n_output_names; i++) + free(graph->output_names[i]); + free(graph->output_names); } static const struct spa_filter_graph_methods impl_filter_graph = { From ce3ff1b54a0e22c6ed54604439aa2babf9ce8ed4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 18 Mar 2025 12:39:15 +0100 Subject: [PATCH 2/3] filter-graph: add support for channel positions Make it possible to define a channel position in filter-graph. Use the channel positions to perform the final channelmix. --- spa/include/spa/filter-graph/filter-graph.h | 1 - spa/plugins/audioconvert/audioconvert.c | 62 ++++++++--- spa/plugins/filter-graph/filter-graph.c | 115 ++++++++++++++++---- 3 files changed, 139 insertions(+), 39 deletions(-) diff --git a/spa/include/spa/filter-graph/filter-graph.h b/spa/include/spa/filter-graph/filter-graph.h index b9c5ea426..05904c7f3 100644 --- a/spa/include/spa/filter-graph/filter-graph.h +++ b/spa/include/spa/filter-graph/filter-graph.h @@ -46,7 +46,6 @@ struct spa_filter_graph_info { #define SPA_FILTER_GRAPH_CHANGE_MASK_FLAGS (1u<<0) #define SPA_FILTER_GRAPH_CHANGE_MASK_PROPS (1u<<1) -#define SPA_FILTER_GRAPH_CHANGE_MASK_PORTS (1u<<2) uint64_t change_mask; uint64_t flags; diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 1b1f5faf8..feaecac37 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -229,7 +229,9 @@ struct filter_graph { struct spa_filter_graph *graph; struct spa_hook listener; uint32_t n_inputs; + uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_outputs; + uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; bool active; }; @@ -960,10 +962,28 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) static void graph_info(void *object, const struct spa_filter_graph_info *info) { struct filter_graph *g = object; + struct spa_dict *props = info->props; + uint32_t i; + if (!g->active) return; + g->n_inputs = info->n_inputs; g->n_outputs = info->n_outputs; + for (i = 0; props && i < props->n_items; i++) { + const char *k = props->items[i].key; + const char *s = props->items[i].value; + if (spa_streq(k, "n_inputs")) + spa_atou32(s, &g->n_inputs, 0); + else if (spa_streq(k, "n_outputs")) + spa_atou32(s, &g->n_outputs, 0); + else if (spa_streq(k, "inputs.audio.position")) + spa_audio_parse_position(s, strlen(s), + g->inputs_position, &g->n_inputs); + else if (spa_streq(k, "outputs.audio.position")) + spa_audio_parse_position(s, strlen(s), + g->outputs_position, &g->n_outputs); + } } static int apply_props(struct impl *impl, const struct spa_pod *props); @@ -995,22 +1015,29 @@ struct spa_filter_graph_events graph_events = { .props_changed = graph_props_changed, }; -static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph, uint32_t channels) +static int setup_filter_graph(struct impl *this, struct filter_graph *g, + uint32_t channels, uint32_t *position) { int res; char rate_str[64], in_ports[64]; struct dir *dir; - if (graph == NULL) + if (g == NULL || g->graph == NULL) return 0; dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; snprintf(rate_str, sizeof(rate_str), "%d", dir->format.info.raw.rate); - if (channels) + if (channels) { snprintf(in_ports, sizeof(in_ports), "%d", channels); + g->n_inputs = channels; + if (position) { + memcpy(g->inputs_position, position, sizeof(uint32_t) * channels); + memcpy(g->outputs_position, position, sizeof(uint32_t) * channels); + } + } - spa_filter_graph_deactivate(graph); - res = spa_filter_graph_activate(graph, + spa_filter_graph_deactivate(g->graph); + res = spa_filter_graph_activate(g->graph, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str), SPA_DICT_ITEM("filter-graph.n_inputs", channels ? in_ports : NULL))); @@ -1108,10 +1135,12 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order) goto error; /* prepare new filter and swap it */ - res = setup_filter_graph(impl, iface, 0); - if (res < 0) - goto error; pending->graph = iface; + res = setup_filter_graph(impl, pending, 0, NULL); + if (res < 0) { + pending->graph = NULL; + goto error; + } pending->active = true; spa_log_info(impl->log, "loading filter-graph order:%d in %d active:%d", order, idx, n_graph + 1); @@ -1913,7 +1942,7 @@ static char *format_position(char *str, size_t len, uint32_t channels, uint32_t return str; } -static int setup_channelmix(struct impl *this, uint32_t channels) +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; @@ -1926,7 +1955,7 @@ static int setup_channelmix(struct impl *this, uint32_t channels) dst_chan = out->format.info.raw.channels; for (i = 0, src_mask = 0; i < src_chan; i++) { - p = in->format.info.raw.position[i]; + p = position[i]; src_mask |= 1ULL << (p < 64 ? p : 0); } for (i = 0, dst_mask = 0; i < dst_chan; i++) { @@ -1941,7 +1970,7 @@ static int setup_channelmix(struct impl *this, uint32_t channels) src_mask = dst_mask; spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), - src_chan, in->format.info.raw.position), src_mask); + src_chan, position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), dst_chan, out->format.info.raw.position), dst_mask); @@ -2233,7 +2262,7 @@ static inline bool resample_is_passthrough(struct impl *this) static int setup_convert(struct impl *this) { struct dir *in, *out; - uint32_t i, rate, maxsize, maxports, duration, channels; + uint32_t i, rate, maxsize, maxports, duration, channels, *position; struct port *p; int res; @@ -2281,6 +2310,8 @@ static int setup_convert(struct impl *this) return -EINVAL; channels = in->format.info.raw.channels; + position = in->format.info.raw.position; + maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); if ((res = setup_in_convert(this)) < 0) return res; @@ -2288,11 +2319,13 @@ static int setup_convert(struct impl *this) struct filter_graph *g = &this->filter_graph[this->graph_index[i]]; if (!g->active) continue; - if ((res = setup_filter_graph(this, g->graph, channels)) < 0) + if ((res = setup_filter_graph(this, g, channels, position)) < 0) return res; channels = g->n_outputs; + position = g->outputs_position; + maxports = SPA_MAX(maxports, channels); } - if ((res = setup_channelmix(this, channels)) < 0) + if ((res = setup_channelmix(this, channels, position)) < 0) return res; if ((res = setup_resample(this)) < 0) return res; @@ -2308,7 +2341,6 @@ static int setup_convert(struct impl *this) p = GET_OUT_PORT(this, i); maxsize = SPA_MAX(maxsize, p->maxsize); } - maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); if ((res = ensure_tmp(this, maxsize, maxports)) < 0) return res; diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 5a7410db7..086c8bcaa 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -183,6 +183,13 @@ struct graph { struct volume volume[2]; + uint32_t n_inputs; + uint32_t n_outputs; + uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t n_inputs_position; + uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t n_outputs_position; + unsigned activated:1; unsigned setup:1; }; @@ -212,14 +219,52 @@ struct impl { float *discard_data; }; +static inline void print_channels(char *buffer, size_t max_size, uint32_t n_channels, uint32_t *positions) +{ + uint32_t i; + struct spa_strbuf buf; + + spa_strbuf_init(&buf, buffer, max_size); + spa_strbuf_append(&buf, "["); + for (i = 0; i < n_channels; i++) { + spa_strbuf_append(&buf, "%s%s", i ? "," : "", + spa_type_audio_channel_to_short_name(positions[i])); + } + spa_strbuf_append(&buf, "]"); +} + static void emit_filter_graph_info(struct impl *impl, bool full) { uint64_t old = full ? impl->info.change_mask : 0; + struct graph *graph = &impl->graph; if (full) impl->info.change_mask = impl->info_all; if (impl->info.change_mask || full) { + char n_inputs[64], n_outputs[64]; + struct spa_dict_item items[6]; + struct spa_dict dict = SPA_DICT(items, 0); + char in_pos[SPA_AUDIO_MAX_CHANNELS * 8]; + char out_pos[SPA_AUDIO_MAX_CHANNELS * 8]; + + snprintf(n_inputs, sizeof(n_inputs), "%d", impl->graph.n_inputs); + snprintf(n_outputs, sizeof(n_outputs), "%d", impl->graph.n_outputs); + + items[dict.n_items++] = SPA_DICT_ITEM("n_inputs", n_inputs); + items[dict.n_items++] = SPA_DICT_ITEM("n_outputs", n_outputs); + if (graph->n_inputs_position) { + print_channels(in_pos, sizeof(in_pos), + graph->n_inputs_position, graph->inputs_position); + items[dict.n_items++] = SPA_DICT_ITEM("inputs.audio.position", in_pos); + } + if (graph->n_outputs_position) { + print_channels(out_pos, sizeof(out_pos), + graph->n_outputs_position, graph->outputs_position); + items[dict.n_items++] = SPA_DICT_ITEM("outputs.audio.position", out_pos); + } + impl->info.props = &dict; spa_filter_graph_emit_info(&impl->hooks, &impl->info); + impl->info.props = NULL; impl->info.change_mask = old; } } @@ -250,7 +295,7 @@ static int impl_process(void *object, uint32_t i, j, n_hndl = graph->n_hndl; struct graph_port *port; - for (i = 0, j = 0; i < impl->info.n_inputs; i++) { + for (i = 0, j = 0; i < graph->n_inputs; i++) { while (j < graph->n_input) { port = &graph->input[j++]; if (port->desc && in[i]) @@ -259,7 +304,7 @@ static int impl_process(void *object, break; } } - for (i = 0; i < impl->info.n_outputs; i++) { + for (i = 0; i < graph->n_outputs; i++) { if (out[i] == NULL) continue; @@ -1456,19 +1501,19 @@ static int impl_activate(void *object, const struct spa_dict *props) if ((str = spa_dict_lookup(props, "filter-graph.n_inputs")) != NULL) { if (spa_atou32(str, &n_ports, 0) && - n_ports != impl->info.n_inputs) { - impl->info.n_inputs = n_ports; - impl->info.n_outputs = 0; - impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PORTS; + n_ports != graph->n_inputs) { + graph->n_inputs = n_ports; + graph->n_outputs = 0; + impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PROPS; graph->setup = false; } } if ((str = spa_dict_lookup(props, "filter-graph.n_outputs")) != NULL) { if (spa_atou32(str, &n_ports, 0) && - n_ports != impl->info.n_outputs) { - impl->info.n_outputs = n_ports; - impl->info.n_inputs = 0; - impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PORTS; + n_ports != graph->n_outputs) { + graph->n_outputs = n_ports; + graph->n_inputs = 0; + impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PROPS; graph->setup = false; } } @@ -1476,7 +1521,7 @@ static int impl_activate(void *object, const struct spa_dict *props) if ((res = setup_graph(graph)) < 0) return res; graph->setup = true; - spa_filter_graph_emit_info(&impl->hooks, &impl->info); + emit_filter_graph_info(impl, false); } /* first make instances */ @@ -1665,24 +1710,28 @@ static int setup_graph(struct graph *graph) res = -EINVAL; goto error; } + if (graph->n_inputs == 0) + graph->n_inputs = impl->info.n_inputs; + if (graph->n_inputs == 0) + graph->n_inputs = n_input; - if (impl->info.n_inputs == 0) - impl->info.n_inputs = n_input; + if (graph->n_outputs == 0) + graph->n_outputs = impl->info.n_outputs; /* compare to the requested number of inputs and duplicate the * graph n_hndl times when needed. */ - n_hndl = impl->info.n_inputs / n_input; + n_hndl = graph->n_inputs / n_input; - if (impl->info.n_outputs == 0) - impl->info.n_outputs = n_output * n_hndl; + if (graph->n_outputs == 0) + graph->n_outputs = n_output * n_hndl; - if (n_hndl != impl->info.n_outputs / n_output) { + if (n_hndl != graph->n_outputs / n_output) { spa_log_error(impl->log, "invalid ports. The input stream has %1$d ports and " "the filter has %2$d inputs. The output stream has %3$d ports " "and the filter has %4$d outputs. input:%1$d / input:%2$d != " "output:%3$d / output:%4$d. Check inputs and outputs objects.", - impl->info.n_inputs, n_input, - impl->info.n_outputs, n_output); + graph->n_inputs, n_input, + graph->n_outputs, n_output); res = -EINVAL; goto error; } @@ -1698,11 +1747,11 @@ static int setup_graph(struct graph *graph) "the filter has %2$d inputs. The output stream has %3$d ports " "and the filter has %4$d outputs. Some filter ports will be " "unconnected..", - impl->info.n_inputs, n_input, - impl->info.n_outputs, n_output); + graph->n_inputs, n_input, + graph->n_outputs, n_output); - if (impl->info.n_outputs == 0) - impl->info.n_outputs = n_output * n_hndl; + if (graph->n_outputs == 0) + graph->n_outputs = n_output * n_hndl; } spa_log_info(impl->log, "using %d instances %d %d", n_hndl, n_input, n_output); @@ -1948,6 +1997,26 @@ static int load_graph(struct graph *graph, const struct spa_dict *props) } impl->info.n_outputs = res; } + else if (spa_streq("inputs.audio.position", key)) { + if (!spa_json_is_array(val, len) || + (len = spa_json_container_len(&it[0], val, len)) < 0) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_audio_parse_position(val, len, graph->inputs_position, + &graph->n_inputs_position); + impl->info.n_inputs = graph->n_inputs_position; + } + else if (spa_streq("outputs.audio.position", key)) { + if (!spa_json_is_array(val, len) || + (len = spa_json_container_len(&it[0], val, len)) < 0) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_audio_parse_position(val, len, graph->outputs_position, + &graph->n_outputs_position); + impl->info.n_outputs = graph->n_outputs_position; + } else if (spa_streq("nodes", key)) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "%s expects an array", key); From 43b6a83518d4b8f72c9c538155bc0f03b832ed30 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 18 Mar 2025 16:30:25 +0100 Subject: [PATCH 3/3] audioconvert: rework the filter-graphs a little Use a simple free/active linked list for the filter-graphs and insert the new filters in the right position in the list. Then simply copy the list to an array for the processing thread. when reconfiguring, set up all the filters again because the number of channels might have changed. --- spa/plugins/audioconvert/audioconvert.c | 244 +++++++++++++----------- 1 file changed, 130 insertions(+), 114 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index feaecac37..cca0a2392 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -224,6 +224,7 @@ struct stage { struct filter_graph { struct impl *impl; + struct spa_list link; int order; struct spa_handle *handle; struct spa_filter_graph *graph; @@ -232,7 +233,8 @@ struct filter_graph { uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_outputs; uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; - bool active; + bool removing; + bool setup; }; struct impl { @@ -245,9 +247,12 @@ struct impl { struct spa_plugin_loader *loader; uint32_t n_graph; - uint32_t graph_index[MAX_GRAPH]; + struct filter_graph *filter_graph[MAX_GRAPH]; + + struct spa_list free_graphs; + struct spa_list active_graphs; + struct filter_graph graphs[MAX_GRAPH]; - struct filter_graph filter_graph[MAX_GRAPH]; int in_filter_props; int filter_props_count; @@ -304,6 +309,8 @@ struct impl { char group_name[128]; + uint32_t maxsize; + uint32_t maxports; uint32_t scratch_size; uint32_t scratch_ports; float *empty; @@ -817,8 +824,8 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; default: - if (this->filter_graph[0].graph) { - res = spa_filter_graph_enum_prop_info(this->filter_graph[0].graph, + if (this->filter_graph[0] && this->filter_graph[0]->graph) { + res = spa_filter_graph_enum_prop_info(this->filter_graph[0]->graph, result.index - 30, &b, ¶m); if (res <= 0) return res; @@ -910,13 +917,13 @@ static int impl_node_enum_params(void *object, int seq, param = spa_pod_builder_pop(&b, &f[0]); break; default: - if (result.index > MAX_GRAPH) + if (result.index-1 >= this->n_graph) return 0; - if (this->filter_graph[result.index-1].graph == NULL) + if (this->filter_graph[result.index-1]->graph == NULL) goto next; - res = spa_filter_graph_get_props(this->filter_graph[result.index-1].graph, + res = spa_filter_graph_get_props(this->filter_graph[result.index-1]->graph, &b, ¶m); if (res < 0) return res; @@ -965,7 +972,7 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) struct spa_dict *props = info->props; uint32_t i; - if (!g->active) + if (g->removing) return; g->n_inputs = info->n_inputs; @@ -992,7 +999,7 @@ static void graph_apply_props(void *object, enum spa_direction direction, const { struct filter_graph *g = object; struct impl *impl = g->impl; - if (!g->active) + if (g->removing) return; if (apply_props(impl, props) > 0) emit_node_info(impl, false); @@ -1002,7 +1009,7 @@ static void graph_props_changed(void *object, enum spa_direction direction) { struct filter_graph *g = object; struct impl *impl = g->impl; - if (!g->active) + if (g->removing) return; impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; impl->params[IDX_Props].user++; @@ -1022,7 +1029,7 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, char rate_str[64], in_ports[64]; struct dir *dir; - if (g == NULL || g->graph == NULL) + if (g == NULL || g->graph == NULL || g->setup) return 0; dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; @@ -1042,82 +1049,118 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str), SPA_DICT_ITEM("filter-graph.n_inputs", channels ? in_ports : NULL))); + g->setup = res >= 0; + return res; } +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position); + +static int setup_filter_graphs(struct impl *impl) +{ + int res; + uint32_t channels, *position; + struct dir *in, *out; + struct filter_graph *g, *t; + + in = &impl->dir[SPA_DIRECTION_INPUT]; + out = &impl->dir[SPA_DIRECTION_OUTPUT]; + + channels = in->format.info.raw.channels; + position = in->format.info.raw.position; + impl->maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); + + spa_list_for_each_safe(g, t, &impl->active_graphs, link) { + if (g->removing) + continue; + if ((res = setup_filter_graph(impl, g, channels, position)) < 0) { + g->removing = true; + spa_log_warn(impl->log, "failed to activate graph %d: %s", g->order, + spa_strerror(res)); + } else { + channels = g->n_outputs; + position = g->outputs_position; + impl->maxports = SPA_MAX(impl->maxports, channels); + } + } + if ((res = setup_channelmix(impl, channels, position)) < 0) + return res; + + return 0; +} + static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *impl = user_data; - uint32_t i, j; - impl->n_graph = 0; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &impl->filter_graph[i]; - if (g->graph == NULL || !g->active) - continue; - impl->graph_index[impl->n_graph++] = i; + struct filter_graph *g; + + impl->n_graph = 0; + spa_list_for_each(g, &impl->active_graphs, link) + if (g->setup && !g->removing) + impl->filter_graph[impl->n_graph++] = g; - for (j = impl->n_graph-1; j > 0; j--) { - if (impl->filter_graph[impl->graph_index[j]].order >= - impl->filter_graph[impl->graph_index[j-1]].order) - break; - SPA_SWAP(impl->graph_index[j], impl->graph_index[j-1]); - } - } impl->recalc = true; return 0; } static void clean_filter_handles(struct impl *impl, bool force) { - uint32_t i; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &impl->filter_graph[i]; - if (!g->active || force) { - if (g->graph) - spa_hook_remove(&g->listener); - if (g->handle) - spa_plugin_loader_unload(impl->loader, g->handle); - spa_zero(*g); - } + struct filter_graph *g, *t; + + spa_list_for_each_safe(g, t, &impl->active_graphs, link) { + if (!g->removing) + continue; + spa_list_remove(&g->link); + if (g->graph) + spa_hook_remove(&g->listener); + if (g->handle) + spa_plugin_loader_unload(impl->loader, g->handle); + spa_zero(*g); + spa_list_append(&impl->free_graphs, &g->link); } } +static inline void insert_graph(struct spa_list *graphs, struct filter_graph *pending) +{ + struct filter_graph *g; + + spa_list_for_each(g, graphs, link) { + if (g->order < pending->order) + break; + } + spa_list_append(&g->link, &pending->link); +} + static int load_filter_graph(struct impl *impl, const char *graph, int order) { char qlimit[64]; int res; void *iface; struct spa_handle *new_handle = NULL; - uint32_t i, idx, n_graph; - struct filter_graph *pending, *old_active = NULL; + struct filter_graph *pending, *g, *t; if (impl->props.filter_graph_disabled) return -EPERM; /* find graph spot */ - idx = SPA_ID_INVALID; - n_graph = 0; - for (i = 0; i < MAX_GRAPH; i++) { - pending = &impl->filter_graph[i]; - /* find the first free spot for our new filter */ - if (!pending->active && idx == SPA_ID_INVALID) - idx = i; - /* deactivate an existing filter of the same order */ - if (pending->active) { - if (pending->order == order) - old_active = pending; - else - n_graph++; - } - } - /* we can at most have MAX_GRAPH-1 active filters */ - if (n_graph >= MAX_GRAPH-1) + if (spa_list_is_empty(&impl->free_graphs)) return -ENOSPC; - pending = &impl->filter_graph[idx]; + /* find free graph for our new filter */ + pending = spa_list_first(&impl->free_graphs, struct filter_graph, link); + pending->impl = impl; pending->order = order; + pending->removing = false; + + /* move active graphs with same order to inactive list */ + spa_list_for_each_safe(g, t, &impl->active_graphs, link) { + if (g->order == order) { + g->removing = true; + spa_log_info(impl->log, "removing filter-graph order:%d", order); + } + } if (graph != NULL && graph[0] != '\0') { snprintf(qlimit, sizeof(qlimit), "%u", impl->quantum_limit); @@ -1136,33 +1179,18 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order) /* prepare new filter and swap it */ pending->graph = iface; - res = setup_filter_graph(impl, pending, 0, NULL); - if (res < 0) { - pending->graph = NULL; - goto error; - } - pending->active = true; - spa_log_info(impl->log, "loading filter-graph order:%d in %d active:%d", - order, idx, n_graph + 1); - } else { - pending->active = false; - spa_log_info(impl->log, "removing filter-graph order:%d active:%d", - order, n_graph); - } - if (old_active) - old_active->active = false; - - /* we call this here on the pending_graph so that the n_input/n_output is updated - * before we switch */ - if (pending->active) + pending->handle = new_handle; spa_filter_graph_add_listener(pending->graph, &pending->listener, &graph_events, pending); + spa_list_remove(&pending->link); + insert_graph(&impl->active_graphs, pending); + + spa_log_info(impl->log, "loading filter-graph order:%d", order); + } + res = setup_filter_graphs(impl); spa_loop_invoke(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, true, impl); - if (pending->active) - pending->handle = new_handle; - if (impl->in_filter_props == 0) clean_filter_handles(impl, false); @@ -1747,16 +1775,15 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, } case SPA_PARAM_Props: { - uint32_t i; bool have_graph = false; + struct filter_graph *g, *t; this->filter_props_count = 0; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &this->filter_graph[i]; - if (!g->active) + + spa_list_for_each_safe(g, t, &this->active_graphs, link) { + if (g->removing) continue; have_graph = true; - this->in_filter_props++; spa_filter_graph_set_props(g->graph, SPA_DIRECTION_INPUT, param); @@ -1963,12 +1990,6 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi dst_mask |= 1ULL << (p < 64 ? p : 0); } - /* if we needed a channel conversion but we already did one before this - * stage, assume we are now with the dst layout */ - if ((out->format.info.raw.channels != in->format.info.raw.channels) && - channels != in->format.info.raw.channels) - src_mask = dst_mask; - spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), src_chan, position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), @@ -2165,8 +2186,9 @@ static void free_tmp(struct impl *this) } } -static int ensure_tmp(struct impl *this, uint32_t maxsize, uint32_t maxports) +static int ensure_tmp(struct impl *this) { + uint32_t maxsize = this->maxsize, maxports = this->maxports; if (maxsize > this->scratch_size || maxports > this->scratch_ports) { float *empty, *scratch, *tmp[2]; uint32_t i; @@ -2262,7 +2284,7 @@ static inline bool resample_is_passthrough(struct impl *this) static int setup_convert(struct impl *this) { struct dir *in, *out; - uint32_t i, rate, maxsize, maxports, duration, channels, *position; + uint32_t i, rate, duration; struct port *p; int res; @@ -2309,39 +2331,25 @@ static int setup_convert(struct impl *this) if (in->format.info.raw.channels == 0 || out->format.info.raw.channels == 0) return -EINVAL; - channels = in->format.info.raw.channels; - position = in->format.info.raw.position; - maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); - if ((res = setup_in_convert(this)) < 0) return res; - for (i = 0; i < this->n_graph; i++) { - struct filter_graph *g = &this->filter_graph[this->graph_index[i]]; - if (!g->active) - continue; - if ((res = setup_filter_graph(this, g, channels, position)) < 0) - return res; - channels = g->n_outputs; - position = g->outputs_position; - maxports = SPA_MAX(maxports, channels); - } - if ((res = setup_channelmix(this, channels, position)) < 0) + if ((res = setup_filter_graphs(this)) < 0) return res; if ((res = setup_resample(this)) < 0) return res; if ((res = setup_out_convert(this)) < 0) return res; - maxsize = this->quantum_limit * sizeof(float); + this->maxsize = this->quantum_limit * sizeof(float); for (i = 0; i < in->n_ports; i++) { p = GET_IN_PORT(this, i); - maxsize = SPA_MAX(maxsize, p->maxsize); + this->maxsize = SPA_MAX(this->maxsize, p->maxsize); } for (i = 0; i < out->n_ports; i++) { p = GET_OUT_PORT(this, i); - maxsize = SPA_MAX(maxsize, p->maxsize); + this->maxsize = SPA_MAX(this->maxsize, p->maxsize); } - if ((res = ensure_tmp(this, maxsize, maxports)) < 0) + if ((res = ensure_tmp(this)) < 0) return res; resample_update_rate_match(this, resample_is_passthrough(this), duration, 0); @@ -2356,11 +2364,12 @@ static int setup_convert(struct impl *this) static void reset_node(struct impl *this) { - uint32_t i; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &this->filter_graph[i]; + struct filter_graph *g; + + spa_list_for_each(g, &this->active_graphs, link) { if (g->graph) spa_filter_graph_deactivate(g->graph); + g->setup = false; } if (this->resample.reset) resample_reset(&this->resample); @@ -3537,7 +3546,7 @@ static void recalc_stages(struct impl *this, struct stage_context *ctx) } if (!filter_passthrough) { for (i = 0; i < this->n_graph; i++) { - struct filter_graph *fg = &this->filter_graph[this->graph_index[i]]; + struct filter_graph *fg = this->filter_graph[i]; if (mix_passthrough && resample_passthrough && out_passthrough && i + 1 == this->n_graph) @@ -4069,6 +4078,13 @@ impl_init(const struct spa_handle_factory *factory, props_reset(&this->props); filter_graph_disabled = this->props.filter_graph_disabled; + spa_list_init(&this->active_graphs); + spa_list_init(&this->free_graphs); + for (i = 0; i < MAX_GRAPH; i++) { + struct filter_graph *g = &this->graphs[i]; + g->impl = this; + spa_list_append(&this->free_graphs, &g->link); + } this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; this->rate_limit.burst = 1;