[pulseaudio-discuss] [PATCH v3] role-ducking: Add support for ducking group

Sangchul Lee sangchul1011 at gmail.com
Wed Apr 13 01:48:44 UTC 2016


Now, trigger_roles, ducking_roles and volume can be divided into several groups by slash.
That means each group can be affected by its own volume policy.

If we need to apply ducking volume level differently that is triggered from
each trigger role(s), this feature would be useful for this purpose.

For example, let's assume that tts should take music and video's volume down to 40%
whereas voice_recognition should take those and tts's volume down to 20%.
In this case, the configuration can be written as below.
  trigger_roles=tts/voice_recognition ducking_roles=music,video/music,video,tts volume=40%/20%

If one of ducking role is affected by more than two trigger roles simultaneously,
volume of the ducking role will be applied by method of multiplication.
And it works in the same way as before without any slash.

Signed-off-by: Sangchul Lee <sc11.lee at samsung.com>
---

Here's updated patch for v3.
Thanks for the review, tanu.

Notes:
 v2 changelog: revise codes according to Tanu's comments
  - commit message enhancement
  - definition of group structure
  - rename variable and revise logs
  - handle error case of empty parsed string
  - fix codes to follow coding convention for private struct
  - pass pointer directly instead of group index

 v3 changelog: it is integrated with the latest upstream codes
  - remove a duplicate member of userdata
  - revise an inappropriate log message and comparison for indicating a failure
  - fix invalid logic regarding removing hashmap for interaction_state

 src/modules/module-role-ducking.c |   6 +-
 src/modules/stream-interaction.c  | 293 +++++++++++++++++++++++++++-----------
 2 files changed, 212 insertions(+), 87 deletions(-)

