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.
This commit is contained in:
Wim Taymans 2025-03-18 12:39:15 +01:00
parent 9b2b420cf5
commit de2ab7cac9
3 changed files with 139 additions and 39 deletions

View file

@ -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;

View file

@ -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);