summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWim Taymans <wtaymans@redhat.com>2020-05-28 17:43:23 +0200
committerWim Taymans <wtaymans@redhat.com>2020-07-03 17:33:07 +0200
commit2bce0d3ca5f3592b20543f56a5c8f1970c281ae0 (patch)
tree6ac75a248c5ee4611e76be25cd47ab0d19368438
parent914967a19db120951120224c4330993acbdbfd91 (diff)
wip: implement interactive tool to manage profiles
-rw-r--r--spa/plugins/pulse/acp.c230
-rw-r--r--spa/plugins/pulse/acp.h68
-rw-r--r--spa/plugins/pulse/alsa-mixer.h4
-rw-r--r--spa/plugins/pulse/alsa-util.c13
-rw-r--r--spa/plugins/pulse/test-probe.c556
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);