/* ALSA card profile test */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #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) { static const char * const levels[] = { "E", "W", "N", "I", "D", "T" }; const char *level_str = levels[SPA_CLAMP(level, 0, (int)SPA_N_ELEMENTS(levels) - 1)]; if (file) { const char *p = strrchr(file, '/'); if (p) file = p + 1; } fprintf(stderr, "%s %16s:%-5d ", level_str, file ? file : "", line); while (level-- > 1) fprintf(stderr, " "); 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 = (float)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.2f); } static int cmd_dec_volume(struct data *data, const struct command *cmd, int argc, char *argv[]) { return adjust_volume(data, cmd, argc, argv, -0.2f); } 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; struct spa_json it; 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 (spa_json_begin_object_relax(&it, data->properties, strlen(data->properties)) > 0) { char key[1024]; const char *value; int len; struct spa_error_location loc; while ((len = spa_json_object_next(&it, key, sizeof(key), &value)) > 0) { char *k = alloca(strlen(key) + 1); char *v = alloca(len + 1); memcpy(k, key, strlen(key) + 1); spa_json_parse_stringn(value, len, v, len + 1); items[n_items++] = ACP_DICT_ITEM_INIT(k, v); if (n_items >= SPA_N_ELEMENTS(items)) break; } if (spa_json_get_error(&it, data->properties, &loc)) { struct spa_debug_context *c = NULL; spa_debugc(c, "invalid --properties: %s", loc.reason); spa_debugc_error_location(c, &loc); return -EINVAL; } } 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 = { .properties = strdup("") }; 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': free(data.properties); 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; }