]> code.delx.au - pulseaudio/blobdiff - src/modules/bluetooth/module-bluetooth-device.c
bluetooth: Fix bluetooth.nrec property not updated
[pulseaudio] / src / modules / bluetooth / module-bluetooth-device.c
index ac76a196c7517666edcefa4cd6a8fa2c057e1932..d62cf06f47fba56a3d962fbe8bcbf2b2cfed128e 100644 (file)
@@ -124,8 +124,18 @@ struct hsp_info {
     void (*sco_source_set_volume)(pa_source *s);
     pa_hook_slot *sink_state_changed_slot;
     pa_hook_slot *source_state_changed_slot;
+    pa_hook_slot *nrec_changed_slot;
 };
 
+struct bluetooth_msg {
+    pa_msgobject parent;
+    pa_card *card;
+};
+
+typedef struct bluetooth_msg bluetooth_msg;
+PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject);
+#define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o))
+
 struct userdata {
     pa_core *core;
     pa_module *module;
@@ -148,6 +158,7 @@ struct userdata {
     pa_rtpoll *rtpoll;
     pa_rtpoll_item *rtpoll_item;
     pa_thread *thread;
+    bluetooth_msg *msg;
 
     uint64_t read_index, write_index;
     pa_usec_t started_at;
@@ -176,6 +187,11 @@ struct userdata {
     pa_bool_t filter_added;
 };
 
+enum {
+    BLUETOOTH_MESSAGE_IO_THREAD_FAILED,
+    BLUETOOTH_MESSAGE_MAX
+};
+
 #define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC)
 #define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC)
 #define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC)
@@ -1115,6 +1131,25 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
     return (r < 0 || !failed) ? r : -1;
 }
 
+/* Called from main thread context */
+static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+    struct bluetooth_msg *u = BLUETOOTH_MSG(obj);
+
+    switch (code) {
+        case BLUETOOTH_MESSAGE_IO_THREAD_FAILED: {
+            if (u->card->module->unload_requested)
+                break;
+
+            pa_log_debug("Switching the profile to off due to IO thread failure.");
+
+            if (pa_card_set_profile(u->card, "off", FALSE) < 0)
+                pa_log_debug("Failed to switch profile to off");
+            break;
+        }
+    }
+    return 0;
+}
+
 /* Run from IO thread */
 static int hsp_process_render(struct userdata *u) {
     int ret = 0;
@@ -1433,7 +1468,6 @@ static int a2dp_process_push(struct userdata *u) {
         void *d;
         ssize_t l;
         size_t to_write, to_decode;
-        unsigned frame_count;
 
         a2dp_prepare_buffer(u);
 
@@ -1509,8 +1543,6 @@ static int a2dp_process_push(struct userdata *u) {
 
             d = (uint8_t*) d + written;
             to_write -= written;
-
-            frame_count++;
         }
 
         memchunk.length -= to_write;
@@ -1700,11 +1732,18 @@ static void thread_func(void *userdata) {
             pollfd->events = (short) (((u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state) && !writable) ? POLLOUT : 0) |
                                       (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state) ? POLLIN : 0));
 
-        if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+        if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) {
+            pa_log_debug("pa_rtpoll_run failed with: %d", ret);
             goto fail;
-
-        if (ret == 0)
+        }
+        if (ret == 0) {
+            pa_log_debug("IO thread shutdown requested, stopping cleanly");
+            if (u->transport)
+                bt_transport_release(u);
+            else
+                stop_stream_fd(u);
             goto finish;
+        }
 
         pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
 
@@ -1721,7 +1760,7 @@ static void thread_func(void *userdata) {
 fail:
     /* If this was no regular exit from the loop we have to continue processing messages until we receive PA_MESSAGE_SHUTDOWN */
     pa_log_debug("IO thread failed");
-    pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+    pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_IO_THREAD_FAILED, NULL, 0, NULL, NULL);
     pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
 
 finish:
@@ -1780,27 +1819,55 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
                 pa_source_volume_changed(u->source, &v);
             }
         }
