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

Sangchul Lee sangchul1011 at gmail.com
Sun Mar 27 13:09:24 UTC 2016


2016-03-26 20:42 GMT+09:00 Tanu Kaskinen <tanuk at iki.fi>:
>> +typedef struct group {
>> +    char *name;
>>      pa_idxset *trigger_roles;
>>      pa_idxset *ducking_roles;
>>      pa_idxset *ducked_inputs;
>> -    bool global;
>>      pa_volume_t volume;
>> +} group;
>
> The convention in PulseAudio is to not use typedefs on private structs.
> See how "struct userdata" is defined: the definition doesn't use a
> typedef.

>> -static bool sink_has_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_input *ignore) {
>> +static bool sink_has_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_input *ignore, uint32_t group_idx) {
>
> Rather than passing the group index as a parameter, you can just give a
> direct pointer to the group (this same comment applies to all functions
> that you pass group_idx as a parameter).

Those are revised now as you said. Thanks.

>> +    roles = pa_modargs_get_value(ma, "trigger_roles", NULL);
>> +    if (roles) {
>> +        const char *group_split_state = NULL;
>> +        char *roles_in_groups = NULL;
>
> In v1 this variable was called "roles_in_group". That was a better
> name, because the string contains roles for only one group.

It's due to my inattention. It is fixed.

Here's the updated patch for v2.

---

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>
---

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

 it would be integrated with the latest upstream codes in the next update(v3)

 src/modules/module-role-ducking.c | 241 +++++++++++++++++++++++++++-----------
 1 file changed, 175 insertions(+), 66 deletions(-)

diff --git a/src/modules/module-role-ducking.c b/src/modules/module-role-ducking.c
index ee31b8c..5eb4fe3 100644
--- a/src/modules/module-role-ducking.c
+++ b/src/modules/module-role-ducking.c
@@ -39,10 +39,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[] = {
@@ -53,14 +53,20 @@ static const char* const valid_modargs[] = {
     NULL
 };
 
-struct userdata {
-    pa_core *core;
-    const char *name;
+struct group {
+    char *name;
     pa_idxset *trigger_roles;
     pa_idxset *ducking_roles;
     pa_idxset *ducked_inputs;
-    bool global;
     pa_volume_t volume;
+};
+
+struct userdata {
+    pa_core *core;
+    const char *name;
+    uint32_t n_groups;
+    struct group **groups;
+    bool global;
     pa_hook_slot
         *sink_input_put_slot,
         *sink_input_unlink_slot,
@@ -68,7 +74,7 @@ struct userdata {
         *sink_input_move_finish_slot;
 };
 
-static bool sink_has_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_input *ignore) {
+static bool sink_has_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_input *ignore, struct group *g) {
     pa_sink_input *j;
     uint32_t idx, role_idx;
     const char *trigger_role;
@@ -85,7 +91,7 @@ static bool sink_has_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_inp
         if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE)))
             continue;
 
-        PA_IDXSET_FOREACH(trigger_role, u->trigger_roles, role_idx) {
+        PA_IDXSET_FOREACH(trigger_role, g->trigger_roles, role_idx) {
             if (pa_streq(role, trigger_role)) {
                 pa_log_debug("Found a '%s' stream that will trigger the ducking.", trigger_role);
                 return true;
@@ -96,7 +102,7 @@ static bool sink_has_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_inp
     return false;
 }
 
-static bool sinks_have_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_input *ignore) {
+static bool sinks_have_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_input *ignore, struct group *g) {
     bool ret = false;
 
     pa_assert(u);
@@ -104,15 +110,15 @@ static bool sinks_have_trigger_streams(struct userdata *u, pa_sink *s, pa_sink_i
     if (u->global) {
         uint32_t idx;
         PA_IDXSET_FOREACH(s, u->core->sinks, idx)
-            if ((ret = sink_has_trigger_streams(u, s, ignore)))
+            if ((ret = sink_has_trigger_streams(u, s, ignore, g)))
                 break;
     } else
-        ret = sink_has_trigger_streams(u, s, ignore);
+        ret = sink_has_trigger_streams(u, s, ignore, g);
 
     return ret;
 }
 
-static void apply_ducking_to_sink(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool duck) {
+static void apply_ducking_to_sink(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool duck, struct group *g) {
     pa_sink_input *j;
     uint32_t idx, role_idx;
     const char *ducking_role;
@@ -131,44 +137,45 @@ static void apply_ducking_to_sink(struct userdata *u, pa_sink *s, pa_sink_input
         if (!(role = pa_proplist_gets(j->proplist, PA_PROP_MEDIA_ROLE)))
             continue;
 
-        PA_IDXSET_FOREACH(ducking_role, u->ducking_roles, role_idx) {
+        PA_IDXSET_FOREACH(ducking_role, g->ducking_roles, role_idx) {
             if ((trigger = pa_streq(role, ducking_role)))
                 break;
         }
         if (!trigger)
             continue;
 
-        i = pa_idxset_get_by_data(u->ducked_inputs, j, NULL);
+        i = pa_idxset_get_by_data(g->ducked_inputs, j, NULL);
         if (duck && !i) {
             pa_cvolume vol;
             vol.channels = 1;
-            vol.values[0] = u->volume;
+            vol.values[0] = g->volume;
 
-            pa_log_debug("Found a '%s' stream that should be ducked.", ducking_role);
-            pa_sink_input_add_volume_factor(j, u->name, &vol);
-            pa_idxset_put(u->ducked_inputs, j, NULL);
+            pa_log_debug("Found a '%s' stream that should be ducked by '%s'.", ducking_role, g->name);
+            pa_sink_input_add_volume_factor(j, g->name, &vol);
+            pa_idxset_put(g->ducked_inputs, j, NULL);
         } else if (!duck && i) { /* This stream should not longer be ducked */
-            pa_log_debug("Found a '%s' stream that should be unducked", ducking_role);
-            pa_idxset_remove_by_data(u->ducked_inputs, j, NULL);
-            pa_sink_input_remove_volume_factor(j, u->name);
+            pa_log_debug("Found a '%s' stream that should be unducked by '%s'", ducking_role, g->name);
+            pa_idxset_remove_by_data(g->ducked_inputs, j, NULL);
+            pa_sink_input_remove_volume_factor(j, g->name);
         }
     }
 }
 
-static void apply_ducking(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool duck) {
+static void apply_ducking(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool duck, struct group *g) {
     pa_assert(u);
 
     if (u->global) {
         uint32_t idx;
         PA_IDXSET_FOREACH(s, u->core->sinks, idx)
-            apply_ducking_to_sink(u, s, ignore, duck);
+            apply_ducking_to_sink(u, s, ignore, duck, g);
     } else
-        apply_ducking_to_sink(u, s, ignore, duck);
+        apply_ducking_to_sink(u, s, ignore, duck, g);
 }
 
 static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, bool duck) {
     bool should_duck = false;
     const char *role;
+    uint32_t j;
 
     pa_assert(u);
     pa_sink_input_assert_ref(i);
@@ -179,8 +186,10 @@ static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, bool duck)
     if (!i->sink)
         return PA_HOOK_OK;
 
-    should_duck = sinks_have_trigger_streams(u, i->sink, duck ? NULL : i);
-    apply_ducking(u, i->sink, duck ? NULL : i, should_duck);
+    for (j = 0; j < u->n_groups; j++) {
+        should_duck = sinks_have_trigger_streams(u, i->sink, duck ? NULL : i, u->groups[j]);
+        apply_ducking(u, i->sink, duck ? NULL : i, should_duck, u->groups[j]);
+    }
 
     return PA_HOOK_OK;
 }
@@ -193,9 +202,13 @@ static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struc
 }
 
 static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
+    uint32_t j;
+
     pa_sink_input_assert_ref(i);
 
-    pa_idxset_remove_by_data(u->ducked_inputs, i, NULL);
+    for (j = 0; j < u->n_groups; j++)
+        pa_idxset_remove_by_data(u->groups[j]->ducked_inputs, i, NULL);
+
     return process(u, i, false);
 }
 
@@ -217,6 +230,11 @@ int pa__init(pa_module *m) {
     pa_modargs *ma = NULL;
     struct userdata *u;
     const char *roles;
+    const char *volumes;
+    uint32_t group_count_tr = 0;
+    uint32_t group_count_du = 0;
+    uint32_t group_count_vol = 0;
+    uint32_t i = 0;
 
     pa_assert(m);
 
@@ -230,41 +248,137 @@ int pa__init(pa_module *m) {
     u->core = m->core;
     u->name = m->name;
 
-    u->ducked_inputs = pa_idxset_new(NULL, NULL);
-
-    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);
+        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);
         }
     }
-    if (pa_idxset_isempty(u->trigger_roles)) {
+    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;
+    else
+        u->n_groups = 1;
+
+    u->groups = pa_xmalloc0(u->n_groups * sizeof(struct group*));
+    for (i = 0; i < u->n_groups; i++) {
+        u->groups[i] = pa_xmalloc0(sizeof(struct group));
+        u->groups[i]->name = pa_sprintf_malloc("%s_group_%u", u->name, i);
+        u->groups[i]->trigger_roles = pa_idxset_new(NULL, NULL);
+        u->groups[i]->ducking_roles = pa_idxset_new(NULL, NULL);
+        u->groups[i]->ducked_inputs = pa_idxset_new(NULL, NULL);
+    }
+
+    roles = pa_modargs_get_value(ma, "trigger_roles", NULL);
+    if (roles) {
+        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->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->ducking_roles = pa_idxset_new(NULL, NULL);
     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))) {
-            if (n[0] != '\0')
-                pa_idxset_put(u->ducking_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]->ducking_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->ducking_roles)) {
+    if (pa_idxset_isempty(u->groups[0]->ducking_roles)) {
         pa_log_debug("Using roles 'music' and 'video' as ducking roles.");
-        pa_idxset_put(u->ducking_roles, pa_xstrdup("music"), NULL);
-        pa_idxset_put(u->ducking_roles, pa_xstrdup("video"), NULL);
+        pa_idxset_put(u->groups[0]->ducking_roles, pa_xstrdup("music"), NULL);
+        pa_idxset_put(u->groups[0]->ducking_roles, pa_xstrdup("video"), NULL);
+    }
+
+    u->groups[0]->volume = pa_sw_volume_from_dB(-20);
+    volumes = pa_modargs_get_value(ma, "volume", NULL);
+    if (volumes) {
+        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)) == -1) {
+                    pa_log("Failed to parse volume");
+                    pa_xfree(n);
+                    goto fail;
+                }
+            } else {
+                pa_log("empty volume");
+                pa_xfree(n);
+                goto fail;
+            }
+            pa_xfree(n);
+        }
     }
 
     u->global = false;
