[pulseaudio-discuss] [PATCH 2/3] stream-interaction: Add support for fading in case of ducking/unducking
Sangchul Lee
sangchul1011 at gmail.com
Wed Jul 20 16:22:01 UTC 2016
Ducking/unducking with fading can be applied according to variables
for fading duration. In case of a stream that should be affected by
more than two groups of fading simultaneously, the lowest volume
among the groups will be applied.
Signed-off-by: Sangchul Lee <sc11.lee at samsung.com>
---
src/modules/stream-interaction.c | 269 +++++++++++++++++++++++++++++++++------
1 file changed, 232 insertions(+), 37 deletions(-)
diff --git a/src/modules/stream-interaction.c b/src/modules/stream-interaction.c
index a610996..2b6a07f 100644
--- a/src/modules/stream-interaction.c
+++ b/src/modules/stream-interaction.c
@@ -34,12 +34,21 @@
#include "stream-interaction.h"
+struct fade_durations {
+ long out;
+ long in;
+};
+
struct group {
char *name;
pa_idxset *trigger_roles;
pa_idxset *interaction_roles;
pa_idxset *interacted_inputs;
+ /* below are only for ducking */
pa_volume_t volume;
+ pa_idxset *triggered_inputs;
+ struct fade_durations fade_durs;
+ bool fade_needed:1;
};
struct userdata {
@@ -58,33 +67,76 @@ struct userdata {
*sink_input_proplist_changed_slot;
};
-static const char *get_trigger_role(struct userdata *u, pa_sink_input *i, struct group *g) {
+static const char *get_trigger_role_with_update_fade(struct userdata *u, pa_sink_input *i, bool new_stream, struct group *g) {
const char *role, *trigger_role;
- uint32_t role_idx;
+ uint32_t role_idx, trigger_idx;
+ void *si;
+
+ pa_assert(u);
+ pa_assert(i);
+ pa_assert(g);
if (!(role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)))
role = "no_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;
+ if (u->duck && new_stream)
+ g->fade_needed = true;
+
+ PA_IDXSET_FOREACH(trigger_role, g->trigger_roles, role_idx) {
+ if (pa_streq(role, trigger_role)) {
+ if (u->duck && (g->fade_durs.in > 0 || g->fade_durs.out > 0)) {
+ if (new_stream) {
+ /* Update fade state for this group which is for determining that
+ * the incoming stream should be ducked with fade in or just be ducked */
+ PA_IDXSET_FOREACH(si, g->triggered_inputs, trigger_idx) {
+ if (i == si) {
+ g->fade_needed = false;
+ break;
+ }
+ }
+ }
+ pa_idxset_put(g->triggered_inputs, i, NULL);
}
- }
- } else {
- PA_IDXSET_FOREACH(trigger_role, g->trigger_roles, role_idx) {
- if (pa_streq(role, trigger_role))
- return trigger_role;
+ return trigger_role;
}
}
return NULL;
}
-static const char *find_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_input *ignore, struct group *g) {
+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;
+
+ pa_assert(u);
+ pa_assert(i);
+ pa_assert(g);
+
+ if (!(role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)))
+ role = "no_role";
+
+ PA_IDXSET_FOREACH(trigger_role, g->trigger_roles, role_idx)
+ if (pa_streq(role, trigger_role))
+ return trigger_role;
+
+ return NULL;
+}
+
+static const char *get_trigger_role_all_groups(struct userdata *u, pa_sink_input *i) {
+ const char *found = NULL;
+ uint32_t j;
+
+ pa_assert(u);
+ pa_assert(i);
+
+ for (j = 0; j < u->n_groups; j++)
+ if ((found = get_trigger_role(u, i, u->groups[j])))
+ return found;
+
+ return found;
+}
+
+static const char *find_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool new_stream, struct group *g) {
pa_sink_input *j;
uint32_t idx;
const char *trigger_role;
@@ -96,7 +148,10 @@ 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, g);
+ if (u->duck && (g->fade_durs.in > 0 || g->fade_durs.out > 0))
+ trigger_role = get_trigger_role_with_update_fade(u, j, new_stream, g);
+ else
+ 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;
}
@@ -104,7 +159,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, struct group *g) {
+static const char *find_global_trigger_stream(struct userdata *u, pa_sink *s, pa_sink_input *ignore, bool new_stream, struct group *g) {
const char *trigger_role = NULL;
pa_assert(u);
@@ -112,26 +167,76 @@ 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, g)))
+ if ((trigger_role = find_trigger_stream(u, s, ignore, new_stream, g)))
break;
} else
- trigger_role = find_trigger_stream(u, s, ignore, g);
+ trigger_role = find_trigger_stream(u, s, ignore, new_stream, 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, struct group *g) {
+static bool is_affected_by_other_groups(struct userdata *u, pa_sink_input *s, struct group *skip_g, pa_volume_t *affected_min_vol) {
+ bool ret = false;
+ uint32_t i;
+ pa_sink_input *si;
+ pa_volume_t vol = PA_VOLUME_MAX;
+
+ pa_assert(u);
+
+ for (i = 0; i < u->n_groups; i++) {
+ uint32_t idx;
+ if (u->groups[i] == skip_g)
+ continue;
+ PA_IDXSET_FOREACH(si, u->groups[i]->interacted_inputs, idx)
+ if (si == s) {
+ if (u->groups[i]->volume < vol)
+ vol = u->groups[i]->volume;
+ ret = true;
+ }
+ }
+ if (ret)
+ *affected_min_vol = vol;
+
+ return ret;
+}
+
+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] = g->volume;
+ if (g->fade_durs.in || g->fade_durs.out) {
+ bool affected = false;
+ pa_volume_t min_vol;
+ pa_cvolume_ramp vol_ramp;
+
+ pa_cvolume_ramp_set(&vol_ramp, i->volume.channels, PA_VOLUME_RAMP_TYPE_LINEAR, g->fade_durs.out, g->volume);
+ if (!g->fade_needed) {
+ pa_cvolume vol;
+ vol.channels = 1;
+ vol.values[0] = g->volume;
+ pa_log_debug("Found a '%s' stream(%u) that should be ducked by '%s'.", interaction_role, i->index, g->name);
+
+ pa_sink_input_add_volume_factor(i, g->name, &vol);
+ pa_sink_input_set_volume_ramp(i, &vol_ramp, false);
+ } else {
+ affected = is_affected_by_other_groups(u, i, g, &min_vol);
+ pa_cvolume_ramp_set(&vol_ramp, i->volume.channels, PA_VOLUME_RAMP_TYPE_LINEAR,
+ g->fade_durs.out, affected ? MIN(g->volume, min_vol) : g->volume);
+ pa_log_debug("Found a '%s' stream(%u) that should be ducked with fade-out(%lums) by '%s'.",
+ interaction_role, i->index, g->fade_durs.out, g->name);
- 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);
+ pa_sink_input_set_volume_ramp(i, &vol_ramp, true);
+ }
+ } else {
+ pa_cvolume vol;
+ vol.channels = 1;
+ vol.values[0] = g->volume;
+
+ pa_log_debug("Found a '%s' stream of '%s' that ducks a '%s' stream(%u).", trigger_role, g->name, interaction_role, i->index);
+ 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);
+ pa_log_debug("Found a '%s' stream that corks/mutes a '%s' stream(%u).", trigger_role, interaction_role, i->index);
pa_sink_input_set_mute(i, true, false);
pa_sink_input_send_event(i, PA_STREAM_EVENT_REQUEST_CORK, NULL);
}
@@ -140,11 +245,26 @@ 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, struct group *g) {
if (u->duck) {
- 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);
+ if (g->fade_durs.in || g->fade_durs.out) {
+ bool affected = false;
+ pa_volume_t min_vol;
+ pa_cvolume_ramp vol_ramp;
+
+ pa_log_debug("In '%s', found a '%s' stream(%u) that should be unducked with fade-in(%lums)",
+ g->name, interaction_role, i->index, g->fade_durs.in);
+ pa_sink_input_remove_volume_factor(i, g->name);
+ affected = is_affected_by_other_groups(u, i, g, &min_vol);
+ pa_cvolume_ramp_set(&vol_ramp, i->volume.channels, PA_VOLUME_RAMP_TYPE_LINEAR,
+ g->fade_durs.in, affected ? min_vol : PA_VOLUME_NORM);
+
+ pa_sink_input_set_volume_ramp(i, &vol_ramp, true);
+ } else {
+ pa_log_debug("In '%s', found a '%s' stream(%u) that should be unducked", g->name, interaction_role, i->index);
+ 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);
+ pa_log_debug("Found a '%s' stream(%u) that should be uncorked/unmuted.", interaction_role, i->index);
if (i->muted)
pa_sink_input_set_mute(i, false, false);
if (corked)
@@ -244,15 +364,19 @@ static pa_hook_result_t process(struct userdata *u, pa_sink_input *i, bool creat
pa_assert(u);
pa_sink_input_assert_ref(i);
- if (!create)
- for (j = 0; j < u->n_groups; j++)
+ if (!create) {
+ for (j = 0; j < u->n_groups; j++) {
pa_idxset_remove_by_data(u->groups[j]->interacted_inputs, i, NULL);
+ if (u->duck)
+ pa_idxset_remove_by_data(u->groups[j]->triggered_inputs, i, NULL);
+ }
+ }
if (!i->sink)
return PA_HOOK_OK;
for (j = 0; j < u->n_groups; j++) {
- trigger_role = find_global_trigger_stream(u, i->sink, create ? NULL : i, u->groups[j]);
+ trigger_role = find_global_trigger_stream(u, i->sink, create ? NULL : i, new_stream, u->groups[j]);
apply_interaction(u, i->sink, trigger_role, create ? NULL : i, new_stream, u->groups[j]);
}
@@ -290,7 +414,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, NULL))
+ if (PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(i)) && get_trigger_role_all_groups(u, i))
return process(u, i, true, false);
return PA_HOOK_OK;
@@ -300,7 +424,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, NULL))
+ if (PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(i)) && get_trigger_role_all_groups(u, i))
return process(u, i, true, false);
return PA_HOOK_OK;
@@ -342,9 +466,12 @@ int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]) {
if (u->duck) {
const char *volumes;
+ const char *durations;
uint32_t group_count_tr = 0;
uint32_t group_count_du = 0;
uint32_t group_count_vol = 0;
+ uint32_t group_count_fd_out = 0;
+ uint32_t group_count_fd_in = 0;
roles = pa_modargs_get_value(ma, "trigger_roles", NULL);
if (roles) {
@@ -373,9 +500,28 @@ int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]) {
pa_xfree(n);
}
}
+ durations = pa_modargs_get_value(ma, "fade_out", NULL);
+ if (durations) {
+ const char *split_state = NULL;
+ char *n = NULL;
+ while ((n = pa_split(durations, "/", &split_state))) {
+ group_count_fd_out++;
+ pa_xfree(n);
+ }
+ }
+ durations = pa_modargs_get_value(ma, "fade_in", NULL);
+ if (durations) {
+ const char *split_state = NULL;
+ char *n = NULL;
+ while ((n = pa_split(durations, "/", &split_state))) {
+ group_count_fd_in++;
+ 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))) {
+ ((group_count_tr != group_count_du) || (group_count_tr != group_count_vol) ||
+ (group_count_fd_out > group_count_tr) || (group_count_fd_in > group_count_tr))) {
pa_log("Invalid number of groups");
goto fail;
}
@@ -390,8 +536,10 @@ int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]) {
u->groups[i]->trigger_roles = pa_idxset_new(NULL, NULL);
u->groups[i]->interaction_roles = pa_idxset_new(NULL, NULL);
u->groups[i]->interacted_inputs = pa_idxset_new(NULL, NULL);
- if (u->duck)
+ if (u->duck) {
+ u->groups[i]->triggered_inputs = pa_idxset_new(NULL, NULL);
u->groups[i]->name = pa_sprintf_malloc("ducking_group_%u", i);
+ }
}
roles = pa_modargs_get_value(ma, "trigger_roles", NULL);
@@ -459,6 +607,7 @@ int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]) {
if (u->duck) {
const char *volumes;
+ const char *durations;
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;
@@ -479,6 +628,50 @@ int pa_stream_interaction_init(pa_module *m, const char* const v_modargs[]) {
pa_xfree(n);
}
}
+
+ u->groups[0]->fade_durs.out = 0L;
+ durations = pa_modargs_get_value(ma, "fade_out", NULL);
+ if (durations) {
+ const char *group_split_state = NULL;
+ char *n = NULL;
+ i = 0;
+ while ((n = pa_split(durations, "/", &group_split_state))) {
+ if (n[0] != '\0') {
+ if (pa_atol(n, &(u->groups[i++]->fade_durs.out))) {
+ pa_log("Failed to pa_atol() for fade out");
+ pa_xfree(n);
+ goto fail;
+ }
+ } else {
+ pa_log("empty fade out duration");
+ pa_xfree(n);
+ goto fail;
+ }
+ pa_xfree(n);
+ }
+ }
+
+ u->groups[0]->fade_durs.in = 0L;
+ durations = pa_modargs_get_value(ma, "fade_in", NULL);
+ if (durations) {
+ const char *group_split_state = NULL;
+ char *n = NULL;
+ i = 0;
+ while ((n = pa_split(durations, "/", &group_split_state))) {
+ if (n[0] != '\0') {
+ if (pa_atol(n, &(u->groups[i++]->fade_durs.in))) {
+ pa_log("Failed to pa_atol() for fade in");
+ pa_xfree(n);
+ goto fail;
+ }
+ } else {
+ pa_log("empty fade in duration");
+ pa_xfree(n);
+ goto fail;
+ }
+ pa_xfree(n);
+ }
+ }
}
if (pa_modargs_get_value_boolean(ma, "global", &global) < 0) {
@@ -524,8 +717,10 @@ void pa_stream_interaction_done(pa_module *m) {
pa_idxset_free(u->groups[j]->trigger_roles, pa_xfree);
pa_idxset_free(u->groups[j]->interaction_roles, pa_xfree);
pa_idxset_free(u->groups[j]->interacted_inputs, pa_xfree);
- if (u->duck)
+ if (u->duck) {
+ pa_idxset_free(u->groups[j]->triggered_inputs, pa_xfree);
pa_xfree(u->groups[j]->name);
+ }
pa_xfree(u->groups[j]);
}
pa_xfree(u->groups);
--
2.7.4
More information about the pulseaudio-discuss
mailing list