mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-09 13:30:06 -05:00
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:
parent
be1a60c5f9
commit
eca4822311
1 changed files with 231 additions and 8 deletions
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue