/* ALSA card profile test * * 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. */ #include #include #include #include #include #include #include #include #include #include #define WHITESPACE "\n\r\t " struct data { int verbose; int card_index; char *properties; struct acp_card *card; bool quit; }; static void acp_debug_dict(struct acp_dict *dict, int indent) { const struct acp_dict_item *it; fprintf(stderr, "%*sproperties: (%d)\n", indent, "", dict->n_items); acp_dict_for_each(it, dict) { fprintf(stderr, "%*s%s = \"%s\"\n", indent+4, "", 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 *card = d->card; struct acp_card_profile *p = card->profiles[index]; fprintf(stderr, "*** profile %s available %s\n", p->name, acp_available_str(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 *card = d->card; struct acp_port *p = card->ports[index]; fprintf(stderr, "*** port %s available %s\n", p->name, acp_available_str(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); } static const 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 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) { vfprintf(stderr, fmt, arg); 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, const struct command *cmd, int argc, char *argv[]); void *extra; }; static int cmd_help(struct data *data, const struct command *cmd, int argc, char *argv[]); static int cmd_quit(struct data *data, const struct command *cmd, 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_device(struct data *data, struct acp_device *d, 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 %c port %u: name:\"%s\" direction:%s prio:%d (available: %s)\n", indent, "", p->flags & ACP_PORT_ACTIVE ? '*' : ' ', p->index, p->name, acp_direction_str(p->direction), p->priority, acp_available_str(p->available)); if (level > 0) { acp_debug_dict(&p->props, indent + 8); } if (level > 1) { fprintf(stderr, "%*sprofiles: (%d)\n", indent+8, "", p->n_profiles); for (i = 0; i < p->n_profiles; i++) { struct acp_card_profile *pr = p->profiles[i]; print_profile(data, pr, indent + 8, 0); } fprintf(stderr, "%*sdevices: (%d)\n", indent+8, "", p->n_devices); for (i = 0; i < p->n_devices; i++) { struct acp_device *d = p->devices[i]; print_device(data, d, indent + 8, 0); } } } static void print_device(struct data *data, struct acp_device *d, int indent, int level) { const char **s; uint32_t i; fprintf(stderr, "%*s %c device %u: direction:%s name:\"%s\" prio:%d flags:%08x devices: ", indent, "", d->flags & ACP_DEVICE_ACTIVE ? '*' : ' ', d->index, acp_direction_str(d->direction), d->name, d->priority, d->flags); for (s = d->device_strings; *s; s++) fprintf(stderr, "\"%s\" ", *s); fprintf(stderr, "\n"); if (level > 0) { fprintf(stderr, "%*srate:%d channels:%d\n", indent+8, "", d->format.rate_mask, d->format.channels); acp_debug_dict(&d->props, indent + 8); } if (level > 1) { fprintf(stderr, "%*sports: (%d)\n", indent+8, "", d->n_ports); for (i = 0; i < d->n_ports; i++) { struct acp_port *p = d->ports[i]; print_port(data, p, indent + 8, 0); } } } static void print_profile(struct data *data, struct acp_card_profile *p, int indent, int level) { uint32_t i; fprintf(stderr, "%*s %c profile %u: name:\"%s\" prio:%d (available: %s)\n", indent, "", p->flags & ACP_PROFILE_ACTIVE ? '*' : ' ', p->index, p->name, p->priority, acp_available_str(p->available)); if (level > 0) { fprintf(stderr, "%*sdescription:\"%s\"\n", indent+8, "", p->description); } if (level > 1) { fprintf(stderr, "%*sdevices: (%d)\n", indent+8, "", p->n_devices); for (i = 0; i < p->n_devices; i++) { struct acp_device *d = p->devices[i]; print_device(data, d, indent + 8, 0); } } } static void print_card(struct data *data, struct acp_card *card, int indent, int level) { fprintf(stderr, "%*scard %d: profiles:%d devices:%d ports:%d\n", indent, "", card->index, card->n_profiles, card->n_devices, card->n_ports); if (level > 0) { acp_debug_dict(&card->props, 4); } } static int cmd_info(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; print_card(data, card, 0, 2); return 0; } static int cmd_card(struct data *data, const struct command *cmd, int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } return 0; } static int cmd_list(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t i; int level = 0; if (spa_streq(cmd->name, "list-verbose")) level = 2; print_card(data, card, 0, level); for (i = 0; i < card->n_profiles; i++) print_profile(data, card->profiles[i], 0, level); for (i = 0; i < card->n_ports; i++) print_port(data, card->ports[i], 0, level); for (i = 0; i < card->n_devices; i++) print_device(data, card->devices[i], 0, level); return 0; } static int cmd_list_profiles(struct data *data, const struct command *cmd, 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, const struct command *cmd, 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, 0); } static int cmd_list_ports(struct data *data, const struct command *cmd, 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, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id, port_id; if (argc < 3) { fprintf(stderr, "arguments: 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, 0); } static int cmd_list_devices(struct data *data, const struct command *cmd, 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, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; float vol; if (argc < 2) { fprintf(stderr, "arguments: 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, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; float vol; if (argc < 3) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } dev_id = atoi(argv[1]); vol = atof(argv[2]); 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, const struct command *cmd, int argc, char *argv[], float adjust) { struct acp_card *card = data->card; uint32_t dev_id; float vol; if (argc < 2) { fprintf(stderr, "arguments: 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, const struct command *cmd, int argc, char *argv[]) { return adjust_volume(data, cmd, argc, argv, 0.2); } static int cmd_dec_volume(struct data *data, const struct command *cmd, int argc, char *argv[]) { return adjust_volume(data, cmd, argc, argv, -0.2); } static int cmd_get_mute(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; bool mute; if (argc < 2) { fprintf(stderr, "arguments: 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, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; bool mute; if (argc < 3) { fprintf(stderr, "arguments: 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, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; bool mute; if (argc < 2) { fprintf(stderr, "arguments: 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 const struct command command_list[] = { { "help", "", "h", "Show available commands", cmd_help }, { "quit", "", "q", "Quit", cmd_quit }, { "card", "", "c", "Probe card", cmd_card }, { "info", "", "i", "List card info", cmd_info }, { "list", "", "l", "List all objects", cmd_list }, { "list-verbose", "", "lv", "List all data", cmd_list }, { "list-profiles", "[id]", "lpr", "List profiles", cmd_list_profiles }, { "set-profile", "", "spr", "Activate a profile", cmd_set_profile }, { "list-ports", "[id]", "lp", "List ports", cmd_list_ports }, { "set-port", "", "sp", "Activate a port", cmd_set_port }, { "list-devices", "[id]", "ld", "List available devices", cmd_list_devices }, { "get-volume", "", "gv", "Get volume from device", cmd_get_volume }, { "set-volume", " ", "v", "Set volume on device", cmd_set_volume }, { "inc-volume", "", "v+", "Increase volume on device", cmd_inc_volume }, { "dec-volume", "", "v-", "Decrease volume on device", cmd_dec_volume }, { "get-mute", "", "gm", "Get mute state from device", cmd_get_mute }, { "set-mute", " ", "sm", "Set mute on device", cmd_set_mute }, { "toggle-mute", "", "m", "Toggle mute on device", cmd_toggle_mute }, }; #define N_COMMANDS sizeof(command_list)/sizeof(command_list[0]) static const struct command *find_command(struct data *data, const char *cmd) { size_t i; for (i = 0; i < N_COMMANDS; i++) { if (spa_streq(command_list[i].name, cmd) || spa_streq(command_list[i].alias, cmd)) return &command_list[i]; } return NULL; } static int cmd_help(struct data *data, const struct command *cmd, int argc, char *argv[]) { size_t i; fprintf(stderr, "Available commands:\n"); for (i = 0; i < N_COMMANDS; i++) { fprintf(stdout, "\t%-15.15s %-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 run_command(struct data *data, int argc, char *argv[64]) { const struct command *command; int res; command = find_command(data, argv[0]); if (command == NULL) { fprintf(stderr, "unknown command %s\n", argv[0]); cmd_help(data, NULL, argc, argv); res = -EINVAL; } else if (command->func) { res = command->func(data, command, argc, argv); if (res < 0) { fprintf(stderr, "error: %s\n", strerror(-res)); } } else { res = -ENOTSUP; } return res; } static int handle_input(struct data *data) { char buf[4096] = { 0, }, *p, *argv[64]; ssize_t r; int res, argc; if ((r = read(STDIN_FILENO, buf, sizeof(buf)-1)) < 0) return -errno; buf[r] = 0; if (r == 0) return -EPIPE; if ((p = strchr(buf, '#'))) *p = '\0'; argc = split_ip(buf, WHITESPACE, 64, argv); if (argc < 1) return -EINVAL; res = run_command(data, argc, argv); if (!data->quit) show_prompt(data); return res; } static int do_probe(struct data *data) { uint32_t n_items = 0; struct acp_dict_item items[64]; struct acp_dict props; acp_set_log_func(log_func, data); 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"); if (data->properties != NULL) { char *p = data->properties, *e, f; while (*p) { const char *k, *v; if ((e = strchr(p, '=')) == NULL) break; *e = '\0'; k = p; p = e+1; if (*p == '\"') { p++; f = '\"'; } else { f = ' '; } if ((e = strchr(p, f)) == NULL && (e = strchr(p, '\0')) == NULL) break; *e = '\0'; v = p; p = e+1; items[n_items++] = ACP_DICT_ITEM_INIT(k, v); if (n_items == 64) break; } } props = ACP_DICT_INIT(items, n_items); data->card = acp_card_new(data->card_index, &props); if (data->card == NULL) return -errno; return 0; } static int do_prompt(struct data *data) { struct pollfd *pfds; int err, count; acp_card_add_listener(data->card, &card_events, data); count = acp_card_poll_descriptors_count(data->card); 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; print_card(data, data->card, 0, 0); fprintf(stderr, "type 'help' for usage.\n"); show_prompt(data); while (!data->quit) { unsigned short revents; err = acp_card_poll_descriptors(data->card, &pfds[1], count-1); if (err < 0) return err; err = poll(pfds, (unsigned int) count, -1); if (err < 0) return -errno; if (pfds[0].revents & POLLIN) { if ((err = handle_input(data)) < 0) { if (err == -EPIPE) break; } } if (count < 2) continue; err = acp_card_poll_descriptors_revents(data->card, &pfds[1], count-1, &revents); if (err < 0) return err; if (revents) acp_card_handle_events(data->card); } return 0; } #define OPTIONS "hvc:p:" static const struct option long_options[] = { { "help", no_argument, NULL, 'h'}, { "verbose", no_argument, NULL, 'v'}, { "card", required_argument, NULL, 'c' }, { "properties", required_argument, NULL, 'p' }, { NULL, 0, NULL, 0 } }; static void show_usage(struct data *data, const char *name, bool is_error) { FILE *fp; fp = is_error ? stderr : stdout; fprintf(fp, "%s [options] [COMMAND]\n", name); fprintf(fp, " -h, --help Show this help\n" " -v --verbose Be verbose\n" " -c --card Card number\n" " -p --properties Extra properties:\n" " 'key=value ... '\n" "\n"); cmd_help(data, NULL, 0, NULL); } int main(int argc, char *argv[]) { int c, res; 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(&data, argv[0], false); return EXIT_SUCCESS; case 'v': data.verbose++; break; case 'c': ret = atoi(optarg); if (ret < 0) { fprintf(stderr, "error: bad card %s\n", optarg); goto error_usage; } data.card_index = ret; break; case 'p': data.properties = strdup(optarg); break; default: fprintf(stderr, "error: unknown option '%c'\n", c); goto error_usage; } } if ((res = do_probe(&data)) < 0) { fprintf(stderr, "failed to probe card: %s\n", strerror(-res)); return res; } if (optind < argc) run_command(&data, argc - optind, &argv[optind]); else do_prompt(&data); if (data.card) acp_card_destroy(data.card); free(data.properties); return 0; error_usage: show_usage(&data, argv[0], true); return EXIT_FAILURE; }