-    } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) {
-        DBusMessageIter arg_i;
-        pa_bluetooth_transport *t;
-        pa_bool_t nrec;
+    } else if (dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged")) {
+        const char *key;
+        DBusMessageIter iter;
+        DBusMessageIter variant;
+        pa_bt_audio_state_t state = PA_BT_AUDIO_STATE_INVALID;
 
-        t = (pa_bluetooth_transport *) pa_bluetooth_discovery_get_transport(u->discovery, u->transport);
-        pa_assert(t);
-
-        if (!dbus_message_iter_init(m, &arg_i)) {
+        if (!dbus_message_iter_init(m, &iter)) {
             pa_log("Failed to parse PropertyChanged: %s", err.message);
             goto fail;
         }
 
-        nrec = t->nrec;
+        if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+            pa_log("Property name not a string.");
+            goto fail;
+        }
 
-        if (pa_bluetooth_transport_parse_property(t, &arg_i) < 0)
+        dbus_message_iter_get_basic(&iter, &key);
+
+        if (!dbus_message_iter_next(&iter)) {
+            pa_log("Property value missing");
             goto fail;
+        }
 
-        if (nrec != t->nrec) {
-            pa_log_debug("dbus: property 'NREC' changed to value '%s'", t->nrec ? "True" : "False");
-            pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
+        dbus_message_iter_recurse(&iter, &variant);
+
+        if (dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_STRING) {
+            const char *value;
+            dbus_message_iter_get_basic(&variant, &value);
+
+            if (pa_streq(key, "State")) {
+                pa_log_debug("dbus: HSHFAG property 'State' changed to value '%s'", value);
+                state = pa_bt_audio_state_from_string(value);
+            }
+        }
+
+        switch(state) {
+            case PA_BT_AUDIO_STATE_INVALID:
+            case PA_BT_AUDIO_STATE_DISCONNECTED:
+            case PA_BT_AUDIO_STATE_CONNECTED:
+            case PA_BT_AUDIO_STATE_CONNECTING:
+                goto fail;
+
+            case PA_BT_AUDIO_STATE_PLAYING:
+                if (u->card) {
+                    pa_log_debug("Changing profile to hfgw");
+                    if (pa_card_set_profile(u->card, "hfgw", FALSE) < 0)
+                        pa_log("Failed to change profile to hfgw");
+                }
+                break;
         }
     }
 
@@ -1985,6 +2052,75 @@ static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct
     return PA_HOOK_OK;
 }
 
+static pa_hook_result_t nrec_changed_cb(pa_bluetooth_transport *t, void *call_data, struct userdata *u) {
+    pa_proplist *p;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    p = pa_proplist_new();
+    pa_proplist_sets(p, "bluetooth.nrec", t->nrec ? "1" : "0");
+    pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, p);
+    pa_proplist_free(p);
+
+    return PA_HOOK_OK;
+}
+
+static void connect_ports(struct userdata *u, void *sink_or_source_new_data, pa_direction_t direction) {
+    union {
+        pa_sink_new_data *sink_new_data;
+        pa_source_new_data *source_new_data;
+    } data;
+    pa_device_port *port;
+
+    if (direction == PA_DIRECTION_OUTPUT) {
+        data.sink_new_data = sink_or_source_new_data;
+        data.sink_new_data->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    } else {
+        data.source_new_data = sink_or_source_new_data;
+        data.source_new_data->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+    }
+
+    switch (u->profile) {
+        case PROFILE_A2DP:
+            pa_assert_se(port = pa_hashmap_get(u->card->ports, "a2dp-output"));
+            pa_assert_se(pa_hashmap_put(data.sink_new_data->ports, port->name, port) >= 0);
+            pa_device_port_ref(port);
+            break;
+
+        case PROFILE_A2DP_SOURCE:
+            pa_assert_se(port = pa_hashmap_get(u->card->ports, "a2dp-input"));
+            pa_assert_se(pa_hashmap_put(data.source_new_data->ports, port->name, port) >= 0);
+            pa_device_port_ref(port);
+            break;
+
+        case PROFILE_HSP:
+            if (direction == PA_DIRECTION_OUTPUT) {
+                pa_assert_se(port = pa_hashmap_get(u->card->ports, "hsp-output"));
+                pa_assert_se(pa_hashmap_put(data.sink_new_data->ports, port->name, port) >= 0);
+            } else {
+                pa_assert_se(port = pa_hashmap_get(u->card->ports, "hsp-input"));
+                pa_assert_se(pa_hashmap_put(data.source_new_data->ports, port->name, port) >= 0);
+            }
+            pa_device_port_ref(port);
+            break;
+
+        case PROFILE_HFGW:
+            if (direction == PA_DIRECTION_OUTPUT) {
+                pa_assert_se(port = pa_hashmap_get(u->card->ports, "hfgw-output"));
+                pa_assert_se(pa_hashmap_put(data.sink_new_data->ports, port->name, port) >= 0);
+            } else {
+                pa_assert_se(port = pa_hashmap_get(u->card->ports, "hfgw-input"));
+                pa_assert_se(pa_hashmap_put(data.source_new_data->ports, port->name, port) >= 0);
+            }
+            pa_device_port_ref(port);
+            break;
+
+        default:
+            pa_assert_not_reached();
+        }
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     char *k;
@@ -2021,6 +2157,7 @@ static int add_sink(struct userdata *u) {
             pa_sink_new_data_done(&data);
             return -1;
         }
+        connect_ports(u, &data, PA_DIRECTION_OUTPUT);
 
         u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
         pa_sink_new_data_done(&data);
@@ -2084,6 +2221,7 @@ static int add_source(struct userdata *u) {
             return -1;
         }
 
+        connect_ports(u, &data, PA_DIRECTION_INPUT);
         u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
         pa_source_new_data_done(&data);
 
@@ -2102,10 +2240,13 @@ static int add_source(struct userdata *u) {
 
     if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) {
         if (u->transport) {
-            const pa_bluetooth_transport *t;
+            pa_bluetooth_transport *t;
             t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport);
             pa_assert(t);
             pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
+
+            if (!u->hsp.nrec_changed_slot)
+                u->hsp.nrec_changed_slot = pa_hook_connect(&t->hooks[PA_BLUETOOTH_TRANSPORT_HOOK_NREC_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) nrec_changed_cb, u);
         } else
             pa_proplist_sets(u->source->proplist, "bluetooth.nrec", (u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC) ? "1" : "0");
     }
