--- src/map-file | 1 + src/pulse/internal.h | 2 ++ src/pulse/stream.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- src/pulse/stream.h | 30 +++++++++++++++++++++++++++--- 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/src/map-file b/src/map-file index 92d6c4b..3a39253 100644 --- a/src/map-file +++ b/src/map-file @@ -311,6 +311,7 @@ pa_stream_ref; pa_stream_set_buffer_attr; pa_stream_set_buffer_attr_callback; pa_stream_set_event_callback; +pa_stream_set_initial_routing; pa_stream_set_latency_update_callback; pa_stream_set_monitor_stream; pa_stream_set_moved_callback; diff --git a/src/pulse/internal.h b/src/pulse/internal.h index c5084d5..350130d 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -165,6 +165,8 @@ struct pa_stream { uint32_t device_index; char *device_name; + char **initial_routing_nodes; + unsigned n_initial_routing_nodes; /* playback */ pa_memblock *write_memblock; diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 7d77a0e..c67cee7 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -102,7 +102,7 @@ static pa_stream *pa_stream_new_with_proplist_internal( PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); PA_CHECK_VALIDITY_RETURN_NULL(c, name || (p && pa_proplist_contains(p, PA_PROP_MEDIA_NAME)), PA_ERR_INVALID); - s = pa_xnew(pa_stream, 1); + s = pa_xnew0(pa_stream, 1); PA_REFCNT_INIT(s); s->context = c; s->mainloop = c->mainloop; @@ -312,6 +312,10 @@ static void stream_free(pa_stream *s) { if (s->format) pa_format_info_free(s->format); + for (i = 0; i < s->n_initial_routing_nodes; i++) + pa_xfree(s->initial_routing_nodes[i]); + + pa_xfree(s->initial_routing_nodes); pa_xfree(s->device_name); pa_xfree(s); } @@ -949,6 +953,38 @@ static void invalidate_indexes(pa_stream *s, bool r, bool w) { request_auto_timing_update(s, true); } +int pa_stream_set_initial_routing(pa_stream *s, const char * const *nodes, unsigned n_nodes) { + unsigned i; + + pa_assert(s); + pa_assert(nodes || n_nodes == 0); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direct_on_input == PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, s->context->version >= 30, PA_ERR_NOTSUPPORTED); + + for (i = 0; i < s->n_initial_routing_nodes; i++) + pa_xfree(s->initial_routing_nodes[i]); + + pa_xfree(s->initial_routing_nodes); + s->n_initial_routing_nodes = n_nodes; + + if (n_nodes == 0) { + s->initial_routing_nodes = NULL; + return 0; + } + + s->initial_routing_nodes = pa_xnew(char *, n_nodes); + + for (i = 0; i < n_nodes; i++) { + pa_assert(nodes[i]); + s->initial_routing_nodes[i] = pa_xstrdup(nodes[i]); + } + + return 0; +} + static void auto_timing_update_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { pa_stream *s = userdata; @@ -1227,6 +1263,7 @@ static int create_stream( PA_CHECK_VALIDITY(s->context, !volume || s->n_formats || (pa_sample_spec_valid(&s->sample_spec) && volume->channels == s->sample_spec.channels), PA_ERR_INVALID); PA_CHECK_VALIDITY(s->context, !sync_stream || (direction == PA_STREAM_PLAYBACK && sync_stream->direction == PA_STREAM_PLAYBACK), PA_ERR_INVALID); PA_CHECK_VALIDITY(s->context, (flags & (PA_STREAM_ADJUST_LATENCY|PA_STREAM_EARLY_REQUESTS)) != (PA_STREAM_ADJUST_LATENCY|PA_STREAM_EARLY_REQUESTS), PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, !dev || s->n_initial_routing_nodes == 0, PA_ERR_INVALID); pa_stream_ref(s); @@ -1258,7 +1295,7 @@ static int create_stream( true); } - if (!dev) + if (!dev && s->direct_on_input == PA_INVALID_INDEX && s->n_initial_routing_nodes == 0) dev = s->direction == PA_STREAM_PLAYBACK ? s->context->conf->default_sink : s->context->conf->default_source; t = pa_tagstruct_command( @@ -1373,8 +1410,12 @@ static int create_stream( pa_tagstruct_put_boolean(t, flags & (PA_STREAM_PASSTHROUGH)); } - if (s->context->version >= 30) - pa_tagstruct_putu32(t, 0); /* n_initial_routing_nodes */ + if (s->context->version >= 30) { + pa_tagstruct_putu32(t, s->n_initial_routing_nodes); + + for (i = 0; i < s->n_initial_routing_nodes; i++) + pa_tagstruct_puts(t, s->initial_routing_nodes[i]); + } pa_pstream_send_tagstruct(s->context->pstream, t); pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL); @@ -2887,6 +2928,7 @@ int pa_stream_set_monitor_stream(pa_stream *s, uint32_t sink_input_idx) { PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); PA_CHECK_VALIDITY(s->context, sink_input_idx != PA_INVALID_INDEX, PA_ERR_INVALID); PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->n_initial_routing_nodes == 0, PA_ERR_INVALID); PA_CHECK_VALIDITY(s->context, s->context->version >= 13, PA_ERR_NOTSUPPORTED); s->direct_on_input = sink_input_idx; diff --git a/src/pulse/stream.h b/src/pulse/stream.h index a6785ec..46b7984 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -418,6 +418,22 @@ int pa_stream_is_suspended(pa_stream *s); * not, and a negative value on error. \since 0.9.11 */ int pa_stream_is_corked(pa_stream *s); +/** Set the initial routing for the stream. \a nodes is an array of node names + * and \a n_nodes is the array size. This must be called before connecting the + * stream. + * + * It is strongly recommended to not use this function, unless some specific + * initial routing has been explicitly requested by the user. Leaving the + * routing unspecified allows the server to apply automatic routing logic. + * + * You may not use both this function and pa_stream_set_monitor_stream() on the + * same stream. + * + * Returns a negative error code on failure. + * + * \since 6.0 */ +int pa_stream_set_initial_routing(pa_stream *s, const char * const *nodes, unsigned n_nodes); + /** Connect the stream to a sink. It is strongly recommended to pass * NULL in both \a dev and \a volume and not to set either * PA_STREAM_START_MUTED nor PA_STREAM_START_UNMUTED -- unless these @@ -430,6 +446,8 @@ int pa_stream_is_corked(pa_stream *s); * reconfigure audio devices to make other sinks/sources or * capabilities available to be able to accept the stream. * + * If you use pa_stream_set_initial_routing(), then \a dev must be NULL. + * * Before 0.9.20 it was not defined whether the \a volume parameter was * interpreted relative to the sink's current volume or treated as * an absolute device volume. Since 0.9.20 it is an absolute volume when @@ -444,7 +462,9 @@ int pa_stream_connect_playback( const pa_cvolume *volume /**< Initial volume, or NULL for default */, pa_stream *sync_stream /**< Synchronize this stream with the specified one, or NULL for a standalone stream */); -/** Connect the stream to a source. */ +/** Connect the stream to a source. + * + * If you use pa_stream_set_initial_routing(), then \a dev must be NULL. */ int pa_stream_connect_record( pa_stream *s /**< The stream to connect to a source */ , const char *dev /**< Name of the source to connect to, or NULL for default */, @@ -781,8 +801,12 @@ pa_operation *pa_stream_proplist_remove(pa_stream *s, const char *const keys[], /** For record streams connected to a monitor source: monitor only a * very specific sink input of the sink. This function needs to be - * called before pa_stream_connect_record() is called. \since - * 0.9.11 */ + * called before pa_stream_connect_record() is called. + * + * You may not use both this function and pa_stream_set_initial_routing() on + * the same stream. + * + * \since 0.9.11 */ int pa_stream_set_monitor_stream(pa_stream *s, uint32_t sink_input_idx); /** Return the sink input index previously set with -- 1.8.3.1