[pulseaudio-discuss] [PATCH 1/6] Implement the "volume sharing" feature.
Colin Guthrie
gmane at colin.guthr.ie
Mon Feb 28 03:38:01 PST 2011
Hiya,
Some quick comments but these are to go on top rather than change this
as I've already merged it :)
'Twas brillig, and Tanu Kaskinen at 24/02/11 14:16 did gyre and gimble:
> When we have a filter sink that does some processing, currently the
> benefits of the flat volume feature are not really available. That's
> because if you have a music player that is connected to the filter sink,
> the hardware sink doesn't have any idea of the music player's stream
> volume.
>
> This problem is solved by this "volume sharing" feature. The volume
> sharing feature works so that the filter sinks that want to avoid the
> previously described problem declare that they don't want to have
> independent volume, but they follow the master sink volume instead.
> The PA_SINK_SHARE_VOLUME_WITH_MASTER sink flag is used for that
> declaration. Then the volume logic is changed so that the hardware
> sink calculates its real volume using also the streams connected to the
> filter sink in addition to the streams that are connected directly to
> the hardware sink. Basically we're trying to create an illusion that
> from volume point of view all streams are connected directly to the
> hardware sink.
>
> For that illusion to work, the volumes of the filter sinks and their
> virtual streams have to be managed carefully according to a set of
> rules:
>
> If a filter sink follows the hardware sink volume, then the filter sink's
> * reference_volume always equals the hw sink's reference_volume
> * real_volume always equals the hw sink's real_volume
> * soft_volume is always 0dB (ie. no soft volume)
>
> If a filter sink doesn't follow the hardware sink volume, then the filter
> sink's
> * reference_volume can be whatever (completely independent from the hw sink)
> * real_volume always equals reference_volume
> * soft_volume always equals real_volume (and reference_volume)
>
> If a filter sink follows the hardware sink volume, and the hardware sink
> supports flat volume, then the filter sink's virtual stream's
> * volume always equals the hw sink's real_volume
> * reference_ratio is calculated normally from the stream volume and the hw
> sink's reference_volume
> * real_ratio always equals 0dB (follows from the first point)
> * soft_volume always equals volume_factor (follows from the previous point)
>
> If a filter sink follows the hardware sink volume, and the hardware sink
> doesn't support flat volume, then the filter sink's virtual stream's
> * volume is always 0dB
> * reference_ratio is always 0dB
> * real_ratio is always 0dB
> * soft_volume always equals volume_factor
>
> If a filter sink doesn't follow the hardware sink volume, then the filter
> sink's virtual stream is handled as a regular stream.
>
> Since the volumes of the virtual streams are controlled by a set of rules,
> the user is not allowed to change the virtual streams' volumes. It would
> probably also make sense to forbid changing the filter sinks' volume, but
> that's not strictly necessary, and currently changing a filter sink's volume
> changes actually the hardware sink's volume, and from there it propagates to
> all filter sinks ("funny" effects are expected when adjusting a single
> channel in cases where all sinks don't have the same channel maps).
> ---
> src/pulse/def.h | 11 +
> src/pulsecore/protocol-native.c | 2 +-
> src/pulsecore/sink-input.c | 204 +++++++++++++--
> src/pulsecore/sink.c | 555 ++++++++++++++++++++++++++++-----------
> src/pulsecore/sink.h | 4 +
> 5 files changed, 591 insertions(+), 185 deletions(-)
>
> diff --git a/src/pulse/def.h b/src/pulse/def.h
> index 4a8da13..f6de7b3 100644
> --- a/src/pulse/def.h
> +++ b/src/pulse/def.h
> @@ -749,6 +749,16 @@ typedef enum pa_sink_flags {
> /**< The HW volume changes are syncronized with SW volume.
> * \since 1.0 */
>
> +/** \cond fulldocs */
> + /* PRIVATE: Server-side values -- do not try to use these at client-side.
> + * The server will filter out these flags anyway, so you should never see
> + * these flags in sinks. */
> +
> + PA_SINK_SHARE_VOLUME_WITH_MASTER = 0x0400U,
> + /**< This sink shares the volume with the master sink (used by some filter
> + * sinks). */
> +/** \endcond */
Out of curiosity, is there any problem letting clients "see" these
flags. I can understand not letting them adjust/set them, but do we
really want to hide them away?
> } pa_sink_flags_t;
>
> /** \cond fulldocs */
> @@ -762,6 +772,7 @@ typedef enum pa_sink_flags {
> #define PA_SINK_DYNAMIC_LATENCY PA_SINK_DYNAMIC_LATENCY
> #define PA_SINK_PASSTHROUGH PA_SINK_PASSTHROUGH
> #define PA_SINK_SYNC_VOLUME PA_SINK_SYNC_VOLUME
> +#define PA_SINK_SHARE_VOLUME_WITH_MASTER PA_SINK_SHARE_VOLUME_WITH_MASTER
Could we have a PA_SINK_SERVER_SIDE_FLAGS define here that current is just:
#define PA_SINK_SERVER_SIDE_FLAGS PA_SINK_SHARE_VOLUME_WITH_MASTER
but could extend to:
#define PA_SINK_SERVER_SIDE_FLAGS
PA_SINK_SHARE_VOLUME_WITH_MASTER|PA_SINK_SOME_SECRET_FLAG
in the future.....
> /** \endcond */
>
> diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
> index 9257524..c812a3e 100644
> --- a/src/pulsecore/protocol-native.c
> +++ b/src/pulsecore/protocol-native.c
> @@ -2902,7 +2902,7 @@ static void sink_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sin
> PA_TAG_STRING, sink->monitor_source ? sink->monitor_source->name : NULL,
> PA_TAG_USEC, pa_sink_get_latency(sink),
> PA_TAG_STRING, sink->driver,
> - PA_TAG_U32, sink->flags,
> + PA_TAG_U32, sink->flags & ~PA_SINK_SHARE_VOLUME_WITH_MASTER,
> PA_TAG_INVALID);
.... and then use PA_SINK_SERVER_SIDE_FLAGS here instead?
It'll just make grepping the code easier and aid future readability I think.
> @@ -1282,6 +1300,156 @@ int pa_sink_input_start_move(pa_sink_input *i) {
> return 0;
> }
>
> +/* Called from main context. If i has an origin sink that uses volume sharing,
> + * then also the origin sink and all streams connected to it need to update
> + * their volume - this function does all that by using recursion. */
> +static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
> + pa_cvolume old_volume;
> +
> + pa_assert(i);
> + pa_assert(dest);
> + pa_assert(i->sink); /* The destination sink should already be set. */
If it should already be set, do you mean that dest==i->sink (in which
case that's what the assert should say) or should the comment read "The
previous sink should already be set"?
> + /* 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))
> + pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume);
> +}
Judging from this comment at the tail of that function, I'm guessing
that the comment in the above hunk needs a little clarification. The
rest of the comments in this function are awesomely detailed! :)
> /* Called from main context */
> int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
> pa_resampler *new_resampler;
> @@ -1355,17 +1523,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
> }
> pa_sink_update_status(dest);
>
> - if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
> - pa_cvolume remapped;
> -
> - /* Make relative volumes absolute */
> - remapped = dest->reference_volume;
> - pa_cvolume_remap(&remapped, &dest->channel_map, &i->channel_map);
> - pa_sw_cvolume_multiply(&i->volume, &i->reference_ratio, &remapped);
> -
> - /* We might need to update the sink's volume if we are in flat volume mode. */
> - pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume);
> - }
> + update_volume_due_to_moving(i, dest);
>
> pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0);
>
> @@ -1374,9 +1532,6 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
> /* Notify everyone */
> pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i);
>
> - if (i->volume_changed)
> - i->volume_changed(i);
> -
> pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
>
> return 0;
The volume_changed now fires before the hook. I don't think this will
really matter tho' (especially considering the current usage of that hook).
> @@ -1395,9 +1549,9 @@ static void propagate_reference_volume(pa_sink *s) {
> *
> * i->volume := s->reference_volume * i->reference_ratio */
>
> - remapped = s->reference_volume;
> - pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map);
> - pa_sw_cvolume_multiply(&i->volume, &remapped, &i->reference_ratio);
> + i->volume = s->reference_volume;
> + pa_cvolume_remap(&i->volume, &s->channel_map, &i->channel_map);
> + pa_sw_cvolume_multiply(&i->volume, &i->volume, &i->reference_ratio);
>
> /* The volume changed, let's tell people so */
> if (!pa_cvolume_equal(&old_volume, &i->volume)) {
I presume that removing the intermediate "remapped" variable here cannot
introduce any crazy race conditions?
> @@ -1445,76 +1647,82 @@ void pa_sink_set_volume(
> }
> }
>
> + /* In case of volume sharing, the volume is set for the root sink first,
> + * from which it's then propagated to the sharing sinks. */
> + while (root_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
> + root_sink = root_sink->input_to_master->sink;
> +
I guess there is no need for a pa_assert_se() here? We know this will be
OK from setup checks etc?
> /* As a special exception we accept mono volumes on all sinks --
> * even on those with more complex channel maps */
>
> + if (volume) {
> + if (pa_cvolume_compatible(volume, &s->sample_spec))
> + new_reference_volume = *volume;
> + else {
> + new_reference_volume = s->reference_volume;
> + pa_cvolume_scale(&new_reference_volume, pa_cvolume_max(volume));
> + }
> +
> + pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_sink->channel_map);
> + }
> +
> /* If volume is NULL we synchronize the sink's real and reference
> * volumes with the stream volumes. If it is not NULL we update
> * the reference_volume with it. */
>
> - old_reference_volume = s->reference_volume;
> -
> if (volume) {
Smells like these two "if (volume)" blocks could be squashed
together.... unless this is for readability?
> @@ -1572,9 +1780,9 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume)
> * i->volume = s->reference_volume * i->reference_ratio
> *
> * This is identical to propagate_reference_volume() */
> - remapped = s->reference_volume;
> - pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map);
> - pa_sw_cvolume_multiply(&i->volume, &remapped, &i->reference_ratio);
> + i->volume = s->reference_volume;
> + pa_cvolume_remap(&i->volume, &s->channel_map, &i->channel_map);
> + pa_sw_cvolume_multiply(&i->volume, &i->volume, &i->reference_ratio);
Again "remapped" intermediate variable removed. Same race possibility
question as above.
> +/* Called from the IO thread. Only called for the root sink in volume sharing
> + * cases, except for internal recursive calls. */
> +static void set_shared_volume_within_thread(pa_sink *s) {
> + pa_sink_input *i = NULL;
> + void *state = NULL;
> +
> + pa_sink_assert_ref(s);
> +
> + PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
> +
> + PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) {
> + if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
> + set_shared_volume_within_thread(i->origin_sink);
> + }
> +}
> +
Is PA_SINK_MESSAGE_SET_VOLUME_SYNCED rather than
PA_SINK_MESSAGE_SET_SHARED_VOLUME correct here?
I'm guessing from the below hunks that it is but it's getting a bit
confusing... with the message defines here...
> /* Called from IO thread, except when it is not */
> int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
> pa_sink *s = PA_SINK(o);
> @@ -1913,7 +2143,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
>
> /* In flat volume mode we need to update the volume as
> * well */
> - return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
> + return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
> }
>
> case PA_SINK_MESSAGE_REMOVE_INPUT: {
> @@ -1956,7 +2186,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
>
> /* In flat volume mode we need to update the volume as
> * well */
> - return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
> + return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
> }
>
> case PA_SINK_MESSAGE_START_MOVE: {
> @@ -2001,7 +2231,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
>
> /* In flat volume mode we need to update the volume as
> * well */
> - return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
> + return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
> }
>
> case PA_SINK_MESSAGE_FINISH_MOVE: {
> @@ -2042,9 +2272,17 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
> pa_sink_request_rewind(s, nbytes);
> }
>
> - /* In flat volume mode we need to update the volume as
> - * well */
> - return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
> + return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
> + }
> +
> + case PA_SINK_MESSAGE_SET_SHARED_VOLUME: {
> + pa_sink *root_sink = s;
> +
> + while (root_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
> + root_sink = root_sink->input_to_master->sink;
> +
> + set_shared_volume_within_thread(root_sink);
> + return 0;
> }
The above changes make me think my previous comment does not require any
action. Just included for context.
This wasn't a massively thorough review, but it's the best I can likely
do without properly getting a handle on all the volume code - so sorry
about that.
In the mean time I've pushed all the changes I have and we can all go
mental on fixing it up :)
Cheers
Col
--
Colin Guthrie
gmane(at)colin.guthr.ie
http://colin.guthr.ie/
Day Job:
Tribalogic Limited [http://www.tribalogic.net/]
Open Source:
Mageia Contributor [http://www.mageia.org/]
PulseAudio Hacker [http://www.pulseaudio.org/]
Trac Hacker [http://trac.edgewall.org/]
More information about the pulseaudio-discuss
mailing list