diff --git a/src/modules/module-role-ducking.c b/src/modules/module-role-ducking.c
index 1e145ba..add2d36 100644
--- a/src/modules/module-role-ducking.c
+++ b/src/modules/module-role-ducking.c
@@ -32,10 +32,10 @@ PA_MODULE_DESCRIPTION("Apply a ducking effect based on streams roles");
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(true);
 PA_MODULE_USAGE(
-        "trigger_roles=<Comma separated list of roles which will trigger a ducking> "
-        "ducking_roles=<Comma separated list of roles which will be ducked> "
+        "trigger_roles=<Comma(and slash) separated list of roles which will trigger a ducking. Slash can divide the roles into groups>"
+        "ducking_roles=<Comma(and slash) separated list of roles which will be ducked. Slash can divide the roles into groups>"
         "global=<Should we operate globally or only inside the same device?>"
-        "volume=<Volume for the attenuated streams. Default: -20dB"
+        "volume=<Volume for the attenuated streams. Default: -20dB. If trigger_roles and ducking_roles are separated by slash, use slash for dividing volume group>"
 );
 
 static const char* const valid_modargs[] = {
diff --git a/src/modules/stream-interaction.c b/src/modules/stream-interaction.c
index 5df17c4..f8ffbf3 100644
--- a/src/modules/stream-interaction.c
+++ b/src/modules/stream-interaction.c
@@ -34,13 +34,18 @@
 
 #include "stream-interaction.h"
 
-struct userdata {
-    pa_core *core;
-    const char *name;
-    pa_hashmap *interaction_state;
+struct group {
+    char *name;
     pa_idxset *trigger_roles;
     pa_idxset *interaction_roles;
+    pa_hashmap *interaction_state;
     pa_volume_t volume;
+};
+
+struct userdata {
+    pa_core *core;
+    uint32_t n_groups;
+    struct group **groups;
     bool global:1;
     bool duck:1;
     pa_hook_slot
@@ -53,21 +58,33 @@ struct userdata {
         *sink_input_proplist_changed_slot;
 };
 
-static const char *get_trigger_role(struct userdata *u, pa_sink_input *i) {
+static const char *get_trigger_role(struct userdata *u, pa_sink_input *i, struct group *g) {
     const char *role, *trigger_role;
     uint32_t role_idx;
 
     if (!(role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)))
         role = "no_role";
 
-    PA_IDXSET_FOREACH(trigger_role, u->trigger_roles, role_idx) {
-        if (pa_streq(role, trigger_role))
-            return trigger_role;
+    if (g == NULL) {
+        /* get it from all groups */
+        uint32_t j;
+        for (j = 0; j < u->n_groups; j++) {
+            PA_IDXSET_FOREACH(trigger_role, u->groups[j]->trigger_roles, role_idx) {
+                if (pa_streq(role, trigger_role))
+                    return trigger_role;
+            }
+        }
+    } else {
+        PA_IDXSET_FOREACH(trigger_role, g->trigger_roles, role_idx) {
+            if (pa_streq(role, trigger_role))
+                return trigger_role;
+        }
     }
+
     return NULL;
 }
 
-static const char *find_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_input *ignore) {
+static const char *find_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_input *ignore, struct group *g) {
     pa_sink_input *j;
     uint32_t idx;
     const char *trigger_role;
@@ -80,7 +97,7 @@ static const char *find_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_i
         if (j == ignore)
             continue;
 
-        trigger_role = get_trigger_role(u, j);
+        trigger_role = get_trigger_role(u, j, g);
         if (trigger_role && !j->muted && pa_sink_input_get_state(j) != PA_SINK_INPUT_CORKED)
             return trigger_role;
     }
@@ -88,7 +105,7 @@ static const char *find_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_i
     return NULL;
 }
 
-static const char *find_global_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_input *ignore) {
+static const char *find_global_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_input *ignore, struct group *g) {
     const char *trigger_role = NULL;
 
     pa_assert(u);
@@ -96,23 +113,23 @@ static const char *find_global_trigger_stream(struct userdata *u, pa_sink *s, pa
     if (u->global) {
         uint32_t idx;
         PA_IDXSET_FOREACH(s, u->core->sinks, idx)
-            if ((trigger_role = find_trigger_stream(u, s, ignore)))
+            if ((trigger_role = find_trigger_stream(u, s, ignore, g)))
                 break;
     } else
-        trigger_role = find_trigger_stream(u, s, ignore);
+        trigger_role = find_trigger_stream(u, s, ignore, g);
 
     return trigger_role;
 }
 
-static void cork_or_duck(struct userdata *u, pa_sink_input *i, const char *interaction_role,  const char *trigger_role, bool interaction_applied) {
+static void cork_or_duck(struct userdata *u, pa_sink_input *i, const char *interaction_role,  const char *trigger_role, bool interaction_applied, struct group *g) {
 
     if (u->duck && !interaction_applied) {
         pa_cvolume vol;
         vol.channels = 1;
-        vol.values[0] = u->volume;
+        vol.values[0] = g->volume;
 
-        pa_log_debug("Found a '%s' stream that ducks a '%s' stream.", trigger_role, interaction_role);
-        pa_sink_input_add_volume_factor(i, u->name, &vol);
+        pa_log_debug("Found a '%s' stream of '%s' that ducks a '%s' stream.", trigger_role, g->name, interaction_role);
+        pa_sink_input_add_volume_factor(i, g->name, &vol);
 
     } else if (!u->duck) {
         pa_log_debug("Found a '%s' stream that corks/mutes a '%s' stream.", trigger_role, interaction_role);
@@ -121,11 +138,11 @@ static void cork_or_duck(struct userdata *u, pa_sink_input *i, const char *inter
     }
 }
 
-static void uncork_or_unduck(struct userdata *u, pa_sink_input *i, const char *interaction_role, bool corked) {
+static void uncork_or_unduck(struct userdata *u, pa_sink_input *i, const char *interaction_role, bool corked, struct group *g) {
 
     if (u->duck) {
-       pa_log_debug("Found a '%s' stream that should be unducked", interaction_role);
-       pa_sink_input_remove_volume_factor(i, u->name);
+       pa_log_debug("In '%s', found a '%s' stream that should be unducked", g->name, interaction_role);
+       pa_sink_input_remove_volume_factor(i, g->name);
     }
     else if (corked || i->muted) {
        pa_log_debug("Found a '%s' stream that should be uncorked/unmuted.", interaction_role);
@@ -136,7 +153,7 @@ static void uncork_or_unduck(struct userdata *u, pa_sink_input *i, const char *i
     }
 }
 
-static inline void apply_interaction_to_sink(struct userdata *u, pa_sink *s, const char *new_trigger, pa_sink_input *ignore, bool new_stream) {
+static inline void apply_interaction_to_sink(struct userdata *u, pa_sink *s, const char *new_trigger, pa_sink_input *ignore, bool new_stream, struct group *g) {
     pa_sink_input *j;
     uint32_t idx, role_idx;
     const char *interaction_role;
@@ -155,10 +172,10 @@ static inline void apply_interaction_to_sink(struct userdata *u, pa_sink *s, con
         if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE)))
             role = "no_role";
 
-        PA_IDXSET_FOREACH(interaction_role, u->interaction_roles, role_idx) {
+        PA_IDXSET_FOREACH(interaction_role, g->interaction_roles, role_idx) {
             if ((trigger = pa_streq(role, interaction_role)))
                 break;
-            if ((trigger = (pa_streq(interaction_role, "any_role") && !get_trigger_role(u, j))))
+            if ((trigger = (pa_streq(interaction_role, "any_role") && !get_trigger_role(u, j, g))))
                break;
         }
         if (!trigger)
@@ -171,34 +188,34 @@ static inline void apply_interaction_to_sink(struct userdata *u, pa_sink *s, con
         corked = (pa_sink_input_get_state(j) == PA_SINK_INPUT_CORKED);
         if (new_stream && corked)
             corked = false;
-        interaction_applied = !!pa_hashmap_get(u->interaction_state, j);
+        interaction_applied = !!pa_hashmap_get(g->interaction_state, j);
 
         if (new_trigger && ((!corked && !j->muted) || u->duck)) {
             if (!interaction_applied)
-                pa_hashmap_put(u->interaction_state, j, PA_INT_TO_PTR(1));
+                pa_hashmap_put(g->interaction_state, j, PA_INT_TO_PTR(1));
 
-            cork_or_duck(u, j, role, new_trigger, interaction_applied);
+            cork_or_duck(u, j, role, new_trigger, interaction_applied, g);
 
         } else if (!new_trigger && interaction_applied) {
-            pa_hashmap_remove(u->interaction_state, j);
+            pa_hashmap_remove(g->interaction_state, j);
 
-            uncork_or_unduck(u, j, role, corked);
+            uncork_or_unduck(u, j, role, corked, g);
         }
     }
 }
 
-static void apply_interaction(struct userdata *u, pa_sink *s, const char *trigger_role, pa_sink_input *ignore, bool new_stream) {
+static void apply_interaction(struct userdata *u, pa_sink *s, const char *trigger_role, pa_sink_input *ignore, bool new_stream, struct group *g) {
     pa_assert(u);
 
     if (u->global) {
         uint32_t idx;
         PA_IDXSET_FOREACH(s, u->core->sinks, idx)
-            apply_interaction_to_sink(u, s, trigger_role, ignore, new_stream);
+            apply_interaction_to_sink(u, s, trigger_role, ignore, new_stream, g);
     } else
-        apply_interaction_to_sink(u, s, trigger_role, ignore, new_stream);
+        apply_interaction_to_sink(u, s, trigger_role, ignore, new_stream, g);
 }
 
-static void remove_interactions(struct userdata *u) {
+static void remove_interactions(struct userdata *u, struct group *g) {
     uint32_t idx, idx_input;
     pa_sink *s;
     pa_sink_input *j;
@@ -207,32 +224,36 @@ static void remove_interactions(struct userdata *u) {
 
     PA_IDXSET_FOREACH(s, u->core->sinks, idx) {
 
-      for (j = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx_input)); j; j = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx_input))) {
+        for (j = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx_input)); j; j = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx_input))) {
 
-         if(!!pa_hashmap_get(u->interaction_state, j)) {
-           corked = (pa_sink_input_get_state(j) == PA_SINK_INPUT_CORKED);
-           if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE)))
-              role = "no_role";
-           uncork_or_unduck(u, j, role, corked);
-         }
-      }
-   }
+            if(!!pa_hashmap_get(g->interaction_state, j)) {
+                corked = (pa_sink_input_get_state(j) == PA_SINK_INPUT_CORKED);
+                if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE)))
+                   role = "no_role";
+                uncork_or_unduck(u, j, role, corked, g);
+            }
+        }
+    }
 }
 
 static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, bool create, bool new_stream) {
     const char *trigger_role;
+    uint32_t j;
 
     pa_assert(u);
     pa_sink_input_assert_ref(i);
 
     if (!create)
-        pa_hashmap_remove(u->interaction_state, i);
+        for (j = 0; j < u->n_groups; j++)
+            pa_hashmap_remove(u->groups[j]->interaction_state, i);
 
     if (!i->sink)
         return PA_HOOK_OK;
 
-    trigger_role = find_global_trigger_stream(u, i->sink, create ? NULL : i);
-    apply_interaction(u, i->sink, trigger_role, create ? NULL : i, new_stream);
+    for (j = 0; j < u->n_groups; j++) {
+        trigger_role = find_global_trigger_stream(u, i->sink, create ? NULL : i, u->groups[j]);
+        apply_interaction(u, i->sink, trigger_role, create ? NULL : i, new_stream, u->groups[j]);
+    }
 
     return PA_HOOK_OK;
 }
