tizenaudio-policy: Add support for the preemptive device routing 08/221108/7
authorSangchul Lee <sc11.lee@samsung.com>
Fri, 20 Dec 2019 03:04:57 +0000 (12:04 +0900)
committerSangchul Lee <sc11.lee@samsung.com>
Tue, 7 Jan 2020 05:12:41 +0000 (14:12 +0900)
This new feature is only able to be applid to two routing types below.
 - STREAM_ROUTE_TYPE_AUTO, STREAM_ROUTE_TYPE_AUTO_LAST_CONNECTED

If user sets the preemptive device to a specific stream role, this device
has priority over all other devices that can be selected by the policy of
the stream role. Once it is set to a particular stream role, audio routing
of all other streams with the same stream role will be affected.

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

  method name     : SetStreamPreemptiveDevice
  method argument : [in] string for stream type (e.g. 'media')
                    [in] string for io direction ('in' or 'out')
                    [in] unsigned int for device id
  return value    : string for return message
                    - success                   : STREAM_MANAGER_RETURN_OK
                    - invalid argument error    : STREAM_MANAGER_RETURN_ERROR_INVALID_ARGUMENT
                    - policy error              : STREAM_MANAGER_RETURN_ERROR_POLICY
                    - internal error            : STREAM_MANAGER_RETURN_ERROR_INTERNAL

  method name     : GetStreamPreemptiveDevice
  method argument : [in] string for stream type (e.g. 'media')
                    [out] unsigned int for input device id
                    [out] unsigned int for output device id
  return value    : string for return message
                    - success                   : STREAM_MANAGER_RETURN_OK
                    - internal error            : STREAM_MANAGER_RETURN_ERROR_INTERNAL

[Version] 11.1.92
[Issue Type] New feature

Change-Id: I12dd1d8d5463426331955a4f7a3799b551685a4d
Signed-off-by: Sangchul Lee <sc11.lee@samsung.com>
packaging/pulseaudio-modules-tizen.spec
src/module-tizenaudio-policy.c
src/stream-manager-dbus-priv.h
src/stream-manager-dbus.c
src/stream-manager-priv.h
src/stream-manager.c
src/stream-manager.h

index 9700467..7db8497 100644 (file)
@@ -1,6 +1,6 @@
 Name:             pulseaudio-modules-tizen
 Summary:          Pulseaudio modules for Tizen
-Version:          11.1.91
+Version:          11.1.92
 Release:          0
 Group:            Multimedia/Audio
 License:          LGPL-2.1+
index 57fc284..877116a 100644 (file)
@@ -626,6 +626,35 @@ static inline bool is_cached_device_connected(const char* device_type, stream_ty
     return false;
 }
 
