From de2ab7cac9155848debff68f409cd709ce0493d4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 18 Mar 2025 12:39:15 +0100 Subject: [PATCH] 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 c691ad676..8df8b528a 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; }; @@ -961,10 +963,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); @@ -996,22 +1016,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))); @@ -1109,10 +1136,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); @@ -1908,7 +1937,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]; @@ -1921,7 +1950,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++) { @@ -1936,7 +1965,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); @@ -2228,7 +2257,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; @@ -2276,6 +2305,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; @@ -2283,11 +2314,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; @@ -2303,7 +2336,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 75e3a00f6..9c5edc116 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; @@ -1457,19 +1502,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; } } @@ -1477,7 +1522,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 */ @@ -1666,24 +1711,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; } @@ -1699,11 +1748,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); @@ -1949,6 +1998,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);