@@ -273,12 +387,6 @@ int pa__init(pa_module *m) {
         goto fail;
     }
 
-    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->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u);
     u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u);
     u->sink_input_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_start_cb, u);
@@ -300,23 +408,24 @@ fail:
 void pa__done(pa_module *m) {
     struct userdata* u;
     pa_sink_input *i;
+    uint32_t j;
 
     pa_assert(m);
 
     if (!(u = m->userdata))
         return;
 
-    if (u->trigger_roles)
-        pa_idxset_free(u->trigger_roles, pa_xfree);
-
-    if (u->ducking_roles)
-        pa_idxset_free(u->ducking_roles, pa_xfree);
-
-    if (u->ducked_inputs) {
-        while ((i = pa_idxset_steal_first(u->ducked_inputs, NULL)))
-            pa_sink_input_remove_volume_factor(i, u->name);
-
-        pa_idxset_free(u->ducked_inputs, NULL);
+    if (u->groups) {
+        for (j = 0; j < u->n_groups; j++) {
+            pa_idxset_free(u->groups[j]->trigger_roles, pa_xfree);
+            pa_idxset_free(u->groups[j]->ducking_roles, pa_xfree);
+            while ((i = pa_idxset_steal_first(u->groups[j]->ducked_inputs, NULL)))
+                pa_sink_input_remove_volume_factor(i, u->groups[j]->name);
+            pa_idxset_free(u->groups[j]->ducked_inputs, NULL);
+            pa_xfree(u->groups[j]->name);
+            pa_xfree(u->groups[j]);
+        }
+        pa_xfree(u->groups);
     }
 
     if (u->sink_input_put_slot)
-- 
2.1.4


More information about the pulseaudio-discuss mailing list