]> code.delx.au - pulseaudio/blobdiff - src/modules/bluetooth/module-bluez5-device.c
bluetooth: Don't abort on SBC decoding error
[pulseaudio] / src / modules / bluetooth / module-bluez5-device.c
index cd9728cc9142dcc949e3b9725564c01d0485b663..61723f86586448fa2ff33245203fa713623f379c 100644 (file)
@@ -39,6 +39,7 @@
 #include <pulsecore/modargs.h>
 #include <pulsecore/poll.h>
 #include <pulsecore/rtpoll.h>
+#include <pulsecore/shared.h>
 #include <pulsecore/socket-util.h>
 #include <pulsecore/thread.h>
 #include <pulsecore/thread-mq.h>
@@ -97,6 +98,7 @@ struct userdata {
     pa_core *core;
 
     pa_hook_slot *device_connection_changed_slot;
+    pa_hook_slot *transport_state_changed_slot;
 
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
@@ -456,7 +458,7 @@ static int a2dp_process_push(struct userdata *u) {
                 pa_log_error("SBC decoding error (%li)", (long) decoded);
                 pa_memblock_release(memchunk.memblock);
                 pa_memblock_unref(memchunk.memblock);
-                return -1;
+                return 0;
             }
 
             /* Reset frame length, it can be changed due to bitpool change */
@@ -1093,10 +1095,12 @@ static void thread_func(void *userdata) {
                 if (n_read < 0)
                     goto io_fail;
 
-                /* We just read something, so we are supposed to write something, too */
-                pending_read_bytes += n_read;
-                do_write += pending_read_bytes / u->write_block_size;
-                pending_read_bytes = pending_read_bytes % u->write_block_size;
+                if (n_read > 0) {
+                    /* We just read something, so we are supposed to write something, too */
+                    pending_read_bytes += n_read;
+                    do_write += pending_read_bytes / u->write_block_size;
+                    pending_read_bytes = pending_read_bytes % u->write_block_size;
+                }
             }
         }
 
@@ -1586,7 +1590,7 @@ static int set_profile_cb(pa_card *c, pa_card_profile *new_profile) {
 off:
     stop_thread(u);
 
-    pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+    pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
 
     return -PA_ERR_IO;
 }
@@ -1669,6 +1673,72 @@ static int add_card(struct userdata *u) {
     return 0;
 }
 
+/* Run from main thread */
+static void handle_transport_state_change(struct userdata *u, struct pa_bluetooth_transport *t) {
+    bool acquire = false;
+    bool release = false;
+    pa_card_profile *cp;
+    pa_device_port *port;
+
+    pa_assert(u);
+    pa_assert(t);
+
+    /* Update profile availability */
+    if (!(cp = pa_hashmap_get(u->card->profiles, pa_bluetooth_profile_to_string(t->profile))))
+        return;
+    pa_card_profile_set_available(cp, transport_state_to_availability(t->state));
+
+    /* Update port availability */
+    pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
+    pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_OUTPUT));
+    pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name));
+    pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_INPUT));
+
+    /* Acquire or release transport as needed */
+    acquire = (t->state == PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == t->profile);
+    release = (t->state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == t->profile);
+
+    if (acquire && transport_acquire(u, true) >= 0) {
+        if (u->source) {
+            pa_log_debug("Resuming source %s because its transport state changed to playing", u->source->name);
+
+            /* We remove the IDLE suspend cause, because otherwise
+             * module-loopback doesn't uncork its streams. FIXME: Messing with
+             * the IDLE suspend cause here is wrong, the correct way to handle
+             * this would probably be to uncork the loopback streams not only
+             * when the other end is unsuspended, but also when the other end's
+             * suspend cause changes to IDLE only (currently there's no
+             * notification mechanism for suspend cause changes, though). */
+            pa_source_suspend(u->source, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+        }
+
+        if (u->sink) {
+            pa_log_debug("Resuming sink %s because its transport state changed to playing", u->sink->name);
+
+            /* FIXME: See the previous comment. */
+            pa_sink_suspend(u->sink, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+        }
+    }
+
+    if (release && u->transport_acquired) {
+        /* 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_log_debug("Suspending source %s because the remote end closed the stream", u->source->name);
+            pa_source_suspend(u->source, true, PA_SUSPEND_USER);
+        }
+
+        if (u->sink) {
+            pa_log_debug("Suspending sink %s because the remote end closed the stream", u->sink->name);
+            pa_sink_suspend(u->sink, true, PA_SUSPEND_USER);
+        }
+    }
+}
+
 /* Run from main thread */
 static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
     pa_assert(d);
@@ -1683,6 +1753,20 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
     return PA_HOOK_OK;
 }
 
+/* Run from main thread */
+static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t == u->transport && t->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)
+        pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
+
+    if (t->device == u->device)
+        handle_transport_state_change(u, t);
+
+    return PA_HOOK_OK;
+}
+
 /* Run 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);
@@ -1693,7 +1777,7 @@ static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t o
                 break;
 
             pa_log_debug("Switching the profile to off due to IO thread failure.");
-            pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+            pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
             break;
     }
 
@@ -1721,8 +1805,12 @@ int pa__init(pa_module* m) {
         goto fail;
     }
 
-    if (!(u->discovery = pa_bluetooth_discovery_get(m->core)))
+    if ((u->discovery = pa_shared_get(u->core, "bluetooth-discovery")))
+        pa_bluetooth_discovery_ref(u->discovery);
+    else {
+        pa_log_error("module-bluez5-discover doesn't seem to be loaded, refusing to load module-bluez5-device");
         goto fail;
+    }
 
     if (!(u->device = pa_bluetooth_discovery_get_device_by_path(u->discovery, path))) {
         pa_log_error("%s is unknown", path);
@@ -1735,6 +1823,10 @@ int pa__init(pa_module* m) {
         pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED),
                         PA_HOOK_NORMAL, (pa_hook_cb_t) device_connection_changed_cb, u);
 
+    u->transport_state_changed_slot =
+        pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED),
+                        PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u);
+
     if (add_card(u) < 0)
         goto fail;
 
@@ -1757,7 +1849,7 @@ int pa__init(pa_module* m) {
 off:
     stop_thread(u);
 
-    pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0);
+    pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0);
 
     return 0;
 
@@ -1784,6 +1876,9 @@ void pa__done(pa_module *m) {
     if (u->device_connection_changed_slot)
         pa_hook_slot_free(u->device_connection_changed_slot);
 
+    if (u->transport_state_changed_slot)
+        pa_hook_slot_free(u->transport_state_changed_slot);
+
     if (u->sbc_info.buffer)
         pa_xfree(u->sbc_info.buffer);
 
@@ -1804,3 +1899,12 @@ void pa__done(pa_module *m) {
 
     pa_xfree(u);
 }
+
+int pa__get_n_used(pa_module *m) {
+    struct userdata *u;
+
+    pa_assert(m);
+    pa_assert_se(u = m->userdata);
+
+    return (u->sink ? pa_sink_linked_by(u->sink) : 0) + (u->source ? pa_source_linked_by(u->source) : 0);
+}