filter-chain: add custom volume support

Add capture.volumes and playback.volumes to control the graph controls
that handle the capture and playback volume respectively.

See #3434
This commit is contained in:
Wim Taymans 2023-09-27 18:00:33 +02:00
parent be1a60c5f9
commit eca4822311

View file

@ -82,6 +82,12 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
* ] * ]
* inputs = [ <portname> ... ] * inputs = [ <portname> ... ]
* outputs = [ <portname> ... ] * outputs = [ <portname> ... ]
* capture.volumes = [
* { control = <portname> min = <value> max = <value> } ...
* ]
* playback.volumes = [
* { control = <portname> min = <value> max = <value> } ...
* ]
* } * }
*\endcode *\endcode
* *
@ -137,6 +143,15 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
* graph will then be duplicated as many times to match the number of input/output * graph will then be duplicated as many times to match the number of input/output
* channels of the streams. * channels of the streams.
* *
* ### Volumes
*
* Normally the volume of the sink/source is handled by the stream software volume.
* With the capture.volumes and playback.volumes properties this can be handled
* by a control port in the graph instead.
*
* The min and max values (defaults 0.0 and 1.0) respectively can be used to scale
* and translate the volume min and max values.
*
* ## Builtin filters * ## Builtin filters
* *
* There are some useful builtin filters available. You select them with the label * There are some useful builtin filters available. You select them with the label
@ -625,6 +640,17 @@ struct graph_hndl {
void **hndl; void **hndl;
}; };
struct volume {
bool mute;
uint32_t n_volumes;
float volumes[SPA_AUDIO_MAX_CHANNELS];
float min_volumes[SPA_AUDIO_MAX_CHANNELS];
float max_volumes[SPA_AUDIO_MAX_CHANNELS];
uint32_t n_ports;
struct port *ports[SPA_AUDIO_MAX_CHANNELS];
};
struct graph { struct graph {
struct impl *impl; struct impl *impl;
@ -643,6 +669,9 @@ struct graph {
uint32_t n_control; uint32_t n_control;
struct port **control_port; struct port **control_port;
struct volume capture_volume;
struct volume playback_volume;
unsigned instantiated:1; unsigned instantiated:1;
}; };
@ -1092,17 +1121,103 @@ static void update_props_param(struct impl *impl)
spa_pod_dynamic_builder_clean(&b); spa_pod_dynamic_builder_clean(&b);
} }
static void param_props_changed(struct impl *impl, const struct spa_pod *param) static int sync_volume(struct graph *graph, struct volume *vol)
{
uint32_t i;
int res = 0;
if (vol->n_ports == 0)
return 0;
for (i = 0; i < vol->n_volumes; i++) {
struct port *p = vol->ports[i % vol->n_ports];
float v = vol->mute ? 0.0f : vol->volumes[i];
res += port_set_control_value(p, &v, i % MAX_HNDL);
}
return res;
}
static int parse_channel_volumes(struct graph *graph, struct volume *vol,
const struct spa_pod *pod)
{
uint32_t i, n_vols;
float vols[SPA_AUDIO_MAX_CHANNELS];
int res = 0;
if ((n_vols = spa_pod_copy_array(pod, SPA_TYPE_Float, vols,
SPA_AUDIO_MAX_CHANNELS)) == 0)
return 0;
if (vol->n_volumes != n_vols)
res++;
vol->n_volumes = n_vols;
for (i = 0; i < n_vols; i++) {
float v = vols[i];
if (v != vol->volumes[i]) {
vol->volumes[i] = v;
res++;
}
}
return res;
}
static void param_props_changed(struct impl *impl, const struct spa_pod *param,
bool capture)
{ {
struct spa_pod_object *obj = (struct spa_pod_object *) param; struct spa_pod_object *obj = (struct spa_pod_object *) param;
struct spa_pod_frame f[1];
const struct spa_pod_prop *prop; const struct spa_pod_prop *prop;
struct graph *graph = &impl->graph; struct graph *graph = &impl->graph;
int changed = 0; int changed = 0;
char buf[1024];
struct spa_pod_dynamic_builder b;
struct volume *vol = capture ? &graph->capture_volume :
&graph->playback_volume;
bool do_param = false;
spa_pod_dynamic_builder_init(&b, buf, sizeof(buf), 1024);
spa_pod_builder_push_object(&b.b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
SPA_POD_OBJECT_FOREACH(obj, prop) { SPA_POD_OBJECT_FOREACH(obj, prop) {
if (prop->key == SPA_PROP_params) switch (prop->key) {
case SPA_PROP_params:
changed += parse_params(graph, &prop->value); changed += parse_params(graph, &prop->value);
spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop));
break;
case SPA_PROP_mute:
{
bool mute;
if (spa_pod_get_bool(&prop->value, &mute) == 0) {
vol->mute = mute;
do_param = true;
}
spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop));
break;
}
case SPA_PROP_channelVolumes:
{
if (parse_channel_volumes(graph, vol, &prop->value) > 0)
do_param = true;
break;
}
case SPA_PROP_softVolumes:
case SPA_PROP_softMute:
break;
default:
spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop));
break;
}
} }
param = spa_pod_builder_pop(&b.b, &f[0]);
if (do_param) {
sync_volume(graph, vol);
pw_stream_set_param(capture ? impl->capture :
impl->playback, SPA_PARAM_Props, param);
}
spa_pod_dynamic_builder_clean(&b);
if (changed > 0) { if (changed > 0) {
struct node *node; struct node *node;
@ -1111,6 +1226,7 @@ static void param_props_changed(struct impl *impl, const struct spa_pod *param)
update_props_param(impl); update_props_param(impl);
} }
} }
static void param_latency_changed(struct impl *impl, const struct spa_pod *param) static void param_latency_changed(struct impl *impl, const struct spa_pod *param)
@ -1206,7 +1322,8 @@ static void io_changed(void *data, uint32_t id, void *area, uint32_t size)
} }
} }
static void param_changed(void *data, uint32_t id, const struct spa_pod *param) static void param_changed(void *data, uint32_t id, const struct spa_pod *param,
bool capture)
{ {
struct impl *impl = data; struct impl *impl = data;
struct graph *graph = &impl->graph; struct graph *graph = &impl->graph;
@ -1229,7 +1346,7 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param)
} }
case SPA_PARAM_Props: case SPA_PARAM_Props:
if (param != NULL) if (param != NULL)
param_props_changed(impl, param); param_props_changed(impl, param, capture);
break; break;
case SPA_PARAM_Latency: case SPA_PARAM_Latency:
param_latency_changed(impl, param); param_latency_changed(impl, param);
@ -1241,8 +1358,13 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param)
return; return;
error: error:
pw_stream_set_error(impl->capture, res, "can't start graph: %s", pw_stream_set_error(capture ? impl->capture : impl->playback,
spa_strerror(res)); res, "can't start graph: %s", spa_strerror(res));
}
static void capture_param_changed(void *data, uint32_t id, const struct spa_pod *param)
{
param_changed(data, id, param, true);
} }
static const struct pw_stream_events in_stream_events = { static const struct pw_stream_events in_stream_events = {
@ -1251,9 +1373,14 @@ static const struct pw_stream_events in_stream_events = {
.process = capture_process, .process = capture_process,
.io_changed = io_changed, .io_changed = io_changed,
.state_changed = state_changed, .state_changed = state_changed,
.param_changed = param_changed .param_changed = capture_param_changed
}; };
static void playback_param_changed(void *data, uint32_t id, const struct spa_pod *param)
{
param_changed(data, id, param, false);
}
static void playback_destroy(void *d) static void playback_destroy(void *d)
{ {
struct impl *impl = d; struct impl *impl = d;
@ -1267,7 +1394,7 @@ static const struct pw_stream_events out_stream_events = {
.process = playback_process, .process = playback_process,
.io_changed = io_changed, .io_changed = io_changed,
.state_changed = state_changed, .state_changed = state_changed,
.param_changed = param_changed, .param_changed = playback_param_changed,
}; };
static int setup_streams(struct impl *impl) static int setup_streams(struct impl *impl)
@ -1794,6 +1921,75 @@ static void link_free(struct link *link)
free(link); free(link);
} }
/**
* {
* control = [name:][portname]
* min = ...
* max = ...
* }
*/
static int parse_volume(struct graph *graph, struct spa_json *json, bool capture)
{
char key[256];
char control[256] = "";
float min = 0.0f, max = 1.0f;
const char *val;
struct node *def_control;
struct port *port;
struct volume *vol = capture ? &graph->capture_volume :
&graph->playback_volume;
if (spa_list_is_empty(&graph->node_list)) {
pw_log_error("can't set volume in graph without nodes");
return -EINVAL;
}
while (spa_json_get_string(json, key, sizeof(key)) > 0) {
if (spa_streq(key, "control")) {
if (spa_json_get_string(json, control, sizeof(control)) <= 0) {
pw_log_error("control expects a string");
return -EINVAL;
}
}
else if (spa_streq(key, "min")) {
if (spa_json_get_float(json, &min) <= 0) {
pw_log_error("min expects a float");
return -EINVAL;
}
}
else if (spa_streq(key, "max")) {
if (spa_json_get_float(json, &max) <= 0) {
pw_log_error("max expects a float");
return -EINVAL;
}
}
else if (spa_json_next(json, &val) < 0)
break;
}
if (capture)
def_control = spa_list_first(&graph->node_list, struct node, link);
else
def_control = spa_list_last(&graph->node_list, struct node, link);
port = find_port(def_control, control, FC_PORT_INPUT | FC_PORT_CONTROL);
if (port == NULL) {
pw_log_error("unknown control port %s", control);
return -ENOENT;
}
if (vol->n_ports >= SPA_AUDIO_MAX_CHANNELS) {
pw_log_error("too many volume controls");
return -ENOENT;
}
pw_log_info("volume %d: %s:%s %f %f", vol->n_ports, port->node->name,
port->node->desc->desc->ports[port->p].name, min, max);
vol->ports[vol->n_ports] = port;
vol->min_volumes[vol->n_ports] = min;
vol->max_volumes[vol->n_ports] = max;
vol->n_ports++;
return 0;
}
/** /**
* type = ladspa * type = ladspa
* name = rev * name = rev
@ -2366,6 +2562,7 @@ static int load_graph(struct graph *graph, struct pw_properties *props)
{ {
struct spa_json it[3]; struct spa_json it[3];
struct spa_json inputs, outputs, *pinputs = NULL, *poutputs = NULL; struct spa_json inputs, outputs, *pinputs = NULL, *poutputs = NULL;
struct spa_json cvolumes, pvolumes, *pcvolumes = NULL, *ppvolumes = NULL;
struct spa_json nodes, *pnodes = NULL, links, *plinks = NULL; struct spa_json nodes, *pnodes = NULL, links, *plinks = NULL;
const char *json, *val; const char *json, *val;
char key[256]; char key[256];
@ -2413,6 +2610,20 @@ static int load_graph(struct graph *graph, struct pw_properties *props)
return -EINVAL; return -EINVAL;
} }
poutputs = &outputs; poutputs = &outputs;
}
else if (spa_streq("capture.volumes", key)) {
if (spa_json_enter_array(&it[1], &cvolumes) <= 0) {
pw_log_error("capture.volumes expects an array");
return -EINVAL;
}
pcvolumes = &cvolumes;
}
else if (spa_streq("playback.volumes", key)) {
if (spa_json_enter_array(&it[1], &pvolumes) <= 0) {
pw_log_error("playback.volumes expects an array");
return -EINVAL;
}
ppvolumes = &pvolumes;
} else if (spa_json_next(&it[1], &val) < 0) } else if (spa_json_next(&it[1], &val) < 0)
break; break;
} }
@ -2430,6 +2641,18 @@ static int load_graph(struct graph *graph, struct pw_properties *props)
return res; return res;
} }
} }
if (pcvolumes != NULL) {
while (spa_json_enter_object(pcvolumes, &it[2]) > 0) {
if ((res = parse_volume(graph, &it[2], true)) < 0)
return res;
}
}
if (ppvolumes != NULL) {
while (spa_json_enter_object(ppvolumes, &it[2]) > 0) {
if ((res = parse_volume(graph, &it[2], false)) < 0)
return res;
}
}
return setup_graph(graph, pinputs, poutputs); return setup_graph(graph, pinputs, poutputs);
} }