diff --git a/pipewire-pulseaudio/src/context.c b/pipewire-pulseaudio/src/context.c index 8481bb143..6c30b51bf 100644 --- a/pipewire-pulseaudio/src/context.c +++ b/pipewire-pulseaudio/src/context.c @@ -338,11 +338,70 @@ static void device_event_info(void *object, const struct pw_device_info *info) global_sync(g); } +static void parse_props(struct global *g, const struct spa_pod *param, bool hw) +{ + struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *) param; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_volume: + spa_pod_get_float(&prop->value, &g->node_info.volume); + SPA_FLAG_UPDATE(g->node_info.flags, NODE_FLAG_HW_VOLUME, hw); + break; + case SPA_PROP_mute: + spa_pod_get_bool(&prop->value, &g->node_info.mute); + SPA_FLAG_UPDATE(g->node_info.flags, NODE_FLAG_HW_MUTE, hw); + break; + case SPA_PROP_channelVolumes: + { + uint32_t n_vals; + + n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + g->node_info.channel_volumes, SPA_AUDIO_MAX_CHANNELS); + + if (n_vals != g->node_info.n_channel_volumes) { + pw_log_debug("channel change %d->%d, trigger remove", + g->node_info.n_channel_volumes, n_vals); + emit_event(g->context, g, PA_SUBSCRIPTION_EVENT_REMOVE); + g->node_info.n_channel_volumes = n_vals; + /* mark as init, this will emit the NEW event when the + * params are updated */ + g->init = true; + } + SPA_FLAG_UPDATE(g->node_info.flags, NODE_FLAG_HW_VOLUME, hw); + break; + } + default: + break; + } + } +} + +static struct global *find_node_for_route(pa_context *c, struct global *card, uint32_t device) +{ + struct global *n; + spa_list_for_each(n, &c->globals, link) { + if (strcmp(n->type, PW_TYPE_INTERFACE_Node) != 0) + continue; + pw_log_info("%d/%d %d/%d", + n->node_info.device_id, card->id, + n->node_info.profile_device_id, device); + if (n->node_info.device_id != card->id) + continue; + if (n->node_info.profile_device_id != device) + continue; + return n; + } + return NULL; +} + static void device_event_param(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { struct global *g = object; + pa_context *c = g->context; switch (id) { case SPA_PARAM_EnumProfile: @@ -410,16 +469,26 @@ static void device_event_param(void *object, int seq, } case SPA_PARAM_Route: { - uint32_t id; + uint32_t id, device; enum spa_direction direction; + struct spa_pod *props = NULL; + struct global *ng; if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamRoute, NULL, SPA_PARAM_ROUTE_index, SPA_POD_Int(&id), - SPA_PARAM_ROUTE_direction, SPA_POD_Id(&direction)) < 0) { + SPA_PARAM_ROUTE_direction, SPA_POD_Id(&direction), + SPA_PARAM_ROUTE_device, SPA_POD_Int(&device), + SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&props)) < 0) { pw_log_warn("device %d: can't parse route", g->id); return; } + ng = find_node_for_route(c, g, device); + + if (props && ng) { + parse_props(ng, props, true); + } + if (direction == SPA_DIRECTION_OUTPUT) g->card_info.active_port_output = id; else @@ -685,10 +754,16 @@ struct global_info device_info = { static void node_event_info(void *object, const struct pw_node_info *info) { struct global *g = object; + const char *str; uint32_t i; pw_log_debug("update %d %"PRIu64, g->id, info->change_mask); - g->info = pw_node_info_update(g->info, info); + info = g->info = pw_node_info_update(g->info, info); + + if (info->props && (str = spa_dict_lookup(info->props, "card.profile.device"))) + g->node_info.profile_device_id = atoi(str); + else + g->node_info.profile_device_id = SPA_ID_INVALID; if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS && !g->subscribed) { uint32_t subscribed[32], n_subscribed = 0; @@ -712,43 +787,6 @@ static void node_event_info(void *object, const struct pw_node_info *info) global_sync(g); } -static void parse_props(struct global *g, const struct spa_pod *param) -{ - struct spa_pod_prop *prop; - struct spa_pod_object *obj = (struct spa_pod_object *) param; - - SPA_POD_OBJECT_FOREACH(obj, prop) { - switch (prop->key) { - case SPA_PROP_volume: - spa_pod_get_float(&prop->value, &g->node_info.volume); - break; - case SPA_PROP_mute: - spa_pod_get_bool(&prop->value, &g->node_info.mute); - break; - case SPA_PROP_channelVolumes: - { - uint32_t n_vals; - - n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - g->node_info.channel_volumes, SPA_AUDIO_MAX_CHANNELS); - - if (n_vals != g->node_info.n_channel_volumes) { - pw_log_debug("channel change %d->%d, trigger remove", - g->node_info.n_channel_volumes, n_vals); - emit_event(g->context, g, PA_SUBSCRIPTION_EVENT_REMOVE); - g->node_info.n_channel_volumes = n_vals; - /* mark as init, this will emit the NEW event when the - * params are updated */ - g->init = true; - } - break; - } - default: - break; - } - } -} - static void node_event_param(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) @@ -758,7 +796,8 @@ static void node_event_param(void *object, int seq, switch (id) { case SPA_PARAM_Props: - parse_props(g, param); + if (!SPA_FLAG_IS_SET(g->node_info.flags, NODE_FLAG_HW_VOLUME | NODE_FLAG_HW_MUTE)) + parse_props(g, param, false); break; default: break; @@ -933,8 +972,10 @@ static int set_mask(pa_context *c, struct global *g) g->mask = PA_SUBSCRIPTION_MASK_CARD; g->event = PA_SUBSCRIPTION_EVENT_CARD; ginfo = &device_info; - spa_list_init(&g->card_info.profiles); - spa_list_init(&g->card_info.ports); + spa_list_init(&g->card_info.profiles); + spa_list_init(&g->card_info.ports); + g->card_info.active_port_output = SPA_ID_INVALID; + g->card_info.active_port_input = SPA_ID_INVALID; } else if (strcmp(g->type, PW_TYPE_INTERFACE_Node) == 0) { if (g->props == NULL) return 0; @@ -971,8 +1012,13 @@ static int set_mask(pa_context *c, struct global *g) if ((str = pw_properties_get(g->props, PW_KEY_CLIENT_ID)) != NULL) g->node_info.client_id = atoi(str); + else + g->node_info.client_id = SPA_ID_INVALID; + if ((str = pw_properties_get(g->props, PW_KEY_DEVICE_ID)) != NULL) g->node_info.device_id = atoi(str); + else + g->node_info.device_id = SPA_ID_INVALID; ginfo = &node_info; g->node_info.volume = 1.0; diff --git a/pipewire-pulseaudio/src/internal.h b/pipewire-pulseaudio/src/internal.h index d66966e87..42565b3f1 100644 --- a/pipewire-pulseaudio/src/internal.h +++ b/pipewire-pulseaudio/src/internal.h @@ -262,13 +262,17 @@ struct global { } link_info; /* for sink/source */ struct { - uint32_t client_id; + uint32_t client_id; /* if of owner client */ uint32_t monitor; +#define NODE_FLAG_HW_VOLUME (1 << 0) +#define NODE_FLAG_HW_MUTE (1 << 4) + uint32_t flags; float volume; bool mute; uint32_t n_channel_volumes; float channel_volumes[SPA_AUDIO_MAX_CHANNELS]; - uint32_t device_id; + uint32_t device_id; /* id of device (card) */ + uint32_t profile_device_id; /* id in profile */ } node_info; struct { uint32_t node_id; diff --git a/pipewire-pulseaudio/src/introspect.c b/pipewire-pulseaudio/src/introspect.c index 099401504..a29792c87 100644 --- a/pipewire-pulseaudio/src/introspect.c +++ b/pipewire-pulseaudio/src/introspect.c @@ -141,18 +141,18 @@ static void sink_callback(pa_context *c, struct global *g, struct sink_data *d) i.latency = 0; i.driver = "PipeWire"; i.flags = PA_SINK_HARDWARE | - PA_SINK_HW_VOLUME_CTRL | PA_SINK_HW_MUTE_CTRL | PA_SINK_LATENCY | PA_SINK_DYNAMIC_LATENCY | PA_SINK_DECIBEL_VOLUME; + if (SPA_FLAG_IS_SET(g->node_info.flags, NODE_FLAG_HW_VOLUME)) + i.flags |= PA_SINK_HW_VOLUME_CTRL; + if (SPA_FLAG_IS_SET(g->node_info.flags, NODE_FLAG_HW_MUTE)) + i.flags |= PA_SINK_HW_MUTE_CTRL; i.proplist = pa_proplist_new_dict(info->props); i.configured_latency = 0; i.base_volume = PA_VOLUME_NORM; i.state = node_state_to_sink(info->state); i.n_volume_steps = PA_VOLUME_NORM+1; - if (info->props && (str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID))) - i.card = atoi(str); - else - i.card = PA_INVALID_INDEX; + i.card = g->node_info.device_id; i.n_ports = 0; i.ports = NULL; i.active_port = NULL; @@ -384,34 +384,16 @@ static int set_node_volume(pa_context *c, struct global *g, const pa_cvolume *vo return 0; } -static int set_device_volume(pa_context *c, struct global *g, const pa_cvolume *volume, bool mute) +static int set_device_volume(pa_context *c, struct global *g, struct global *cg, uint32_t id, + uint32_t device_id, const pa_cvolume *volume, bool mute) { - struct global *cg; - uint32_t id = SPA_ID_INVALID, card_id, device_id; char buf[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); struct spa_pod_frame f[2]; struct spa_pod *param; - struct pw_node_info *info = g->info; uint32_t i, n_channel_volumes; float channel_volumes[SPA_AUDIO_MAX_CHANNELS]; float *vols; - const char *str; - - if (info->props && (str = spa_dict_lookup(info->props, "card.profile.device"))) - device_id = atoi(str); - else - device_id = 0; - - if (info->props && (str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID))) - card_id = atoi(str); - else - card_id = PA_INVALID_INDEX; - - pw_log_info("card:%u device:%u global:%u", card_id, device_id, g->id); - - if ((cg = pa_context_find_global(c, card_id)) == NULL) - return PA_ERR_NOENTITY; if (volume) { for (i = 0; i < volume->channels; i++) @@ -455,6 +437,34 @@ static int set_device_volume(pa_context *c, struct global *g, const pa_cvolume * return 0; } + +static int set_volume(pa_context *c, struct global *g, const pa_cvolume *volume, bool mute, + uint32_t mask) +{ + struct global *cg; + uint32_t id = SPA_ID_INVALID, card_id, device_id; + int res; + + card_id = g->node_info.device_id; + device_id = g->node_info.profile_device_id; + + pw_log_info("card:%u global:%u flags:%08x", card_id, g->id, g->node_info.flags); + + if (SPA_FLAG_IS_SET(g->node_info.flags, NODE_FLAG_HW_VOLUME | NODE_FLAG_HW_MUTE) && + (cg = pa_context_find_global(c, card_id)) != NULL) { + if (mask & PA_SUBSCRIPTION_MASK_SINK) + id = cg->card_info.active_port_output; + else if (mask & PA_SUBSCRIPTION_MASK_SOURCE) + id = cg->card_info.active_port_input; + } + if (id != SPA_ID_INVALID && device_id != SPA_ID_INVALID) { + res = set_device_volume(c, g, cg, id, device_id, volume, mute); + } else { + res = set_node_volume(c, g, volume, mute); + } + return res; +} + struct volume_data { pa_context_success_cb_t cb; uint32_t mask; @@ -482,9 +492,10 @@ static void do_node_volume_mute(pa_operation *o, void *userdata) g = NULL; } if (g) { - error = set_device_volume(c, g, + error = set_volume(c, g, d->have_volume ? &d->volume : NULL, - d->have_volume ? g->node_info.mute : d->mute); + d->have_volume ? g->node_info.mute : d->mute, + d->mask); } else { error = PA_ERR_INVALID; } @@ -651,22 +662,13 @@ static int set_device_route(pa_context *c, struct global *g, const char *port, e uint32_t id = SPA_ID_INVALID, card_id, device_id; char buf[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - struct pw_node_info *info = g->info; - const char *str; - if (info->props && (str = spa_dict_lookup(info->props, "card.profile.device"))) - device_id = atoi(str); - else - device_id = 0; - - if (info->props && (str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID))) - card_id = atoi(str); - else - card_id = PA_INVALID_INDEX; + card_id = g->node_info.device_id; + device_id = g->node_info.profile_device_id; pw_log_info("port \"%s\": card:%u device:%u global:%u", port, card_id, device_id, g->id); - if ((cg = pa_context_find_global(c, card_id)) == NULL) + if ((cg = pa_context_find_global(c, card_id)) == NULL || device_id == SPA_ID_INVALID) return PA_ERR_NOENTITY; spa_list_for_each(p, &cg->card_info.ports, link) { @@ -859,7 +861,11 @@ static void source_callback(pa_context *c, struct global *g, struct source_data } else { i.monitor_of_sink = PA_INVALID_INDEX; i.monitor_of_sink_name = NULL; - flags |= PA_SOURCE_HARDWARE | PA_SOURCE_HW_VOLUME_CTRL | PA_SOURCE_HW_MUTE_CTRL; + flags |= PA_SOURCE_HARDWARE; + if (SPA_FLAG_IS_SET(g->node_info.flags, NODE_FLAG_HW_VOLUME)) + flags |= PA_SINK_HW_VOLUME_CTRL; + if (SPA_FLAG_IS_SET(g->node_info.flags, NODE_FLAG_HW_MUTE)) + flags |= PA_SINK_HW_MUTE_CTRL; } i.latency = 0; i.driver = "PipeWire"; @@ -869,10 +875,7 @@ static void source_callback(pa_context *c, struct global *g, struct source_data i.base_volume = PA_VOLUME_NORM; i.state = node_state_to_source(info->state); i.n_volume_steps = PA_VOLUME_NORM+1; - if (info->props && (str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID))) - i.card = atoi(str); - else - i.card = PA_INVALID_INDEX; + i.card = g->node_info.device_id; i.n_ports = 0; i.ports = NULL; i.active_port = NULL;