--- man/pacat.1.xml.in | 26 ++++++++++++++++++++++++++ src/utils/pacat.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/man/pacat.1.xml.in b/man/pacat.1.xml.in index 1c5a6c0..c546246 100644 --- a/man/pacat.1.xml.in +++ b/man/pacat.1.xml.in @@ -84,6 +84,32 @@ USA. </option> <option> + <p><opt>--routing</opt><arg>=NODES</arg></p> + + <optdesc><p>Specify how the stream should be routed. The argument is + a comma-separated list of node names. The possibility to specify multiple + nodes exists so that the stream can be played to multiple devices + simultaneously. As of version 6.0, the server doesn't yet support this, + however.</p> + + <p>The --device and --monitor-stream options also control the routing, + and to avoid confusion, it's not possible to use --routing together with + --device and --monitor-stream. Which option is "better", then? --routing + is the most recent addition, and it sort of supersedes both the --device + and --monitor-stream options, but sometimes it may be more convenient to + use the old options. Choose whichever option suits you best.</p> + + <p>As of PulseAudio 6.0, there is one significant drawback with --routing + compared to --device: the node names for devices are not "stable", which + means that the node name of a device can be different after a reboot or + after the device has been removed and added back to the system. Sink and + source names, which are used with the --device option, tend to stay the + same for longer times (ideally forever, but we can't really promise + that). This problem with node names will hopefully go away eventually. + </p></optdesc> + </option> + + <option> <p><opt>--monitor-stream</opt><arg>=INDEX</arg></p> <optdesc><p>Record from the sink input with index INDEX.</p></optdesc> diff --git a/src/utils/pacat.c b/src/utils/pacat.c index e1abc31..ea68236 100644 --- a/src/utils/pacat.c +++ b/src/utils/pacat.c @@ -41,6 +41,7 @@ #include <pulse/rtclock.h> #include <pulsecore/core-util.h> +#include <pulsecore/dynarray.h> #include <pulsecore/i18n.h> #include <pulsecore/log.h> #include <pulsecore/macro.h> @@ -67,6 +68,7 @@ static pa_io_event* stdio_event = NULL; static pa_proplist *proplist = NULL; static char *device = NULL; +static pa_dynarray *routing = NULL; static SNDFILE* sndfile = NULL; @@ -449,6 +451,8 @@ static void stream_event_callback(pa_stream *s, const char *name, pa_proplist *p /* This is called whenever the context status changes */ static void context_state_callback(pa_context *c, void *userdata) { + int r; + pa_assert(c); switch (pa_context_get_state(c)) { @@ -482,6 +486,15 @@ static void context_state_callback(pa_context *c, void *userdata) { pa_stream_set_event_callback(stream, stream_event_callback, NULL); pa_stream_set_buffer_attr_callback(stream, stream_buffer_attr_callback, NULL); + if (pa_dynarray_size(routing) > 0) { + r = pa_stream_set_initial_routing(stream, (const char * const *) pa_dynarray_get_raw_array(routing), + pa_dynarray_size(routing)); + if (r < 0) { + pa_log("pa_stream_set_initial_routing() failed: %s", pa_strerror(r)); + goto fail; + } + } + pa_zero(buffer_attr); buffer_attr.maxlength = (uint32_t) -1; buffer_attr.prebuf = (uint32_t) -1; @@ -677,6 +690,7 @@ static void help(const char *argv0) { " -v, --verbose Enable verbose operations\n\n" " -s, --server=SERVER The name of the server to connect to\n" " -d, --device=DEVICE The name of the sink/source to connect to\n" + " --routing=NODES Comma-separated list of node names\n" " -n, --client-name=NAME How to call this client on the server\n" " --stream-name=NAME How to call this stream on the server\n" " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n" @@ -710,6 +724,7 @@ static void help(const char *argv0) { enum { ARG_VERSION = 256, + ARG_ROUTING, ARG_STREAM_NAME, ARG_VOLUME, ARG_SAMPLERATE, @@ -746,6 +761,7 @@ int main(int argc, char *argv[]) { {"record", 0, NULL, 'r'}, {"playback", 0, NULL, 'p'}, {"device", 1, NULL, 'd'}, + {"routing", 1, NULL, ARG_ROUTING}, {"server", 1, NULL, 's'}, {"client-name", 1, NULL, 'n'}, {"stream-name", 1, NULL, ARG_STREAM_NAME}, @@ -797,6 +813,7 @@ int main(int argc, char *argv[]) { } proplist = pa_proplist_new(); + routing = pa_dynarray_new(pa_xfree); while ((c = getopt_long(argc, argv, "rpd:s:n:hv", long_options, NULL)) != -1) { @@ -829,6 +846,21 @@ int main(int argc, char *argv[]) { device = pa_xstrdup(optarg); break; + case ARG_ROUTING: { + char *node_name; + const char *split_state = NULL; + + if (!*optarg) { + pa_log(_("Invalid value passed to --routing: empty string.")); + goto quit; + } + + while ((node_name = pa_split(optarg, ",", &split_state))) + pa_dynarray_append(routing, node_name); + + break; + } + case 's': pa_xfree(server); server = pa_xstrdup(optarg); @@ -1003,6 +1035,16 @@ int main(int argc, char *argv[]) { goto quit; } + if (device && pa_dynarray_size(routing) > 0) { + pa_log(_("--device and --routing are mutually exclusive options.")); + goto quit; + } + + if (monitor_stream != PA_INVALID_INDEX && pa_dynarray_size(routing) > 0) { + pa_log(_("--monitor-stream and --routing are mutually exclusive options.")); + goto quit; + } + if (optind+1 == argc) { int fd; @@ -1226,6 +1268,10 @@ quit: pa_xfree(buffer); pa_xfree(server); + + if (routing) + pa_dynarray_free(routing); + pa_xfree(device); if (sndfile) -- 1.8.3.1