bluetooth: Release transport when not available
[profile/ivi/pulseaudio-panda.git] / src / modules / bluetooth / module-bluetooth-device.c
index f5ccb77..ad68cfa 100644 (file)
@@ -243,11 +243,47 @@ static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool)
 }
 
 /* from IO thread, except in SCO over PCM */
+static void bt_transport_config_mtu(struct userdata *u) {
+    /* Calculate block sizes */
+    if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
+        u->read_block_size = u->read_link_mtu;
+        u->write_block_size = u->write_link_mtu;
+    } else {
+        u->read_block_size =
+            (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+            / u->a2dp.frame_length * u->a2dp.codesize;
+
+        u->write_block_size =
+            (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+            / u->a2dp.frame_length * u->a2dp.codesize;
+    }
+
+    if (USE_SCO_OVER_PCM(u))
+        return;
+
+    if (u->sink) {
+        pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
+        pa_sink_set_fixed_latency_within_thread(u->sink,
+                                                (u->profile == PROFILE_A2DP ?
+                                                 FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) +
+                                                pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+    }
+
+    if (u->source)
+        pa_source_set_fixed_latency_within_thread(u->source,
+                                                  (u->profile == PROFILE_A2DP_SOURCE ?
+                                                   FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) +
+                                                  pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
+}
 
-static int setup_stream(struct userdata *u) {
+/* from IO thread, except in SCO over PCM */
+
+static void setup_stream(struct userdata *u) {
     struct pollfd *pollfd;
     int one;
 
+    bt_transport_config_mtu(u);
+
     pa_make_fd_nonblock(u->stream_fd);
     pa_make_socket_low_delay(u->stream_fd);
 
@@ -277,8 +313,16 @@ static int setup_stream(struct userdata *u) {
                 10,
                 pa_rtclock_now(),
                 TRUE);
+}
 
-    return 0;
+static bool bt_transport_is_acquired(struct userdata *u) {
+    if (u->accesstype == NULL) {
+        pa_assert(u->stream_fd < 0);
+        return FALSE;
+    } else {
+        pa_assert(u->stream_fd >= 0);
+        return TRUE;
+    }
 }
 
 static void bt_transport_release(struct userdata *u) {
@@ -286,7 +330,7 @@ static void bt_transport_release(struct userdata *u) {
     const pa_bluetooth_transport *t;
 
     /* Ignore if already released */
-    if (!u->accesstype)
+    if (!bt_transport_is_acquired(u))
         return;
 
     pa_log_debug("Releasing transport %s", u->transport);
@@ -318,7 +362,12 @@ static int bt_transport_acquire(struct userdata *u, pa_bool_t start) {
     const char *accesstype = "rw";
     const pa_bluetooth_transport *t;
 
-    if (u->accesstype) {
+    if (u->transport == NULL) {
+        pa_log("Transport no longer available.");
+        return -1;
+    }
+
+    if (bt_transport_is_acquired(u)) {
         if (start)
             goto done;
         return 0;
@@ -346,7 +395,9 @@ static int bt_transport_acquire(struct userdata *u, pa_bool_t start) {
 
 done:
     pa_log_info("Transport %s resuming", u->transport);
-    return setup_stream(u);
+    setup_stream(u);
+
+    return 0;
 }
 
 /* Run from IO thread */
@@ -365,7 +416,9 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
             switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
 
                 case PA_SINK_SUSPENDED:
-                    pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
+                    /* Ignore if transition is PA_SINK_INIT->PA_SINK_SUSPENDED */
+                    if (!PA_SINK_IS_OPENED(u->sink->thread_info.state))
+                        break;
 
                     /* Stop the device if the source is suspended as well */
                     if (!u->source || u->source->state == PA_SOURCE_SUSPENDED)
@@ -439,7 +492,9 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
             switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
 
                 case PA_SOURCE_SUSPENDED:
-                    pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state));
+                    /* Ignore if transition is PA_SOURCE_INIT->PA_SOURCE_SUSPENDED */
+                    if (!PA_SOURCE_IS_OPENED(u->source->thread_info.state))
+                        break;
 
                     /* Stop the device if the sink is suspended as well */
                     if (!u->sink || u->sink->state == PA_SINK_SUSPENDED)
@@ -1128,10 +1183,67 @@ finish:
     pa_log_debug("IO thread shutting down");
 }
 
+static pa_bt_audio_state_t parse_state_property_change(DBusMessage *m) {
+    DBusMessageIter iter;
+    DBusMessageIter variant;
+    const char *key;
+    const char *value;
+    pa_bt_audio_state_t state;
+
+    if (!dbus_message_iter_init(m, &iter)) {
+        pa_log("Failed to parse PropertyChanged");
+        return PA_BT_AUDIO_STATE_INVALID;
+    }
+
+    if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+        pa_log("Property name not a string");
+        return PA_BT_AUDIO_STATE_INVALID;
+    }
+
+    dbus_message_iter_get_basic(&iter, &key);
+
+    if (!pa_streq(key, "State"))
+        return PA_BT_AUDIO_STATE_INVALID;
+
+    if (!dbus_message_iter_next(&iter)) {
+        pa_log("Property value missing");
+        return PA_BT_AUDIO_STATE_INVALID;
+    }
+
+    dbus_message_iter_recurse(&iter, &variant);
+
+    if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) {
+        pa_log("Property value not a string");
+        return PA_BT_AUDIO_STATE_INVALID;
+    }
+
+    dbus_message_iter_get_basic(&variant, &value);
+
+    pa_log_debug("dbus: %s property 'State' changed to value '%s'", dbus_message_get_interface(m), value);
+
+    state = pa_bt_audio_state_from_string(value);
+
+    if (state == PA_BT_AUDIO_STATE_INVALID)
+        pa_log("Unexpected value for property 'State': '%s'", value);
+
+    return state;
+}
+
+static pa_port_available_t audio_state_to_availability(pa_bt_audio_state_t state) {
+    if (state < PA_BT_AUDIO_STATE_CONNECTED)
+        return PA_PORT_AVAILABLE_NO;
+    else if (state >= PA_BT_AUDIO_STATE_PLAYING)
+        return PA_PORT_AVAILABLE_YES;
+    else
+        return PA_PORT_AVAILABLE_UNKNOWN;
+}
+
 /* Run from main thread */
 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
     DBusError err;
     struct userdata *u;
+    bool acquire = FALSE;
+    bool release = FALSE;
 
     pa_assert(bus);
     pa_assert(m);
@@ -1181,39 +1293,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
             }
         }
     } 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;
-
-        if (!dbus_message_iter_init(m, &iter)) {
-            pa_log("Failed to parse PropertyChanged: %s", err.message);
-            goto fail;
-        }
-
-        if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
-            pa_log("Property name not a string.");
-            goto fail;
-        }
-
-        dbus_message_iter_get_basic(&iter, &key);
-
-        if (!dbus_message_iter_next(&iter)) {
-            pa_log("Property value missing");
-            goto fail;
-        }
-
-        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);
-            }
-        }
+        pa_bt_audio_state_t state = parse_state_property_change(m);
 
         switch(state) {
             case PA_BT_AUDIO_STATE_INVALID:
@@ -1230,6 +1310,85 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
                 }
                 break;
         }
