stream-manager: Support ducking API 41/208741/14 accepted/tizen/unified/20190716.111227 submit/tizen/20190716.020521
authorJeongmo Yang <jm80.yang@samsung.com>
Fri, 28 Jun 2019 00:57:20 +0000 (09:57 +0900)
committerJeongmo Yang <jm80.yang@samsung.com>
Mon, 15 Jul 2019 10:32:26 +0000 (19:32 +0900)
- Add new handler for ducking API

  server          : org.pulseaudio.Server
  object path     : /org/pulseaudio/StreamManager
  interface       : org.pulseaudio.StreamManager

  method name     : ActivateDucking
  method argument : unsigned int for stream index
                    boolean for enable
                    string for target stream
                    unsigned int for duration
                    double for ratio
  return value    : string for return message
                    - success : "STREAM_MANAGER_RETURN_OK"

[Version] 11.1.58
[Issue Type] New feature

Change-Id: Ib10aa11fe84ff73419798ead2b0db3b3f88c3c74
Signed-off-by: Jeongmo Yang <jm80.yang@samsung.com>
packaging/pulseaudio-modules-tizen.spec
src/stream-manager-dbus-priv.h
src/stream-manager-dbus.c
src/stream-manager-priv.h
src/stream-manager.c

index c5d741a..5b9b038 100644 (file)
@@ -1,6 +1,6 @@
 Name:             pulseaudio-modules-tizen
 Summary:          Pulseaudio modules for Tizen
-Version:          11.1.57
+Version:          11.1.58
 Release:          0
 Group:            Multimedia/Audio
 License:          LGPL-2.1+
index 8023f82..81df35e 100644 (file)
 #define STREAM_MANAGER_METHOD_NAME_CONTROL_FILTER                    "ControlFilter"
 #define STREAM_MANAGER_METHOD_NAME_CHECK_STREAM_EXIST_BY_PID         "CheckStreamExistByPid"
 #define STREAM_MANAGER_METHOD_NAME_GET_PID_OF_LATEST_STREAM          "GetPidOfLatestStream"
+#define STREAM_MANAGER_METHOD_NAME_ACTIVATE_DUCKING                  "ActivateDucking"
+#define STREAM_MANAGER_METHOD_NAME_GET_DUCKING_STATE                 "GetDuckingState"
 /* signal */
 #define STREAM_MANAGER_SIGNAL_NAME_VOLUME_CHANGED                    "VolumeChanged"
