diff options
author | Wim Taymans <wtaymans@redhat.com> | 2020-05-28 17:43:23 +0200 |
---|---|---|
committer | Wim Taymans <wtaymans@redhat.com> | 2020-07-03 17:33:07 +0200 |
commit | 2bce0d3ca5f3592b20543f56a5c8f1970c281ae0 (patch) | |
tree | 6ac75a248c5ee4611e76be25cd47ab0d19368438 | |
parent | 914967a19db120951120224c4330993acbdbfd91 (diff) |
wip: implement interactive tool to manage profiles
-rw-r--r-- | spa/plugins/pulse/acp.c | 230 | ||||
-rw-r--r-- | spa/plugins/pulse/acp.h | 68 | ||||
-rw-r--r-- | spa/plugins/pulse/alsa-mixer.h | 4 | ||||
-rw-r--r-- | spa/plugins/pulse/alsa-util.c | 13 | ||||
-rw-r--r-- | spa/plugins/pulse/test-probe.c | 556 |
5 files changed, 704 insertions, 167 deletions
diff --git a/spa/plugins/pulse/acp.c b/spa/plugins/pulse/acp.c index 5d3f7e35..34c03044 100644 --- a/spa/plugins/pulse/acp.c +++ b/spa/plugins/pulse/acp.c @@ -26,7 +26,7 @@ #include "alsa-mixer.h" #include "alsa-ucm.h" -int _acp_log_level = 0; +int _acp_log_level = 1; acp_log_func _acp_log_func; void *_acp_log_data; @@ -40,12 +40,19 @@ static void port_free(void *data) static void init_device(pa_alsa_card *impl, pa_alsa_device *dev, pa_alsa_direction_t direction, pa_alsa_mapping *m) { + uint32_t i; + dev->card = impl; dev->mapping = m; dev->device.name = m->name; dev->device.description = m->description; dev->device.priority = m->priority; dev->device.device_strings = m->device_strings; + dev->device.format.format_mask = m->sample_spec.format; + dev->device.format.rate_mask = m->sample_spec.rate; + dev->device.format.channels = m->channel_map.channels; + for (i = 0; i < m->channel_map.channels; i++) + dev->device.format.map[i]= m->channel_map.map[i]; dev->direction = direction; if (direction == PA_ALSA_DIRECTION_OUTPUT) { dev->mixer_path_set = m->output_path_set; @@ -64,79 +71,70 @@ static void init_device(pa_alsa_card *impl, pa_alsa_device *dev, pa_alsa_directi if (m->ucm_context.ucm) dev->ucm_context = &m->ucm_context; pa_dynarray_init(&dev->port_array, NULL); + + pa_proplist_as_dict(dev->proplist, &dev->device.props); } static void add_profiles(pa_alsa_card *impl) { pa_alsa_profile *ap; - void *state, *state2; + void *state; struct acp_card_profile *cp; pa_alsa_device_port *dp; - int port_count, count = 0; + pa_alsa_device *dev; + int n_profiles, n_ports, n_devices; + uint32_t idx; + + n_devices = 0; + pa_dynarray_init(&impl->out.devices, NULL); PA_HASHMAP_FOREACH(ap, impl->profile_set->profiles, state) { pa_alsa_mapping *m; - uint32_t idx; cp = &ap->profile; - if (ap->output_mappings) { - cp->n_sinks = pa_idxset_size(ap->output_mappings); - pa_dynarray_init(&ap->out.sinks, NULL); + pa_dynarray_init(&ap->out.devices, NULL); + if (ap->output_mappings) { PA_IDXSET_FOREACH(m, ap->output_mappings, idx) { - init_device(impl, &m->sink, PA_ALSA_DIRECTION_OUTPUT, m); - + dev = &m->sink; + if (dev->mapping == NULL) { + init_device(impl, dev, PA_ALSA_DIRECTION_OUTPUT, m); + pa_dynarray_append(&impl->out.devices, dev); + dev->device.index = n_devices++; + } if (impl->use_ucm) - pa_alsa_ucm_add_ports_combination(m->sink.ports, &m->ucm_context, + pa_alsa_ucm_add_ports_combination(dev->ports, &m->ucm_context, true, impl->ports, ap); else pa_alsa_path_set_add_ports(m->output_path_set, ap, impl->ports, - m->sink.ports); - - if (m->channel_map.channels > cp->max_sink_channels) - cp->max_sink_channels = m->channel_map.channels; + dev->ports); - PA_HASHMAP_FOREACH(dp, m->sink.ports, state2) - pa_dynarray_append(&m->sink.port_array, dp); - - m->sink.device.ports = m->sink.port_array.array.data; - m->sink.device.n_ports = pa_dynarray_size(&m->sink.port_array); - - pa_proplist_as_dict(m->sink.proplist, &m->sink.device.props); - pa_dynarray_append(&ap->out.sinks, &m->sink); + pa_dynarray_append(&ap->out.devices, &m->sink); } - cp->sinks = ap->out.sinks.array.data; } if (ap->input_mappings) { - cp->n_sources = pa_idxset_size(ap->input_mappings); - pa_dynarray_init(&ap->out.sources, NULL); - PA_IDXSET_FOREACH(m, ap->input_mappings, idx) { - init_device(impl, &m->source, PA_ALSA_DIRECTION_INPUT, m); + dev = &m->source; + if (dev->mapping == NULL) { + init_device(impl, dev, PA_ALSA_DIRECTION_INPUT, m); + pa_dynarray_append(&impl->out.devices, dev); + dev->device.index = n_devices++; + } + if (impl->use_ucm) - pa_alsa_ucm_add_ports_combination(m->source.ports, &m->ucm_context, + pa_alsa_ucm_add_ports_combination(dev->ports, &m->ucm_context, false, impl->ports, ap); else pa_alsa_path_set_add_ports(m->input_path_set, ap, impl->ports, - m->source.ports); - - if (m->channel_map.channels > cp->max_source_channels) - cp->max_source_channels = m->channel_map.channels; + dev->ports); - PA_HASHMAP_FOREACH(dp, m->source.ports, state2) - pa_dynarray_append(&m->source.port_array, dp); - - m->source.device.ports = m->source.port_array.array.data; - m->source.device.n_ports = pa_dynarray_size(&m->source.port_array); - - pa_proplist_as_dict(m->source.proplist, &m->source.device.props); - pa_dynarray_append(&ap->out.sources, &m->source); + pa_dynarray_append(&ap->out.devices, dev); } - cp->sources = ap->out.sources.array.data; } - cp->index = count++; + cp->n_devices = pa_dynarray_size(&ap->out.devices); + cp->devices = ap->out.devices.array.data; pa_hashmap_put(impl->profiles, cp->name, cp); } ap = pa_xnew0(pa_alsa_profile, 1); @@ -145,30 +143,37 @@ static void add_profiles(pa_alsa_card *impl) ap->profile.available = ACP_AVAILABLE_YES; pa_hashmap_put(impl->profiles, ap->profile.name, ap); + PA_DYNARRAY_FOREACH(dev, &impl->out.devices, idx) { + PA_HASHMAP_FOREACH(dp, dev->ports, state) + pa_dynarray_append(&dev->port_array, dp); + dev->device.ports = dev->port_array.array.data; + dev->device.n_ports = pa_dynarray_size(&dev->port_array); + } pa_dynarray_init(&impl->out.ports, NULL); - port_count = 0; + n_ports = 0; PA_HASHMAP_FOREACH(dp, impl->ports, state) { void *state2; dp->card = impl; - dp->port.index = port_count++; + dp->port.index = n_ports++; pa_dynarray_init(&dp->prof, NULL); - count = 0; + n_profiles = 0; PA_HASHMAP_FOREACH(cp, dp->profiles, state2) { pa_dynarray_append(&dp->prof, cp); - count++; + n_profiles++; } - dp->port.n_profiles = count; + dp->port.n_profiles = n_profiles; dp->port.profiles = dp->prof.array.data; pa_proplist_as_dict(dp->proplist, &dp->port.props); pa_dynarray_append(&impl->out.ports, dp); } - count = 0; + n_profiles = 0; pa_dynarray_init(&impl->out.profiles, NULL); PA_HASHMAP_FOREACH(cp, impl->profiles, state) { - cp->index = count++; + cp->index = n_profiles++; pa_dynarray_append(&impl->out.profiles, cp); } + } static pa_available_t calc_port_state(pa_alsa_device_port *p, pa_alsa_card *impl) { @@ -605,11 +610,6 @@ static int choose_profile(pa_alsa_card *impl, const char *profile) return acp_card_set_profile(&impl->card, best); } -static int device_disable(pa_alsa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev) -{ - return 0; -} - static void find_mixer(pa_alsa_card *impl, pa_alsa_device *dev, const char *element, bool ignore_dB) { const char *mdev; pa_alsa_mapping *mapping = dev->mapping; @@ -661,6 +661,8 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; + pa_log_debug("%p mixer changed %d", dev, mask); + if (mask & SND_CTL_EVENT_MASK_VALUE) { if (dev->read_volume) dev->read_volume(dev); @@ -867,12 +869,48 @@ static int setup_mixer(pa_alsa_card *impl, pa_alsa_device *dev, bool ignore_dB) return 0; } +static int device_disable(pa_alsa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev) +{ + dev->device.flags &= ~ACP_DEVICE_ACTIVE; + if (dev->active_port) { + dev->active_port->port.flags &= ~ACP_PORT_ACTIVE; + dev->active_port = NULL; + } + return 0; +} + +static pa_alsa_device_port *find_best_port(pa_hashmap *ports) +{ + void *state; + pa_alsa_device_port *p, *best = NULL, *alt = NULL; + + if (!ports) + return NULL; + + /* First run: skip unavailable ports */ + PA_HASHMAP_FOREACH(p, ports, state) { + if (!alt || p->port.priority > alt->port.priority) + alt = p; + + if (p->port.available == ACP_AVAILABLE_NO) + continue; + + if (!best || p->port.priority > best->port.priority) + best = p; + } + if (!best) + best = alt; + return best; +} + + static int device_enable(pa_alsa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev) { const char *mod_name; bool ignore_dB = false; - if ((mod_name = pa_proplist_gets(mapping->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { + if (impl->use_ucm && + (mod_name = pa_proplist_gets(mapping->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { if (snd_use_case_set(impl->ucm.ucm_mgr, "_enamod", mod_name) < 0) pa_log("Failed to enable ucm modifier %s", mod_name); else @@ -881,14 +919,13 @@ static int device_enable(pa_alsa_card *impl, pa_alsa_mapping *mapping, pa_alsa_d pa_log_info("Device: %s mapping '%s' (%s).", dev->device.description, mapping->description, mapping->name); - if (impl->use_ucm) { -// pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card, u->pcm_handle, ignore_dB); - find_mixer(impl, dev, NULL, ignore_dB); - } else { - find_mixer(impl, dev, NULL, ignore_dB); -// if (dev->mixer_path_set) -// pa_alsa_add_ports(&data, u->mixer_path_set, card); - } + dev->device.flags |= ACP_DEVICE_ACTIVE; + + find_mixer(impl, dev, NULL, ignore_dB); + + dev->active_port = find_best_port(dev->ports); + dev->active_port->port.flags |= ACP_PORT_ACTIVE; + if (setup_mixer(impl, dev, ignore_dB) < 0) return -1; @@ -901,10 +938,15 @@ int acp_card_set_profile(struct acp_card *card, uint32_t new_index) pa_alsa_mapping *am; uint32_t old_index = impl->card.active_profile_index; struct acp_card_profile **profiles = card->profiles; - pa_alsa_profile *op = old_index != (uint32_t)-1 ? (pa_alsa_profile*)profiles[old_index] : NULL; - pa_alsa_profile *np = (pa_alsa_profile*)profiles[new_index]; + pa_alsa_profile *op, *np; uint32_t idx; + if (new_index >= card->n_profiles) + return -EINVAL; + + op = old_index != (uint32_t)-1 ? (pa_alsa_profile*)profiles[old_index] : NULL; + np = (pa_alsa_profile*)profiles[new_index]; + if (op && op->output_mappings) { PA_IDXSET_FOREACH(am, op->output_mappings, idx) { if (np->output_mappings && @@ -943,9 +985,12 @@ int acp_card_set_profile(struct acp_card *card, uint32_t new_index) device_enable(impl, am, &am->source); } } + if (op) + op->profile.flags &= ~ACP_PROFILE_ACTIVE; + np->profile.flags |= ACP_PROFILE_ACTIVE; impl->card.active_profile_index = new_index; - pa_log_info("%s: active_profile: %s", impl->name, np->profile.name); + pa_log_info("active_profile: %s (%d)", np->profile.name, new_index); if (impl->events && impl->events->profile_changed) impl->events->profile_changed(impl->user_data, old_index, @@ -975,6 +1020,8 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) if (impl == NULL) return NULL; + pa_alsa_refcnt_inc(); + snprintf(device_id, sizeof(device_id), "%d", index); impl->proplist = pa_proplist_new_dict(props); @@ -1042,6 +1089,9 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) card->n_ports = pa_dynarray_size(&impl->out.ports); card->ports = impl->out.ports.array.data; + card->n_devices = pa_dynarray_size(&impl->out.devices); + card->devices = impl->out.devices.array.data; + pa_proplist_as_dict(impl->proplist, &card->props); init_jacks(impl); @@ -1063,6 +1113,7 @@ void acp_card_add_listener(struct acp_card *card, void acp_card_destroy(struct acp_card *card) { + pa_alsa_refcnt_dec(); } int acp_card_poll_descriptors_count(struct acp_card *card) @@ -1140,11 +1191,50 @@ int acp_card_handle_events(struct acp_card *card) return count; } +static void sync_mixer(pa_alsa_device *d, pa_alsa_device_port *port) +{ + pa_alsa_setting *setting = NULL; + + if (!d->mixer_path) + return; + + /* port may be NULL, because if we use a synthesized mixer path, then the + * sink has no ports. */ + if (port && !d->ucm_context) { + pa_alsa_port_data *data; + data = PA_ALSA_DEVICE_PORT_DATA(port); + setting = data->setting; + } + + pa_alsa_path_select(d->mixer_path, setting, d->mixer_handle, d->muted); + + if (d->set_mute) + d->set_mute(d, d->muted); + if (d->set_volume) + d->set_volume(d, &d->real_volume); +} + +/* Called from IO context */ + + int acp_device_set_port(struct acp_device *dev, uint32_t port_index) { pa_alsa_device *d = (pa_alsa_device*)dev; pa_alsa_card *impl = d->card; - pa_alsa_device_port *p = (pa_alsa_device_port*)dev->ports[port_index]; + pa_alsa_device_port *p; + + if (port_index >= impl->card.n_ports) + return -EINVAL; + + p = (pa_alsa_device_port*)impl->card.ports[port_index]; + + if (!pa_hashmap_get(d->ports, p->port.name)) + return -EINVAL; + + if (d->active_port) + d->active_port->port.flags &= ~ACP_PORT_ACTIVE; + d->active_port = p; + p->port.flags |= ACP_PORT_ACTIVE; if (impl->use_ucm) { pa_alsa_ucm_port_data *data; @@ -1153,16 +1243,16 @@ int acp_device_set_port(struct acp_device *dev, uint32_t port_index) d->mixer_path = data->path; mixer_volume_init(d); - //sync_mixer(u, p); + sync_mixer(d, p); return pa_alsa_ucm_set_port(d->ucm_context, p, true); } else { pa_alsa_port_data *data; data = PA_ALSA_DEVICE_PORT_DATA(p); - pa_assert_se(d->mixer_path = data->path); + d->mixer_path = data->path; mixer_volume_init(d); - //sync_mixer(u, p); + sync_mixer(d, p); #if 0 if (data->suspend_when_unavailable && p->available == PA_AVAILABLE_NO) pa_sink_suspend(s, true, PA_SUSPEND_UNAVAILABLE); diff --git a/spa/plugins/pulse/acp.h b/spa/plugins/pulse/acp.h index 5dc6b499..6e2febe8 100644 --- a/spa/plugins/pulse/acp.h +++ b/spa/plugins/pulse/acp.h @@ -54,6 +54,14 @@ struct acp_dict { const struct acp_dict_item *items; }; +struct acp_format { + uint32_t flags; + uint32_t format_mask; + uint32_t rate_mask; + uint32_t channels; + uint32_t map[64]; +}; + #define ACP_DICT_INIT(items,n_items) (struct acp_dict) { 0, n_items, items } #define ACP_DICT_INIT_ARRAY(items) (struct acp_dict) { 0, sizeof(items)/sizeof((items)[0]), items } @@ -95,61 +103,66 @@ struct acp_card_events { void (*mute_changed) (void *data, struct acp_device *dev); }; -struct acp_device { +struct acp_port { + uint32_t index; +#define ACP_PORT_ACTIVE (1<<0) + uint32_t flags; + char *name; char *description; - unsigned priority; + uint32_t priority; enum acp_direction direction; + enum acp_available available; struct acp_dict props; - char **device_strings; + uint32_t n_profiles; + uint32_t preferred_profile_index; + struct acp_card_profile **profiles; - struct acp_port **ports; - uint32_t n_ports; - uint32_t active_port_index; + int64_t latency_offset; }; -struct acp_card_profile { +struct acp_device { uint32_t index; +#define ACP_DEVICE_ACTIVE (1<<0) +#define ACP_DEVICE_HW_VOLUME (1<<1) +#define ACP_DEVICE_HW_MUTE (1<<2) + uint32_t flags; char *name; char *description; - - char *input_name; - char *output_name; - uint32_t priority; - enum acp_available available; + enum acp_direction direction; + struct acp_dict props; + + char **device_strings; + struct acp_format format; - uint32_t n_sinks; - struct acp_device **sinks; - uint32_t n_sources; - struct acp_device **sources; - uint32_t max_sink_channels; - uint32_t max_source_channels; + struct acp_port **ports; + uint32_t n_ports; }; -struct acp_port { +struct acp_card_profile { uint32_t index; +#define ACP_PROFILE_ACTIVE (1<<0) + uint32_t flags; char *name; char *description; - char *preferred_profile; uint32_t priority; enum acp_available available; - struct acp_dict props; - - uint32_t n_profiles; - struct acp_card_profile **profiles; + char *input_name; + char *output_name; - enum acp_direction direction; - int64_t latency_offset; + uint32_t n_devices; + struct acp_device **devices; }; struct acp_card { uint32_t index; + uint32_t flags; struct acp_dict props; @@ -157,6 +170,9 @@ struct acp_card { struct acp_card_profile **profiles; uint32_t active_profile_index; + uint32_t n_devices; + struct acp_device **devices; + uint32_t n_ports; struct acp_port **ports; uint32_t preferred_input_port_index; diff --git a/spa/plugins/pulse/alsa-mixer.h b/spa/plugins/pulse/alsa-mixer.h index 334f991a..217978fc 100644 --- a/spa/plugins/pulse/alsa-mixer.h +++ b/spa/plugins/pulse/alsa-mixer.h @@ -380,8 +380,7 @@ struct pa_alsa_profile { pa_idxset *output_mappings; struct { - pa_dynarray sinks; - pa_dynarray sources; + pa_dynarray devices; } out; }; @@ -461,6 +460,7 @@ struct pa_alsa_card { struct { pa_dynarray ports; pa_dynarray profiles; + pa_dynarray devices; } out; const struct acp_card_events *events; diff --git a/spa/plugins/pulse/alsa-util.c b/spa/plugins/pulse/alsa-util.c index 0d2cff59..cec2cdbd 100644 --- a/spa/plugins/pulse/alsa-util.c +++ b/spa/plugins/pulse/alsa-util.c @@ -32,7 +32,6 @@ #include <modules/udev-util.h> #endif - static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_sample_format_t *f) { static const snd_pcm_format_t format_trans[] = { @@ -854,7 +853,6 @@ finish: snd_output_close(out); } -#if 0 static PA_PRINTF_FUNC(5,6) void alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt,...) { va_list ap; char *alsa_file; @@ -869,14 +867,17 @@ static PA_PRINTF_FUNC(5,6) void alsa_error_handler(const char *file, int line, c pa_xfree(alsa_file); } -#endif static int n_error_handler_installed = 0; +typedef void (*snd_lib2_error_handler_t)(const char *file, int line, const char *function, int err, const char *fmt, ...) PA_PRINTF_FUNC(5,6) /* __attribute__ ((format (printf, 5, 6))) */; + +extern int snd_lib_error_set_handler(snd_lib2_error_handler_t handler); + void pa_alsa_refcnt_inc(void) { /* This is not really thread safe, but we do our best */ -// if (n_error_handler_installed++ == 0) -// snd_lib_error_set_handler(alsa_error_handler); + if (n_error_handler_installed++ == 0) + snd_lib_error_set_handler(alsa_error_handler); } void pa_alsa_refcnt_dec(void) { @@ -885,7 +886,7 @@ void pa_alsa_refcnt_dec(void) { pa_assert_se((r = n_error_handler_installed--) >= 1); if (r == 1) { -// snd_lib_error_set_handler(NULL); + snd_lib_error_set_handler(NULL); snd_config_update_free_global(); } } diff --git a/spa/plugins/pulse/test-probe.c b/spa/plugins/pulse/test-probe.c index f66fe2fe..94885f35 100644 --- a/spa/plugins/pulse/test-probe.c +++ b/spa/plugins/pulse/test-probe.c @@ -30,13 +30,19 @@ #include <time.h> #include <stdbool.h> #include <getopt.h> +#include <termios.h> + +#include <spa/utils/defs.h> #include "acp.h" +#define WHITESPACE "\n\r\t " + struct data { - bool verbose; - int card; - struct acp_card *c; + int verbose; + int card_index; + struct acp_card *card; + bool quit; }; static const char *str_available(enum acp_available status) @@ -63,38 +69,105 @@ static const char *str_direction(enum acp_direction direction) return "error"; } +static void acp_debug_dict(struct acp_dict *dict, int indent) +{ + const struct acp_dict_item *it; + acp_dict_for_each(it, dict) { + fprintf(stderr, "%*s%s = \"%s\"\n", indent, "", it->key, it->value); + } +} + +static char *split_walk(char *str, const char *delimiter, size_t *len, char **state) +{ + char *s = *state ? *state : str; + + if (*s == '\0') + return NULL; + + *len = strcspn(s, delimiter); + *state = s + *len; + *state += strspn(*state, delimiter); + return s; +} + +static int split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]) +{ + char *state = NULL, *s; + size_t len; + int n = 0; + + while (true) { + if ((s = split_walk(str, delimiter, &len, &state)) == NULL) + break; + tokens[n++] = s; + if (n >= max_tokens) + break; + s[len] = '\0'; + } + return n; +} + + +static void card_props_changed(void *data) +{ + struct data *d = data; + struct acp_card *card = d->card; + fprintf(stderr, "*** properties changed:\n"); + acp_debug_dict(&card->props, 4); + fprintf(stderr, "***\n"); +} + +static void card_profile_changed(void *data, uint32_t old_index, uint32_t new_index) +{ + struct data *d = data; + struct acp_card *card = d->card; + struct acp_card_profile *op = card->profiles[old_index]; + struct acp_card_profile *np = card->profiles[new_index]; + fprintf(stderr, "*** profile changed from %s to %s\n", op->name, np->name); +} + static void card_profile_available(void *data, uint32_t index, enum acp_available old, enum acp_available available) { struct data *d = data; - struct acp_card *c = d->c; - struct acp_card_profile *p = c->profiles[index]; - fprintf(stderr, "profile %s available %s\n", p->name, str_available(available)); + struct acp_card *card = d->card; + struct acp_card_profile *p = card->profiles[index]; + fprintf(stderr, "*** profile %s available %s\n", p->name, str_available(available)); } static void card_port_available(void *data, uint32_t index, enum acp_available old, enum acp_available available) { struct data *d = data; - struct acp_card *c = d->c; - struct acp_port *p = c->ports[index]; - fprintf(stderr, "port %s available %s\n", p->name, str_available(available)); + struct acp_card *card = d->card; + struct acp_port *p = card->ports[index]; + fprintf(stderr, "*** port %s available %s\n", p->name, str_available(available)); +} + +static void on_volume_changed(void *data, struct acp_device *dev) +{ + float vol; + acp_device_get_volume(dev, &vol, 1); + fprintf(stderr, "*** volume %s changed to %f\n", dev->name, vol); +} + +static void on_mute_changed(void *data, struct acp_device *dev) +{ + bool mute; + acp_device_get_mute(dev, &mute); + fprintf(stderr, "*** mute %s changed to %d\n", dev->name, mute); } struct acp_card_events card_events = { ACP_VERSION_CARD_EVENTS, + .props_changed = card_props_changed, + .profile_changed = card_profile_changed, .profile_available = card_profile_available, .port_available = card_port_available, + .volume_changed = on_volume_changed, + .mute_changed = on_mute_changed, }; -static void acp_debug_dict(struct acp_dict *dict) -{ - const struct acp_dict_item *it; - acp_dict_for_each(it, dict) { - fprintf(stderr, " %s = \"%s\"\n", it->key, it->value); - } -} - static ACP_PRINTF_FUNC(6,0) void log_func(void *data, int level, const char *file, int line, const char *func, const char *fmt, va_list arg) @@ -103,80 +176,430 @@ static ACP_PRINTF_FUNC(6,0) void log_func(void *data, fprintf(stderr, "\n"); } +static void show_prompt(struct data *data) +{ + fprintf(stderr, ">>>"); +} + +struct command { + const char *name; + const char *args; + const char *alias; + const char *description; + int (*func) (struct data *data, int argc, char *argv[]); +}; + +static int cmd_help(struct data *data, int argc, char *argv[]); + +static int cmd_quit(struct data *data, int argc, char *argv[]) +{ + data->quit = true; + return 0; +} + +static void print_profile(struct data *data, struct acp_card_profile *p, int indent, int level); + +static void print_port(struct data *data, struct acp_port *p, int indent, int level) +{ + uint32_t i; + + fprintf(stderr, "%*s%cport %u: name:\"%s\" direction:%s available:%s\n", + indent, "", p->flags & ACP_PORT_ACTIVE ? '*' : ' ', p->index, + p->name, str_direction(p->direction), str_available(p->available)); + if (level > 0) { + acp_debug_dict(&p->props, indent + 4); + } + if (level > 1) { + for (i = 0; i < p->n_profiles; i++) { + struct acp_card_profile *pr = p->profiles[i]; + print_profile(data, pr, indent + 2, 0); + } + } +} + +static void print_device(struct data *data, struct acp_device *d, int indent, int level) +{ + char **s; + uint32_t i; + + fprintf(stderr, "%*s%cdevice %u: direction:%s name:\"%s\" devices: ", + indent, "", d->flags & ACP_DEVICE_ACTIVE ? '*' : ' ', d->index, + str_direction(d->direction), d->name); + for (s = d->device_strings; *s; s++) + fprintf(stderr, "\"%s\" ", *s); + fprintf(stderr, "\n"); + if (level > 0) { + fprintf(stderr, "%*s rate:%d channels:%d\n", indent, "", + d->format.rate_mask, d->format.channels); + acp_debug_dict(&d->props, indent + 4); + } + if (level > 1) { + for (i = 0; i < d->n_ports; i++) { + struct acp_port *p = d->ports[i]; + print_port(data, p, indent + 2, 0); + } + } +} + +static void print_profile(struct data *data, struct acp_card_profile *p, int indent, int level) +{ + uint32_t i; + + fprintf(stderr, "%*s%cprofile %u: name:\"%s\" (available: %s)\n", + indent, "", p->flags & ACP_PROFILE_ACTIVE ? '*' : ' ', p->index, + p->name, str_available(p->available)); + if (level > 0) { + fprintf(stderr, "%*s description:\"%s\" priority:%d n_devices:%d\n", + indent, "", p->description, + p->priority, p->n_devices); + } + if (level > 1) { + for (i = 0; i < p->n_devices; i++) { + struct acp_device *d = p->devices[i]; + print_device(data, d, indent + 2, 0); + } + } +} + +static int cmd_list_profiles(struct data *data, int argc, char *argv[]) +{ + uint32_t i; + struct acp_card *card = data->card; + + if (argc > 1) { + i = atoi(argv[1]); + if (i >= card->n_profiles) + return -EINVAL; + print_profile(data, card->profiles[i], 0, 2); + } else { + for (i = 0; i < card->n_profiles; i++) + print_profile(data, card->profiles[i], 0, 0); + } + return 0; +} + +static int cmd_set_profile(struct data *data, int argc, char *argv[]) +{ + struct acp_card *card = data->card; + uint32_t index; + + if (argc > 1) + index = atoi(argv[1]); + else + index = card->active_profile_index; + + return acp_card_set_profile(card, index); +} + +static int cmd_list_ports(struct data *data, int argc, char *argv[]) +{ + uint32_t i; + struct acp_card *card = data->card; + + if (argc > 1) { + i = atoi(argv[1]); + if (i >= card->n_ports) + return -EINVAL; + print_port(data, card->ports[i], 0, 2); + } else { + for (i = 0; i < card->n_ports; i++) + print_port(data, card->ports[i], 0, 0); + } + return 0; +} + +static int cmd_set_port(struct data *data, int argc, char *argv[]) +{ + struct acp_card *card = data->card; + uint32_t dev_id, port_id; + + if (argc < 3) { + fprintf(stderr, "arguments: <device_id> <port_id> missing\n"); + return -EINVAL; + } + dev_id = atoi(argv[1]); + port_id = atoi(argv[2]); + + if (dev_id >= card->n_devices) + return -EINVAL; + + return acp_device_set_port(card->devices[dev_id], port_id); +} + +static int cmd_list_devices(struct data *data, int argc, char *argv[]) +{ + uint32_t i; + struct acp_card *card = data->card; + + if (argc > 1) { + i = atoi(argv[1]); + if (i >= card->n_devices) + return -EINVAL; + print_device(data, card->devices[i], 0, 2); + } else { + for (i = 0; i < card->n_devices; i++) + print_device(data, card->devices[i], 0, 0); + } + return 0; +} + +static int cmd_get_volume(struct data *data, int argc, char *argv[]) +{ + struct acp_card *card = data->card; + uint32_t dev_id; + float vol; + + if (argc < 2) { + fprintf(stderr, "arguments: <device_id> missing\n"); + return -EINVAL; + } + dev_id = atoi(argv[1]); + if (dev_id >= card->n_devices) + return -EINVAL; + + acp_device_get_volume(card->devices[dev_id], &vol, 1); + + fprintf(stderr, "volume: %f\n", vol); + return 0; +} + +static int cmd_set_volume(struct data *data, int argc, char *argv[]) +{ + struct acp_card *card = data->card; + uint32_t dev_id; + float vol; + + if (argc < 3) { + fprintf(stderr, "arguments: <device_id> <volume> missing\n"); + return -EINVAL; + } + dev_id = atoi(argv[1]); + vol = atof(argv[1]); + + if (dev_id >= card->n_devices) + return -EINVAL; + + return acp_device_set_volume(card->devices[dev_id], &vol, 1); +} + +static int adjust_volume(struct data *data, int argc, char *argv[], float adjust) +{ + struct acp_card *card = data->card; + uint32_t dev_id; + float vol; + + if (argc < 2) { + fprintf(stderr, "arguments: <device_id> missing\n"); + return -EINVAL; + } + dev_id = atoi(argv[1]); + if (dev_id >= card->n_devices) + return -EINVAL; + acp_device_get_volume(card->devices[dev_id], &vol, 1); + vol += adjust; + acp_device_set_volume(card->devices[dev_id], &vol, 1); + fprintf(stderr, "volume: %f\n", vol); + return 0; +} + +static int cmd_inc_volume(struct data *data, int argc, char *argv[]) +{ + return adjust_volume(data, argc, argv, 0.2); +} + +static int cmd_dec_volume(struct data *data, int argc, char *argv[]) +{ + return adjust_volume(data, argc, argv, -0.2); +} + +static int cmd_get_mute(struct data *data, int argc, char *argv[]) +{ + struct acp_card *card = data->card; + uint32_t dev_id; + bool mute; + + if (argc < 2) { + fprintf(stderr, "arguments: <device_id> missing\n"); + return -EINVAL; + } + dev_id = atoi(argv[1]); + if (dev_id >= card->n_devices) + return -EINVAL; + + acp_device_get_mute(card->devices[dev_id], &mute); + + fprintf(stderr, "muted: %s\n", mute ? "yes" : "no"); + return 0; +} + +static int cmd_set_mute(struct data *data, int argc, char *argv[]) +{ + struct acp_card *card = data->card; + uint32_t dev_id; + bool mute; + + if (argc < 3) { + fprintf(stderr, "arguments: <device_id> <mute> missing\n"); + return -EINVAL; + } + dev_id = atoi(argv[1]); + mute = atoi(argv[2]); + if (dev_id >= card->n_devices) + return -EINVAL; + + acp_device_set_mute(card->devices[dev_id], mute); + fprintf(stderr, "muted: %s\n", mute ? "yes" : "no"); + return 0; +} + +static int cmd_toggle_mute(struct data *data, int argc, char *argv[]) +{ + struct acp_card *card = data->card; + uint32_t dev_id; + bool mute; + + if (argc < 2) { + fprintf(stderr, "arguments: <device_id> missing\n"); + return -EINVAL; + } + dev_id = atoi(argv[1]); + if (dev_id >= card->n_devices) + return -EINVAL; + acp_device_get_mute(card->devices[dev_id], &mute); + mute = !mute; + acp_device_set_mute(card->devices[dev_id], mute); + fprintf(stderr, "muted: %s\n", mute ? "yes" : "no"); + return 0; +} + +static struct command command_list[] = { + { "help", "", "h", "Show this help", cmd_help }, + { "quit", "", "q", "Quit", cmd_quit }, + { "list-profiles", "[id]", "lpr", "List profiles", cmd_list_profiles }, + { "set-profile", "<id>", "spr", "Activate a profile", cmd_set_profile }, + { "list-ports", "[id]", "lp", "List ports", cmd_list_ports }, + { "set-port", "<id>", "sp", "Activate a port", cmd_set_port }, + { "list-devices", "[id]", "ld", "List available devices", cmd_list_devices }, + { "get-volume", "<id>", "gv", "Get volume from device", cmd_get_volume }, + { "set-volume", "<id> <vol>", "v", "Set volume on device", cmd_set_volume }, + { "inc-volume", "<id>", "v+", "Increase volume on device", cmd_inc_volume }, + { "dec-volume", "<id>", "v-", "Decrease volume on device", cmd_dec_volume }, + { "get-mute", "<id>", "gm", "Get mute state from device", cmd_get_mute }, + { "set-mute", "<id> <val>", "sm", "Set mute on device", cmd_set_mute }, + { "toggle-mute", "<id>", "m", "Toggle mute on device", cmd_toggle_mute }, +}; + +static struct command *find_command(struct data *data, const char *cmd) +{ + size_t i; + for (i = 0; i < SPA_N_ELEMENTS(command_list); i++) { + if (!strcmp(command_list[i].name, cmd) || + !strcmp(command_list[i].alias, cmd)) + return &command_list[i]; + } + return NULL; +} + +static int cmd_help(struct data *data, int argc, char *argv[]) +{ + size_t i; + fprintf(stderr, "Available commands:\n"); + for (i = 0; i < SPA_N_ELEMENTS(command_list); i++) { + fprintf(stdout, "\t%-20.20s %-10.10s\t%s (%s)\n", + command_list[i].name, + command_list[i].args, + command_list[i].description, + command_list[i].alias); + } + return 0; +} + +static int handle_keyboard(struct data *data) +{ + char buf[4096] = { 0, }, *p, *argv[64]; + ssize_t r; + int res, argc; + struct command *command; + + if ((r = read(STDIN_FILENO, buf, sizeof(buf)-1)) < 0) + return -errno; + buf[r] = 0; + + if ((p = strchr(buf, '#'))) + *p = '\0'; + + argc = split_ip(buf, WHITESPACE, 64, argv); + if (argc < 1) + return -EINVAL; + + command = find_command(data, argv[0]); + if (command == NULL) { + cmd_help(data, argc, argv); + res = -EINVAL; + } else if (command->func) { + res = command->func(data, argc, argv); + if (res < 0) { + fprintf(stderr, "error: %s\n", strerror(-res)); + } + } else { + res = -ENOTSUP; + } + if (!data->quit) + show_prompt(data); + + return res; +} + static int do_probe(struct data *data) { struct acp_card *card; uint32_t n_items = 0; struct acp_dict_item items[2]; struct acp_dict props; - uint32_t i, j; + uint32_t i; struct pollfd *pfds; int err, count; - char **s; acp_set_log_func(log_func, data); - acp_set_log_level(5); + acp_set_log_level(data->verbose); items[n_items++] = ACP_DICT_ITEM_INIT("use-ucm", "true"); items[n_items++] = ACP_DICT_ITEM_INIT("verbose", data->verbose ? "true" : "false"); props = ACP_DICT_INIT(items, n_items); - data->c = card = acp_card_new(data->card, &props); + data->card = card = acp_card_new(data->card_index, &props); for (i = 0; i < card->n_profiles; i++) { struct acp_card_profile *p = card->profiles[i]; - fprintf(stderr, "profile %u: sink/sources:%d/%d channels %d/%d name:%s (%s)\n", i, - p->n_sinks, p->n_sources, - p->max_sink_channels, p->max_source_channels, p->name, - str_available(p->available)); - for (j = 0; j < p->n_sources; j++) { - struct acp_device *d = p->sources[j]; - fprintf(stderr, " source %u: name:%s devices: ", j, d->name); - for (s = d->device_strings; *s; s++) { - fprintf(stderr, "%s ", *s); - } - fprintf(stderr, "\n"); - acp_debug_dict(&d->props); - } - for (j = 0; j < p->n_sinks; j++) { - struct acp_device *d = p->sinks[j]; - fprintf(stderr, " sink %u: name:%s devices: ", j, d->name); - for (s = d->device_strings; *s; s++) { - fprintf(stderr, "%s ", *s); - } - fprintf(stderr, "\n"); - acp_debug_dict(&d->props); - } + print_profile(data, p, 0, 0); } - for (i = 0; i < card->n_ports; i++) { struct acp_port *p = card->ports[i]; - fprintf(stderr, "port %u: name:%s direction:%s available:%s\n", i, - p->name, str_direction(p->direction), str_available(p->available)); - acp_debug_dict(&p->props); - - fprintf(stderr, " profiles: "); - for (j = 0; j < p->n_profiles; j++) - fprintf(stderr, "%u ", p->profiles[j]->index); - fprintf(stderr, "\n"); + print_port(data, p, 0, 0); + } + for (i = 0; i < card->n_devices; i++) { + struct acp_device *d = card->devices[i]; + print_device(data, d, 0, 0); } acp_card_add_listener(card, &card_events, data); count = acp_card_poll_descriptors_count(card); - if (count == 0) { - fprintf(stderr, "card has no events...\n"); - return 0; - } + if (count == 0) + fprintf(stderr, "card has no events\n"); + count++; pfds = alloca(sizeof(struct pollfd) * count); + pfds[0].fd = STDIN_FILENO; + pfds[0].events = POLLIN; - fprintf(stderr, "listening for %d card events...\n", count); + fprintf(stderr, "type 'help' for usage.\n"); + show_prompt(data); - while (true) { + while (!data->quit) { unsigned short revents; - err = acp_card_poll_descriptors(card, pfds, count); + err = acp_card_poll_descriptors(card, &pfds[1], count-1); if (err < 0) return err; @@ -184,14 +607,19 @@ static int do_probe(struct data *data) if (err < 0) return -errno; - err = acp_card_poll_descriptors_revents(card, pfds, count, &revents); + if (pfds[0].revents & POLLIN) + handle_keyboard(data); + + if (count < 2) + continue; + + err = acp_card_poll_descriptors_revents(card, &pfds[1], count-1, &revents); if (err < 0) return err; if (revents) acp_card_handle_events(card); } - acp_card_destroy(card); return 0; } @@ -228,13 +656,15 @@ int main(int argc, char *argv[]) int longopt_index = 0, ret; struct data data = { 0, }; + data.verbose = 1; + while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) { switch (c) { case 'h': show_usage(argv[0], false); return EXIT_SUCCESS; case 'v': - data.verbose = true; + data.verbose++; break; case 'c': ret = atoi(optarg); @@ -242,7 +672,7 @@ int main(int argc, char *argv[]) fprintf(stderr, "error: bad card %s\n", optarg); goto error_usage; } - data.card = ret; + data.card_index = ret; break; default: fprintf(stderr, "error: unknown option '%c'\n", c); |