diff options
author | Pantelis Antoniou <pantelis.antoniou@konsulko.com> | 2020-01-24 16:18:12 +0200 |
---|---|---|
committer | Wim Taymans <wtaymans@redhat.com> | 2020-01-27 12:23:33 +0100 |
commit | 00fb00924b1a924e6ad92c76eb97c48662e817aa (patch) | |
tree | 5629db2be6431ab0f657a35eacde889118f39de5 | |
parent | 3f11f7e505457095d65c77723d07c8662b82e3c8 (diff) |
pwcat: Add list-targets option
The --list-targets option will report on the available
targets for the --target command (and some info about it).
Example:
$ pwplay --list-targets
Available targets ("*" denotes default):
30: name="alsa_card.pci-0000:00:03.0.playback.3.0" description="Built-in Audio (HDMI 0)" prio=696
31: name="alsa_card.pci-0000:00:03.0.playback.7.0" description="Built-in Audio (HDMI 1)" prio=632
32: name="alsa_card.pci-0000:00:03.0.playback.8.0" description="Built-in Audio (HDMI 2)" prio=616
33: name="alsa_card.pci-0000:00:03.0.playback.9.0" description="Built-in Audio (HDMI 3)" prio=600
* 34: name="alsa_card.pci-0000:00:1b.0.playback.0.0" description="Built-in Audio" prio=936
Getting this working required abandoning the simple stream API, and
the resultant increase in code size.
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
-rw-r--r-- | src/tools/pwcat.c | 504 |
1 files changed, 371 insertions, 133 deletions
diff --git a/src/tools/pwcat.c b/src/tools/pwcat.c index cbb86f86..626f1a0a 100644 --- a/src/tools/pwcat.c +++ b/src/tools/pwcat.c @@ -39,8 +39,10 @@ #include <spa/param/audio/format-utils.h> #include <spa/param/props.h> +#include <spa/utils/result.h> #include <pipewire/pipewire.h> +#include <pipewire/global.h> #define DEFAULT_MEDIA_TYPE "Audio" #define DEFAULT_MEDIA_CATEGORY_PLAYBACK "Playback" @@ -72,11 +74,23 @@ struct data; typedef int (*fill_fn)(struct data *d, void *dest, unsigned int n_frames); +struct target { + struct spa_list link; + uint32_t id; + char *name; + char *desc; + int prio; +}; + struct data { struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + struct spa_hook core_listener; + struct pw_registry *registry; + struct spa_hook registry_listener; struct pw_stream *stream; - - double accumulator; + struct spa_hook stream_listener; enum mode mode; bool verbose; @@ -101,10 +115,14 @@ struct data { enum spa_audio_format spa_format; float volume; + bool volume_is_set; fill_fn fill; uint32_t target_id; + bool list_targets; + bool targets_listed; + struct spa_list targets; bool drained; }; @@ -434,6 +452,147 @@ sf_fmt_record_fill_fn(int format) } static void +target_destroy(struct target *target) +{ + if (!target) + return; + if (target->name) + free(target->name); + if (target->desc) + free(target->desc); + free(target); +} + +static struct target * +target_create(uint32_t id, const char *name, const char *desc, int prio) +{ + struct target *target; + + target = malloc(sizeof(*target)); + if (!target) + return NULL; + target->id = id; + target->name = strdup(name); + target->desc = strdup(desc ? : ""); + target->prio = prio; + + if (!target->name || !target->desc) { + target_destroy(target); + return NULL; + } + return target; +} + +static void on_core_info(void *userdata, const struct pw_core_info *info) +{ + struct data *data = userdata; + + if (data->verbose) + fprintf(stdout, "remote %"PRIu32" is named \"%s\"\n", + info->id, info->name); +} + +static void on_core_done(void *userdata, uint32_t id, int seq) +{ + struct data *data = userdata; + + if (data->verbose) + printf("core done\n"); + + /* if we're listing targets just exist */ + if (data->list_targets) { + data->targets_listed = true; + pw_main_loop_quit(data->loop); + } +} + +static void on_core_error(void *userdata, uint32_t id, int seq, int res, const char *message) +{ + struct data *data = userdata; + + fprintf(stderr, "remote error: id=%"PRIu32" seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + + pw_main_loop_quit(data->loop); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .info = on_core_info, + .done = on_core_done, + .error = on_core_error, +}; + +static void registry_event_global(void *userdata, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) +{ + struct data *data = userdata; + const struct spa_dict_item *item; + const char *name, *desc, *media_class, *prio_session; + int prio; + enum mode mode = mode_none; + struct target *target; + + /* only once */ + if (data->targets_listed) + return; + + /* must be listing targets and interface must be a node */ + if (!data->list_targets || strcmp(type, PW_TYPE_INTERFACE_Node)) + return; + + name = spa_dict_lookup(props, PW_KEY_NODE_NAME); + desc = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION); + media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + prio_session = spa_dict_lookup(props, PW_KEY_PRIORITY_SESSION); + + /* name and media class must exist */ + if (!name || !media_class) + return; + + /* get allowed mode from the media class */ + /* TODO extend to something else besides Audio/Source|Sink */ + if (!strcmp(media_class, "Audio/Source")) + mode = mode_record; + else if (!strcmp(media_class, "Audio/Sink")) + mode = mode_playback; + + /* modes must match */ + if (mode != data->mode) + return; + + prio = prio_session ? atoi(prio_session) : -1; + + if (data->verbose) { + printf("registry: id=%"PRIu32" type=%s name=\"%s\" media_class=\"%s\" desc=\"%s\" prio=%d\n", + id, type, name, media_class, desc ? : "", prio); + + spa_dict_for_each(item, props) { + fprintf(stdout, "\t\t%s = \"%s\"\n", item->key, item->value); + } + } + + target = target_create(id, name, desc, prio); + if (target) + spa_list_append(&data->targets, &target->link); +} + +static void registry_event_global_remove(void *userdata, uint32_t id) +{ + struct data *data = userdata; + + if (data->verbose) + printf("registry: remove id=%"PRIu32"\n", id); +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, + .global_remove = registry_event_global_remove, +}; + +static void on_state_changed(void *userdata, enum pw_stream_state old, enum pw_stream_state state, const char *error) { @@ -445,10 +604,8 @@ on_state_changed(void *userdata, enum pw_stream_state old, pw_stream_state_as_string(old), pw_stream_state_as_string(state)); - if (state == PW_STREAM_STATE_STREAMING) { - if (data->verbose) - printf("stream node %"PRIu32"\n", - pw_stream_get_node_id(data->stream)); + if (state == PW_STREAM_STATE_STREAMING && !data->volume_is_set) { + ret = pw_stream_set_control(data->stream, SPA_PROP_volume, 1, &data->volume, 0); @@ -456,6 +613,14 @@ on_state_changed(void *userdata, enum pw_stream_state old, printf("set stream volume to %.3f - %s\n", data->volume, ret == 0 ? "success" : "FAILED"); + data->volume_is_set = true; + + } + + if (state == PW_STREAM_STATE_STREAMING) { + if (data->verbose) + printf("stream node %"PRIu32"\n", + pw_stream_get_node_id(data->stream)); } } @@ -562,6 +727,7 @@ enum { OPT_CHANNELS, OPT_FORMAT, OPT_VOLUME, + OPT_LIST_TARGETS, }; static const struct option long_options[] = { @@ -585,6 +751,7 @@ static const struct option long_options[] = { {"format", required_argument, NULL, OPT_FORMAT }, {"volume", required_argument, NULL, OPT_VOLUME }, + {"list-targets", no_argument, NULL, OPT_LIST_TARGETS }, {NULL, 0, NULL, 0 } }; @@ -612,6 +779,7 @@ static void show_usage(const char *name, bool is_error) " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source file\n" + " --list-targets List available targets for --target\n" "\n", DEFAULT_MEDIA_TYPE, DEFAULT_MEDIA_CATEGORY_PLAYBACK, @@ -647,9 +815,10 @@ int main(int argc, char *argv[]) struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const char *prog; int exit_code = EXIT_FAILURE, c, format = 0, ret; - struct pw_properties *props; + struct pw_properties *props = NULL; const char *s; - unsigned int nom; + unsigned int nom = 0; + struct target *target, *target_default; pw_init(&argc, &argv); @@ -670,6 +839,9 @@ int main(int argc, char *argv[]) /* negative means no volume adjustment */ data.volume = -1.0; + /* initialize list everytime */ + spa_list_init(&data.targets); + while ((c = getopt_long(argc, argv, "hvprR:", long_options, NULL)) != -1) { switch (c) { @@ -762,6 +934,10 @@ int main(int argc, char *argv[]) data.volume = atof(optarg); break; + case OPT_LIST_TARGETS: + data.list_targets = true; + break; + default: fprintf(stderr, "error: unknown option '%c'\n", c); goto error_usage; @@ -796,7 +972,7 @@ int main(int argc, char *argv[]) if (data.mode == mode_record && !format) format = sf_str_to_fmt(DEFAULT_FORMAT); - if (optind >= argc) { + if (!data.list_targets && optind >= argc) { fprintf(stderr, "error: filename argument missing\n"); goto error_usage; } @@ -810,90 +986,92 @@ int main(int argc, char *argv[]) data.info.format = format; } - data.file = sf_open(data.filename, - data.mode == mode_playback ? SFM_READ : SFM_WRITE, - &data.info); - if (!data.file) { - fprintf(stderr, "error: failed to open audio file \"%s\"n", - data.filename); - goto error_open_file; - } + if (!data.list_targets) { + data.file = sf_open(data.filename, + data.mode == mode_playback ? SFM_READ : SFM_WRITE, + &data.info); + if (!data.file) { + fprintf(stderr, "error: failed to open audio file \"%s\"n", + data.filename); + goto error_open_file; + } - if (data.verbose) - printf("opened file \"%s\"\n", data.filename); + if (data.verbose) + printf("opened file \"%s\"\n", data.filename); - format = data.info.format; - if (!sf_is_valid_type(format) || - !sf_is_valid_subtype(format) || - sf_format_samplesize(format) <= 0) { - fprintf(stderr, "error: Invalid file format, require WAV PCM8/16/32 or float/double\n"); - goto error_bad_file; - } + format = data.info.format; + if (!sf_is_valid_type(format) || + !sf_is_valid_subtype(format) || + sf_format_samplesize(format) <= 0) { + fprintf(stderr, "error: Invalid file format, require WAV PCM8/16/32 or float/double\n"); + goto error_bad_file; + } + + if (data.mode == mode_playback) { + data.rate = data.info.samplerate; + data.channels = data.info.channels; + } + data.samplesize = sf_format_samplesize(format); + data.stride = data.samplesize * data.channels; + data.spa_format = sf_format_to_pw(format); + data.fill = data.mode == mode_playback ? + sf_fmt_playback_fill_fn(format) : + sf_fmt_record_fill_fn(format); - if (data.mode == mode_playback) { - data.rate = data.info.samplerate; - data.channels = data.info.channels; - } - data.samplesize = sf_format_samplesize(format); - data.stride = data.samplesize * data.channels; - data.spa_format = sf_format_to_pw(format); - data.fill = data.mode == mode_playback ? - sf_fmt_playback_fill_fn(format) : - sf_fmt_record_fill_fn(format); - - data.latency_unit = unit_none; - s = data.latency; - while (*s && isdigit(*s)) - s++; - if (!*s) - data.latency_unit = unit_samples; - else if (!strcmp(s, "none")) data.latency_unit = unit_none; - else if (!strcmp(s, "s") || !strcmp(s, "sec") || !strcmp(s, "secs")) - data.latency_unit = unit_sec; - else if (!strcmp(s, "ms") || !strcmp(s, "msec") || !strcmp(s, "msecs")) - data.latency_unit = unit_msec; - else if (!strcmp(s, "us") || !strcmp(s, "usec") || !strcmp(s, "usecs")) - data.latency_unit = unit_usec; - else if (!strcmp(s, "ns") || !strcmp(s, "nsec") || !strcmp(s, "nsecs")) - data.latency_unit = unit_nsec; - else { - fprintf(stderr, "error: bad latency value %s (bad unit)\n", data.latency); - goto error_bad_file; - } - data.latency_value = atoi(data.latency); - if (!data.latency_value) { - fprintf(stderr, "error: bad latency value %s (is zero)\n", data.latency); - goto error_bad_file; - } + s = data.latency; + while (*s && isdigit(*s)) + s++; + if (!*s) + data.latency_unit = unit_samples; + else if (!strcmp(s, "none")) + data.latency_unit = unit_none; + else if (!strcmp(s, "s") || !strcmp(s, "sec") || !strcmp(s, "secs")) + data.latency_unit = unit_sec; + else if (!strcmp(s, "ms") || !strcmp(s, "msec") || !strcmp(s, "msecs")) + data.latency_unit = unit_msec; + else if (!strcmp(s, "us") || !strcmp(s, "usec") || !strcmp(s, "usecs")) + data.latency_unit = unit_usec; + else if (!strcmp(s, "ns") || !strcmp(s, "nsec") || !strcmp(s, "nsecs")) + data.latency_unit = unit_nsec; + else { + fprintf(stderr, "error: bad latency value %s (bad unit)\n", data.latency); + goto error_bad_file; + } + data.latency_value = atoi(data.latency); + if (!data.latency_value) { + fprintf(stderr, "error: bad latency value %s (is zero)\n", data.latency); + goto error_bad_file; + } - switch (data.latency_unit) { - case unit_sec: - nom = data.latency_value * data.rate; - break; - case unit_msec: - nom = nearbyint((data.latency_value * data.rate) / 1000.0); - break; - case unit_usec: - nom = nearbyint((data.latency_value * data.rate) / 1000000.0); - break; - case unit_nsec: - nom = nearbyint((data.latency_value * data.rate) / 1000000000.0); - break; - case unit_samples: - nom = data.latency_value; - break; - default: - nom = 0; - break; - } + switch (data.latency_unit) { + case unit_sec: + nom = data.latency_value * data.rate; + break; + case unit_msec: + nom = nearbyint((data.latency_value * data.rate) / 1000.0); + break; + case unit_usec: + nom = nearbyint((data.latency_value * data.rate) / 1000000.0); + break; + case unit_nsec: + nom = nearbyint((data.latency_value * data.rate) / 1000000000.0); + break; + case unit_samples: + nom = data.latency_value; + break; + default: + nom = 0; + break; + } - if (data.verbose) - printf("rate=%u channels=%u fmt=%s samplesize=%u stride=%u latency=%u (%.3fs)\n", - data.rate, data.channels, - sf_fmt_to_str(format), - data.samplesize, - data.stride, nom, (double)nom/data.rate); + if (data.verbose) + printf("rate=%u channels=%u fmt=%s samplesize=%u stride=%u latency=%u (%.3fs)\n", + data.rate, data.channels, + sf_fmt_to_str(format), + data.samplesize, + data.stride, nom, (double)nom/data.rate); + } /* make a main loop. If you already have another main loop, you can add * the fd of this pipewire mainloop to it. */ @@ -907,6 +1085,12 @@ int main(int argc, char *argv[]) pw_loop_add_signal(l, SIGINT, do_quit, &data); pw_loop_add_signal(l, SIGTERM, do_quit, &data); + data.context = pw_context_new(l, NULL, 0); + if (!data.context) { + fprintf(stderr, "error: pw_context_new() failed\n"); + goto error_no_context; + } + props = pw_properties_new( PW_KEY_MEDIA_TYPE, data.media_type, PW_KEY_MEDIA_CATEGORY, data.media_category, @@ -922,50 +1106,66 @@ int main(int argc, char *argv[]) if (nom) pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", nom, data.rate); - data.stream = pw_stream_new_simple(l, - prog, - props, - &stream_events, - &data); - if (!data.stream) { - fprintf(stderr, "error: failed to create simple stream\n"); - goto error_no_stream; + data.core = pw_context_connect(data.context, NULL, 0); + if (!data.core) { + fprintf(stderr, "error: pw_context_connect() failed\n"); + goto error_ctx_connect_failed; } + pw_core_add_listener(data.core, &data.core_listener, &core_events, &data); - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, - &SPA_AUDIO_INFO_RAW_INIT( - .format = data.spa_format, - .channels = data.channels, - .rate = data.rate )); - - if (data.verbose) - printf("connecting %s stream; target_id=%"PRIu32"\n", - data.mode == mode_playback ? "playback" : "record", - data.target_id); - - ret = pw_stream_connect(data.stream, - data.mode == mode_playback ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT, - data.target_id, - PW_STREAM_FLAG_AUTOCONNECT | - PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS, - params, 1); - if (ret != 0) { - fprintf(stderr, "error: failed connect\n"); - goto error_connect_fail; + data.registry = pw_core_get_registry(data.core, PW_VERSION_REGISTRY, 0); + if (!data.registry) { + fprintf(stderr, "error: pw_core_get_registry() failed\n"); + goto error_no_registry; } + pw_registry_add_listener(data.registry, &data.registry_listener, ®istry_events, &data); - if (data.verbose) { - const struct pw_properties *props; - void *pstate; - const char *key, *val; - - if ((props = pw_stream_get_properties(data.stream)) != NULL) { - printf("stream properties:\n"); - pstate = NULL; - while ((key = pw_properties_iterate(props, &pstate)) != NULL && - (val = pw_properties_get(props, key)) != NULL) { - printf("\t%s = \"%s\"\n", key, val); + pw_core_sync(data.core, 0, 0); + + if (!data.list_targets) { + data.stream = pw_stream_new(data.core, prog, props); + if (!data.stream) { + fprintf(stderr, "error: failed to create simple stream\n"); + goto error_no_stream; + } + props = NULL; + pw_stream_add_listener(data.stream, &data.stream_listener, &stream_events, &data); + + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + &SPA_AUDIO_INFO_RAW_INIT( + .format = data.spa_format, + .channels = data.channels, + .rate = data.rate )); + + if (data.verbose) + printf("connecting %s stream; target_id=%"PRIu32"\n", + data.mode == mode_playback ? "playback" : "record", + data.target_id); + + ret = pw_stream_connect(data.stream, + data.mode == mode_playback ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT, + data.target_id, + PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS, + params, 1); + if (ret != 0) { + fprintf(stderr, "error: failed connect\n"); + goto error_connect_fail; + } + + if (data.verbose) { + const struct pw_properties *props; + void *pstate; + const char *key, *val; + + if ((props = pw_stream_get_properties(data.stream)) != NULL) { + printf("stream properties:\n"); + pstate = NULL; + while ((key = pw_properties_iterate(props, &pstate)) != NULL && + (val = pw_properties_get(props, key)) != NULL) { + printf("\t%s = \"%s\"\n", key, val); + } } } } @@ -974,16 +1174,54 @@ int main(int argc, char *argv[]) pw_main_loop_run(data.loop); /* we're returning OK only if got to the point to drain */ - if (data.drained) - exit_code = EXIT_SUCCESS; + if (!data.list_targets) { + if (data.drained) + exit_code = EXIT_SUCCESS; + } else { + if (data.targets_listed) { + exit_code = EXIT_SUCCESS; + + /* first find the highest priority */ + target_default = NULL; + spa_list_for_each(target, &data.targets, link) { + if (!target_default) { + target_default = target; + continue; + } + if (target->prio > target_default->prio) + target_default = target; + } -error_connect_fail: - pw_stream_destroy(data.stream); + printf("Available targets (\"*\" denotes default):\n"); + spa_list_for_each(target, &data.targets, link) { + printf("%s\t%"PRIu32": name=\"%s\" description=\"%s\" prio=%d\n", + target == target_default ? "*" : "", + target->id, target->name, target->desc, target->prio); + } + } + } -error_no_stream: - pw_main_loop_destroy(data.loop); + /* destroy targets */ + while (!spa_list_is_empty(&data.targets)) { + target = spa_list_last(&data.targets, struct target, link); + spa_list_remove(&target->link); + target_destroy(target); + } +error_connect_fail: + if (data.stream) + pw_stream_destroy(data.stream); +error_no_stream: +error_no_registry: + pw_core_disconnect(data.core); +error_ctx_connect_failed: + if (props) + pw_properties_free(props); error_no_props: + pw_context_destroy(data.context); + +error_no_context: + pw_main_loop_destroy(data.loop); error_no_main_loop: error_bad_file: |