+#define STREAM_MANAGER_SIGNAL_NAME_DUCKING_STATE_CHANGED             "DuckingStateChanged"
 #define STREAM_MANAGER_SIGNAL_NAME_COMMAND                           "Command"
 
 enum method_handler_index {
@@ -77,6 +80,8 @@ enum method_handler_index {
     METHOD_HANDLER_CONTROL_FILTER,
     METHOD_HANDLER_CHECK_STREAM_EXIST_BY_PID,
     METHOD_HANDLER_GET_PID_OF_LATEST_STREAM,
+    METHOD_HANDLER_ACTIVATE_DUCKING,
+    METHOD_HANDLER_GET_DUCKING_STATE,
     METHOD_HANDLER_MAX
 };
 
@@ -223,6 +228,19 @@ pa_dbus_interface_info stream_manager_interface_info = {
     "   <arg name=\"io_direction\" direction=\"in\" type=\"s\"/>"              \
     "   <arg name=\"ret_msg\" direction=\"out\" type=\"s\"/>"                  \
     "  </method>"                                                              \
+    "  <method name=\"STREAM_MANAGER_METHOD_NAME_ACTIVATE_DUCKING\">"        \
+    "   <arg name=\"index\" direction=\"in\" type=\"u\"/>"                   \
+    "   <arg name=\"enable\" direction=\"in\" type=\"b\"/>"                  \
+    "   <arg name=\"target_type\" direction=\"in\" type=\"s\"/>"             \
+    "   <arg name=\"duration\" direction=\"in\" type=\"u\"/>"                \
+    "   <arg name=\"ratio\" direction=\"in\" type=\"d\"/>"                   \
+    "   <arg name=\"ret_msg\" direction=\"out\" type=\"s\"/>"                \
+    "  </method>"                                                            \
+    "  <method name=\"STREAM_MANAGER_METHOD_NAME_GET_DUCKING_STATE\">"       \
+    "   <arg name=\"index\" direction=\"in\" type=\"u\"/>"                   \
+    "   <arg name=\"is_ducked\" direction=\"out\" type=\"b\"/>"              \
+    "   <arg name=\"ret_msg\" direction=\"out\" type=\"s\"/>"                \
+    "  </method>"                                                            \
     "  <signal name=\"STREAM_MANAGER_SIGNAL_NAME_VOLUME_CHANGED\">"          \
     "   <arg name=\"direction\" type=\"s\"/>"                                \
     "   <arg name=\"volume_type\" type=\"s\"/>"                              \
@@ -241,6 +259,7 @@ pa_dbus_interface_info stream_manager_interface_info = {
     "</node>"
 #endif
 
+void send_ducking_state_changed_signal(DBusConnection *conn, const int index, const int is_ducked);
 void send_command_signal(DBusConnection *conn, const char *name, int value);
 int32_t init_sm_dbus(pa_stream_manager *m);
 void deinit_sm_dbus(pa_stream_manager *m);
index 5e7c715..4d0ab37 100644 (file)
@@ -64,6 +64,8 @@ static void handle_unset_filter(DBusConnection *conn, DBusMessage *msg, void *us
 static void handle_control_filter(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_check_stream_exist_by_pid(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_get_pid_of_latest_stream(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_activate_ducking(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_ducking_state(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void send_volume_changed_signal(DBusConnection *conn, const char *direction, const char *volume_type, const uint32_t volume_level);
 
 static pa_dbus_arg_info get_stream_info_args[]  = { { "stream_type", "s", "in" },
@@ -150,7 +152,44 @@ static pa_dbus_arg_info get_pid_of_latest_stream_args[]  = { { "io_direction", "
                                                             { "stream_types", "as", "in" },
                                                                      { "pid", "u", "out" },
                                                                { "ret_msg", "s", "out" } };
-static const char* signature_args_for_in[] = { "s", "", "uauau", "usi", "uu","ssu", "ss", "ss", "ssu", "ss", "sud", "su", "s", "s", "uu", "iu", "su", "s", "ssss", "s", "sss", "uss","sas"};
+static pa_dbus_arg_info activate_ducking_args[] = { { "index", "u", "in" },
+                                                  { "enable", "b",  "in" },
+                                           { "target_stream", "s",  "in" },
+                                                { "duration", "u",  "in" },
+                                                   { "ratio", "d",  "in" },
+                                               { "ret_msg", "s", "out" } };
+
+static pa_dbus_arg_info get_ducking_state_args[] = { { "index", "u", "in" },
+                                               { "is_ducked", "b",  "out" },
+                                                { "ret_msg", "s", "out" } };
+
+static const char* signature_args_for_in[] = {
+    "s",        /* METHOD_HANDLER_GET_STREAM_INFO */
+    "",         /* METHOD_HANDLER_GET_STREAM_LIST */
+    "uauau",    /* METHOD_HANDLER_SET_STREAM_ROUTE_DEVICES */
+    "usi",      /* METHOD_HANDLER_SET_STREAM_ROUTE_OPTION */
+    "uu",       /* METHOD_HANDLER_SET_STREAM_PREFERRED_DEVICE */
+    "ssu",      /* METHOD_HANDLER_SET_VOLUME_LEVEL */
+    "ss",       /* METHOD_HANDLER_GET_VOLUME_LEVEL */
+    "ss",       /* METHOD_HANDLER_GET_VOLUME_MAX_LEVEL */
+    "ssu",      /* METHOD_HANDLER_SET_VOLUME_MUTE */
+    "ss",       /* METHOD_HANDLER_GET_VOLUME_MUTE */
+    "sud",      /* METHOD_HANDLER_SET_VOLUME_RATIO */
+    "su",       /* METHOD_HANDLER_GET_VOLUME_RATIO */
+    "s",        /* METHOD_HANDLER_GET_CURRENT_VOLUME_TYPE */
+    "s",        /* METHOD_HANDLER_GET_CURRENT_MEDIA_ROUTING_PATH */
+    "uu",       /* METHOD_HANDLER_UPDATE_FOCUS_STATUS */
+    "iu",       /* METHOD_HANDLER_UPDATE_FOCUS_STATUS_BY_FOCUS_ID */
+    "su",       /* METHOD_HANDLER_UPDATE_RESTRICTION */
+    "s",        /* METHOD_HANDLER_UPDATE_CALL_PARAMETERS */
+    "ssss",     /* METHOD_HANDLER_SET_FILTER */
+    "s",        /* METHOD_HANDLER_UNSET_FILTER */
+    "sss",      /* METHOD_HANDLER_CONTROL_FILTER */
+    "uss",      /* METHOD_HANDLER_CHECK_STREAM_EXIST_BY_PID */
+    "sas",      /* METHOD_HANDLER_GET_PID_OF_LATEST_STREAM */
+    "ubsud",    /* METHOD_HANDLER_ACTIVATE_DUCKING */
+    "u"         /* METHOD_HANDLER_GET_DUCKING_STATE */
+    };
 
 static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
     [METHOD_HANDLER_GET_STREAM_INFO] = {
@@ -268,6 +307,16 @@ static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
         .arguments = get_pid_of_latest_stream_args,
         .n_arguments = sizeof(get_pid_of_latest_stream_args) / sizeof(pa_dbus_arg_info),
         .receive_cb = handle_get_pid_of_latest_stream },
+    [METHOD_HANDLER_ACTIVATE_DUCKING] = {
+        .method_name = STREAM_MANAGER_METHOD_NAME_ACTIVATE_DUCKING,
+        .arguments = activate_ducking_args,
+        .n_arguments = sizeof(activate_ducking_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_activate_ducking },
+    [METHOD_HANDLER_GET_DUCKING_STATE] = {
+        .method_name = STREAM_MANAGER_METHOD_NAME_GET_DUCKING_STATE,
+        .arguments = get_ducking_state_args,
+        .n_arguments = sizeof(get_ducking_state_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_get_ducking_state },
 };
 
 static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, void *userdata) {
@@ -1702,6 +1751,116 @@ invalid_argument:
     dbus_message_unref(reply);
 }
 
+static void handle_activate_ducking(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    dbus_uint32_t id = 0;
+    dbus_bool_t enable = 0;
+    const char *target_stream = NULL;
+    uint32_t idx = 0;
+    pa_sink_input *stream = NULL;
+    dbus_uint32_t duration = 0;
+    double ratio = 0.0;
+    DBusMessage *reply = NULL;
+    pa_stream_manager *m = (pa_stream_manager*)userdata;
+    stream_ducking *sd = NULL;
+    bool target_matched = false;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_UINT32, &id,
+                                       DBUS_TYPE_BOOLEAN, &enable,
+                                       DBUS_TYPE_STRING, &target_stream,
+                                       DBUS_TYPE_UINT32, &duration,
+                                       DBUS_TYPE_DOUBLE, &ratio,
+                                       DBUS_TYPE_INVALID));
+
+    pa_log_info("id[%u], enable[%u], target stream[%s], duration[%u], ratio[%lf]",
+        id, enable, target_stream, duration, ratio);
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    /* get stream_ducking */
+    pa_assert_se((sd = pa_hashmap_get(m->stream_duckings, (const void*)id)));
+
+    /* set volume ramp factor to target stream */
+    PA_IDXSET_FOREACH(stream, m->core->sink_inputs, idx) {
+        if (!pa_safe_streq(target_stream, pa_proplist_gets(stream->proplist, PA_PROP_MEDIA_ROLE)))
+            continue;
+
+        target_matched = true;
+        sd->ducking_stream_count++;
+
+        if (enable) {
+            pa_volume_t set_vol;
+            pa_cvolume_ramp vol_ramp;
+
+            snprintf(sd->vol_ramp_key, VOLUME_RAMP_KEY_LENGTH, "stream_ducking_%u_%s", id, target_stream);
+
+            pa_idxset_put(sd->idx_ducking_streams, (void *)stream, NULL);
+
+            set_vol = PA_VOLUME_NORM * ratio;
+
+            pa_log_info("ducking: [%p] set vol [%u], key [%s]", stream, set_vol, sd->vol_ramp_key);
+
+            pa_cvolume_ramp_set(&vol_ramp, stream->volume.channels,
+                PA_VOLUME_RAMP_TYPE_LINEAR, (long)duration, set_vol);
+
+            pa_sink_input_add_volume_ramp_factor(stream, sd->vol_ramp_key, &vol_ramp, true);
+        } else {
+            pa_log_info("unducking : remove volume ramp factor [key:%s]", sd->vol_ramp_key);
+
+            pa_sink_input_remove_volume_ramp_factor(stream, sd->vol_ramp_key, true);
+        }
+    }
+
+    pa_log_info("ducking stream count %d", sd->ducking_stream_count);
+
+    pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_OK], DBUS_TYPE_INVALID));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+    dbus_message_unref(reply);
+
+    if (target_matched == false) {
+        /* change ducking state and send signal here,
+           because ramp_finish_cb could not be called in this case */
+        sd->is_ducked = enable;
+
+        pa_log_info("send signal for ramp finished(but, no stream matched) - is_ducked %d", sd->is_ducked);
+
+        send_ducking_state_changed_signal(pa_dbus_connection_get(m->dbus_conn), sd->trigger_index, sd->is_ducked);
+    }
+}
+
+static void handle_get_ducking_state(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    dbus_uint32_t id = 0;
+    DBusMessage *reply = NULL;
+    pa_stream_manager *m = (pa_stream_manager*)userdata;
+    stream_ducking *sd = NULL;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_UINT32, &id,
+                                       DBUS_TYPE_INVALID));
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    /* get stream_ducking */
+    pa_assert_se((sd = pa_hashmap_get(m->stream_duckings, (const void*)id)));
+
+    pa_log_info("id[%u], is_ducked[%u]", id, sd->is_ducked);
+
+    pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &sd->is_ducked, DBUS_TYPE_INVALID));
+    pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[RET_MSG_OK], DBUS_TYPE_INVALID));
+
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+    dbus_message_unref(reply);
+}
+
 static DBusHandlerResult handle_methods(DBusConnection *conn, DBusMessage *msg, void *userdata) {
     int idx = 0;
     pa_stream_manager *m = (pa_stream_manager*)userdata;
@@ -1775,6 +1934,27 @@ static void send_volume_changed_signal(DBusConnection *conn, const char *directi
     return;
 }
 
+void send_ducking_state_changed_signal(DBusConnection *conn, const int index, const int is_ducked)
+{
+    DBusMessage *signal_msg;
+    DBusMessageIter msg_iter;
+
+    pa_assert(conn);
+
+    pa_log_debug("trigger index %d : is_ducked[%d]", index, is_ducked);
+
+    pa_assert_se((signal_msg = dbus_message_new_signal(STREAM_MANAGER_OBJECT_PATH, STREAM_MANAGER_INTERFACE, STREAM_MANAGER_SIGNAL_NAME_DUCKING_STATE_CHANGED)));
+    dbus_message_iter_init_append(signal_msg, &msg_iter);
+
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, &index);
+    dbus_message_iter_append_basic(&msg_iter, DBUS_TYPE_INT32, &is_ducked);
+
+    pa_assert_se(dbus_connection_send(conn, signal_msg, NULL));
+    dbus_message_unref(signal_msg);
+
+    return;
+}
+
 void send_command_signal(DBusConnection *conn, const char *name, int value) {
     DBusMessage *signal_msg;
     DBusMessageIter msg_iter;
index 72ff199..e2496f3 100644 (file)
@@ -113,6 +113,8 @@ typedef enum _notify_command_type {
 #define STREAM_FOCUS_STATE_RELEASED    "0"
 #define STREAM_FOCUS_STATE_ACQUIRED    "1"
 
+#define VOLUME_RAMP_KEY_LENGTH         24
+
 #define AVAIL_DEVICES_MAX 16
 #define AVAIL_FRAMEWORKS_MAX 16
 #define AVAIL_STREAMS_MAX 32
@@ -191,6 +193,14 @@ typedef struct _stream_parent {
     } preferred_device;
 } stream_parent;
 
+typedef struct _stream_ducking {
+    int trigger_index;
+    int ducking_stream_count;
+    pa_idxset *idx_ducking_streams;
+    char vol_ramp_key[VOLUME_RAMP_KEY_LENGTH];
+    bool is_ducked;
+} stream_ducking;
+
 typedef struct _filter_info {
     const char *filter_apply;
     const char *group;
@@ -216,6 +226,7 @@ struct _stream_manager {
     pa_hashmap *latency_infos;
     pa_hashmap *stream_parents;
     pa_hashmap *muted_streams;
+    pa_hashmap *stream_duckings;
     pa_idxset *mirroring_streams;
     cur_max_priority_stream cur_highest_priority;
     stream_restrictions restrictions;
index ca4638c..53b45db 100644 (file)
@@ -63,7 +63,8 @@
 #define CONVERT_TO_DEVICE_DIRECTION(stream_type) \
     ((stream_type == STREAM_SINK_INPUT) ? DM_DEVICE_DIRECTION_OUT : DM_DEVICE_DIRECTION_IN)
 
-#define STREAM_MANAGER_CLIENT_NAME "SOUND_MANAGER_STREAM_INFO" /* The client via sound-manager */
+#define STREAM_MANAGER_CLIENT_INFO    "SOUND_MANAGER_STREAM_INFO"    /* The stream info client via sound-manager */
+#define STREAM_MANAGER_CLIENT_DUCKING "SOUND_MANAGER_STREAM_DUCKING" /* The ducking client via sound-manager */
 #define VIRTUAL_STREAM_NAME "VIRTUAL_STREAM" /* The virtual stream created by sound-manager */
 #define DEFAULT_ROLE "media"
 #define SKIP_ROLE "skip"
@@ -2286,6 +2287,12 @@ static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *
 }
 
 static pa_hook_result_t sink_input_ramp_finish_cb(pa_core *core, pa_sink_input *i, pa_stream_manager *m) {
+    bool is_ducked = false;
+    stream_ducking *sd = NULL;
+    pa_sink_input *stream = NULL;
+    uint32_t idx;
+    void *state;
+
     pa_core_assert_ref(core);
     pa_sink_input_assert_ref(i);
 
@@ -2298,7 +2305,35 @@ static pa_hook_result_t sink_input_ramp_finish_cb(pa_core *core, pa_sink_input *
     /* Find a context id from all the ducked stream list by this stream index.
      * Check the number of managed streams of the context id, if it is the last one
      * then broadcast a signal with context id.*/
-    // send_command_signal(pa_dbus_connection_get(m->dbus_conn), "ducking_finished", context_id);
+    PA_HASHMAP_FOREACH(sd, m->stream_duckings, state) {
+        PA_IDXSET_FOREACH(stream, sd->idx_ducking_streams, idx) {
+            if (stream == i) {
+                pa_log_info("matched");
+                break;
+            }
+        }
+
+        if (stream == i)
+            break;
+    }
+
+    if (stream != i) {
+        pa_log_error("not found matched stream for %p", i);
+        return PA_HOOK_OK;
+    }
+
+    is_ducked = i->thread_info.ramp.ramps[0].start > i->thread_info.ramp.ramps[0].end;
+
+    /* remove trigger when unducked */
+    if (is_ducked == false)
+        pa_idxset_remove_by_data(sd->idx_ducking_streams, (void *)i, NULL);
+
+    /* send signal when all streams are ducked */
+    if (--sd->ducking_stream_count == 0) {
+        pa_log_info("send signal for ramp finished - is_ducked %d", is_ducked);
+        sd->is_ducked = is_ducked;
+        send_ducking_state_changed_signal(pa_dbus_connection_get(m->dbus_conn), sd->trigger_index, sd->is_ducked);
+    }
 
     return PA_HOOK_OK;
 }
@@ -3115,8 +3150,8 @@ static pa_hook_result_t device_connection_changed_hook_cb(pa_core *c, pa_tz_devi
 }
 
 static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, pa_stream_manager *m) {
-    pa_client *client = NULL;
     stream_parent *sp = NULL;
+    stream_ducking *sd = NULL;
     const char *name = NULL;
 
     pa_core_assert_ref(core);
@@ -3125,26 +3160,36 @@ static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t
     pa_log_info("subscribe_cb() is called, t(%x), idx(%u)", t, idx);
 
     if (t == (PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE)) {
+        pa_client *client = NULL;
+
         if ((client = pa_idxset_get_by_index(core->clients, idx)) == NULL) {
             pa_log_error(" - could not find any client that has idx(%u)", idx);
             return;
         }
+
         name = pa_proplist_gets(client->proplist, PA_PROP_APPLICATION_NAME);
-        if (!pa_safe_streq(name, STREAM_MANAGER_CLIENT_NAME)) {
-            pa_log_warn(" - this is not a client(%s) that we should take care of, skip it", name);
+
+        if (pa_safe_streq(name, STREAM_MANAGER_CLIENT_INFO)) {
+            /* add a stream parent */
+            sp = pa_xmalloc0(sizeof(stream_parent));
+            sp->idx_sink_inputs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+            sp->idx_source_outputs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+            sp->idx_route_in_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+            sp->idx_route_out_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+            pa_hashmap_put(m->stream_parents, (void*)idx, sp);
+            pa_log_debug(" - add sp(%p), idx(%u)", sp, idx);
+            return;
+        } else if (pa_safe_streq(name, STREAM_MANAGER_CLIENT_DUCKING)) {
+            /* add a stream ducking */
+            sd = pa_xmalloc0(sizeof(stream_ducking));
+            sd->idx_ducking_streams = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+            sd->trigger_index = idx;
+            pa_hashmap_put(m->stream_duckings, (void *)idx, sd);
+            pa_log_debug(" - add sd(%p), idx(%u)", sd, idx);
             return;
         }
-        /* add a stream parent */
-        sp = pa_xmalloc0(sizeof(stream_parent));
-        sp->idx_sink_inputs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
-        sp->idx_source_outputs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
-        sp->idx_route_in_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-        sp->idx_route_out_devices = pa_idxset_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-        pa_hashmap_put(m->stream_parents, (void*)idx, sp);
-        pa_log_debug(" - add sp(%p), idx(%u)", sp, idx);
-
     } else if (t == (PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_REMOVE)) {
-        /* remove the stream parent */
+        /* try to find stream parent with idx */
         sp = pa_hashmap_get(m->stream_parents, (const void*)idx);
         if (sp) {
             pa_log_debug(" - remove sp(%p), idx(%u)", sp, idx);
@@ -3156,10 +3201,32 @@ static void subscribe_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t
             if (sp->idx_route_out_devices)
                 pa_idxset_free(sp->idx_route_out_devices, pa_xfree);
             pa_xfree(sp);
-        } else {
-            pa_log_error(" - could not find any stream_parent that has idx(%u)", idx);
+            return;
+        }
+
+        /* try to find sd with idx */
+        sd = pa_hashmap_get(m->stream_duckings, (const void*)idx);
+        if (sd) {
+            uint32_t ducking_idx = 0;
+            pa_sink_input *stream = NULL;
+
+            pa_log_info(" - remove sd(%p), idx(%u)", sd, idx);
+
+            PA_IDXSET_FOREACH(stream, sd->idx_ducking_streams, ducking_idx) {
+                pa_log_info("remove volume ramp for remained stream %p, key %s", stream, sd->vol_ramp_key);
+                pa_sink_input_remove_volume_ramp_factor(stream, sd->vol_ramp_key, true);
+            }
+
+            pa_idxset_free(sd->idx_ducking_streams, NULL);
+            pa_hashmap_remove(m->stream_duckings, (const void*)idx);
+            pa_xfree(sd);
+
+            pa_log_info("done");
+            return;
         }
     }
+
+    pa_log_warn(" - this is not a client(%s) that we should take care of, skip it", name ? name : "NULL");
 }
 
 /* Message callback from HAL interface */
@@ -3292,6 +3359,7 @@ pa_stream_manager* pa_stream_manager_get(pa_core *c) {
     m->stream_parents = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
     m->muted_streams = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
     m->mirroring_streams = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+    m->stream_duckings = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
 
     m->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_new_cb, m);
     m->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_put_cb, m);
@@ -3413,6 +3481,9 @@ void pa_stream_manager_unref(pa_stream_manager *m) {
     if (m->stream_parents)
         pa_hashmap_free(m->stream_parents);
 
+    if (m->stream_duckings)
+        pa_hashmap_free(m->stream_duckings);
+
     deinit_volumes(m);
     deinit_stream_map(m);
     deinit_ipc(m);