@@ -2401,6 +2542,11 @@ static void stop_thread(struct userdata *u) {
         u->hsp.source_state_changed_slot = NULL;
     }
 
+    if (u->hsp.nrec_changed_slot) {
+        pa_hook_slot_free(u->hsp.nrec_changed_slot);
+        u->hsp.nrec_changed_slot = NULL;
+    }
+
     if (u->sink) {
         if (u->profile == PROFILE_HSP) {
             k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
@@ -2565,6 +2711,13 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
     }
 
     stop_thread(u);
+
+    if (u->profile != PROFILE_OFF && u->transport) {
+        bt_transport_release(u);
+        pa_xfree(u->transport);
+        u->transport = NULL;
+    }
+
     shutdown_bt(u);
 
     if (USE_SCO_OVER_PCM(u))
@@ -2601,6 +2754,69 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
     return 0;
 }
 
+static void create_ports_for_profile(struct userdata *u, pa_card_new_data *card_new_data, pa_card_profile *profile) {
+    pa_device_port *port;
+    enum profile *d;
+
+    d = PA_CARD_PROFILE_DATA(profile);
+
+    switch (*d) {
+        case PROFILE_A2DP:
+            pa_assert_se(port = pa_device_port_new(u->core, "a2dp-output", _("Bluetooth High Quality (A2DP)"), 0));
+            pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+            port->is_output = 1;
+            port->is_input = 0;
+            port->priority = profile->priority * 100;
+            pa_hashmap_put(port->profiles, profile->name, profile);
+            break;
+
+        case PROFILE_A2DP_SOURCE:
+            pa_assert_se(port = pa_device_port_new(u->core, "a2dp-input", _("Bluetooth High Quality (A2DP)"), 0));
+            pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+            port->is_output = 0;
+            port->is_input = 1;
+            port->priority = profile->priority * 100;
+            pa_hashmap_put(port->profiles, profile->name, profile);
+            break;
+
+        case PROFILE_HSP:
+            pa_assert_se(port = pa_device_port_new(u->core, "hsp-output", _("Bluetooth Telephony (HSP/HFP)"), 0));
+            pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+            port->is_output = 1;
+            port->is_input = 0;
+            port->priority = profile->priority * 100;
+            pa_hashmap_put(port->profiles, profile->name, profile);
+
+            pa_assert_se(port = pa_device_port_new(u->core, "hsp-input", _("Bluetooth Telephony (HSP/HFP)"), 0));
+            pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+            port->is_output = 0;
+            port->is_input = 1;
+            port->priority = profile->priority * 100;
+            pa_hashmap_put(port->profiles, profile->name, profile);
+            break;
+
+        case PROFILE_HFGW:
+            pa_assert_se(port = pa_device_port_new(u->core, "hfgw-output", _("Bluetooth Handsfree Gateway"), 0));
+            pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+            port->is_output = 1;
+            port->is_input = 0;
+            port->priority = profile->priority * 100;
+            pa_hashmap_put(port->profiles, profile->name, profile);
+
+            pa_assert_se(port = pa_device_port_new(u->core, "hfgw-input", _("Bluetooth Handsfree Gateway"), 0));
+            pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+            port->is_output = 0;
+            port->is_input = 1;
+            port->priority = profile->priority * 100;
+            pa_hashmap_put(port->profiles, profile->name, profile);
+            break;
+
+        default:
+            pa_assert_not_reached();
+    }
+
+}
+
 /* Run from main thread */
 static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
     pa_card_new_data data;
