Merge branch 'mini-eq/export-notify-controls' into 'master'

filter-chain: export notify controls

See merge request pipewire/pipewire!2823
This commit is contained in:
bhack 2026-05-27 10:37:11 +00:00
commit b8117cb7a7
6 changed files with 1618 additions and 11 deletions

View file

@ -44,6 +44,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.filter-graph");
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
#define DEFAULT_RATE 48000
#define DEFAULT_EXPORT_INTERVAL_MS 33u
#define spa_filter_graph_emit(hooks,method,version,...) \
spa_hook_list_call_simple(hooks, struct spa_filter_graph_events, \
@ -189,12 +190,19 @@ struct graph {
uint32_t n_control;
struct port **control_port;
uint32_t n_notify;
struct port **notify_port;
uint32_t n_input_names;
char **input_names;
uint32_t n_output_names;
char **output_names;
uint32_t n_export_control_names;
char **export_control_names;
uint32_t notify_interval_ms;
struct volume volume[2];
uint32_t default_inputs;
@ -267,7 +275,8 @@ static void emit_filter_graph_info(struct impl *impl, bool full)
if (impl->info.change_mask || full) {
char n_inputs[64], n_outputs[64], latency[64];
char n_default_inputs[64], n_default_outputs[64];
struct spa_dict_item items[6];
char export_interval[64];
struct spa_dict_item items[8];
struct spa_dict dict = SPA_DICT(items, 0);
char in_pos[MAX_CHANNELS * 8];
char out_pos[MAX_CHANNELS * 8];
@ -293,6 +302,11 @@ static void emit_filter_graph_info(struct impl *impl, bool full)
graph->n_outputs_position, graph->outputs_position);
items[dict.n_items++] = SPA_DICT_ITEM("outputs.audio.position", out_pos);
}
if (graph->n_export_control_names > 0) {
snprintf(export_interval, sizeof(export_interval), "%u",
graph->notify_interval_ms);
items[dict.n_items++] = SPA_DICT_ITEM("export.interval.ms", export_interval);
}
items[dict.n_items++] = SPA_DICT_ITEM("latency",
spa_dtoa(latency, sizeof(latency),
(graph->min_latency + graph->max_latency) / 2.0f));
@ -457,6 +471,18 @@ static void get_ranges(struct impl *impl, struct spa_fga_port *p,
}
}
static void port_get_name(struct port *port, char *name, size_t size)
{
struct node *node = port->node;
const struct spa_fga_descriptor *d = node->desc->desc;
struct spa_fga_port *p = &d->ports[port->p];
if (node->name[0] != '\0')
snprintf(name, size, "%s:%s", node->name, p->name);
else
snprintf(name, size, "%s", p->name);
}
static int impl_enum_prop_info(void *object, uint32_t idx, struct spa_pod_builder *b,
struct spa_pod **param)
{
@ -483,10 +509,7 @@ static int impl_enum_prop_info(void *object, uint32_t idx, struct spa_pod_builde
get_ranges(impl, p, &def, &min, &max);
if (node->name[0] != '\0')
snprintf(name, sizeof(name), "%s:%s", node->name, p->name);
else
snprintf(name, sizeof(name), "%s", p->name);
port_get_name(port, name, sizeof(name));
spa_pod_builder_push_object(b, &f[0],
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo);
@ -558,10 +581,7 @@ static int impl_get_props(void *object, struct spa_pod_builder *b, struct spa_po
struct spa_fga_port *p = &d->ports[port->p];
float v, min, max;
if (node->name[0] != '\0')
snprintf(name, sizeof(name), "%s:%s", node->name, p->name);
else
snprintf(name, sizeof(name), "%s", p->name);
port_get_name(port, name, sizeof(name));
if (port->control_initialized)
v = port->control_current;
@ -586,6 +606,119 @@ static int impl_get_props(void *object, struct spa_pod_builder *b, struct spa_po
return 1;
}
static int impl_enum_notify_info(void *object, uint32_t idx, struct spa_pod_builder *b,
struct spa_pod **param)
{
struct impl *impl = object;
struct graph *graph = &impl->graph;
struct spa_pod *pod;
struct spa_pod_frame f[2];
struct port *port;
struct node *node;
const struct spa_fga_descriptor *d;
struct spa_fga_port *p;
float def, min, max;
char name[512];
if (idx >= graph->n_notify)
return 0;
port = graph->notify_port[idx];
node = port->node;
d = node->desc->desc;
p = &d->ports[port->p];
get_ranges(impl, p, &def, &min, &max);
port_get_name(port, name, sizeof(name));
spa_pod_builder_push_object(b, &f[0],
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo);
spa_pod_builder_add (b,
SPA_PROP_INFO_name, SPA_POD_String(name),
0);
spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0);
if (p->hint & SPA_FGA_HINT_BOOLEAN) {
if (min == max) {
spa_pod_builder_bool(b, def <= 0.0f ? false : true);
} else {
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0);
spa_pod_builder_bool(b, def <= 0.0f ? false : true);
spa_pod_builder_bool(b, false);
spa_pod_builder_bool(b, true);
spa_pod_builder_pop(b, &f[1]);
}
} else if (p->hint & SPA_FGA_HINT_INTEGER) {
if (min == max) {
spa_pod_builder_int(b, (int32_t)def);
} else {
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0);
spa_pod_builder_int(b, (int32_t)def);
spa_pod_builder_int(b, (int32_t)min);
spa_pod_builder_int(b, (int32_t)max);
spa_pod_builder_pop(b, &f[1]);
}
} else {
if (min == max) {
spa_pod_builder_float(b, def);
} else {
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0);
spa_pod_builder_float(b, def);
spa_pod_builder_float(b, min);
spa_pod_builder_float(b, max);
spa_pod_builder_pop(b, &f[1]);
}
}
spa_pod_builder_prop(b, SPA_PROP_INFO_params, 0);
spa_pod_builder_bool(b, true);
pod = spa_pod_builder_pop(b, &f[0]);
if (pod == NULL)
return -ENOSPC;
if (param)
*param = pod;
return 1;
}
static int impl_get_notify(void *object, struct spa_pod_builder *b, struct spa_pod **props)
{
struct impl *impl = object;
struct graph *graph = &impl->graph;
struct spa_pod_frame f[2];
struct spa_pod *res;
uint32_t i;
char name[512];
spa_pod_builder_push_object(b, &f[0],
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
spa_pod_builder_prop(b, SPA_PROP_params, 0);
spa_pod_builder_push_struct(b, &f[1]);
for (i = 0; i < graph->n_notify; i++) {
struct port *port = graph->notify_port[i];
const struct spa_fga_descriptor *d = port->node->desc->desc;
struct spa_fga_port *p = &d->ports[port->p];
float v = port->control_data[0];
port_get_name(port, name, sizeof(name));
spa_pod_builder_string(b, name);
if (p->hint & SPA_FGA_HINT_BOOLEAN) {
spa_pod_builder_bool(b, v <= 0.0f ? false : true);
} else if (p->hint & SPA_FGA_HINT_INTEGER) {
spa_pod_builder_int(b, (int32_t)v);
} else {
spa_pod_builder_float(b, v);
}
}
spa_pod_builder_pop(b, &f[1]);
res = spa_pod_builder_pop(b, &f[0]);
if (res == NULL)
return -ENOSPC;
if (props)
*props = res;
return 1;
}
static int port_id_set_control_value(struct port *port, uint32_t id, float value)
{
struct node *node = port->node;
@ -2161,6 +2294,34 @@ static int setup_graph_controls(struct graph *graph)
return 0;
}
static int setup_graph_notify(struct graph *graph)
{
struct impl *impl = graph->impl;
struct node *first;
uint32_t i;
if (graph->n_export_control_names == 0)
return 0;
graph->notify_port = calloc(graph->n_export_control_names, sizeof(struct port *));
if (graph->notify_port == NULL)
return -errno;
first = spa_list_first(&graph->node_list, struct node, link);
for (i = 0; i < graph->n_export_control_names; i++) {
const char *name = graph->export_control_names[i];
struct port *port;
port = find_port(first, name, SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL);
if (port == NULL) {
spa_log_error(impl->log, "export control port %s not found", name);
return -ENOENT;
}
graph->notify_port[graph->n_notify++] = port;
}
return 0;
}
/**
* filter.graph = {
* nodes = [
@ -2186,6 +2347,7 @@ static int load_graph(struct graph *graph, const struct spa_dict *props)
struct spa_json inputs, outputs, *pinputs = NULL, *poutputs = NULL;
struct spa_json ivolumes, ovolumes, *pivolumes = NULL, *povolumes = NULL;
struct spa_json nodes, *pnodes = NULL, links, *plinks = NULL;
struct spa_json export_controls, *pexport_controls = NULL;
struct node *first, *last;
const char *json, *val;
char key[256];
@ -2193,6 +2355,7 @@ static int load_graph(struct graph *graph, const struct spa_dict *props)
spa_list_init(&graph->node_list);
spa_list_init(&graph->link_list);
graph->notify_interval_ms = DEFAULT_EXPORT_INTERVAL_MS;
if ((json = spa_dict_lookup(props, "filter.graph")) == NULL) {
spa_log_error(impl->log, "missing filter.graph property");
@ -2290,6 +2453,21 @@ static int load_graph(struct graph *graph, const struct spa_dict *props)
}
spa_json_enter(&it[0], &ovolumes);
povolumes = &ovolumes;
}
else if (spa_streq("export.controls", key)) {
if (!spa_json_is_array(val, len)) {
spa_log_error(impl->log, "%s expects an array", key);
return -EINVAL;
}
spa_json_enter(&it[0], &export_controls);
pexport_controls = &export_controls;
}
else if (spa_streq("export.interval.ms", key)) {
if (spa_json_parse_int(val, len, &res) <= 0 || res < 0) {
spa_log_error(impl->log, "%s expects a non-negative integer", key);
return -EINVAL;
}
graph->notify_interval_ms = res;
} else {
spa_log_warn(impl->log, "unexpected graph key '%s'", key);
}
@ -2350,8 +2528,23 @@ static int load_graph(struct graph *graph, const struct spa_dict *props)
graph->n_output_names++;
}
}
if (pexport_controls != NULL) {
graph->n_export_control_names = count_array(pexport_controls);
graph->export_control_names = calloc(graph->n_export_control_names, sizeof(char *));
if (graph->export_control_names == NULL)
return -ENOMEM;
graph->n_export_control_names = 0;
while (spa_json_get_string(pexport_controls, key, sizeof(key)) > 0) {
graph->export_control_names[graph->n_export_control_names] = strdup(key);
if (graph->export_control_names[graph->n_export_control_names] == NULL)
return -ENOMEM;
graph->n_export_control_names++;
}
}
if ((res = setup_graph_controls(graph)) < 0)
return res;
if ((res = setup_graph_notify(graph)) < 0)
return res;
first = spa_list_first(&graph->node_list, struct node, link);
last = spa_list_last(&graph->node_list, struct node, link);
@ -2391,8 +2584,13 @@ static void graph_free(struct graph *graph)
for (i = 0; i < graph->n_output_names; i++)
free(graph->output_names[i]);
free(graph->output_names);
for (i = 0; i < graph->n_export_control_names; i++)
free(graph->export_control_names[i]);
free(graph->export_control_names);
free(graph->control_port);
graph->control_port = NULL;
free(graph->notify_port);
graph->notify_port = NULL;
}
static const struct spa_filter_graph_methods impl_filter_graph = {
@ -2405,6 +2603,8 @@ static const struct spa_filter_graph_methods impl_filter_graph = {
.deactivate = impl_deactivate,
.reset = impl_reset,
.process = impl_process,
.enum_notify_info = impl_enum_notify_info,
.get_notify = impl_get_notify,
};
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)