+
+        if (state != PA_BT_AUDIO_STATE_INVALID) {
+            pa_device_port *port;
+            pa_port_available_t available = audio_state_to_availability(state);
+
+            pa_assert_se(port = pa_hashmap_get(u->card->ports, "hfgw-output"));
+            pa_device_port_set_available(port, available);
+
+            pa_assert_se(port = pa_hashmap_get(u->card->ports, "hfgw-input"));
+            pa_device_port_set_available(port, available);
+
+            acquire = (available == PA_PORT_AVAILABLE_YES && u->profile == PROFILE_HFGW);
+            release = (available != PA_PORT_AVAILABLE_YES && u->profile == PROFILE_HFGW);
+        }
+    } else if (dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged")) {
+        pa_bt_audio_state_t state = parse_state_property_change(m);
+
+        if (state != PA_BT_AUDIO_STATE_INVALID) {
+            pa_device_port *port;
+            pa_port_available_t available = audio_state_to_availability(state);
+
+            pa_assert_se(port = pa_hashmap_get(u->card->ports, "hsp-output"));
+            pa_device_port_set_available(port, available);
+
+            pa_assert_se(port = pa_hashmap_get(u->card->ports, "hsp-input"));
+            pa_device_port_set_available(port, available);
+
+            acquire = (available == PA_PORT_AVAILABLE_YES && u->profile == PROFILE_HSP);
+            release = (available != PA_PORT_AVAILABLE_YES && u->profile == PROFILE_HSP);
+        }
+    } else if (dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged")) {
+        pa_bt_audio_state_t state = parse_state_property_change(m);
+
+        if (state != PA_BT_AUDIO_STATE_INVALID) {
+            pa_device_port *port;
+            pa_port_available_t available = audio_state_to_availability(state);
+
+            pa_assert_se(port = pa_hashmap_get(u->card->ports, "a2dp-input"));
+            pa_device_port_set_available(port, available);
+
+            acquire = (available == PA_PORT_AVAILABLE_YES && u->profile == PROFILE_A2DP_SOURCE);
+            release = (available != PA_PORT_AVAILABLE_YES && u->profile == PROFILE_A2DP_SOURCE);
+        }
+    } else if (dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged")) {
+        pa_bt_audio_state_t state = parse_state_property_change(m);
+
+        if (state != PA_BT_AUDIO_STATE_INVALID) {
+            pa_device_port *port;
+            pa_port_available_t available = audio_state_to_availability(state);
+
+            pa_assert_se(port = pa_hashmap_get(u->card->ports, "a2dp-output"));
+            pa_device_port_set_available(port, available);
+
+            acquire = (available == PA_PORT_AVAILABLE_YES && u->profile == PROFILE_A2DP);
+            release = (available != PA_PORT_AVAILABLE_YES && u->profile == PROFILE_A2DP);
+        }
+    }
+
+    if (acquire)
+        if (bt_transport_acquire(u, FALSE) >= 0) {
+            if (u->source)
+                pa_source_suspend(u->source, FALSE, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+
+            if (u->sink)
+                pa_sink_suspend(u->sink, FALSE, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+        }
+
+    if (release && bt_transport_is_acquired(u)) {
+        /* FIXME: this release is racy, since the audio stream might have
+           been set up again in the meantime (but not processed yet by PA).
+           BlueZ should probably release the transport automatically, and
+           in that case we would just mark the transport as released */
+
+        /* Remote side closed the stream so we consider it PA_SUSPEND_USER */
+        if (u->source)
+            pa_source_suspend(u->source, TRUE, PA_SUSPEND_USER);
+
+        if (u->sink)
+            pa_sink_suspend(u->sink, TRUE, PA_SUSPEND_USER);
     }
 
 fail:
@@ -1484,6 +1643,14 @@ static const char *profile_to_string(enum profile profile) {
     }
 }
 
+static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
+    return 0;
+}
+
+static int source_set_port_cb(pa_source *s, pa_device_port *p) {
+    return 0;
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     char *k;
@@ -1532,11 +1699,7 @@ static int add_sink(struct userdata *u) {
 
         u->sink->userdata = u;
         u->sink->parent.process_msg = sink_process_msg;
-
-        pa_sink_set_max_request(u->sink, u->write_block_size);
-        pa_sink_set_fixed_latency(u->sink,
-                                  (u->profile == PROFILE_A2DP ? FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) +
-                                  pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+        u->sink->set_port = sink_set_port_cb;
     }
 
     if (u->profile == PROFILE_HSP) {
@@ -1571,7 +1734,7 @@ static int add_source(struct userdata *u) {
         data.module = u->module;
         pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
         pa_proplist_sets(data.proplist, "bluetooth.protocol", profile_to_string(u->profile));
-        if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW))
+        if (u->profile == PROFILE_HSP)
             pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
 
         data.card = u->card;
@@ -1595,10 +1758,7 @@ static int add_source(struct userdata *u) {
 
         u->source->userdata = u;
         u->source->parent.process_msg = source_process_msg;
-
-        pa_source_set_fixed_latency(u->source,
-                                    (u->profile == PROFILE_A2DP_SOURCE ? FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) +
-                                    pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
+        u->source->set_port = source_set_port_cb;
     }
 
     if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) {
@@ -1623,7 +1783,7 @@ static int add_source(struct userdata *u) {
     return 0;
 }
 
-static int bt_transport_config_a2dp(struct userdata *u) {
+static void bt_transport_config_a2dp(struct userdata *u) {
     const pa_bluetooth_transport *t;
     struct a2dp_info *a2dp = &u->a2dp;
     a2dp_sbc_t *config;
@@ -1730,31 +1890,17 @@ static int bt_transport_config_a2dp(struct userdata *u) {
     a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
     a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
 
-    u->read_block_size =
-        (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-        / a2dp->frame_length * a2dp->codesize;
-
-    u->write_block_size =
-        (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-        / a2dp->frame_length * a2dp->codesize;
-
     pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
                 a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, a2dp->sbc.bitpool);
-
-    return 0;
 }
 
-static int bt_transport_config(struct userdata *u) {
+static void bt_transport_config(struct userdata *u) {
     if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
-        u->read_block_size = u->read_link_mtu;
-        u->write_block_size = u->write_link_mtu;
         u->sample_spec.format = PA_SAMPLE_S16LE;
         u->sample_spec.channels = 1;
         u->sample_spec.rate = 8000;
-        return 0;
-    }
-
-    return bt_transport_config_a2dp(u);
+    } else
+        bt_transport_config_a2dp(u);
 }
 
 /* Run from main thread */
@@ -1788,7 +1934,9 @@ static int setup_bt(struct userdata *u) {
     if (bt_transport_acquire(u, FALSE) < 0)
         return -1;
 
-    return bt_transport_config(u);
+    bt_transport_config(u);
+
+    return 0;
 }
 
 /* Run from main thread */
@@ -2051,7 +2199,7 @@ 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) {
+static void create_ports_for_profile(struct userdata *u, const pa_bluetooth_device *device, pa_card_new_data *card_new_data, pa_card_profile *profile) {
     pa_device_port *port;
     enum profile *d;
 
@@ -2064,6 +2212,7 @@ static void create_ports_for_profile(struct userdata *u, pa_card_new_data *card_
             port->is_output = 1;
             port->is_input = 0;
             port->priority = profile->priority * 100;
+            port->available = audio_state_to_availability(device->audio_sink_state);
             pa_hashmap_put(port->profiles, profile->name, profile);
             break;
 
@@ -2073,6 +2222,7 @@ static void create_ports_for_profile(struct userdata *u, pa_card_new_data *card_
             port->is_output = 0;
             port->is_input = 1;
             port->priority = profile->priority * 100;
+            port->available = audio_state_to_availability(device->audio_source_state);
             pa_hashmap_put(port->profiles, profile->name, profile);
             break;
 
@@ -2082,6 +2232,7 @@ static void create_ports_for_profile(struct userdata *u, pa_card_new_data *card_
             port->is_output = 1;
             port->is_input = 0;
             port->priority = profile->priority * 100;
+            port->available = audio_state_to_availability(device->headset_state);
             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));
@@ -2089,6 +2240,7 @@ static void create_ports_for_profile(struct userdata *u, pa_card_new_data *card_
             port->is_output = 0;
             port->is_input = 1;
             port->priority = profile->priority * 100;
+            port->available = audio_state_to_availability(device->headset_state);
             pa_hashmap_put(port->profiles, profile->name, profile);
             break;
 
@@ -2098,6 +2250,7 @@ static void create_ports_for_profile(struct userdata *u, pa_card_new_data *card_
             port->is_output = 1;
             port->is_input = 0;
             port->priority = profile->priority * 100;
+            port->available = audio_state_to_availability(device->hfgw_state);
             pa_hashmap_put(port->profiles, profile->name, profile);
 
             pa_assert_se(port = pa_device_port_new(u->core, "hfgw-input", _("Bluetooth Handsfree Gateway"), 0));
@@ -2105,6 +2258,7 @@ static void create_ports_for_profile(struct userdata *u, pa_card_new_data *card_
             port->is_output = 0;
             port->is_input = 1;
             port->priority = profile->priority * 100;
+            port->available = audio_state_to_availability(device->hfgw_state);
             pa_hashmap_put(port->profiles, profile->name, profile);
             break;
 
@@ -2168,7 +2322,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);
+        create_ports_for_profile(u, device, &data, p);
 
         pa_hashmap_put(data.profiles, p->name, p);
     }
@@ -2183,7 +2337,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);
+        create_ports_for_profile(u, device, &data, p);
 
         pa_hashmap_put(data.profiles, p->name, p);
     }
@@ -2199,7 +2353,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);
+        create_ports_for_profile(u, device, &data, p);
 
         pa_hashmap_put(data.profiles, p->name, p);
     }
@@ -2214,7 +2368,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);
+        create_ports_for_profile(u, device, &data, p);
 
         pa_hashmap_put(data.profiles, p->name, p);
     }
@@ -2413,6 +2567,9 @@ int pa__init(pa_module* m) {
                 mike,
                 "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
                 "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
+                "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
                 NULL) < 0) {
 
         pa_xfree(speaker);