This is not expected to be used by most clients - the default system-wide setting is preferred to provide a consistent user experience. However certain clients (at the moment, this is just web browsers), need to be able to disable clients from modifying system-wide volume, so this provides such a mechanism. --- PROTOCOL | 6 ++++++ configure.ac | 2 +- src/modules/module-tunnel.c | 5 +++++ src/pulse/def.h | 9 ++++++++- src/pulse/stream.c | 11 ++++++++++- src/pulsecore/cli-text.c | 3 ++- src/pulsecore/protocol-native.c | 14 ++++++++++++-- src/pulsecore/sink-input.c | 22 +++++++++++++++------- src/pulsecore/sink-input.h | 4 +++- src/pulsecore/sink.c | 18 ++++++++++++++++++ 10 files changed, 80 insertions(+), 14 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index 3c08fea..3fba5eb 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -371,6 +371,12 @@ PA_COMMAND_DISABLE_SRBCHANNEL Tells the client to stop listening on the additional SHM ringbuffer channel. Acked by client by sending PA_COMMAND_DISABLE_SRBCHANNEL back. +## v31, implemented by >= 6.0 + +new flag at end of CREATE_PLAYBACK_STREAM: + + bool no_flat_volume + #### If you just changed the protocol, read this ## module-tunnel depends on the sink/source/sink-input/source-input protocol ## internals, so if you changed these, you might have broken module-tunnel. diff --git a/configure.ac b/configure.ac index 837e81e..255212e 100644 --- a/configure.ac +++ b/configure.ac @@ -41,7 +41,7 @@ AC_SUBST(PA_MINOR, pa_minor) AC_SUBST(PA_MAJORMINOR, pa_major.pa_minor) AC_SUBST(PA_API_VERSION, 12) -AC_SUBST(PA_PROTOCOL_VERSION, 30) +AC_SUBST(PA_PROTOCOL_VERSION, 31) # The stable ABI for client applications, for the version info x:y:z # always will hold y=z diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 193d091..bd45702 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -1757,6 +1757,11 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t } #endif +#ifdef TUNNEL_SINK + if (u->version >= 31) + pa_tagstruct_put_boolean(reply, false); /* allow flat-volumes on this stream */ +#endif + pa_pstream_send_tagstruct(u->pstream, reply); pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u, NULL); diff --git a/src/pulse/def.h b/src/pulse/def.h index dfc0c10..d0a37f8 100644 --- a/src/pulse/def.h +++ b/src/pulse/def.h @@ -349,11 +349,17 @@ typedef enum pa_stream_flags { * consider absolute when the sink is in flat volume mode, * relative otherwise. \since 0.9.20 */ - PA_STREAM_PASSTHROUGH = 0x80000U + PA_STREAM_PASSTHROUGH = 0x80000U, /**< Used to tag content that will be rendered by passthrough sinks. * The data will be left as is and not reformatted, resampled. * \since 1.0 */ + PA_STREAM_NO_FLAT_VOLUME = 0x100000U, + /**< Used to disable "flat-volumes" behaviour on an individual stream. + * Changes to this stream's volume will not affect device volume, even + * if the flat-volumes mode is enabled on the server. + * \since 6.0 */ + } pa_stream_flags_t; /** \cond fulldocs */ @@ -382,6 +388,7 @@ typedef enum pa_stream_flags { #define PA_STREAM_FAIL_ON_SUSPEND PA_STREAM_FAIL_ON_SUSPEND #define PA_STREAM_RELATIVE_VOLUME PA_STREAM_RELATIVE_VOLUME #define PA_STREAM_PASSTHROUGH PA_STREAM_PASSTHROUGH +#define PA_STREAM_NO_FLAT_VOLUME PA_STREAM_NO_FLAT_VOLUME /** \endcond */ diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 8e35c29..ba5e7fb 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -1213,7 +1213,8 @@ static int create_stream( PA_STREAM_START_UNMUTED| PA_STREAM_FAIL_ON_SUSPEND| PA_STREAM_RELATIVE_VOLUME| - PA_STREAM_PASSTHROUGH)), PA_ERR_INVALID); + PA_STREAM_PASSTHROUGH| + PA_STREAM_NO_FLAT_VOLUME)), PA_ERR_INVALID); PA_CHECK_VALIDITY(s->context, s->context->version >= 12 || !(flags & PA_STREAM_VARIABLE_RATE), PA_ERR_NOTSUPPORTED); PA_CHECK_VALIDITY(s->context, s->context->version >= 13 || !(flags & PA_STREAM_PEAK_DETECT), PA_ERR_NOTSUPPORTED); @@ -1372,6 +1373,9 @@ static int create_stream( pa_tagstruct_put_boolean(t, flags & (PA_STREAM_PASSTHROUGH)); } + if (s->context->version >= 31 && s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, flags & (PA_STREAM_NO_FLAT_VOLUME)); + pa_pstream_send_tagstruct(s->context->pstream, t); pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL); @@ -1389,9 +1393,14 @@ int pa_stream_connect_playback( const pa_cvolume *volume, pa_stream *sync_stream) { + char *e; + pa_assert(s); pa_assert(PA_REFCNT_VALUE(s) >= 1); + if ((e = getenv("PULSE_DISABLE_FLAT_VOLUME"))) + flags |= PA_STREAM_NO_FLAT_VOLUME; + return create_stream(PA_STREAM_PLAYBACK, s, dev, attr, flags, volume, sync_stream); } diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c index 2992ae8..00226cd 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -607,7 +607,7 @@ char *pa_sink_input_list_to_string(pa_core *c) { s, " index: %u\n" "\tdriver: <%s>\n" - "\tflags: %s%s%s%s%s%s%s%s%s%s%s%s\n" + "\tflags: %s%s%s%s%s%s%s%s%s%s%s%s%s\n" "\tstate: %s\n" "\tsink: %u <%s>\n" "\tvolume: %s\n" @@ -631,6 +631,7 @@ char *pa_sink_input_list_to_string(pa_core *c) { i->flags & PA_SINK_INPUT_NO_CREATE_ON_SUSPEND ? "NO_CREATE_SUSPEND " : "", i->flags & PA_SINK_INPUT_KILL_ON_SUSPEND ? "KILL_ON_SUSPEND " : "", i->flags & PA_SINK_INPUT_PASSTHROUGH ? "PASSTHROUGH " : "", + i->flags & PA_SINK_INPUT_NO_FLAT_VOLUME ? "NO_FLAT_VOLUME " : "", state_table[pa_sink_input_get_state(i)], i->sink->index, i->sink->name, volume_str, diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 5f2c35d..df45a99 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -2011,7 +2011,8 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u muted_set = false, fail_on_suspend = false, relative_volume = false, - passthrough = false; + passthrough = false, + no_flat_volume = false; pa_sink_input_flags_t flags = 0; pa_proplist *p = NULL; @@ -2151,6 +2152,14 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u } } + if (c->version >= 31) { + + if (pa_tagstruct_get_boolean(t, &no_flat_volume) < 0 ) { + protocol_error(c); + goto finish; + } + } + if (!pa_tagstruct_eof(t)) { protocol_error(c); goto finish; @@ -2182,7 +2191,8 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u (variable_rate ? PA_SINK_INPUT_VARIABLE_RATE : 0) | (dont_inhibit_auto_suspend ? PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) | (fail_on_suspend ? PA_SINK_INPUT_NO_CREATE_ON_SUSPEND|PA_SINK_INPUT_KILL_ON_SUSPEND : 0) | - (passthrough ? PA_SINK_INPUT_PASSTHROUGH : 0); + (passthrough ? PA_SINK_INPUT_PASSTHROUGH : 0) | + (no_flat_volume ? PA_SINK_INPUT_NO_FLAT_VOLUME : 0); /* Only since protocol version 15 there's a separate muted_set * flag. For older versions we synthesize it here */ diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 6169d47..81dcea3 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -1240,7 +1240,7 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &i->sample_spec)); pa_assert(i->volume_writable); - if (!absolute && pa_sink_flat_volume_enabled(i->sink)) { + if (!absolute && pa_sink_input_is_flat_volume(i)) { v = i->sink->reference_volume; pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map); @@ -1263,7 +1263,7 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s pa_sink_input_set_volume_direct(i, volume); i->save_volume = save; - if (pa_sink_flat_volume_enabled(i->sink)) { + if (pa_sink_input_is_flat_volume(i)) { /* We are in flat volume mode, so let's update all sink input * volumes and update the flat volume of the sink */ @@ -1383,13 +1383,21 @@ bool pa_sink_input_is_volume_readable(pa_sink_input *i) { } /* Called from main context */ +bool pa_sink_input_is_flat_volume(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_assert_ctl_context(); + + return PA_LIKELY(((i->flags & PA_SINK_INPUT_NO_FLAT_VOLUME) == 0) && pa_sink_flat_volume_enabled(i->sink)); +} + +/* Called from main context */ pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool absolute) { pa_sink_input_assert_ref(i); pa_assert_ctl_context(); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); pa_assert(pa_sink_input_is_volume_readable(i)); - if (absolute || !pa_sink_flat_volume_enabled(i->sink)) + if (absolute || !pa_sink_input_is_flat_volume(i)) *volume = i->volume; else *volume = i->reference_ratio; @@ -1597,7 +1605,7 @@ int pa_sink_input_start_move(pa_sink_input *i) { if (pa_sink_input_is_passthrough(i)) pa_sink_leave_passthrough(i->sink); - if (pa_sink_flat_volume_enabled(i->sink)) + if (pa_sink_input_is_flat_volume(i)) /* We might need to update the sink's volume if we are in flat * volume mode. */ pa_sink_set_volume(i->sink, NULL, false, false); @@ -1636,7 +1644,7 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) { if (PA_UNLIKELY(!root_sink)) return; - if (pa_sink_flat_volume_enabled(i->sink)) { + if (pa_sink_input_is_flat_volume(i)) { /* Ok, so the origin sink uses volume sharing, and flat volume is * enabled. The volume will have to be updated as follows: * @@ -1707,7 +1715,7 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) { update_volume_due_to_moving(origin_sink_input, dest); } else { - if (pa_sink_flat_volume_enabled(i->sink)) { + if (pa_sink_input_is_flat_volume(i)) { /* Ok, so this is a regular stream, and flat volume is enabled. The * volume will have to be updated as follows: * @@ -1740,7 +1748,7 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) { /* If i->sink == dest, then recursion has finished, and we can finally call * pa_sink_set_volume(), which will do the rest of the updates. */ - if ((i->sink == dest) && pa_sink_flat_volume_enabled(i->sink)) + if ((i->sink == dest) && pa_sink_input_is_flat_volume(i)) pa_sink_set_volume(i->sink, NULL, false, i->save_volume); } diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index a48476c..e840ae6 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -61,7 +61,8 @@ typedef enum pa_sink_input_flags { PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND = 256, PA_SINK_INPUT_NO_CREATE_ON_SUSPEND = 512, PA_SINK_INPUT_KILL_ON_SUSPEND = 1024, - PA_SINK_INPUT_PASSTHROUGH = 2048 + PA_SINK_INPUT_PASSTHROUGH = 2048, + PA_SINK_INPUT_NO_FLAT_VOLUME = 4096, } pa_sink_input_flags_t; struct pa_sink_input { @@ -369,6 +370,7 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency); bool pa_sink_input_is_passthrough(pa_sink_input *i); bool pa_sink_input_is_volume_readable(pa_sink_input *i); +bool pa_sink_input_is_flat_volume(pa_sink_input *i); void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool save, bool absolute); void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor); int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key); diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index ccf6ea1..f7716c9 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -1656,6 +1656,9 @@ static void compute_reference_ratios(pa_sink *s) { pa_assert(pa_sink_flat_volume_enabled(s)); PA_IDXSET_FOREACH(i, s->inputs, idx) { + if (!pa_sink_input_is_flat_volume(i)) + continue; + compute_reference_ratio(i); if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) @@ -1678,6 +1681,9 @@ static void compute_real_ratios(pa_sink *s) { unsigned c; pa_cvolume remapped; + if (!pa_sink_input_is_flat_volume(i)) + continue; + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { /* The origin sink uses volume sharing, so this input's real ratio * is handled as a special case - the real ratio must be 0 dB, and @@ -1783,6 +1789,9 @@ static void get_maximum_input_volume(pa_sink *s, pa_cvolume *max_volume, const p PA_IDXSET_FOREACH(i, s->inputs, idx) { pa_cvolume remapped; + if (!pa_sink_input_is_flat_volume(i)) + continue; + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { get_maximum_input_volume(i->origin_sink, max_volume, channel_map); @@ -1829,6 +1838,9 @@ static void update_real_volume(pa_sink *s, const pa_cvolume *new_volume, pa_chan pa_cvolume_remap(&s->real_volume, channel_map, &s->channel_map); PA_IDXSET_FOREACH(i, s->inputs, idx) { + if (!pa_sink_input_is_flat_volume(i)) + continue; + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { if (pa_sink_flat_volume_enabled(s)) { pa_cvolume new_input_volume; @@ -1894,6 +1906,9 @@ static void propagate_reference_volume(pa_sink *s) { PA_IDXSET_FOREACH(i, s->inputs, idx) { pa_cvolume new_volume; + if (!pa_sink_input_is_flat_volume(i)) + continue; + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) { propagate_reference_volume(i->origin_sink); @@ -2107,6 +2122,9 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume) PA_IDXSET_FOREACH(i, s->inputs, idx) { pa_cvolume new_volume; + if (!pa_sink_input_is_flat_volume(i)) + continue; + /* 2. Since the sink's reference and real volumes are equal * now our ratios should be too. */ i->reference_ratio = i->real_ratio; -- 1.9.3