+static int32_t select_device_by_preemptive_device(struct userdata *u, pa_stream_manager_hook_data_for_select *data) {
+    uint32_t dm_device_id = 0;
+    pa_tz_device *device = NULL;
+    const char *device_type;
+
+    pa_assert(u);
+    pa_assert(data);
+
+    if (pa_stream_manager_get_preemptive_device_id(u->stream_manager, data->stream_type, data->stream_role, &dm_device_id))
+        return -1;
+
+    device = pa_device_manager_get_device_by_id(u->device_manager, dm_device_id);
+    device_type = pa_tz_device_get_type(device);
+    pa_log_debug("preemptive device is type:[%s] id:[%u]", device_type, dm_device_id);
+    if (data->stream_type == STREAM_SINK_INPUT)
+        *(data->proper_sink) = pa_tz_device_get_sink(device, data->device_role);
+    else
+        *(data->proper_source) = pa_tz_device_get_source(device, data->device_role);
+
+    if (data->origins_from_new_data)
+        pa_proplist_sets(GET_STREAM_NEW_PROPLIST(data->stream, data->stream_type),
+                         PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, device_type);
+    else
+        pa_proplist_sets(GET_STREAM_PROPLIST(data->stream, data->stream_type),
+                         PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, device_type);
+
+    return 0;
+}
+
 static void select_device_by_auto_or_auto_all_routing(struct userdata *u, pa_stream_manager_hook_data_for_select *data, pa_idxset *conn_devices)  {
     const char *device_type = NULL;
     uint32_t idx = 0;
@@ -639,6 +668,11 @@ static void select_device_by_auto_or_auto_all_routing(struct userdata *u, pa_str
     pa_assert(data);
     pa_assert(conn_devices);
 
+    if (data->route_type == STREAM_ROUTE_TYPE_AUTO) {
+        if (!select_device_by_preemptive_device(u, data))
+            return;
+    }
+
     PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) {
         pa_log_debug("[SELECT][AUTO(_ALL)] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, data->stream_role, device_type);
         if (!is_cached_device_connected(device_type, data->stream_type))
@@ -659,27 +693,32 @@ static void select_device_by_auto_or_auto_all_routing(struct userdata *u, pa_str
                 continue;
 
             if (data->stream_type == STREAM_SINK_INPUT) {
-                if (data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL && u->module_combine_sink) {
-                    *(data->proper_sink) = (pa_sink*)pa_namereg_get(u->core, SINK_NAME_COMBINED, PA_NAMEREG_SINK);
-                    pa_log_info("  -- found the combine-sink, set it to the sink");
+                if (data->origins_from_new_data) {
+                    if (data->route_type == STREAM_ROUTE_TYPE_AUTO_ALL && u->module_combine_sink) {
+                        *(data->proper_sink) = (pa_sink*)pa_namereg_get(u->core, SINK_NAME_COMBINED, PA_NAMEREG_SINK);
+                        pa_log_info("  -- found the combine-sink, set it to the sink");
+                    } else {
+                        *(data->proper_sink) = pa_tz_device_get_sink(device, data->device_role);
+                    }
                 } else {
-                    *(data->proper_sink) = pa_tz_device_get_sink(device, data->device_role);
+                    if (data->route_type == STREAM_ROUTE_TYPE_AUTO)
+                        data->new_sink = pa_tz_device_get_sink(device, data->device_role);
                 }
-                if (*(data->proper_sink) == NULL)
-                    continue;
             } else {
-                *(data->proper_source) = pa_tz_device_get_source(device, data->device_role);
-                if (*(data->proper_source) == NULL)
-                    continue;
+                if (data->origins_from_new_data) {
+                    *(data->proper_source) = pa_tz_device_get_source(device, data->device_role);
+                } else {
+                    if (data->route_type == STREAM_ROUTE_TYPE_AUTO)
+                        data->new_source = pa_tz_device_get_source(device, data->device_role);
+                }
             }
-
             if (data->route_type == STREAM_ROUTE_TYPE_AUTO) {
                 if (data->origins_from_new_data)
                     pa_proplist_sets(GET_STREAM_NEW_PROPLIST(data->stream, data->stream_type),
-                                        PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type);
+                                     PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type);
                 else
                     pa_proplist_sets(GET_STREAM_PROPLIST(data->stream, data->stream_type),
-                                        PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type);
+                                     PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type);
                 return;
             }
         }
@@ -703,6 +742,9 @@ static void select_device_by_auto_last_connected_routing(struct userdata *u, pa_
     pa_assert(data);
     pa_assert(conn_devices);
 
+    if (!select_device_by_preemptive_device(u, data))
+        return;
+
     PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) {
         pa_log_debug("[SELECT][AUTO_LAST_CONN] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, data->stream_role, device_type);
         if (!is_cached_device_connected(device_type, data->stream_type))
@@ -739,17 +781,24 @@ static void select_device_by_auto_last_connected_routing(struct userdata *u, pa_
     }
     /* update active device info. */
     if (latest_device) {
-        if (data->stream_type == STREAM_SINK_INPUT)
-            *(data->proper_sink) = pa_tz_device_get_sink(latest_device, data->device_role);
-        else
-            *(data->proper_source) = pa_tz_device_get_source(latest_device, data->device_role);
-
+        dm_device_type = pa_tz_device_get_type(latest_device);
+        if (data->origins_from_new_data) {
+            if (data->stream_type == STREAM_SINK_INPUT)
+                *(data->proper_sink) = pa_tz_device_get_sink(latest_device, data->device_role);
+            else
+                *(data->proper_source) = pa_tz_device_get_source(latest_device, data->device_role);
+        } else {
+            if (data->stream_type == STREAM_SINK_INPUT)
+                data->new_sink = pa_tz_device_get_sink(latest_device, data->device_role);
+            else
+                data->new_source = pa_tz_device_get_source(latest_device, data->device_role);
+        }
         if (data->origins_from_new_data)
             pa_proplist_sets(GET_STREAM_NEW_PROPLIST(data->stream, data->stream_type),
-                                PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, pa_tz_device_get_type(latest_device));
+                                PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type);
         else
             pa_proplist_sets(GET_STREAM_PROPLIST(data->stream, data->stream_type),
-                                PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, pa_tz_device_get_type(latest_device));
+                                PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type);
     }
 }
 
@@ -946,12 +995,14 @@ static pa_hook_result_t select_proper_sink_or_source_hook_cb(pa_core *c, pa_stre
     }
 
 not_found:
-    if ((data->stream_type == STREAM_SINK_INPUT) ? !(*(data->proper_sink)) : !(*(data->proper_source))) {
-        pa_log_warn("[SELECT] could not find a proper sink/source, set it to null sink/source");
-        if (data->stream_type == STREAM_SINK_INPUT)
-            *(data->proper_sink) = null_sink;
-        else
-            *(data->proper_source) = null_source;
+    if (data->origins_from_new_data) {
+        if ((data->stream_type == STREAM_SINK_INPUT) ? !(*(data->proper_sink)) : !(*(data->proper_source))) {
+            pa_log_warn("[SELECT] could not find a proper sink/source, set it to null sink/source");
+            if (data->stream_type == STREAM_SINK_INPUT)
+                *(data->proper_sink) = null_sink;
+            else
+                *(data->proper_source) = null_source;
+        }
     }
 
     return PA_HOOK_OK;
@@ -1234,6 +1285,42 @@ static pa_hook_result_t update_combine_sink_and_bt_sco(struct userdata *u, pa_st
     return PA_HOOK_OK;
 }
 
+static int32_t update_route_by_preemptive_device(struct userdata *u, pa_stream_manager_hook_data_for_route *data, hal_route_info *route_info, pa_tz_device **device) {
+    uint32_t dm_device_id = 0;
+    const char *dm_device_type = NULL;
+    bool use_internal_codec = false;
+
+    pa_assert(u);
+    pa_assert(data);
+    pa_assert(route_info);
+    pa_assert(device);
+
+    if (pa_stream_manager_get_preemptive_device_id(u->stream_manager, data->stream_type, data->stream_role, &dm_device_id))
+        return -1;
+
+    /* if this device id is no longer valid - disconnected. */
+    if (!(*device = pa_device_manager_get_device_by_id(u->device_manager, dm_device_id)))
+        return -1;
+
+    dm_device_type = pa_tz_device_get_type(*device);
+    pa_log_debug("preemptive device is type:[%s] id:[%u]", dm_device_type, dm_device_id);
+
+    if (skip_usb_device(data->stream_role, *device))
+        return -1;
+
+    if ((use_internal_codec = pa_tz_device_is_use_internal_codec(*device))) {
+        /* if it needs to skip it, keep going to next device for proper UCM setting */
+        if (skip_device(data->stream_role, dm_device_type))
+            return -1;
+        if (skip_bt_sco_device(u, data->stream_role, dm_device_type))
+            return -1;
+        fill_device_info(route_info, dm_device_type, CONVERT_TO_HAL_DIRECTION(data->stream_type), dm_device_id);
+        return 0;
+    }
+
+    return -1;
+}
+
 static pa_hook_result_t handle_auto_or_auto_all_routing(struct userdata *u, pa_stream_manager_hook_data_for_route *data,
                                                         hal_route_info *route_info, pa_idxset *conn_devices, pa_tz_device **device) {
     const char *device_type = NULL;
@@ -1252,6 +1339,13 @@ static pa_hook_result_t handle_auto_or_auto_all_routing(struct userdata *u, pa_s
     pa_assert(conn_devices);
     pa_assert(device);
 
+    if (data->route_type == STREAM_ROUTE_TYPE_AUTO) {
+        if (!update_route_by_preemptive_device(u, data, route_info, device)) {
+            dm_device_type = pa_tz_device_get_type(*device);
+            goto update_auto_active_dev;
+        }
+    }
+
     PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) {
         pa_log_debug("[ROUTE][AUTO(_ALL)] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, route_info->role, device_type);
         if (!is_cached_device_connected(device_type, data->stream_type))
@@ -1284,6 +1378,7 @@ static pa_hook_result_t handle_auto_or_auto_all_routing(struct userdata *u, pa_s
             continue;
 
         if (data->route_type == STREAM_ROUTE_TYPE_AUTO) {
+update_auto_active_dev:
             if (data->origins_from_new_data)
                 pa_proplist_sets(GET_STREAM_NEW_PROPLIST(data->stream, data->stream_type),
                                 PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type);
@@ -1320,6 +1415,12 @@ static pa_hook_result_t handle_auto_last_connected_routing(struct userdata *u, p
     pa_assert(conn_devices);
     pa_assert(device);
 
+    if (!update_route_by_preemptive_device(u, data, route_info, device)) {
+        latest_device = *device;
+        dm_device_type = pa_tz_device_get_type(*device);
+        goto update_auto_active_dev;
+    }
+
     PA_IDXSET_FOREACH(device_type, data->idx_avail_devices, idx) {
         pa_log_debug("[ROUTE][AUTO_LAST_CONN] avail_device[%u] for this role[%-16s]: type[%-16s]", idx, data->stream_role, device_type);
         if (!is_cached_device_connected(device_type, data->stream_type))
@@ -1365,6 +1466,7 @@ static pa_hook_result_t handle_auto_last_connected_routing(struct userdata *u, p
         else
             pa_log_debug("  -- it does not use internal audio codec, skip it");
 
+update_auto_active_dev:
         if (data->origins_from_new_data)
             pa_proplist_sets(GET_STREAM_NEW_PROPLIST(data->stream, data->stream_type),
                             PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, dm_device_type);
index 6f1585f..166c3a3 100644 (file)
@@ -32,6 +32,8 @@
 #define STREAM_MANAGER_METHOD_NAME_SET_STREAM_ROUTE_OPTION           "SetStreamRouteOption"
 #define STREAM_MANAGER_METHOD_NAME_SET_STREAM_PREFERRED_DEVICE       "SetStreamPreferredDevice"
 #define STREAM_MANAGER_METHOD_NAME_GET_STREAM_PREFERRED_DEVICE       "GetStreamPreferredDevice"
+#define STREAM_MANAGER_METHOD_NAME_SET_STREAM_PREEMPTIVE_DEVICE      "SetStreamPreemptiveDevice"
+#define STREAM_MANAGER_METHOD_NAME_GET_STREAM_PREEMPTIVE_DEVICE      "GetStreamPreemptiveDevice"
 #define STREAM_MANAGER_METHOD_NAME_SET_VOLUME_LEVEL                  "SetVolumeLevel"
 #define STREAM_MANAGER_METHOD_NAME_GET_VOLUME_LEVEL                  "GetVolumeLevel"
 #define STREAM_MANAGER_METHOD_NAME_GET_VOLUME_MAX_LEVEL              "GetVolumeMaxLevel"
@@ -64,6 +66,8 @@ enum method_handler_index {
     METHOD_HANDLER_SET_STREAM_ROUTE_OPTION,
     METHOD_HANDLER_SET_STREAM_PREFERRED_DEVICE,
     METHOD_HANDLER_GET_STREAM_PREFERRED_DEVICE,
+    METHOD_HANDLER_SET_STREAM_PREEMPTIVE_DEVICE,
+    METHOD_HANDLER_GET_STREAM_PREEMPTIVE_DEVICE,
     METHOD_HANDLER_SET_VOLUME_LEVEL,
     METHOD_HANDLER_GET_VOLUME_LEVEL,
     METHOD_HANDLER_GET_VOLUME_MAX_LEVEL,
@@ -143,6 +147,18 @@ pa_dbus_interface_info stream_manager_interface_info = {
     "   <arg name=\"out_device_id\" direction=\"out\" type=\"u\"/>"          \
     "   <arg name=\"ret_msg\" direction=\"out\" type=\"s\"/>"                \
     "  </method>"                                                            \
+    "  <method name=\"STREAM_MANAGER_METHOD_NAME_SET_STREAM_PREEMPTIVE_DEVICE\">" \
+    "   <arg name=\"stream_type\" direction=\"in\" type=\"s\"/>"             \
+    "   <arg name=\"io_direction\" direction=\"in\" type=\"s\"/>"            \
+    "   <arg name=\"device_id\" direction=\"in\" type=\"u\"/>"               \
+    "   <arg name=\"ret_msg\" direction=\"out\" type=\"s\"/>"                \
+    "  </method>"                                                            \
+    "  <method name=\"STREAM_MANAGER_METHOD_NAME_GET_STREAM_PREEMPTIVE_DEVICE\">" \
+    "   <arg name=\"stream_type\" direction=\"in\" type=\"s\"/>"             \
+    "   <arg name=\"in_device_id\" direction=\"out\" type=\"u\"/>"           \
+    "   <arg name=\"out_device_id\" direction=\"out\" type=\"u\"/>"          \
+    "   <arg name=\"ret_msg\" direction=\"out\" type=\"s\"/>"                \
+    "  </method>"                                                            \
     "  <method name=\"STREAM_MANAGER_METHOD_NAME_SET_VOLUME_LEVEL\">"        \
     "   <arg name=\"io_direction\" direction=\"in\" type=\"s\"/>"            \
     "   <arg name=\"type\" direction=\"in\" type=\"s\"/>"                    \
index d220cd4..1e30846 100644 (file)
@@ -48,6 +48,8 @@ static void handle_set_stream_route_devices(DBusConnection *conn, DBusMessage *m
 static void handle_set_stream_route_option(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_set_stream_preferred_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_get_stream_preferred_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_set_stream_preemptive_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
+static void handle_get_stream_preemptive_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_set_volume_level(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_get_volume_level(DBusConnection *conn, DBusMessage *msg, void *userdata);
 static void handle_get_volume_max_level(DBusConnection *conn, DBusMessage *msg, void *userdata);
@@ -95,6 +97,14 @@ static pa_dbus_arg_info get_stream_preferred_device_args[]  = { { "parent_id", "
                                                             { "in_device_id", "u", "out" },
                                                            { "out_device_id", "u", "out" },
                                                                { "ret_msg", "s", "out" } };
+static pa_dbus_arg_info set_stream_preemptive_device_args[]  = { { "stream_type", "s", "in" },
+                                                                { "io_direction", "s", "in" },
+                                                                   { "device_id", "u", "in" },
+                                                                  { "ret_msg", "s", "out" } };
+static pa_dbus_arg_info get_stream_preemptive_device_args[]  = { { "stream_type", "s", "in" },
+                                                               { "in_device_id", "u", "out" },
+                                                              { "out_device_id", "u", "out" },
+                                                                  { "ret_msg", "s", "out" } };
 static pa_dbus_arg_info set_volume_level_args[]  = { { "io_direction", "s", "in" },
                                                              { "type", "s", "in" },
                                                             { "level", "u", "in" },
@@ -177,6 +187,8 @@ static const char* signature_args_for_in[] = {
     "usi",      /* METHOD_HANDLER_SET_STREAM_ROUTE_OPTION */
     "usu",      /* METHOD_HANDLER_SET_STREAM_PREFERRED_DEVICE */
     "u",        /* METHOD_HANDLER_GET_STREAM_PREFERRED_DEVICE */
+    "ssu",      /* METHOD_HANDLER_SET_STREAM_PREEMPTIVE_DEVICE */
+    "s",        /* METHOD_HANDLER_GET_STREAM_PREEMPTIVE_DEVICE */
     "ssu",      /* METHOD_HANDLER_SET_VOLUME_LEVEL */
     "ss",       /* METHOD_HANDLER_GET_VOLUME_LEVEL */
     "ss",       /* METHOD_HANDLER_GET_VOLUME_MAX_LEVEL */
@@ -230,6 +242,16 @@ static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
         .arguments = get_stream_preferred_device_args,
         .n_arguments = sizeof(get_stream_preferred_device_args) / sizeof(pa_dbus_arg_info),
         .receive_cb = handle_get_stream_preferred_device },
+    [METHOD_HANDLER_SET_STREAM_PREEMPTIVE_DEVICE] = {
+        .method_name = STREAM_MANAGER_METHOD_NAME_SET_STREAM_PREEMPTIVE_DEVICE,
+        .arguments = set_stream_preemptive_device_args,
+        .n_arguments = sizeof(set_stream_preemptive_device_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_set_stream_preemptive_device },
+    [METHOD_HANDLER_GET_STREAM_PREEMPTIVE_DEVICE] = {
+        .method_name = STREAM_MANAGER_METHOD_NAME_GET_STREAM_PREEMPTIVE_DEVICE,
+        .arguments = get_stream_preemptive_device_args,
+        .n_arguments = sizeof(get_stream_preemptive_device_args) / sizeof(pa_dbus_arg_info),
+        .receive_cb = handle_get_stream_preemptive_device },
     [METHOD_HANDLER_SET_VOLUME_LEVEL] = {
         .method_name = STREAM_MANAGER_METHOD_NAME_SET_VOLUME_LEVEL,
         .arguments = set_volume_level_args,
@@ -886,6 +908,272 @@ finish:
     dbus_message_unref(reply);
 }
 
+static uint32_t get_num_of_target_streams(pa_stream_manager *m, pa_idxset *streams, stream_direction_t direction, const char *role) {
+    void *s;
+    const char *_role;
+    uint32_t idx;
+    uint32_t count = 0;
+
+    pa_assert(m);
+    pa_assert(streams);
+    pa_assert(role);
+
+    PA_IDXSET_FOREACH(s, streams, idx) {
+        _role = pa_proplist_gets((direction == STREAM_DIRECTION_OUT) ?
+                                 PA_SINK_INPUT(s)->proplist : PA_SOURCE_OUTPUT(s)->proplist, PA_PROP_MEDIA_ROLE);
+        if (pa_safe_streq(_role, role))
+            count++;
+    }
+    return count;
+}
+
+static void routing_process_to_preemptive_device(pa_stream_manager *m, stream_direction_t direction, const char *role, pa_tz_device *tz_device) {
+    void *stream;
+    void *device = NULL;
+    const char *_role;
+    uint32_t count = 0;
+    uint32_t idx;
+
+    pa_assert(m);
+    pa_assert(role);
+    pa_assert(tz_device);
+
+    count = get_num_of_target_streams(m, (direction == STREAM_DIRECTION_OUT) ?
+                                      m->core->sink_inputs : m->core->source_outputs,
+                                      direction, role);
+    PA_IDXSET_FOREACH(stream, (direction == STREAM_DIRECTION_OUT) ? m->core->sink_inputs : m->core->source_outputs, idx) {
+        _role = pa_proplist_gets((direction == STREAM_DIRECTION_OUT) ?
+                                 PA_SINK_INPUT(stream)->proplist : PA_SOURCE_OUTPUT(stream)->proplist, PA_PROP_MEDIA_ROLE);
+        if (!pa_safe_streq(_role, role))
+            continue;
+
+        /* move stream to the preemptive device */
+        device = pa_hashmap_first((direction == STREAM_DIRECTION_OUT) ?
+                                tz_device->playback_devices : tz_device->capture_devices);
+        if (direction == STREAM_DIRECTION_OUT) {
+            pa_sink_input_move_to(stream, PA_SINK(device), false);
+            pa_proplist_sets(GET_STREAM_PROPLIST(stream, STREAM_SINK_INPUT),
+                             PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, tz_device->type);
+        } else {
+            pa_source_output_move_to(stream, PA_SOURCE(device), false);
+            pa_proplist_sets(GET_STREAM_PROPLIST(stream, STREAM_SOURCE_OUTPUT),
+                             PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, tz_device->type);
+        }
+
+        /* change routing */
+        if (--count == 0) {
+            process_stream(m, stream, (direction == STREAM_DIRECTION_OUT) ? STREAM_SINK_INPUT : STREAM_SOURCE_OUTPUT,
+                           PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED, false);
+        }
+    }
+}
+
+static void rollback_process_from_preemptive_device(pa_stream_manager *m, stream_direction_t direction, const char *role, pa_tz_device *tz_device) {
+    void *device;
+    void *stream;
+    void *new_device;
+    uint32_t idx;
+    uint32_t count = 0;
+    const char *active_device = NULL;
+    const char *_role;
+
+    pa_assert(m);
+    pa_assert(role);
+    pa_assert(tz_device);
+
+    device = pa_hashmap_first((direction == STREAM_DIRECTION_OUT) ?
+                              tz_device->playback_devices : tz_device->capture_devices);
+
+    PA_IDXSET_FOREACH(stream, (direction == STREAM_DIRECTION_OUT) ? PA_SINK(device)->inputs : PA_SOURCE(device)->outputs, idx) {
+        _role = pa_proplist_gets((direction == STREAM_DIRECTION_OUT) ?
+                                 PA_SINK_INPUT(stream)->proplist : PA_SOURCE_OUTPUT(stream)->proplist, PA_PROP_MEDIA_ROLE);
+        if (!pa_safe_streq(_role, role))
+            continue;
+
+        if (count++ == 0) {
+            /* find and set new device */
+            do_notify(m, NOTIFY_COMMAND_SELECT_PROPER_SINK_OR_SOURCE_FOR_INIT,
+                     (direction == STREAM_DIRECTION_OUT) ? STREAM_SINK_INPUT : STREAM_SOURCE_OUTPUT,
+                     false, stream);
+            new_device = (direction == STREAM_DIRECTION_OUT) ? (void*)(PA_SINK_INPUT(stream)->sink) :
+                                                               (void*)(PA_SOURCE_OUTPUT(stream)->source);
+            active_device = pa_proplist_gets(GET_STREAM_PROPLIST(stream, (direction == STREAM_DIRECTION_OUT) ? STREAM_SINK_INPUT : STREAM_SOURCE_OUTPUT),
+                                                                 PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV);
+            /* change routing */
+            process_stream(m, stream, (direction == STREAM_DIRECTION_OUT) ? STREAM_SINK_INPUT : STREAM_SOURCE_OUTPUT,
+                           PROCESS_COMMAND_CHANGE_ROUTE_BY_STREAM_FOCUS_CHANGED, false);
+
+            if (direction == STREAM_DIRECTION_OUT)
+                pa_sink_input_move_to(stream, PA_SINK(new_device), false);
+            else
+                pa_source_output_move_to(stream, PA_SOURCE(new_device), false);
+
+            continue;
+        }
+
+        /* move stream to the new device */
+        if (direction == STREAM_DIRECTION_OUT) {
+            pa_sink_input_move_to(stream, PA_SINK(new_device), false);
+            if (active_device)
+                pa_proplist_sets(GET_STREAM_PROPLIST(stream, STREAM_SINK_INPUT), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, active_device);
+        } else {
+            pa_source_output_move_to(stream, PA_SOURCE(new_device), false);
+            if (active_device)
+                pa_proplist_sets(GET_STREAM_PROPLIST(stream, STREAM_SOURCE_OUTPUT), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV, active_device);
+        }
+    }
+}
+
+static void handle_set_stream_preemptive_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    const char *stream_type = NULL;
+    const char *device_direction = NULL;
+    stream_direction_t direction;
+    uint32_t device_id = 0;
+    stream_info *s = NULL;
+    pa_tz_device *device = NULL;
+    char *device_type = NULL;
+    const char *prev_device_type = NULL;
+    uint32_t prev_device_id = 0;
+    uint32_t idx;
+    bool found = false;
+    ret_msg_t ret = RET_MSG_OK;
+    DBusMessage *reply = NULL;
+
+    pa_stream_manager *m = (pa_stream_manager*)userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &stream_type,
+                                       DBUS_TYPE_STRING, &device_direction,
+                                       DBUS_TYPE_UINT32, &device_id,
+                                       DBUS_TYPE_INVALID));
+    pa_log_info("stream type[%s], device direction[%s], device_id[%u]", stream_type, device_direction, device_id);
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    if ((s = pa_hashmap_get(m->stream_infos, stream_type)) == NULL) {
+        pa_log_error("could not find this stream type(%s)", stream_type);
+        ret = RET_MSG_ERROR_INTERNAL;
+        goto finish;
+    }
+
+    if (pa_safe_streq(device_direction, "in"))
+        direction = STREAM_DIRECTION_IN;
+    else if (pa_safe_streq(device_direction, "out"))
+        direction = STREAM_DIRECTION_OUT;
+    else {
+        ret = RET_MSG_ERROR_INVALID_ARGUMENT;
+        goto finish;
+    }
+
+    /* allow only auto routing type */
+    if (s->route_type != STREAM_ROUTE_TYPE_AUTO &&
+        s->route_type != STREAM_ROUTE_TYPE_AUTO_LAST_CONNECTED) {
+        pa_log_error("not allowed this route type[%d]", s->route_type);
+        ret = RET_MSG_ERROR_POLICY;
+        goto finish;
+    }
+
+    if (device_id > 0) {
+        if (!(device = pa_device_manager_get_device_by_id(m->dm, device_id))) {
+            pa_log_error("could not get device by id[%u]", device_id);
+            ret = RET_MSG_ERROR_INVALID_ARGUMENT;
+            goto finish;
+        }
+
+        PA_IDXSET_FOREACH(device_type, (direction == STREAM_DIRECTION_OUT) ? s->idx_avail_out_devices : s->idx_avail_in_devices, idx) {
+            if (pa_safe_streq(device_type, device->type)) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            pa_log_error("not supported this device type[%s]", device->type);
+            ret = RET_MSG_ERROR_INVALID_ARGUMENT;
+            goto finish;
+        }
+    }
+
+    if (device_id == 0 && s->preemptive_device[direction].id == 0) {
+        pa_log_debug("there's no preemptive device, nothing to do");
+        goto finish;
+    }
+
+    prev_device_type = s->preemptive_device[direction].type;
+    prev_device_id = s->preemptive_device[direction].id;
+    s->preemptive_device[direction].type = device_id > 0 ? device->type : NULL;
+    s->preemptive_device[direction].id = device_id;
+
+    pa_log_info("preemptive [%s] device is set, [%s, id:%u]",
+                direction == STREAM_DIRECTION_OUT ? "out" :  "in",
+                device_id > 0 ? device->type : NULL,
+                device_id);
+
+    /* move streams and change routing */
+    if (device_id == 0) {
+        if (!(device = pa_device_manager_get_device_by_id(m->dm, prev_device_id)) || !pa_safe_streq(device->type, prev_device_type)) {
+            pa_log_debug("could not find the previous device of id[%u], type[%s], nothing to do.", prev_device_id, prev_device_type);
+            goto finish;
+        }
+        rollback_process_from_preemptive_device(m, direction, stream_type, device);
+    } else {
+        routing_process_to_preemptive_device(m, direction, stream_type, device);
+    }
+
+finish:
+    pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[ret], DBUS_TYPE_INVALID));
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+    dbus_message_unref(reply);
+}
+
+static void handle_get_stream_preemptive_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+    const char *stream_type = NULL;
+    stream_info *s = NULL;
+    uint32_t in_device_id = 0;
+    uint32_t out_device_id = 0;
+    const char *in_device_type;
+    const char *out_device_type;
+    ret_msg_t ret = RET_MSG_OK;
+    DBusMessage *reply = NULL;
+
+    pa_stream_manager *m = (pa_stream_manager*)userdata;
+
+    pa_assert(conn);
+    pa_assert(msg);
+    pa_assert(m);
+
+    pa_assert_se(dbus_message_get_args(msg, NULL,
+                                       DBUS_TYPE_STRING, &stream_type,
+                                       DBUS_TYPE_INVALID));
+    pa_log_info("stream type[%s]", stream_type);
+
+    pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+    if ((s = pa_hashmap_get(m->stream_infos, stream_type)) == NULL) {
+        pa_log_error("could not find this stream type(%s)", stream_type);
+        ret = RET_MSG_ERROR_INTERNAL;
+        goto finish;
+    }
+
+    in_device_id = s->preemptive_device[STREAM_DIRECTION_IN].id;
+    in_device_type = s->preemptive_device[STREAM_DIRECTION_IN].type;
+    out_device_id = s->preemptive_device[STREAM_DIRECTION_OUT].id;
+    out_device_type = s->preemptive_device[STREAM_DIRECTION_OUT].type;
+
+    pa_log_info("preemptive IN device: type[%s] id[%u]", in_device_type, in_device_id);
+    pa_log_info("preemptive OUT device: type[%s] id[%u]", out_device_type, out_device_id);
+
+finish:
+    pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, &in_device_id, DBUS_TYPE_INVALID));
+    pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_UINT32, &out_device_id, DBUS_TYPE_INVALID));
+    pa_assert_se(dbus_message_append_args(reply, DBUS_TYPE_STRING, &stream_manager_dbus_ret_str[ret], DBUS_TYPE_INVALID));
+    pa_assert_se(dbus_connection_send(conn, reply, NULL));
+    dbus_message_unref(reply);
+}
+
 static void handle_set_volume_level(DBusConnection *conn, DBusMessage *msg, void *userdata) {
     const char *direction = NULL;
     const char *type = NULL;
index 2173f2e..339b4b8 100644 (file)
@@ -174,6 +174,10 @@ typedef struct _stream_info {
     pa_idxset *idx_avail_in_devices;
     pa_idxset *idx_avail_out_devices;
     pa_idxset *idx_avail_frameworks;
+    struct _preemptive_device {
+        const char *type;
+        uint32_t id;
+    } preemptive_device[STREAM_DIRECTION_MAX];
 } stream_info;
 
 typedef struct _prior_max_priority_stream {
index 519c279..2e7b11c 100644 (file)
@@ -129,6 +129,8 @@ bool stream_is_call_family(pa_object *stream) {
     const char *stream_role;
     pa_proplist *prop;
 
+    pa_assert(stream);
+
     if (pa_sink_input_isinstance(stream))
         prop = PA_SINK_INPUT(stream)->proplist;
     else
@@ -1704,15 +1706,20 @@ static ret_msg_t prepare_and_invoke_hook_to_select_device(pa_stream_manager *m,
                                                         PA_PROP_MEDIA_ROUTE_AUTO_PREFERRED_DEVICE_ROLE);
         fill_device_info_to_hook_data(m, &hook_call_select_data, command, type, s, is_new_data);
         hook_call_select_data.sample_spec = GET_STREAM_SAMPLE_SPEC(s, type);
-        if (type == STREAM_SINK_INPUT)
-            hook_call_select_data.proper_sink = &(((pa_sink_input*)s)->sink);
-        else if (type == STREAM_SOURCE_OUTPUT)
-            hook_call_select_data.proper_source = &(((pa_source_output*)s)->source);
     }
     if (hook_call_select_data.route_type == STREAM_ROUTE_TYPE_MANUAL)
         CONVERT_TO_DEVICE_ROLE(hook_call_select_data.stream_role, hook_call_select_data.device_role);
-    if (pa_hook_fire(pa_communicator_hook(m->comm.comm, PA_COMMUNICATOR_HOOK_SELECT_INIT_SINK_OR_SOURCE), &hook_call_select_data))
+
+    if (pa_hook_fire(pa_communicator_hook(m->comm.comm, PA_COMMUNICATOR_HOOK_SELECT_INIT_SINK_OR_SOURCE), &hook_call_select_data)) {
         ret = RET_MSG_ERROR_INTERNAL;
+    } else {
+        if (!is_new_data) {
+            if (type == STREAM_SINK_INPUT)
+                pa_sink_input_move_to(PA_SINK_INPUT(s), hook_call_select_data.new_sink, false);
+            else
+                pa_source_output_move_to(PA_SOURCE_OUTPUT(s), hook_call_select_data.new_source, false);
+        }
+    }
 BREAK_WITH_FREE:
     pa_xfree(filtered_avail_devices);
 
@@ -3011,6 +3018,16 @@ static void check_and_move_streams_by_device_connection_change(pa_stream_manager
             stream_role = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROLE);
             pa_log_debug("  -- idx(%u), route_type(%d), stream_role(%s)", s_idx, route_type, stream_role);
             if (is_connected) {
+                uint32_t preemptive_device_id = 0;
+                if (!pa_stream_manager_get_preemptive_device_id(m, stream_type, stream_role, &preemptive_device_id)) {
+                    pa_tz_device *device = pa_device_manager_get_device_by_id(m->dm, preemptive_device_id);
+                    if (device) {
+                        pa_log_debug("Skip moving this stream[%s, idx:%u] set to the preemptive device(%s, id:%u)", stream_role,
+                                     (stream_type == STREAM_SINK_INPUT) ? PA_SINK_INPUT(s)->index : PA_SOURCE_OUTPUT(s)->index,
+                                     device->type, preemptive_device_id);
+                        continue;
+                    }
+                }
                 /* CONNECTED: move a stream to the new device if possible */
                 if (sink && (sink != ((pa_sink_input*)s)->sink)) {
                     if ((cur_device_type = pa_proplist_gets(GET_STREAM_PROPLIST(s, stream_type), PA_PROP_MEDIA_ROUTE_AUTO_ACTIVE_DEV))) {
@@ -3594,6 +3611,26 @@ int32_t pa_stream_manager_get_route_type(void *stream, stream_type_t stream_type
     return get_route_type(stream, stream_type, is_new_data, stream_route_type);
 }
 
+int32_t pa_stream_manager_get_preemptive_device_id(pa_stream_manager *m, stream_type_t stream_type, const char *role, uint32_t *id) {
+    stream_info *s = NULL;
+
+    pa_assert(m);
+    pa_assert(role);
+    pa_assert(id);
+
+    if (!(s = pa_hashmap_get(m->stream_infos, role))) {
+        pa_log_warn("%s is not valid role, return false", role);
+        return -1;
+    }
+
+    if (s->preemptive_device[stream_type == STREAM_SINK_INPUT ? STREAM_DIRECTION_OUT : STREAM_DIRECTION_IN].type) {
+        *id = s->preemptive_device[stream_type == STREAM_SINK_INPUT ? STREAM_DIRECTION_OUT : STREAM_DIRECTION_IN].id;
+        return 0;
+    }
+
+    return -1;
+}
+
 bool pa_stream_manager_check_filter_apply_stream(void *stream, stream_type_t stream_type) {
     return is_filter_apply_stream(stream, stream_type);
 }
index 2838ab7..1aae92c 100644 (file)
@@ -123,6 +123,8 @@ typedef struct _hook_call_data_for_select {
     stream_route_type_t route_type;
     pa_sink **proper_sink;
     pa_source **proper_source;
+    pa_sink *new_sink;
+    pa_source *new_source;
     pa_sample_spec sample_spec;
     pa_idxset *idx_avail_devices;
     pa_idxset *idx_manual_devices;
@@ -151,6 +153,7 @@ typedef struct _hook_call_data_for_update_info {
 } pa_stream_manager_hook_data_for_update_info;
 
 int32_t pa_stream_manager_get_route_type(void *stream, stream_type_t stream_type, bool is_new_data, stream_route_type_t *stream_route_type);
+int32_t pa_stream_manager_get_preemptive_device_id(pa_stream_manager *m, stream_type_t stream_type, const char *role, uint32_t *id);
 bool pa_stream_manager_check_name_is_vstream(void *stream, stream_type_t type, bool is_new_data);
 bool pa_stream_manager_check_filter_apply_stream(void *stream, stream_type_t stream_type);
 bool pa_stream_manager_is_valid_stream_role(pa_core *c, const char *role);