diff --git a/src/modules/module-protocol-pulse/defs.h b/src/modules/module-protocol-pulse/defs.h index 7b4541abc..d874e89a5 100644 --- a/src/modules/module-protocol-pulse/defs.h +++ b/src/modules/module-protocol-pulse/defs.h @@ -274,6 +274,32 @@ enum { SUBSCRIPTION_EVENT_TYPE_MASK = 0x0030U }; +enum { + STATE_INVALID = -1, + STATE_RUNNING = 0, + STATE_IDLE = 1, + STATE_SUSPENDED = 2, + STATE_INIT = -2, + STATE_UNLINKED = -3 +}; + +static inline int node_state(enum pw_node_state state) +{ + switch (state) { + case PW_NODE_STATE_ERROR: + return STATE_UNLINKED; + case PW_NODE_STATE_CREATING: + return STATE_INIT; + case PW_NODE_STATE_SUSPENDED: + return STATE_SUSPENDED; + case PW_NODE_STATE_IDLE: + return STATE_IDLE; + case PW_NODE_STATE_RUNNING: + return STATE_RUNNING; + } + return STATE_INVALID; +} + static inline bool pw_endswith(const char *s, const char *sfx) { size_t l1, l2; diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index 0fc0a70f4..7a99aa6a0 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -97,6 +97,11 @@ struct sample_spec { uint32_t rate; uint8_t channels; }; +#define SAMPLE_SPEC_INIT (struct sample_spec) { \ + .format = SAMPLE_FLOAT32LE, \ + .rate = 44100, \ + .channels = 2, \ + }; static inline uint32_t sample_spec_frame_size(const struct sample_spec *ss) { @@ -247,6 +252,12 @@ struct channel_map { enum channel_position map[CHANNELS_MAX]; }; +#define CHANNEL_MAP_INIT (struct channel_map) { \ + .channels = 2, \ + .map[0] = CHANNEL_POSITION_FRONT_LEFT, \ + .map[1] = CHANNEL_POSITION_FRONT_RIGHT, \ + } + static inline uint32_t channel_pa2id(enum channel_position channel) { if (channel < 0 || (size_t)channel >= SPA_N_ELEMENTS(audio_channels)) @@ -283,11 +294,6 @@ static void channel_map_to_positions(const struct channel_map *map, uint32_t *po pos[i] = channel_pa2id(map->map[i]); } -struct volume { - uint8_t channels; - float values[CHANNELS_MAX]; -}; - enum encoding { ENCODING_ANY, ENCODING_PCM, diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index ab1a1fba4..bfbd325ea 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -57,6 +57,7 @@ #include "defs.h" #include "format.c" +#include "volume.c" #include "message.c" #include "manager.h" @@ -2800,9 +2801,9 @@ static int fill_sink_info(struct client *client, struct message *m, struct pw_manager_object *o) { struct pw_node_info *info = o->info; - struct sample_spec ss; - struct volume volume; - struct channel_map map; + struct volume_info volume_info = VOLUME_INFO_INIT; + struct sample_spec ss = SAMPLE_SPEC_INIT; + struct channel_map map = CHANNEL_MAP_INIT; const char *name, *str; char *monitor_name = NULL; uint32_t module_id = SPA_ID_INVALID; @@ -2812,27 +2813,6 @@ static int fill_sink_info(struct client *client, struct message *m, if (o == NULL || info == NULL || info->props == NULL || !is_sink(o)) return ERR_NOENTITY; - ss = (struct sample_spec) { - .format = SAMPLE_FLOAT32LE, - .rate = 44100, - .channels = 2, }; - map = (struct channel_map) { - .channels = 2, - .map[0] = 1, - .map[1] = 2, }; - volume = (struct volume) { - .channels = 2, - .values[0] = 1.0f, - .values[1] = 1.0f, }; - - spa_list_for_each(p, &o->param_list, link) { - switch (p->id) { - case SPA_PARAM_Format: - format_parse_param(p->param, &ss, &map); - break; - } - } - if ((name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME)) != NULL) { size_t size = strlen(name) + 10; monitor_name = alloca(size); @@ -2843,6 +2823,16 @@ static int fill_sink_info(struct client *client, struct message *m, if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL) card_id = (uint32_t)atoi(str); + spa_list_for_each(p, &o->param_list, link) { + switch (p->id) { + case SPA_PARAM_Format: + format_parse_param(p->param, &ss, &map); + break; + } + } + if (volume_info.volume.channels != map.channels) + volume_info.volume.channels = map.channels; + message_put(m, TAG_U32, o->id, /* sink index */ TAG_STRING, spa_dict_lookup(info->props, PW_KEY_NODE_NAME), @@ -2850,8 +2840,8 @@ static int fill_sink_info(struct client *client, struct message *m, TAG_SAMPLE_SPEC, &ss, TAG_CHANNEL_MAP, &map, TAG_U32, module_id, /* module index */ - TAG_CVOLUME, &volume, - TAG_BOOLEAN, false, + TAG_CVOLUME, &volume_info.volume, + TAG_BOOLEAN, volume_info.mute, TAG_U32, o->id | 0x10000U, /* monitor source */ TAG_STRING, monitor_name, /* monitor source name */ TAG_USEC, 0LL, /* latency */ @@ -2867,9 +2857,9 @@ static int fill_sink_info(struct client *client, struct message *m, } if (client->version >= 15) { message_put(m, - TAG_VOLUME, 1.0f, /* base volume */ - TAG_U32, 0, /* state */ - TAG_U32, 256, /* n_volume_steps */ + TAG_VOLUME, volume_info.base, /* base volume */ + TAG_U32, node_state(info->state), /* state */ + TAG_U32, volume_info.steps, /* n_volume_steps */ TAG_U32, card_id, /* card index */ TAG_INVALID); } @@ -2897,34 +2887,22 @@ static int fill_source_info(struct client *client, struct message *m, struct pw_manager_object *o) { struct pw_node_info *info = o->info; - struct sample_spec ss; - struct volume volume; - struct channel_map map; + struct volume_info volume_info = VOLUME_INFO_INIT; + struct sample_spec ss = SAMPLE_SPEC_INIT; + struct channel_map map = CHANNEL_MAP_INIT; bool is_monitor; const char *name, *desc, *str; char *monitor_name = NULL; char *monitor_desc = NULL; uint32_t module_id = SPA_ID_INVALID; uint32_t card_id = SPA_ID_INVALID; + struct pw_manager_param *p; is_monitor = is_sink(o); if (o == NULL || info == NULL || info->props == NULL || (!is_source(o) && !is_monitor)) return ERR_NOENTITY; - ss = (struct sample_spec) { - .format = SAMPLE_FLOAT32LE, - .rate = 44100, - .channels = 2, }; - volume = (struct volume) { - .channels = 2, - .values[0] = 1.0f, - .values[1] = 1.0f, }; - map = (struct channel_map) { - .channels = 2, - .map[0] = 1, - .map[1] = 2, }; - if ((name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME)) != NULL) { size_t size = strlen(name) + 10; monitor_name = alloca(size); @@ -2940,15 +2918,25 @@ static int fill_source_info(struct client *client, struct message *m, if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL) card_id = (uint32_t)atoi(str); + spa_list_for_each(p, &o->param_list, link) { + switch (p->id) { + case SPA_PARAM_Format: + format_parse_param(p->param, &ss, &map); + break; + } + } + if (volume_info.volume.channels != map.channels) + volume_info.volume.channels = map.channels; + message_put(m, TAG_U32, is_monitor ? o->id | 0x10000 : o->id, /* source index */ - TAG_STRING, is_monitor ? monitor_name : name, - TAG_STRING, is_monitor ? monitor_desc : desc, + TAG_STRING, is_monitor ? monitor_name : name, + TAG_STRING, is_monitor ? monitor_desc : desc, TAG_SAMPLE_SPEC, &ss, TAG_CHANNEL_MAP, &map, TAG_U32, module_id, /* module index */ - TAG_CVOLUME, &volume, - TAG_BOOLEAN, false, + TAG_CVOLUME, &volume_info.volume, + TAG_BOOLEAN, volume_info.mute, TAG_U32, is_monitor ? o->id : SPA_ID_INVALID, /* monitor of sink */ TAG_STRING, is_monitor ? name : NULL, /* monitor of sink name */ TAG_USEC, 0LL, /* latency */ @@ -2964,9 +2952,9 @@ static int fill_source_info(struct client *client, struct message *m, } if (client->version >= 15) { message_put(m, - TAG_VOLUME, 1.0f, /* base volume */ - TAG_U32, 0, /* state */ - TAG_U32, 256, /* n_volume_steps */ + TAG_VOLUME, volume_info.base, /* base volume */ + TAG_U32, node_state(info->state), /* state */ + TAG_U32, volume_info.steps, /* n_volume_steps */ TAG_U32, card_id, /* card index */ TAG_INVALID); } @@ -2994,34 +2982,33 @@ static int fill_sink_input_info(struct client *client, struct message *m, struct pw_manager_object *o) { struct pw_node_info *info = o->info; - struct sample_spec ss; - struct volume volume; - struct channel_map map; + struct volume_info volume_info = VOLUME_INFO_INIT; + struct sample_spec ss = SAMPLE_SPEC_INIT; + struct channel_map map = CHANNEL_MAP_INIT; struct pw_manager_object *peer; const char *str; uint32_t module_id = SPA_ID_INVALID, client_id = SPA_ID_INVALID; + struct pw_manager_param *p; if (o == NULL || info == NULL || info->props == NULL || !is_sink_input(o)) return ERR_NOENTITY; - ss = (struct sample_spec) { - .format = SAMPLE_FLOAT32LE, - .rate = 44100, - .channels = 2, }; - volume = (struct volume) { - .channels = 2, - .values[0] = 1.0f, - .values[1] = 1.0f, }; - map = (struct channel_map) { - .channels = 2, - .map[0] = 1, - .map[1] = 2, }; - if ((str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID)) != NULL) module_id = (uint32_t)atoi(str); if ((str = spa_dict_lookup(info->props, PW_KEY_CLIENT_ID)) != NULL) client_id = (uint32_t)atoi(str); + spa_list_for_each(p, &o->param_list, link) { + switch (p->id) { + case SPA_PARAM_Format: + format_parse_param(p->param, &ss, &map); + break; + case SPA_PARAM_Props: + volume_parse_param(p->param, &volume_info); + break; + } + } + peer = find_linked(client, o->id, PW_DIRECTION_OUTPUT); message_put(m, @@ -3032,7 +3019,7 @@ static int fill_sink_input_info(struct client *client, struct message *m, TAG_U32, peer ? peer->id : SPA_ID_INVALID, /* sink index */ TAG_SAMPLE_SPEC, &ss, TAG_CHANNEL_MAP, &map, - TAG_CVOLUME, &volume, + TAG_CVOLUME, &volume_info.volume, TAG_USEC, 0LL, /* latency */ TAG_USEC, 0LL, /* sink latency */ TAG_STRING, "PipeWire", /* resample method */ @@ -3040,7 +3027,7 @@ static int fill_sink_input_info(struct client *client, struct message *m, TAG_INVALID); if (client->version >= 11) message_put(m, - TAG_BOOLEAN, false, /* muted */ + TAG_BOOLEAN, volume_info.mute, /* muted */ TAG_INVALID); if (client->version >= 13) message_put(m, @@ -3048,7 +3035,7 @@ static int fill_sink_input_info(struct client *client, struct message *m, TAG_INVALID); if (client->version >= 19) message_put(m, - TAG_BOOLEAN, false, /* corked */ + TAG_BOOLEAN, info->state != PW_NODE_STATE_RUNNING, /* corked */ TAG_INVALID); if (client->version >= 20) message_put(m, @@ -3070,35 +3057,34 @@ static int fill_source_output_info(struct client *client, struct message *m, struct pw_manager_object *o) { struct pw_node_info *info = o->info; - struct sample_spec ss; - struct volume volume; - struct channel_map map; + struct volume_info volume_info = VOLUME_INFO_INIT; + struct sample_spec ss = SAMPLE_SPEC_INIT; + struct channel_map map = CHANNEL_MAP_INIT; struct pw_manager_object *peer; const char *str; uint32_t module_id = SPA_ID_INVALID, client_id = SPA_ID_INVALID; uint32_t peer_id; + struct pw_manager_param *p; if (o == NULL || info == NULL || info->props == NULL || !is_source_output(o)) return ERR_NOENTITY; - ss = (struct sample_spec) { - .format = SAMPLE_FLOAT32LE, - .rate = 44100, - .channels = 2, }; - volume = (struct volume) { - .channels = 2, - .values[0] = 1.0f, - .values[1] = 1.0f, }; - map = (struct channel_map) { - .channels = 2, - .map[0] = 1, - .map[1] = 2, }; - if ((str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID)) != NULL) module_id = (uint32_t)atoi(str); if ((str = spa_dict_lookup(info->props, PW_KEY_CLIENT_ID)) != NULL) client_id = (uint32_t)atoi(str); + spa_list_for_each(p, &o->param_list, link) { + switch (p->id) { + case SPA_PARAM_Format: + format_parse_param(p->param, &ss, &map); + break; + case SPA_PARAM_Props: + volume_parse_param(p->param, &volume_info); + break; + } + } + peer = find_linked(client, o->id, PW_DIRECTION_INPUT); if (peer) { peer_id = peer->id; @@ -3127,15 +3113,15 @@ static int fill_source_output_info(struct client *client, struct message *m, TAG_INVALID); if (client->version >= 19) message_put(m, - TAG_BOOLEAN, false, /* corked */ + TAG_BOOLEAN, info->state != PW_NODE_STATE_RUNNING, /* corked */ TAG_INVALID); if (client->version >= 22) { struct format_info fi; spa_zero(fi); fi.encoding = ENCODING_PCM; message_put(m, - TAG_CVOLUME, &volume, - TAG_BOOLEAN, false, /* muted */ + TAG_CVOLUME, &volume_info.volume, + TAG_BOOLEAN, volume_info.mute, /* muted */ TAG_BOOLEAN, true, /* has_volume */ TAG_BOOLEAN, true, /* volume writable */ TAG_FORMAT_INFO, &fi, diff --git a/src/modules/module-protocol-pulse/volume.c b/src/modules/module-protocol-pulse/volume.c new file mode 100644 index 000000000..641785af2 --- /dev/null +++ b/src/modules/module-protocol-pulse/volume.c @@ -0,0 +1,85 @@ +/* PipeWire + * + * Copyright © 2020 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +struct volume { + uint8_t channels; + float values[CHANNELS_MAX]; +}; + +#define VOLUME_INIT (struct volume) { \ + .channels = 2, \ + .values[0] = 1.0f, \ + .values[1] = 1.0f, \ + } + +struct volume_info { + struct volume volume; + bool mute; + float level; + float base; + uint32_t steps; +}; + +#define VOLUME_INFO_INIT (struct volume_info) { \ + .volume = VOLUME_INIT, \ + .mute = false, \ + .level = 1.0, \ + .base = 1.0, \ + .steps = 256, \ + } + + +static int volume_parse_param(const struct spa_pod *param, struct volume_info *info) +{ + struct spa_pod_object *obj = (struct spa_pod_object *) param; + struct spa_pod_prop *prop; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_volume: + spa_pod_get_float(&prop->value, &info->level); + break; + case SPA_PROP_mute: + spa_pod_get_bool(&prop->value, &info->mute); + break; + case SPA_PROP_channelVolumes: + info->volume.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + info->volume.values, SPA_AUDIO_MAX_CHANNELS); + break; + case SPA_PROP_volumeBase: + spa_pod_get_float(&prop->value, &info->base); + break; + case SPA_PROP_volumeStep: + { + float step; + if (spa_pod_get_float(&prop->value, &step) >= 0) + info->steps = 0x10000u * step; + break; + } + default: + break; + } + } + return 0; +}