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 | 268 +++++++++++++++++++++++++++++++++------ 1 file changed, 231 insertions(+), 37 deletions(-) diff --git a/src/modules/stream-interaction.c b/src/modules/stream-interaction.c index 32df9fb..d8e2a6b 100644 --- a/src/modules/stream-interaction.c +++ b/src/modules/stream-interaction.c @@ -34,12 +34,20 @@ #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; pa_volume_t volume; + pa_idxset *triggered_inputs; + struct fade_durations fade_durs; + bool fade_needed:1; }; struct userdata { @@ -58,33 +66,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 +147,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 +158,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 +166,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 +244,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 +363,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 +413,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 +423,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; @@ -343,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) { @@ -374,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; } @@ -391,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); @@ -462,6 +609,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; @@ -482,6 +630,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) { @@ -529,8 +721,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