@@ -2655,6 +2871,7 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
 
         d = PA_CARD_PROFILE_DATA(p);
         *d = PROFILE_A2DP;
+        create_ports_for_profile(u, &data, p);
 
         pa_hashmap_put(data.profiles, p->name, p);
     }
@@ -2669,6 +2886,7 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
 
         d = PA_CARD_PROFILE_DATA(p);
         *d = PROFILE_A2DP_SOURCE;
+        create_ports_for_profile(u, &data, p);
 
         pa_hashmap_put(data.profiles, p->name, p);
     }
@@ -2684,6 +2902,7 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
 
         d = PA_CARD_PROFILE_DATA(p);
         *d = PROFILE_HSP;
+        create_ports_for_profile(u, &data, p);
 
         pa_hashmap_put(data.profiles, p->name, p);
     }
@@ -2698,6 +2917,7 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
 
         d = PA_CARD_PROFILE_DATA(p);
         *d = PROFILE_HFGW;
+        create_ports_for_profile(u, &data, p);
 
         pa_hashmap_put(data.profiles, p->name, p);
     }
@@ -2806,7 +3026,7 @@ int pa__init(pa_module* m) {
     struct userdata *u;
     const char *address, *path;
     DBusError err;
-    char *mike, *speaker, *transport;
+    char *mike, *speaker;
     const pa_bluetooth_device *device;
 
     pa_assert(m);
@@ -2875,6 +3095,12 @@ int pa__init(pa_module* m) {
     if (add_card(u, device) < 0)
         goto fail;
 
+    if (!(u->msg = pa_msgobject_new(bluetooth_msg)))
+        goto fail;
+
+    u->msg->parent.process_msg = device_process_msg;
+    u->msg->card = u->card;
+
     if (!dbus_connection_add_filter(pa_dbus_connection_get(u->connection), filter_cb, u, NULL)) {
         pa_log_error("Failed to add filter function");
         goto fail;
@@ -2883,18 +3109,17 @@ int pa__init(pa_module* m) {
 
     speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path);
     mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path);
-    transport = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'");
 
     if (pa_dbus_add_matches(
                 pa_dbus_connection_get(u->connection), &err,
                 speaker,
                 mike,
-                transport,
+                "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
                 NULL) < 0) {
 
         pa_xfree(speaker);
         pa_xfree(mike);
-        pa_xfree(transport);
 
         pa_log("Failed to add D-Bus matches: %s", err.message);
         goto fail;
@@ -2902,7 +3127,6 @@ int pa__init(pa_module* m) {
 
     pa_xfree(speaker);
     pa_xfree(mike);
-    pa_xfree(transport);
 
     /* Connect to the BT service */
     init_bt(u);
@@ -2963,7 +3187,10 @@ void pa__done(pa_module *m) {
             speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path);
             mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path);
 
-            pa_dbus_remove_matches(pa_dbus_connection_get(u->connection), speaker, mike, NULL);
+            pa_dbus_remove_matches(pa_dbus_connection_get(u->connection), speaker, mike,
+                "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
+                NULL);
 
             pa_xfree(speaker);
             pa_xfree(mike);
@@ -2975,6 +3202,9 @@ void pa__done(pa_module *m) {
         pa_dbus_connection_unref(u->connection);
     }
 
+    if (u->msg)
+        pa_xfree(u->msg);
+
     if (u->card)
         pa_card_free(u->card);