@@ -268,7 +289,7 @@ static pa_hook_result_t sink_input_state_changed_cb(pa_core *core, pa_sink_input
     pa_core_assert_ref(core);
     pa_sink_input_assert_ref(i);
 
-    if (PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(i)) && get_trigger_role(u, i))
+    if (PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(i)) && get_trigger_role(u, i, NULL))
         return process(u, i, true, false);
 
     return PA_HOOK_OK;
@@ -278,7 +299,7 @@ static pa_hook_result_t sink_input_mute_changed_cb(pa_core *core, pa_sink_input
     pa_core_assert_ref(core);
     pa_sink_input_assert_ref(i);
 
-    if (PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(i)) && get_trigger_role(u, i))
+    if (PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(i)) && get_trigger_role(u, i, NULL))
         return process(u, i, true, false);
 
     return PA_HOOK_OK;
@@ -299,6 +320,7 @@ int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]) {
     struct userdata *u;
     const char *roles;
     bool global = false;
+    uint32_t i = 0;
 
     pa_assert(m);
 
@@ -310,52 +332,152 @@ int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]) {
     m->userdata = u = pa_xnew(struct userdata, 1);
 
     u->core = m->core;
-    u->name = m->name;
-    u->interaction_state = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
 
     u->duck = false;
-    if (pa_streq(u->name, "module-role-ducking")) {
+    if (pa_streq(m->name, "module-role-ducking"))
         u->duck = true;
-        u->volume = pa_sw_volume_from_dB(-20);
-        if (pa_modargs_get_value_volume(ma, "volume", &u->volume) < 0) {
-           pa_log("Failed to parse a volume parameter: volume");
-           goto fail;
+
+    u->n_groups = 1;
+
+    if (u->duck) {
+        const char *volumes;
+        uint32_t group_count_tr = 0;
+        uint32_t group_count_du = 0;
+        uint32_t group_count_vol = 0;
+
+        roles = pa_modargs_get_value(ma, "trigger_roles", NULL);
+        if (roles) {
+            const char *split_state = NULL;
+            char *n = NULL;
+            while ((n = pa_split(roles, "/", &split_state))) {
+                group_count_tr++;
+                pa_xfree(n);
+            }
+        }
+        roles = pa_modargs_get_value(ma, "ducking_roles", NULL);
+        if (roles) {
+            const char *split_state = NULL;
+            char *n = NULL;
+            while ((n = pa_split(roles, "/", &split_state))) {
+                group_count_du++;
+                pa_xfree(n);
+            }
+        }
+        volumes = pa_modargs_get_value(ma, "volume", NULL);
+        if (volumes) {
+            const char *split_state = NULL;
+            char *n = NULL;
+            while ((n = pa_split(volumes, "/", &split_state))) {
+                group_count_vol++;
+                pa_xfree(n);
+            }
+        }
+
+        if ((group_count_tr > 1 || group_count_du > 1 || group_count_vol > 1) &&
+            ((group_count_tr != group_count_du) || (group_count_tr != group_count_vol))) {
+            pa_log("Invalid number of groups");
+            goto fail;
         }
+
+        if (group_count_tr > 0)
+            u->n_groups = group_count_tr;
+    }
+
+    u->groups = pa_xnew0(struct group*, u->n_groups);
+    for (i = 0; i < u->n_groups; i++) {
+        u->groups[i] = pa_xnew0(struct group, 1);
+        u->groups[i]->trigger_roles = pa_idxset_new(NULL, NULL);
+        u->groups[i]->interaction_roles = pa_idxset_new(NULL, NULL);
+        u->groups[i]->interaction_state = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+        if (u->duck)
+            u->groups[i]->name = pa_sprintf_malloc("ducking_group_%u", i);
     }
 
-    u->trigger_roles = pa_idxset_new(NULL, NULL);
     roles = pa_modargs_get_value(ma, "trigger_roles", NULL);
     if (roles) {
-        const char *split_state = NULL;
-        char *n = NULL;
-        while ((n = pa_split(roles, ",", &split_state))) {
-            if (n[0] != '\0')
-                pa_idxset_put(u->trigger_roles, n, NULL);
-            else
-                pa_xfree(n);
+        const char *group_split_state = NULL;
+        char *roles_in_group = NULL;
+        i = 0;
+        while ((roles_in_group = pa_split(roles, "/", &group_split_state))) {
+            if (roles_in_group[0] != '\0') {
+                const char *split_state = NULL;
+                char *n = NULL;
+                while ((n = pa_split(roles_in_group, ",", &split_state))) {
+                    if (n[0] != '\0')
+                        pa_idxset_put(u->groups[i]->trigger_roles, n, NULL);
+                    else {
+                        pa_log("empty trigger role");
+                        pa_xfree(n);
+                        goto fail;
+                    }
+                }
+                i++;
+            } else {
+                pa_log("empty trigger roles");
+                pa_xfree(roles_in_group);
+                goto fail;
+            }
         }
     }
-    if (pa_idxset_isempty(u->trigger_roles)) {
+    if (pa_idxset_isempty(u->groups[0]->trigger_roles)) {
         pa_log_debug("Using role 'phone' as trigger role.");
-        pa_idxset_put(u->trigger_roles, pa_xstrdup("phone"), NULL);
+        pa_idxset_put(u->groups[0]->trigger_roles, pa_xstrdup("phone"), NULL);
     }
 
-    u->interaction_roles = pa_idxset_new(NULL, NULL);
     roles = pa_modargs_get_value(ma, u->duck ? "ducking_roles" : "cork_roles", NULL);
     if (roles) {
-        const char *split_state = NULL;
-        char *n = NULL;
-        while ((n = pa_split(roles, ",", &split_state))) {
-            if (n[0] != '\0')
-                pa_idxset_put(u->interaction_roles, n, NULL);
-            else
-                pa_xfree(n);
+        const char *group_split_state = NULL;
+        char *roles_in_group = NULL;
+        i = 0;
+        while ((roles_in_group = pa_split(roles, "/", &group_split_state))) {
+            if (roles_in_group[0] != '\0') {
+                const char *split_state = NULL;
+                char *n = NULL;
+                while ((n = pa_split(roles_in_group, ",", &split_state))) {
+                    if (n[0] != '\0')
+                        pa_idxset_put(u->groups[i]->interaction_roles, n, NULL);
+                    else {
+                        pa_log("empty ducking role");
+                        pa_xfree(n);
+                        goto fail;
+                     }
+                }
+                i++;
+            } else {
+                pa_log("empty ducking roles");
+                pa_xfree(roles_in_group);
+                goto fail;
+            }
         }
     }
-    if (pa_idxset_isempty(u->interaction_roles)) {
+    if (pa_idxset_isempty(u->groups[0]->interaction_roles)) {
         pa_log_debug("Using roles 'music' and 'video' as %s roles.", u->duck ? "ducking" : "cork");
-        pa_idxset_put(u->interaction_roles, pa_xstrdup("music"), NULL);
-        pa_idxset_put(u->interaction_roles, pa_xstrdup("video"), NULL);
+        pa_idxset_put(u->groups[0]->interaction_roles, pa_xstrdup("music"), NULL);
+        pa_idxset_put(u->groups[0]->interaction_roles, pa_xstrdup("video"), NULL);
+    }
+
+    if (u->duck) {
+        const char *volumes;
+        u->groups[0]->volume = pa_sw_volume_from_dB(-20);
+        if ((volumes = pa_modargs_get_value(ma, "volume", NULL))) {
+            const char *group_split_state = NULL;
+            char *n = NULL;
+            i = 0;
+            while ((n = pa_split(volumes, "/", &group_split_state))) {
+                if (n[0] != '\0') {
+                    if (pa_parse_volume(n, &(u->groups[i++]->volume)) < 0) {
+                        pa_log("Failed to parse volume");
+                        pa_xfree(n);
+                        goto fail;
+                    }
+                } else {
+                    pa_log("empty volume");
+                    pa_xfree(n);
+                    goto fail;
+                }
+                pa_xfree(n);
+            }
+        }
     }
 
     if (pa_modargs_get_value_boolean(ma, "global", &global) < 0) {
@@ -394,11 +516,19 @@ void pa_stream_interaction_done(pa_module *m) {
     if (!(u = m->userdata))
         return;
 
-    if (u->trigger_roles)
-        pa_idxset_free(u->trigger_roles, pa_xfree);
-
-    if (u->interaction_roles)
-        pa_idxset_free(u->interaction_roles, pa_xfree);
+    if (u->groups) {
+        uint32_t j;
+        for (j = 0; j < u->n_groups; j++) {
+            remove_interactions(u, u->groups[j]);
+            pa_idxset_free(u->groups[j]->trigger_roles, pa_xfree);
+            pa_idxset_free(u->groups[j]->interaction_roles, pa_xfree);
+            pa_hashmap_free(u->groups[j]->interaction_state);
+            if (u->duck)
+                pa_xfree(u->groups[j]->name);
+            pa_xfree(u->groups[j]);
+        }
+        pa_xfree(u->groups);
+    }
 
     if (u->sink_input_put_slot)
         pa_hook_slot_free(u->sink_input_put_slot);
@@ -415,11 +545,6 @@ void pa_stream_interaction_done(pa_module *m) {
     if (u->sink_input_proplist_changed_slot)
         pa_hook_slot_free(u->sink_input_proplist_changed_slot);
 
-    if (u->interaction_state) {
-        remove_interactions(u);
-        pa_hashmap_free(u->interaction_state);
-    }
-
     pa_xfree(u);
 
 }
-- 
2.1.4



More information about the pulseaudio-discuss mailing list