summaryrefslogtreecommitdiffstats
path: root/src/modules/bluetooth
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/bluetooth')
-rw-r--r--src/modules/bluetooth/bluetooth-util.c437
-rw-r--r--src/modules/bluetooth/bluetooth-util.h58
-rw-r--r--src/modules/bluetooth/ipc.c1
-rw-r--r--src/modules/bluetooth/ipc.h73
-rw-r--r--src/modules/bluetooth/module-bluetooth-device.c1136
-rw-r--r--src/modules/bluetooth/module-bluetooth-discover.c108
-rw-r--r--src/modules/bluetooth/module-bluetooth-proximity.c6
-rw-r--r--src/modules/bluetooth/sbc.c43
-rw-r--r--src/modules/bluetooth/sbc.h14
9 files changed, 1161 insertions, 715 deletions
diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c
index ccc8bee3..66e1c31e 100644
--- a/src/modules/bluetooth/bluetooth-util.c
+++ b/src/modules/bluetooth/bluetooth-util.c
@@ -24,32 +24,38 @@
#endif
#include <pulsecore/core-util.h>
-#include <modules/dbus-util.h>
+#include <pulsecore/shared.h>
+#include <pulsecore/dbus-shared.h>
#include "bluetooth-util.h"
-enum mode {
- MODE_FIND,
- MODE_GET,
- MODE_DISCOVER
-};
-
struct pa_bluetooth_discovery {
- DBusConnection *connection;
+ PA_REFCNT_DECLARE;
+
+ pa_core *core;
+ pa_dbus_connection *connection;
PA_LLIST_HEAD(pa_dbus_pending, pending);
+ pa_hashmap *devices;
+ pa_hook hook;
+};
- enum mode mode;
+static void get_properties_reply(DBusPendingCall *pending, void *userdata);
+static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func);
- /* If mode == MODE_FIND look for a specific device by its address.
- If mode == MODE_GET look for a specific device by its path. */
- const char *looking_for;
- pa_bluetooth_device *found_device;
+static pa_bt_audio_state_t pa_bt_audio_state_from_string(const char* value) {
+ pa_assert(value);
- /* If looking_for is NULL we do long-time discovery */
- pa_hashmap *devices;
- pa_bluetooth_device_callback_t callback;
- struct userdata *userdata;
-};
+ if (pa_streq(value, "disconnected"))
+ return PA_BT_AUDIO_STATE_DISCONNECTED;
+ else if (pa_streq(value, "connecting"))
+ return PA_BT_AUDIO_STATE_CONNECTING;
+ else if (pa_streq(value, "connected"))
+ return PA_BT_AUDIO_STATE_CONNECTED;
+ else if (pa_streq(value, "playing"))
+ return PA_BT_AUDIO_STATE_PLAYING;
+
+ return PA_BT_AUDIO_STATE_INVALID;
+}
static pa_bluetooth_uuid *uuid_new(const char *uuid) {
pa_bluetooth_uuid *u;
@@ -73,9 +79,9 @@ static pa_bluetooth_device* device_new(const char *path) {
d = pa_xnew(pa_bluetooth_device, 1);
- d->device_info_valid = d->audio_sink_info_valid = d->headset_info_valid = 0;
+ d->dead = FALSE;
- d->data = NULL;
+ d->device_info_valid = 0;
d->name = NULL;
d->path = pa_xstrdup(path);
@@ -87,14 +93,14 @@ static pa_bluetooth_device* device_new(const char *path) {
d->class = -1;
d->trusted = -1;
- d->audio_sink_connected = -1;
-
- d->headset_connected = -1;
+ d->audio_state = PA_BT_AUDIO_STATE_INVALID;
+ d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID;
+ d->headset_state = PA_BT_AUDIO_STATE_INVALID;
return d;
}
-void pa_bluetooth_device_free(pa_bluetooth_device *d) {
+static void device_free(pa_bluetooth_device *d) {
pa_bluetooth_uuid *u;
pa_assert(d);
@@ -111,24 +117,14 @@ void pa_bluetooth_device_free(pa_bluetooth_device *d) {
pa_xfree(d);
}
-static pa_bool_t device_is_loaded(pa_bluetooth_device *d) {
- pa_assert(d);
-
- /* FIXME: e83621724d7939b97b4f01f0d7e965d61ef8e55e, f1daa282f030e4e2381341e0f65faca47c4b891b is borked, probably needs to be reversed */
-
- return d->device_info_valid && (d->audio_sink_info_valid || d->headset_info_valid);
-}
-
static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
pa_assert(d);
- pa_assert(d->device_info_valid);
- pa_assert(d->audio_sink_info_valid || d->headset_info_valid);
-
- /* FIXME: e83621724d7939b97b4f01f0d7e965d61ef8e55e, f1daa282f030e4e2381341e0f65faca47c4b891b is borked, probably needs to be reversed */
-
- return d->device_info_valid > 0 &&
- (d->audio_sink_info_valid > 0 || d->headset_info_valid > 0);
+ return
+ d->device_info_valid &&
+ (d->audio_state != PA_BT_AUDIO_STATE_INVALID ||
+ d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
+ d->headset_state != PA_BT_AUDIO_STATE_INVALID);
}
static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) {
@@ -224,11 +220,25 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device
while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
pa_bluetooth_uuid *node;
const char *value;
+ DBusMessage *m;
dbus_message_iter_get_basic(&ai, &value);
node = uuid_new(value);
PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
+ /* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
+ send_and_add_to_pending(y, d, m, get_properties_reply);
+
+ /* Vudentz said the interfaces are here when the UUIDs are announced */
+ if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties"));
+ send_and_add_to_pending(y, d, m, get_properties_reply);
+ } else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties"));
+ send_and_add_to_pending(y, d, m, get_properties_reply);
+ }
+
if (!dbus_message_iter_next(&ai))
break;
}
@@ -241,12 +251,12 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device
return 0;
}
-static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusMessageIter *i) {
+static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessageIter *i) {
const char *key;
DBusMessageIter variant_i;
pa_assert(u);
- pa_assert(connected);
+ pa_assert(state);
pa_assert(i);
if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
@@ -268,19 +278,18 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusM
dbus_message_iter_recurse(i, &variant_i);
-/* pa_log_debug("Parsing property org.bluez.{AudioSink|Headset}.%s", key); */
+/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|Headset}.%s", key); */
switch (dbus_message_iter_get_arg_type(&variant_i)) {
- case DBUS_TYPE_BOOLEAN: {
+ case DBUS_TYPE_STRING: {
- dbus_bool_t value;
+ const char *value;
dbus_message_iter_get_basic(&variant_i, &value);
- if (pa_streq(key, "Connected"))
- *connected = !!value;
-
-/* pa_log_debug("Value %s", pa_yes_no(value)); */
+ if (pa_streq(key, "State"))
+ *state = pa_bt_audio_state_from_string(value);
+/* pa_log_debug("Value %s", value); */
break;
}
@@ -289,21 +298,26 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusM
return 0;
}
-static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_bool_t good) {
+static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_bool_t dead) {
pa_assert(y);
pa_assert(d);
- if (y->mode != MODE_DISCOVER)
+ if (!device_is_audio(d))
return;
- if (!device_is_loaded(d))
- return;
+ d->dead = dead;
+ pa_hook_fire(&y->hook, d);
+}
- if (!device_is_audio(d))
- return;
+static void remove_all_devices(pa_bluetooth_discovery *y) {
+ pa_bluetooth_device *d;
- y->callback(y->userdata, d, good);
+ pa_assert(y);
+ while ((d = pa_hashmap_steal_first(y->devices))) {
+ run_callback(y, d, TRUE);
+ device_free(d);
+ }
}
static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
@@ -328,10 +342,12 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
d->device_info_valid = valid;
- else if (dbus_message_is_method_call(p->message, "org.bluez.Headset", "GetProperties"))
- d->headset_info_valid = valid;
- else if (dbus_message_is_method_call(p->message, "org.bluez.AudioSink", "GetProperties"))
- d->audio_sink_info_valid = valid;
+
+ if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
+ pa_log_debug("Bluetooth daemon is apparently not available.");
+ remove_all_devices(y);
+ goto finish2;
+ }
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
@@ -363,12 +379,16 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
if (parse_device_property(y, d, &dict_i) < 0)
goto finish;
+ } else if (dbus_message_has_interface(p->message, "org.bluez.Audio")) {
+ if (parse_audio_property(y, &d->audio_state, &dict_i) < 0)
+ goto finish;
+
} else if (dbus_message_has_interface(p->message, "org.bluez.Headset")) {
- if (parse_audio_property(y, &d->headset_connected, &dict_i) < 0)
+ if (parse_audio_property(y, &d->headset_state, &dict_i) < 0)
goto finish;
} else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) {
- if (parse_audio_property(y, &d->audio_sink_connected, &dict_i) < 0)
+ if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0)
goto finish;
}
}
@@ -378,8 +398,9 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
}
finish:
- run_callback(y, d, TRUE);
+ run_callback(y, d, FALSE);
+finish2:
dbus_message_unref(r);
PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
@@ -393,9 +414,9 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bl
pa_assert(y);
pa_assert(m);
- pa_assert_se(dbus_connection_send_with_reply(y->connection, m, &call, -1));
+ pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1));
- p = pa_dbus_pending_new(m, call, y, d);
+ p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, d);
PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p);
dbus_pending_call_set_notify(call, func, p, NULL);
@@ -409,24 +430,18 @@ static void found_device(pa_bluetooth_discovery *y, const char* path) {
pa_assert(y);
pa_assert(path);
+ if (pa_hashmap_get(y->devices, path))
+ return;
+
d = device_new(path);
- if (y->mode == MODE_DISCOVER) {
- pa_assert(y->devices);
- pa_hashmap_put(y->devices, d->path, d);
- } else {
- pa_assert(!y->found_device);
- y->found_device = d;
- }
+ pa_hashmap_put(y->devices, d->path, d);
pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
send_and_add_to_pending(y, d, m, get_properties_reply);
- pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Headset", "GetProperties"));
- send_and_add_to_pending(y, d, m, get_properties_reply);
-
- pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.AudioSink", "GetProperties"));
- send_and_add_to_pending(y, d, m, get_properties_reply);
+ /* Before we read the other properties (Audio, AudioSink, Headset) we wait
+ * that the UUID is read */
}
static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
@@ -445,9 +460,15 @@ static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
pa_assert_se(y = p->context_data);
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+ if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
+ pa_log_debug("Bluetooth daemon is apparently not available.");
+ remove_all_devices(y);
+ goto finish;
+ }
+
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r));
- goto end;
+ goto finish;
}
if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
@@ -460,7 +481,7 @@ static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
found_device(y, paths[i]);
}
-end:
+finish:
if (paths)
dbus_free_string_array (paths);
@@ -470,57 +491,11 @@ end:
pa_dbus_pending_free(p);
}
-static void find_device_reply(DBusPendingCall *pending, void *userdata) {
- DBusError e;
- DBusMessage *r;
- char *path = NULL;
- pa_dbus_pending *p;
- pa_bluetooth_discovery *y;
-
- pa_assert(pending);
-
- dbus_error_init(&e);
-
- pa_assert_se(p = userdata);
- pa_assert_se(y = p->context_data);
- pa_assert_se(r = dbus_pending_call_steal_reply(pending));
-
- if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
- pa_log("Error from FindDevice reply: %s", dbus_message_get_error_name(r));
- goto end;
- }
-
- if (!dbus_message_get_args(r, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) {
- pa_log("org.bluez.Adapter.FindDevice returned an error: '%s'\n", e.message);
- dbus_error_free(&e);
- } else
- found_device(y, path);
-
-end:
- dbus_message_unref(r);
-
- PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p);
- pa_dbus_pending_free(p);
-}
-
static void found_adapter(pa_bluetooth_discovery *y, const char *path) {
DBusMessage *m;
- if (y->mode == MODE_FIND) {
- pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "FindDevice"));
-
- pa_assert_se(dbus_message_append_args(m,
- DBUS_TYPE_STRING, &y->looking_for,
- DBUS_TYPE_INVALID));
-
- send_and_add_to_pending(y, NULL, m, find_device_reply);
-
- } else {
- pa_assert(y->mode == MODE_DISCOVER);
-
- pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices"));
- send_and_add_to_pending(y, NULL, m, list_devices_reply);
- }
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices"));
+ send_and_add_to_pending(y, NULL, m, list_devices_reply);
}
static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {
@@ -539,9 +514,15 @@ static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {
pa_assert_se(y = p->context_data);
pa_assert_se(r = dbus_pending_call_steal_reply(pending));
+ if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) {
+ pa_log_debug("Bluetooth daemon is apparently not available.");
+ remove_all_devices(y);
+ goto finish;
+ }
+
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r));
- goto end;
+ goto finish;
}
if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
@@ -554,7 +535,7 @@ static void list_adapters_reply(DBusPendingCall *pending, void *userdata) {
found_adapter(y, paths[i]);
}
-end:
+finish:
if (paths)
dbus_free_string_array (paths);
@@ -600,11 +581,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
pa_log_debug("Device %s removed", path);
if ((d = pa_hashmap_remove(y->devices, path))) {
-
- pa_assert_se(y->mode == MODE_DISCOVER);
- run_callback(y, d, FALSE);
-
- pa_bluetooth_device_free(d);
+ run_callback(y, d, TRUE);
+ device_free(d);
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
@@ -635,7 +613,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
found_adapter(y, path);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
- } else if (dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
+ } else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
+ dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
@@ -653,19 +632,46 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
if (parse_device_property(y, d, &arg_i) < 0)
goto fail;
+ } else if (dbus_message_has_interface(m, "org.bluez.Audio")) {
+ if (parse_audio_property(y, &d->audio_state, &arg_i) < 0)
+ goto fail;
+
} else if (dbus_message_has_interface(m, "org.bluez.Headset")) {
- if (parse_audio_property(y, &d->headset_connected, &arg_i) < 0)
+ if (parse_audio_property(y, &d->headset_state, &arg_i) < 0)
goto fail;
- d->headset_info_valid = 1;
} else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) {
- if (parse_audio_property(y, &d->audio_sink_connected, &arg_i) < 0)
+ if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0)
goto fail;
- d->audio_sink_info_valid = 1;
}
- pa_assert_se(y->mode == MODE_DISCOVER);
- run_callback(y, d, TRUE);
+ run_callback(y, d, FALSE);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
+ const char *name, *old_owner, *new_owner;
+
+ if (!dbus_message_get_args(m, &err,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &old_owner,
+ DBUS_TYPE_STRING, &new_owner,
+ DBUS_TYPE_INVALID)) {
+ pa_log("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message);
+ goto fail;
+ }
+
+ if (pa_streq(name, "org.bluez")) {
+ if (old_owner && *old_owner) {
+ pa_log_debug("Bluetooth daemon disappeared.");
+ remove_all_devices(y);
+ }
+
+ if (new_owner && *new_owner) {
+ pa_log_debug("Bluetooth daemon appeared.");
+ list_adapters(y);
+ }
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
@@ -677,86 +683,87 @@ fail:
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
-pa_bluetooth_device* pa_bluetooth_find_device(DBusConnection *c, const char* address) {
- pa_bluetooth_discovery y;
-
- memset(&y, 0, sizeof(y));
- y.mode = MODE_FIND;
- y.looking_for = address;
- y.connection = c;
- PA_LLIST_HEAD_INIT(pa_dbus_pending, y.pending);
+const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) {
+ pa_bluetooth_device *d;
+ void *state = NULL;
- list_adapters(&y);
+ pa_assert(y);
+ pa_assert(PA_REFCNT_VALUE(y) > 0);
+ pa_assert(address);
- pa_dbus_sync_pending_list(&y.pending);
- pa_assert(!y.pending);
+ if (!pa_hook_is_firing(&y->hook))
+ pa_bluetooth_discovery_sync(y);
- if (y.found_device) {
- pa_assert(device_is_loaded(y.found_device));
+ while ((d = pa_hashmap_iterate(y->devices, &state, NULL)))
+ if (pa_streq(d->address, address))
+ return d;
- if (!device_is_audio(y.found_device)) {
- pa_bluetooth_device_free(y.found_device);
- return NULL;
- }
- }
-
- return y.found_device;
+ return NULL;
}
-pa_bluetooth_device* pa_bluetooth_get_device(DBusConnection *c, const char* path) {
- pa_bluetooth_discovery y;
+const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) {
+ pa_assert(y);
+ pa_assert(PA_REFCNT_VALUE(y) > 0);
+ pa_assert(path);
+
+ if (!pa_hook_is_firing(&y->hook))
+ pa_bluetooth_discovery_sync(y);
- memset(&y, 0, sizeof(y));
- y.mode = MODE_GET;
- y.connection = c;
- PA_LLIST_HEAD_INIT(pa_dbus_pending, y.pending);
+ return pa_hashmap_get(y->devices, path);
+}
- found_device(&y, path);
+static int setup_dbus(pa_bluetooth_discovery *y) {
+ DBusError err;
- pa_dbus_sync_pending_list(&y.pending);
- pa_assert(!y.pending);
+ dbus_error_init(&err);
- if (y.found_device) {
- pa_assert(device_is_loaded(y.found_device));
+ y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err);
- if (!device_is_audio(y.found_device)) {
- pa_bluetooth_device_free(y.found_device);
- return NULL;
- }
+ if (dbus_error_is_set(&err) || !y->connection) {
+ pa_log("Failed to get D-Bus connection: %s", err.message);
+ dbus_error_free(&err);
+ return -1;
}
- return y.found_device;
+ return 0;
}
-pa_bluetooth_discovery* pa_bluetooth_discovery_new(DBusConnection *c, pa_bluetooth_device_callback_t cb, struct userdata *u) {
+pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
DBusError err;
pa_bluetooth_discovery *y;
pa_assert(c);
- pa_assert(cb);
dbus_error_init(&err);
+ if ((y = pa_shared_get(c, "bluetooth-discovery")))
+ return pa_bluetooth_discovery_ref(y);
+
y = pa_xnew0(pa_bluetooth_discovery, 1);
- y->mode = MODE_DISCOVER;
- y->connection = c;
- y->callback = cb;
- y->userdata = u;
+ PA_REFCNT_INIT(y);
+ y->core = c;
y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending);
+ pa_hook_init(&y->hook, y);
+ pa_shared_set(c, "bluetooth-discovery", y);
+
+ if (setup_dbus(y) < 0)
+ goto fail;
/* dynamic detection of bluetooth audio devices */
- if (!dbus_connection_add_filter(c, filter_cb, y, NULL)) {
+ if (!dbus_connection_add_filter(pa_dbus_connection_get(y->connection), filter_cb, y, NULL)) {
pa_log_error("Failed to add filter function");
goto fail;
}
if (pa_dbus_add_matches(
- c, &err,
+ pa_dbus_connection_get(y->connection), &err,
+ "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL) < 0) {
pa_log("Failed to add D-Bus matches: %s", err.message);
@@ -768,46 +775,77 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_new(DBusConnection *c, pa_bluetoo
return y;
fail:
+
+ if (y)
+ pa_bluetooth_discovery_unref(y);
+
dbus_error_free(&err);
+
return NULL;
}
-void pa_bluetooth_discovery_free(pa_bluetooth_discovery *y) {
- pa_bluetooth_device *d;
+pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
+ pa_assert(y);
+ pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+ PA_REFCNT_INC(y);
+
+ return y;
+}
+void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
pa_assert(y);
+ pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+ if (PA_REFCNT_DEC(y) > 0)
+ return;
pa_dbus_free_pending_list(&y->pending);
if (y->devices) {
- while ((d = pa_hashmap_steal_first(y->devices))) {
- run_callback(y, d, FALSE);
- pa_bluetooth_device_free(d);
- }
-
+ remove_all_devices(y);
pa_hashmap_free(y->devices, NULL, NULL);
}
if (y->connection) {
- pa_dbus_remove_matches(y->connection,
+ pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),
+ "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'",
"type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterRemoved'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL);
- dbus_connection_remove_filter(y->connection, filter_cb, y);
+ dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y);
+
+ pa_dbus_connection_unref(y->connection);
}
+
+ pa_hook_done(&y->hook);
+
+ if (y->core)
+ pa_shared_remove(y->core, "bluetooth-discovery");
+
+ pa_xfree(y);
}
void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) {
pa_assert(y);
+ pa_assert(PA_REFCNT_VALUE(y) > 0);
pa_dbus_sync_pending_list(&y->pending);
}
+pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y) {
+ pa_assert(y);
+ pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+ return &y->hook;
+}
+
const char*pa_bluetooth_get_form_factor(uint32_t class) {
unsigned i;
const char *r;
@@ -867,3 +905,16 @@ char *pa_bluetooth_cleanup_name(const char *name) {
return t;
}
+
+pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
+ pa_assert(uuid);
+
+ while (uuids) {
+ if (strcasecmp(uuids->uuid, uuid) == 0)
+ return TRUE;
+
+ uuids = uuids->next;
+ }
+
+ return FALSE;
+}
diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h
index 0364c972..265caf40 100644
--- a/src/modules/bluetooth/bluetooth-util.h
+++ b/src/modules/bluetooth/bluetooth-util.h
@@ -28,6 +28,20 @@
#include <pulsecore/macro.h>
#include <pulsecore/core-util.h>
+/* UUID copied from bluez/audio/device.h */
+#define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805F9B34FB"
+
+#define HSP_HS_UUID "00001108-0000-1000-8000-00805F9B34FB"
+#define HSP_AG_UUID "00001112-0000-1000-8000-00805F9B34FB"
+
+#define HFP_HS_UUID "0000111E-0000-1000-8000-00805F9B34FB"
+#define HFP_AG_UUID "0000111F-0000-1000-8000-00805F9B34FB"
+
+#define ADVANCED_AUDIO_UUID "0000110D-0000-1000-8000-00805F9B34FB"
+
+#define A2DP_SOURCE_UUID "0000110A-0000-1000-8000-00805F9B34FB"
+#define A2DP_SINK_UUID "0000110B-0000-1000-8000-00805F9B34FB"
+
typedef struct pa_bluetooth_uuid pa_bluetooth_uuid;
typedef struct pa_bluetooth_device pa_bluetooth_device;
typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
@@ -39,12 +53,20 @@ struct pa_bluetooth_uuid {
PA_LLIST_FIELDS(pa_bluetooth_uuid);
};
+/* This enum is shared among Audio, Headset, and AudioSink, although not all values are acceptable in all profiles */
+typedef enum pa_bt_audio_state {
+ PA_BT_AUDIO_STATE_INVALID = -1,
+ PA_BT_AUDIO_STATE_DISCONNECTED,
+ PA_BT_AUDIO_STATE_CONNECTING,
+ PA_BT_AUDIO_STATE_CONNECTED,
+ PA_BT_AUDIO_STATE_PLAYING,
+ PA_BT_AUDIO_STATE_LAST
+} pa_bt_audio_state_t;
+
struct pa_bluetooth_device {
- void *data; /* arbitrary information for the one owning the discovery object */
+ pa_bool_t dead;
int device_info_valid; /* 0: no results yet; 1: good results; -1: bad results ... */
- int audio_sink_info_valid; /* ... same here ... */
- int headset_info_valid; /* ... and here */
/* Device information */
char *name;
@@ -57,25 +79,31 @@ struct pa_bluetooth_device {
int class;
int trusted;
- /* AudioSink information */
- int audio_sink_connected;
+ /* Audio state */
+ pa_bt_audio_state_t audio_state;
- /* Headset information */
- int headset_connected;
-};
+ /* AudioSink state */
+ pa_bt_audio_state_t audio_sink_state;
-void pa_bluetooth_device_free(pa_bluetooth_device *d);
+ /* Headset state */
+ pa_bt_audio_state_t headset_state;
+};
-pa_bluetooth_device* pa_bluetooth_get_device(DBusConnection *c, const char* path);
-pa_bluetooth_device* pa_bluetooth_find_device(DBusConnection *c, const char* address);
+pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);
+pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
+void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *d);
-typedef void (*pa_bluetooth_device_callback_t)(struct userdata *u, pa_bluetooth_device *d, pa_bool_t good);
-pa_bluetooth_discovery* pa_bluetooth_discovery_new(DBusConnection *c, pa_bluetooth_device_callback_t cb, struct userdata *u);
-void pa_bluetooth_discovery_free(pa_bluetooth_discovery *d);
void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *d);
-const char*pa_bluetooth_get_form_factor(uint32_t class);
+const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path);
+const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address);
+
+pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *d);
+
+const char* pa_bluetooth_get_form_factor(uint32_t class);
char *pa_bluetooth_cleanup_name(const char *name);
+pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid);
+
#endif
diff --git a/src/modules/bluetooth/ipc.c b/src/modules/bluetooth/ipc.c
index f14c92c4..dcecad8a 100644
--- a/src/modules/bluetooth/ipc.c
+++ b/src/modules/bluetooth/ipc.c
@@ -35,6 +35,7 @@ static const char *strtypes[] = {
/* This table contains the string representation for messages names */
static const char *strnames[] = {
"BT_GET_CAPABILITIES",
+ "BT_OPEN",
"BT_SET_CONFIGURATION",
"BT_NEW_STREAM",
"BT_START_STREAM",
diff --git a/src/modules/bluetooth/ipc.h b/src/modules/bluetooth/ipc.h
index f030acfa..2e170f50 100644
--- a/src/modules/bluetooth/ipc.h
+++ b/src/modules/bluetooth/ipc.h
@@ -71,7 +71,7 @@ extern "C" {
#include <sys/un.h>
#include <errno.h>
-#define BT_SUGGESTED_BUFFER_SIZE 128
+#define BT_SUGGESTED_BUFFER_SIZE 512
#define BT_IPC_SOCKET_NAME "\0/org/bluez/audio"
/* Generic message header definition, except for RESPONSE messages */
@@ -94,10 +94,12 @@ typedef struct {
/* Messages names */
#define BT_GET_CAPABILITIES 0
-#define BT_SET_CONFIGURATION 1
-#define BT_NEW_STREAM 2
-#define BT_START_STREAM 3
-#define BT_STOP_STREAM 4
+#define BT_OPEN 1
+#define BT_SET_CONFIGURATION 2
+#define BT_NEW_STREAM 3
+#define BT_START_STREAM 4
+#define BT_STOP_STREAM 5
+#define BT_CLOSE 6
#define BT_CONTROL 7
#define BT_CAPABILITIES_TRANSPORT_A2DP 0
@@ -112,19 +114,31 @@ typedef struct {
struct bt_get_capabilities_req {
bt_audio_msg_header_t h;
- char device[18]; /* Address of the remote Device */
+ char source[18]; /* Address of the local Device */
+ char destination[18];/* Address of the remote Device */
+ char object[128]; /* DBus object path */
uint8_t transport; /* Requested transport */
uint8_t flags; /* Requested flags */
+ uint8_t seid; /* Requested capability configuration */
} __attribute__ ((packed));
/**
- * SBC Codec parameters as per A2DP profile 1.0 § 4.3
+ * SBC Codec parameters as per A2DP profile 1.0 § 4.3
*/
-#define BT_A2DP_CODEC_SBC 0x00
-#define BT_A2DP_CODEC_MPEG12 0x01
-#define BT_A2DP_CODEC_MPEG24 0x02
-#define BT_A2DP_CODEC_ATRAC 0x03
+/* A2DP seid are 6 bytes long so HSP/HFP are assigned to 7-8 bits */
+#define BT_A2DP_SEID_RANGE (1 << 6) - 1
+
+#define BT_A2DP_SBC_SOURCE 0x00
+#define BT_A2DP_SBC_SINK 0x01
+#define BT_A2DP_MPEG12_SOURCE 0x02
+#define BT_A2DP_MPEG12_SINK 0x03
+#define BT_A2DP_MPEG24_SOURCE 0x04
+#define BT_A2DP_MPEG24_SINK 0x05
+#define BT_A2DP_ATRAC_SOURCE 0x06
+#define BT_A2DP_ATRAC_SINK 0x07
+#define BT_A2DP_UNKNOWN_SOURCE 0x08
+#define BT_A2DP_UNKNOWN_SINK 0x09
#define BT_SBC_SAMPLING_FREQ_16000 (1 << 3)
#define BT_SBC_SAMPLING_FREQ_32000 (1 << 2)
@@ -163,10 +177,16 @@ struct bt_get_capabilities_req {
#define BT_PCM_FLAG_NREC 0x01
#define BT_PCM_FLAG_PCM_ROUTING 0x02
+#define BT_WRITE_LOCK (1 << 1)
+#define BT_READ_LOCK 1
+
typedef struct {
+ uint8_t seid;
uint8_t transport;
uint8_t type;
uint8_t length;
+ uint8_t configured;
+ uint8_t lock;
uint8_t data[0];
} __attribute__ ((packed)) codec_capabilities_t;
@@ -199,20 +219,35 @@ typedef struct {
struct bt_get_capabilities_rsp {
bt_audio_msg_header_t h;
+ char source[18]; /* Address of the local Device */
+ char destination[18];/* Address of the remote Device */
+ char object[128]; /* DBus object path */
uint8_t data[0]; /* First codec_capabilities_t */
} __attribute__ ((packed));
+struct bt_open_req {
+ bt_audio_msg_header_t h;
+ char source[18]; /* Address of the local Device */
+ char destination[18];/* Address of the remote Device */
+ char object[128]; /* DBus object path */
+ uint8_t seid; /* Requested capability configuration to lock */
+ uint8_t lock; /* Requested lock */
+} __attribute__ ((packed));
+
+struct bt_open_rsp {
+ bt_audio_msg_header_t h;
+ char source[18]; /* Address of the local Device */
+ char destination[18];/* Address of the remote Device */
+ char object[128]; /* DBus object path */
+} __attribute__ ((packed));
+
struct bt_set_configuration_req {
bt_audio_msg_header_t h;
- char device[18]; /* Address of the remote Device */
- uint8_t access_mode; /* Requested access mode */
codec_capabilities_t codec; /* Requested codec */
} __attribute__ ((packed));
struct bt_set_configuration_rsp {
bt_audio_msg_header_t h;
- uint8_t transport; /* Granted transport */
- uint8_t access_mode; /* Granted access mode */
uint16_t link_mtu; /* Max length that transport supports */
} __attribute__ ((packed));
@@ -241,6 +276,14 @@ struct bt_stop_stream_rsp {
bt_audio_msg_header_t h;
} __attribute__ ((packed));
+struct bt_close_req {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
+struct bt_close_rsp {
+ bt_audio_msg_header_t h;
+} __attribute__ ((packed));
+
struct bt_suspend_stream_ind {
bt_audio_msg_header_t h;
} __attribute__ ((packed));
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 93673cb9..609efd17 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -30,13 +30,15 @@
#include <linux/sockios.h>
#include <arpa/inet.h>
-#include <pulse/xmalloc.h>
-#include <pulse/timeval.h>
-#include <pulse/sample.h>
#include <pulse/i18n.h>
+#include <pulse/rtclock.h>
+#include <pulse/sample.h>
+#include <pulse/timeval.h>
+#include <pulse/xmalloc.h>
#include <pulsecore/module.h>
#include <pulsecore/modargs.h>
+#include <pulsecore/core-rtclock.h>
#include <pulsecore/core-util.h>
#include <pulsecore/core-error.h>
#include <pulsecore/socket-util.h>
@@ -44,10 +46,8 @@
#include <pulsecore/thread-mq.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/time-smoother.h>
-#include <pulsecore/rtclock.h>
#include <pulsecore/namereg.h>
-
-#include <modules/dbus-util.h>
+#include <pulsecore/dbus-shared.h>
#include "module-bluetooth-device-symdef.h"
#include "ipc.h"
@@ -65,32 +65,43 @@ PA_MODULE_LOAD_ONCE(FALSE);
PA_MODULE_USAGE(
"name=<name for the card/sink/source, to be prefixed> "
"card_name=<name for the card> "
+ "card_properties=<properties for the card> "
"sink_name=<name for the sink> "
+ "sink_properties=<properties for the sink> "
"source_name=<name for the source> "
+ "source_properties=<properties for the source> "
"address=<address of the device> "
"profile=<a2dp|hsp> "
"rate=<sample rate> "
"channels=<number of channels> "
- "path=<device object path> "
+ "path=<device object path>");
+
+/*
+#ifdef NOKIA
"sco_sink=<SCO over PCM sink name> "
- "sco_source=<SCO over PCM source name>");
+ "sco_source=<SCO over PCM source name>"
+#endif
+*/
/* TODO: not close fd when entering suspend mode in a2dp */
-/* TODO: BT_PCM_FLAG_NREC */
-
static const char* const valid_modargs[] = {
"name",
"card_name",
+ "card_properties",
"sink_name",
+ "sink_properties",
"source_name",
+ "source_properties",
"address",
"profile",
"rate",
"channels",
"path",
+#ifdef NOKIA
"sco_sink",
"sco_source",
+#endif
NULL
};
@@ -98,7 +109,7 @@ struct a2dp_info {
sbc_capabilities_t sbc_capabilities;
sbc_t sbc; /* Codec data */
pa_bool_t sbc_initialized; /* Keep track if the encoder is initialized */
- size_t codesize; /* SBC codesize */
+ size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
void* buffer; /* Codec transfer buffer */
size_t buffer_size; /* Size of the buffer */
@@ -108,8 +119,10 @@ struct a2dp_info {
struct hsp_info {
pcm_capabilities_t pcm_capabilities;
+#ifdef NOKIA
pa_sink *sco_sink;
pa_source *sco_source;
+#endif
pa_hook_slot *sink_state_changed_slot;
pa_hook_slot *source_state_changed_slot;
};
@@ -124,6 +137,12 @@ struct userdata {
pa_core *core;
pa_module *module;
+ char *address;
+ char *path;
+ pa_bluetooth_discovery *discovery;
+
+ pa_dbus_connection *connection;
+
pa_card *card;
pa_sink *sink;
pa_source *source;
@@ -149,19 +168,24 @@ struct userdata {
struct a2dp_info a2dp;
struct hsp_info hsp;
- pa_dbus_connection *connection;
enum profile profile;
pa_modargs *modargs;
- pa_bluetooth_device *device;
-
- int stream_write_type, stream_read_type;
+ int stream_write_type;
int service_write_type, service_read_type;
};
+#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC)
+
+#define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC)
+
+#ifdef NOKIA
#define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source))
+#endif
static int init_bt(struct userdata *u);
static int init_profile(struct userdata *u);
@@ -264,7 +288,8 @@ static ssize_t service_expect(struct userdata*u, bt_audio_msg_header_t *rsp, siz
return 0;
}
-static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *rsp) {
+/* Run from main thread */
+static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capabilities_rsp *rsp) {
uint16_t bytes_left;
const codec_capabilities_t *codec;
@@ -295,12 +320,15 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *
pa_assert(codec->type == BT_HFP_CODEC_PCM);
+ if (codec->configured && seid == 0)
+ return codec->seid;
+
memcpy(&u->hsp.pcm_capabilities, codec, sizeof(u->hsp.pcm_capabilities));
} else if (u->profile == PROFILE_A2DP) {
while (bytes_left > 0) {
- if (codec->type == BT_A2DP_CODEC_SBC)
+ if ((codec->type == BT_A2DP_SBC_SINK) && !codec->lock)
break;
bytes_left -= codec->length;
@@ -310,7 +338,10 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *
if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities))
return -1;
- pa_assert(codec->type == BT_A2DP_CODEC_SBC);
+ pa_assert(codec->type == BT_A2DP_SBC_SINK);
+
+ if (codec->configured && seid == 0)
+ return codec->seid;
memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities));
}
@@ -318,13 +349,15 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *
return 0;
}
-static int get_caps(struct userdata *u) {
+/* Run from main thread */
+static int get_caps(struct userdata *u, uint8_t seid) {
union {
struct bt_get_capabilities_req getcaps_req;
struct bt_get_capabilities_rsp getcaps_rsp;
bt_audio_error_t error;
uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
} msg;
+ int ret;
pa_assert(u);
@@ -332,8 +365,9 @@ static int get_caps(struct userdata *u) {
msg.getcaps_req.h.type = BT_REQUEST;
msg.getcaps_req.h.name = BT_GET_CAPABILITIES;
msg.getcaps_req.h.length = sizeof(msg.getcaps_req);
+ msg.getcaps_req.seid = seid;
- pa_strlcpy(msg.getcaps_req.device, u->device->address, sizeof(msg.getcaps_req.device));
+ pa_strlcpy(msg.getcaps_req.object, u->path, sizeof(msg.getcaps_req.object));
if (u->profile == PROFILE_A2DP)
msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
else {
@@ -348,9 +382,14 @@ static int get_caps(struct userdata *u) {
if (service_expect(u, &msg.getcaps_rsp.h, sizeof(msg), BT_GET_CAPABILITIES, 0) < 0)
return -1;
- return parse_caps(u, &msg.getcaps_rsp);
+ ret = parse_caps(u, seid, &msg.getcaps_rsp);
+ if (ret <= 0)
+ return ret;
+
+ return get_caps(u, ret);
}
+/* Run from main thread */
static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
switch (freq) {
@@ -396,6 +435,7 @@ static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
}
}
+/* Run from main thread */
static int setup_a2dp(struct userdata *u) {
sbc_capabilities_t *cap;
int i;
@@ -424,8 +464,8 @@ static int setup_a2dp(struct userdata *u) {
break;
}
- if ((unsigned) i >= PA_ELEMENTSOF(freq_table)) {
- for (; i >= 0; i--) {
+ if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
+ for (--i; i >= 0; i--) {
if (cap->frequency & freq_table[i].cap) {
u->sample_spec.rate = freq_table[i].rate;
cap->frequency = freq_table[i].cap;
@@ -439,6 +479,11 @@ static int setup_a2dp(struct userdata *u) {
}
}
+ pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
+
+ if (cap->capability.configured)
+ return 0;
+
if (u->sample_spec.channels <= 1) {
if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
@@ -498,6 +543,7 @@ static int setup_a2dp(struct userdata *u) {
return 0;
}
+/* Run from main thread */
static void setup_sbc(struct a2dp_info *a2dp) {
sbc_capabilities_t *active_capabilities;
@@ -585,17 +631,36 @@ static void setup_sbc(struct a2dp_info *a2dp) {
}
a2dp->sbc.bitpool = active_capabilities->max_bitpool;
- a2dp->codesize = (uint16_t) sbc_get_codesize(&a2dp->sbc);
+ a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
+ a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
}
+/* Run from main thread */
static int set_conf(struct userdata *u) {
union {
+ struct bt_open_req open_req;
+ struct bt_open_rsp open_rsp;
struct bt_set_configuration_req setconf_req;
struct bt_set_configuration_rsp setconf_rsp;
bt_audio_error_t error;
uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
} msg;
+ memset(&msg, 0, sizeof(msg));
+ msg.open_req.h.type = BT_REQUEST;
+ msg.open_req.h.name = BT_OPEN;
+ msg.open_req.h.length = sizeof(msg.open_req);
+
+ pa_strlcpy(msg.open_req.object, u->path, sizeof(msg.open_req.object));
+ msg.open_req.seid = u->profile == PROFILE_A2DP ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1;
+ msg.open_req.lock = u->profile == PROFILE_A2DP ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
+
+ if (service_send(u, &msg.open_req.h) < 0)
+ return -1;
+
+ if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0)
+ return -1;
+
if (u->profile == PROFILE_A2DP ) {
u->sample_spec.format = PA_SAMPLE_S16LE;
@@ -614,15 +679,14 @@ static int set_conf(struct userdata *u) {
msg.setconf_req.h.name = BT_SET_CONFIGURATION;
msg.setconf_req.h.length = sizeof(msg.setconf_req);
- pa_strlcpy(msg.setconf_req.device, u->device->address, sizeof(msg.setconf_req.device));
- msg.setconf_req.access_mode = u->profile == PROFILE_A2DP ? BT_CAPABILITIES_ACCESS_MODE_WRITE : BT_CAPABILITIES_ACCESS_MODE_READWRITE;
-
- msg.setconf_req.codec.transport = u->profile == PROFILE_A2DP ? BT_CAPABILITIES_TRANSPORT_A2DP : BT_CAPABILITIES_TRANSPORT_SCO;
-
if (u->profile == PROFILE_A2DP) {
memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, sizeof(u->a2dp.sbc_capabilities));
- msg.setconf_req.h.length += msg.setconf_req.codec.length - sizeof(msg.setconf_req.codec);
+ } else {
+ msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ msg.setconf_req.codec.seid = BT_A2DP_SEID_RANGE + 1;
+ msg.setconf_req.codec.length = sizeof(pcm_capabilities_t);
}
+ msg.setconf_req.h.length += msg.setconf_req.codec.length - sizeof(msg.setconf_req.codec);
if (service_send(u, &msg.setconf_req.h) < 0)
return -1;
@@ -630,24 +694,17 @@ static int set_conf(struct userdata *u) {
if (service_expect(u, &msg.setconf_rsp.h, sizeof(msg), BT_SET_CONFIGURATION, sizeof(msg.setconf_rsp)) < 0)
return -1;
- if ((u->profile == PROFILE_A2DP && msg.setconf_rsp.transport != BT_CAPABILITIES_TRANSPORT_A2DP) ||
- (u->profile == PROFILE_HSP && msg.setconf_rsp.transport != BT_CAPABILITIES_TRANSPORT_SCO)) {
- pa_log("Transport doesn't match what we requested.");
- return -1;
- }
-
- if ((u->profile == PROFILE_A2DP && msg.setconf_rsp.access_mode != BT_CAPABILITIES_ACCESS_MODE_WRITE) ||
- (u->profile == PROFILE_HSP && msg.setconf_rsp.access_mode != BT_CAPABILITIES_ACCESS_MODE_READWRITE)) {
- pa_log("Access mode doesn't match what we requested.");
- return -1;
- }
-
u->link_mtu = msg.setconf_rsp.link_mtu;
/* setup SBC encoder now we agree on parameters */
if (u->profile == PROFILE_A2DP) {
setup_sbc(&u->a2dp);
- u->block_size = u->a2dp.codesize;
+
+ u->block_size =
+ ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / u->a2dp.frame_length
+ * u->a2dp.codesize);
+
pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
u->a2dp.sbc.allocation, u->a2dp.sbc.subbands, u->a2dp.sbc.blocks, u->a2dp.sbc.bitpool);
} else
@@ -667,6 +724,7 @@ static int start_stream_fd(struct userdata *u) {
uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
} msg;
struct pollfd *pollfd;
+ int one;
pa_assert(u);
pa_assert(u->rtpoll);
@@ -695,13 +753,29 @@ static int start_stream_fd(struct userdata *u) {
pa_make_fd_nonblock(u->stream_fd);
pa_make_socket_low_delay(u->stream_fd);
+ one = 1;
+ if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0)
+ pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno));
+
+ pa_log_debug("Stream properly set up, we're ready to roll!");
+
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
pollfd->fd = u->stream_fd;
pollfd->events = pollfd->revents = 0;
- u->read_index = 0;
- u->write_index = 0;
+ u->read_index = u->write_index = 0;
+ u->started_at = 0;
+
+ if (u->source)
+ u->read_smoother = pa_smoother_new(
+ PA_USEC_PER_SEC,
+ PA_USEC_PER_SEC*2,
+ TRUE,
+ TRUE,
+ 10,
+ pa_rtclock_now(),
+ TRUE);
return 0;
}
@@ -737,9 +811,15 @@ static int stop_stream_fd(struct userdata *u) {
pa_close(u->stream_fd);
u->stream_fd = -1;
+ if (u->read_smoother) {
+ pa_smoother_free(u->read_smoother);
+ u->read_smoother = NULL;
+ }
+
return r;
}
+/* Run from IO thread */
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
pa_bool_t failed = FALSE;
@@ -747,7 +827,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
pa_assert(u->sink == PA_SINK(o));
- pa_log_debug("got message: %d", code);
switch (code) {
case PA_SINK_MESSAGE_SET_STATE:
@@ -775,8 +854,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
if (!u->source || u->source->state == PA_SOURCE_SUSPENDED)
if (start_stream_fd(u) < 0)
failed = TRUE;
-
- u->started_at = pa_rtclock_usec();
break;
case PA_SINK_UNLINKED:
@@ -787,7 +864,24 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
break;
case PA_SINK_MESSAGE_GET_LATENCY: {
- *((pa_usec_t*) data) = 0;
+
+ if (u->read_smoother) {
+ pa_usec_t wi, ri;
+
+ ri = pa_smoother_get(u->read_smoother, pa_rtclock_now());
+ wi = pa_bytes_to_usec(u->write_index + u->block_size, &u->sample_spec);
+
+ *((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
+ } else {
+ pa_usec_t ri, wi;
+
+ ri = pa_rtclock_now() - u->started_at;
+ wi = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+
+ *((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
+ }
+
+ *((pa_usec_t*) data) += u->sink->fixed_latency;
return 0;
}
}
@@ -797,6 +891,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
return (r < 0 || !failed) ? r : -1;
}
+/* Run from IO thread */
static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SOURCE(o)->userdata;
pa_bool_t failed = FALSE;
@@ -804,7 +899,6 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
pa_assert(u->source == PA_SOURCE(o));
- pa_log_debug("got message: %d", code);
switch (code) {
case PA_SOURCE_MESSAGE_SET_STATE:
@@ -818,7 +912,8 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
if (!u->sink || u->sink->state == PA_SINK_SUSPENDED)
stop_stream_fd(u);
- pa_smoother_pause(u->read_smoother, pa_rtclock_usec());
+ if (u->read_smoother)
+ pa_smoother_pause(u->read_smoother, pa_rtclock_now());
break;
case PA_SOURCE_IDLE:
@@ -831,7 +926,8 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
if (start_stream_fd(u) < 0)
failed = TRUE;
- pa_smoother_resume(u->read_smoother, pa_rtclock_usec());
+ /* We don't resume the smoother here. Instead we
+ * wait until the first packet arrives */
break;
case PA_SOURCE_UNLINKED:
@@ -842,7 +938,12 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
break;
case PA_SOURCE_MESSAGE_GET_LATENCY: {
- *((pa_usec_t*) data) = 0;
+ pa_usec_t wi, ri;
+
+ wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
+ ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
+
+ *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->fixed_latency;
return 0;
}
@@ -853,54 +954,71 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
return (r < 0 || !failed) ? r : -1;
}
+/* Run from IO thread */
static int hsp_process_render(struct userdata *u) {
int ret = 0;
- pa_memchunk memchunk;
pa_assert(u);
pa_assert(u->profile == PROFILE_HSP);
pa_assert(u->sink);
- pa_sink_render_full(u->sink, u->block_size, &memchunk);
+ /* First, render some data */
+ if (!u->write_memchunk.memblock)
+ pa_sink_render_full(u->sink, u->block_size, &u->write_memchunk);
+
+ pa_assert(u->write_memchunk.length == u->block_size);
for (;;) {
ssize_t l;
const void *p;
- p = (const uint8_t*) pa_memblock_acquire(memchunk.memblock) + memchunk.index;
- l = pa_write(u->stream_fd, p, memchunk.length, &u->stream_write_type);
- pa_memblock_release(memchunk.memblock);
+ /* Now write that data to the socket. The socket is of type
+ * SEQPACKET, and we generated the data of the MTU size, so this
+ * should just work. */
- pa_log_debug("Memblock written to socket: %lli bytes", (long long) l);
+ p = (const uint8_t*) pa_memblock_acquire(u->write_memchunk.memblock) + u->write_memchunk.index;
+ l = pa_write(u->stream_fd, p, u->write_memchunk.length, &u->stream_write_type);
+ pa_memblock_release(u->write_memchunk.memblock);
pa_assert(l != 0);
if (l < 0) {
- if (errno == EINTR || errno == EAGAIN) /*** FIXME: EAGAIN handling borked ***/
+
+ if (errno == EINTR)
+ /* Retry right away if we got interrupted */
continue;
- else {
- pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno));
- ret = -1;
+
+ else if (errno == EAGAIN)
+ /* Hmm, apparently the socket was not writable, give up for now */
break;
- }
- } else {
- pa_assert((size_t) l <= memchunk.length);
- memchunk.index += (size_t) l;
- memchunk.length -= (size_t) l;
+ pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno));
+ ret = -1;
+ break;
+ }
- u->write_index += (uint64_t) l;
+ pa_assert((size_t) l <= u->write_memchunk.length);
- if (memchunk.length <= 0)
- break;
+ if ((size_t) l != u->write_memchunk.length) {
+ pa_log_error("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
+ (unsigned long long) l,
+ (unsigned long long) u->write_memchunk.length);
+ ret = -1;
+ break;
}
- }
- pa_memblock_unref(memchunk.memblock);
+ u->write_index += (uint64_t) u->write_memchunk.length;
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
+
+ ret = 1;
+ break;
+ }
return ret;
}
+/* Run from IO thread */
static int hsp_process_push(struct userdata *u) {
int ret = 0;
pa_memchunk memchunk;
@@ -908,6 +1026,7 @@ static int hsp_process_push(struct userdata *u) {
pa_assert(u);
pa_assert(u->profile == PROFILE_HSP);
pa_assert(u->source);
+ pa_assert(u->read_smoother);
memchunk.memblock = pa_memblock_new(u->core->mempool, u->block_size);
memchunk.index = memchunk.length = 0;
@@ -915,26 +1034,69 @@ static int hsp_process_push(struct userdata *u) {
for (;;) {
ssize_t l;
void *p;
+ struct msghdr m;
+ struct cmsghdr *cm;
+ uint8_t aux[1024];
+ struct iovec iov;
+ pa_bool_t found_tstamp = FALSE;
+ pa_usec_t tstamp;
+
+ memset(&m, 0, sizeof(m));
+ memset(&aux, 0, sizeof(aux));
+ memset(&iov, 0, sizeof(iov));
+
+ m.msg_iov = &iov;
+ m.msg_iovlen = 1;
+ m.msg_control = aux;
+ m.msg_controllen = sizeof(aux);
p = pa_memblock_acquire(memchunk.memblock);
- l = pa_read(u->stream_fd, p, pa_memblock_get_length(memchunk.memblock), &u->stream_read_type);
+ iov.iov_base = p;
+ iov.iov_len = pa_memblock_get_length(memchunk.memblock);
+ l = recvmsg(u->stream_fd, &m, 0);
pa_memblock_release(memchunk.memblock);
if (l <= 0) {
- if (l < 0 && (errno == EINTR || errno == EAGAIN)) /*** FIXME: EAGAIN handling borked ***/
+
+ if (l < 0 && errno == EINTR)
+ /* Retry right away if we got interrupted */
continue;
- else {
- pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
- ret = -1;
+
+ else if (l < 0 && errno == EAGAIN)
+ /* Hmm, apparently the socket was not readable, give up for now. */
break;
- }
- } else {
- memchunk.length = (size_t) l;
- u->read_index += (uint64_t) l;
- pa_source_post(u->source, &memchunk);
+ pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
+ ret = -1;
break;
}
+
+ pa_assert((size_t) l <= pa_memblock_get_length(memchunk.memblock));
+
+ memchunk.length = (size_t) l;
+ u->read_index += (uint64_t) l;
+
+ for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm))
+ if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
+ struct timeval *tv = (struct timeval*) CMSG_DATA(cm);
+ pa_rtclock_from_wallclock(tv);
+ tstamp = pa_timeval_load(tv);
+ found_tstamp = TRUE;
+ break;
+ }
+
+ if (!found_tstamp) {
+ pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!");
+ tstamp = pa_rtclock_now();
+ }
+
+ pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+ pa_smoother_resume(u->read_smoother, tstamp, TRUE);
+
+ pa_source_post(u->source, &memchunk);
+
+ ret = 1;
+ break;
}
pa_memblock_unref(memchunk.memblock);
@@ -942,132 +1104,156 @@ static int hsp_process_push(struct userdata *u) {
return ret;
}
+/* Run from IO thread */
+static void a2dp_prepare_buffer(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->a2dp.buffer_size >= u->link_mtu)
+ return;
+
+ u->a2dp.buffer_size = 2 * u->link_mtu;
+ pa_xfree(u->a2dp.buffer);
+ u->a2dp.buffer = pa_xmalloc(u->a2dp.buffer_size);
+}
+
+/* Run from IO thread */
static int a2dp_process_render(struct userdata *u) {
- size_t frame_size;
struct a2dp_info *a2dp;
struct rtp_header *header;
struct rtp_payload *payload;
- size_t left;
+ size_t nbytes;
void *d;
const void *p;
+ size_t to_write, to_encode;
unsigned frame_count;
- size_t written;
- uint64_t writing_at;
+ int ret = 0;
pa_assert(u);
pa_assert(u->profile == PROFILE_A2DP);
pa_assert(u->sink);
- a2dp = &u->a2dp;
+ /* First, render some data */
+ if (!u->write_memchunk.memblock)
+ pa_sink_render_full(u->sink, u->block_size, &u->write_memchunk);
- if (a2dp->buffer_size < u->link_mtu) {
- a2dp->buffer_size = 2*u->link_mtu;
- pa_xfree(a2dp->buffer);
- a2dp->buffer = pa_xmalloc(a2dp->buffer_size);
- }
+ pa_assert(u->write_memchunk.length == u->block_size);
- header = (struct rtp_header*) a2dp->buffer;
+ a2dp_prepare_buffer(u);
+
+ a2dp = &u->a2dp;
+ header = a2dp->buffer;
payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
- d = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
- left = a2dp->buffer_size - sizeof(*header) - sizeof(*payload);
- frame_size = sbc_get_frame_length(&a2dp->sbc);
frame_count = 0;
- writing_at = u->write_index;
+ /* Try to create a packet of the full MTU */
- do {
- ssize_t encoded;
+ p = (const uint8_t*) pa_memblock_acquire(u->write_memchunk.memblock) + u->write_memchunk.index;
+ to_encode = u->write_memchunk.length;
- if (!u->write_memchunk.memblock)
- pa_sink_render_full(u->sink, u->block_size, &u->write_memchunk);
+ d = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
+ to_write = a2dp->buffer_size - sizeof(*header) - sizeof(*payload);
+
+ while (PA_LIKELY(to_encode > 0 && to_write > 0)) {
+ size_t written;
+ ssize_t encoded;
- p = (const uint8_t*) pa_memblock_acquire(u->write_memchunk.memblock) + u->write_memchunk.index;
encoded = sbc_encode(&a2dp->sbc,
- p, u->write_memchunk.length,
- d, left,
+ p, to_encode,
+ d, to_write,
&written);
- PA_ONCE_BEGIN {
- pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&a2dp->sbc)));
- } PA_ONCE_END;
-
- pa_memblock_release(u->write_memchunk.memblock);
-
- if (encoded <= 0) {
- pa_log_error("SBC encoding error (%d)", encoded);
+ if (PA_UNLIKELY(encoded <= 0)) {
+ pa_log_error("SBC encoding error (%li)", (long) encoded);
+ pa_memblock_release(u->write_memchunk.memblock);
return -1;
}
- pa_assert((size_t) encoded <= u->write_memchunk.length);
- pa_assert((size_t) encoded == sbc_get_codesize(&a2dp->sbc));
+/* pa_log_debug("SBC: encoded: %lu; written: %lu", (unsigned long) encoded, (unsigned long) written); */
+/* pa_log_debug("SBC: codesize: %lu; frame_length: %lu", (unsigned long) a2dp->codesize, (unsigned long) a2dp->frame_length); */
- pa_assert((size_t) written <= left);
- pa_assert((size_t) written == sbc_get_frame_length(&a2dp->sbc));
+ pa_assert_fp((size_t) encoded <= to_encode);
+ pa_assert_fp((size_t) encoded == a2dp->codesize);
-/* pa_log_debug("SBC: encoded: %d; written: %d", encoded, written); */
+ pa_assert_fp((size_t) written <= to_write);
+ pa_assert_fp((size_t) written == a2dp->frame_length);
- u->write_memchunk.index += encoded;
- u->write_memchunk.length -= encoded;
-
- if (u->write_memchunk.length <= 0) {
- pa_memblock_unref(u->write_memchunk.memblock);
- pa_memchunk_reset(&u->write_memchunk);
- }
-
- u->write_index += encoded;
+ p = (const uint8_t*) p + encoded;
+ to_encode -= encoded;
d = (uint8_t*) d + written;
- left -= written;
+ to_write -= written;
frame_count++;
+ }
- } while (((uint8_t*) d - ((uint8_t*) a2dp->buffer + sbc_get_frame_length(&a2dp->sbc))) < (ptrdiff_t) u->link_mtu);
+ pa_memblock_release(u->write_memchunk.memblock);
+
+ pa_assert(to_encode == 0);
+
+ PA_ONCE_BEGIN {
+ pa_log_debug("Using SBC encoder implementation: %s", pa_strnull(sbc_get_implementation_info(&a2dp->sbc)));
+ } PA_ONCE_END;
/* write it to the fifo */
memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload));
- payload->frame_count = frame_count;
header->v = 2;
header->pt = 1;
header->sequence_number = htons(a2dp->seq_num++);
- header->timestamp = htonl(writing_at / frame_size);
+ header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec));
header->ssrc = htonl(1);
+ payload->frame_count = frame_count;
- p = a2dp->buffer;
- left = (uint8_t*) d - (uint8_t*) a2dp->buffer;
+ nbytes = (uint8_t*) d - (uint8_t*) a2dp->buffer;
for (;;) {
ssize_t l;
- l = pa_write(u->stream_fd, p, left, &u->stream_write_type);
-/* pa_log_debug("write: requested %lu bytes; written %li bytes; mtu=%li", (unsigned long) left, (long) l, (unsigned long) u->link_mtu); */
+ l = pa_write(u->stream_fd, a2dp->buffer, nbytes, &u->stream_write_type);
pa_assert(l != 0);
if (l < 0) {
- if (errno == EINTR || errno == EAGAIN) /*** FIXME: EAGAIN handling borked ***/
- continue;
- else {
- pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
- return -1;
- }
- } else {
- pa_assert((size_t) l <= left);
- d = (uint8_t*) d + l;
- left -= l;
+ if (errno == EINTR)
+ /* Retry right away if we got interrupted */
+ continue;
- if (left <= 0)
+ else if (errno == EAGAIN)
+ /* Hmm, apparently the socket was not writable, give up for now */
break;
+
+ pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
+ ret = -1;
+ break;
+ }
+
+ pa_assert((size_t) l <= nbytes);
+
+ if ((size_t) l != nbytes) {
+ pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
+ (unsigned long long) l,
+ (unsigned long long) nbytes);
+ ret = -1;
+ break;
}
+
+ u->write_index += (uint64_t) u->write_memchunk.length;
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
+
+ ret = 1;
+
+ break;
}
- return 0;
+ return ret;
}
static void thread_func(void *userdata) {
struct userdata *u = userdata;
- pa_bool_t do_write = FALSE, writable = FALSE;
+ unsigned do_write = 0;
+ pa_bool_t writable = FALSE;
pa_assert(u);
@@ -1080,9 +1266,6 @@ static void thread_func(void *userdata) {
goto fail;
pa_thread_mq_install(&u->thread_mq);
- pa_rtpoll_install(u->rtpoll);
-
- pa_smoother_set_time_offset(u->read_smoother, pa_rtclock_usec());
for (;;) {
struct pollfd *pollfd;
@@ -1093,13 +1276,20 @@ static void thread_func(void *userdata) {
if (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state)) {
+ /* We should send two blocks to the device before we expect
+ * a response. */
+
+ if (u->write_index == 0 && u->read_index <= 0)
+ do_write = 2;
+
if (pollfd && (pollfd->revents & POLLIN)) {
+ int n_read;
- if (hsp_process_push(u) < 0)
+ if ((n_read = hsp_process_push(u)) < 0)
goto fail;
/* We just read something, so we are supposed to write something, too */
- do_write = TRUE;
+ do_write += n_read;
}
}
@@ -1112,44 +1302,70 @@ static void thread_func(void *userdata) {
if (pollfd->revents & POLLOUT)
writable = TRUE;
- if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && !do_write && writable) {
+ if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) {
pa_usec_t time_passed;
- uint64_t should_have_written;
+ pa_usec_t audio_sent;
/* Hmm, there is no input stream we could synchronize
* to. So let's do things by time */
- time_passed = pa_rtclock_usec() - u->started_at;
- should_have_written = pa_usec_to_bytes(time_passed, &u->sink->sample_spec);
+ time_passed = pa_rtclock_now() - u->started_at;
+ audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+
+ if (audio_sent <= time_passed) {
+ pa_usec_t audio_to_send = time_passed - audio_sent;
+
+ /* Never try to catch up for more than 100ms */
+ if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) {
+ pa_usec_t skip_usec;
+ uint64_t skip_bytes;
+ pa_memchunk tmp;
+
+ skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC;
+ skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec);
- do_write = u->write_index <= should_have_written ;
-/* pa_log_debug("Time has come: %s", pa_yes_no(do_write)); */
+ pa_log_warn("Skipping %llu us (= %llu bytes) in audio stream",
+ (unsigned long long) skip_usec,
+ (unsigned long long) skip_bytes);
+
+ pa_sink_render_full(u->sink, skip_bytes, &tmp);
+ pa_memblock_unref(tmp.memblock);
+ u->write_index += skip_bytes;
+ }
+
+ do_write = 1;
+ }
}
- if (writable && do_write) {
- if (u->write_index == 0)
- u->started_at = pa_rtclock_usec();
+ if (writable && do_write > 0) {
+ int n_written;
+
+ if (u->write_index <= 0)
+ u->started_at = pa_rtclock_now();
if (u->profile == PROFILE_A2DP) {
- if (a2dp_process_render(u) < 0)
+ if ((n_written = a2dp_process_render(u)) < 0)
goto fail;
} else {
- if (hsp_process_render(u) < 0)
+ if ((n_written = hsp_process_render(u)) < 0)
goto fail;
}
- do_write = FALSE;
+ if (n_written == 0)
+ pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!");
+
+ do_write -= n_written;
writable = FALSE;
}
- if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && !do_write) {
+ if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) {
pa_usec_t time_passed, next_write_at, sleep_for;
/* Hmm, there is no input stream we could synchronize
* to. So let's estimate when we need to wake up the latest */
- time_passed = pa_rtclock_usec() - u->started_at;
- next_write_at = pa_bytes_to_usec(u->write_index, &u->sink->sample_spec);
+ time_passed = pa_rtclock_now() - u->started_at;
+ next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);
sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
/* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
@@ -1177,7 +1393,11 @@ static void thread_func(void *userdata) {
pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
if (pollfd && (pollfd->revents & ~(POLLOUT|POLLIN))) {
- pa_log_error("FD error.");
+ pa_log_info("FD error: %s%s%s%s",
+ pollfd->revents & POLLERR ? "POLLERR " :"",
+ pollfd->revents & POLLHUP ? "POLLHUP " :"",
+ pollfd->revents & POLLPRI ? "POLLPRI " :"",
+ pollfd->revents & POLLNVAL ? "POLLNVAL " :"");
goto fail;
}
}
@@ -1192,146 +1412,105 @@ finish:
pa_log_debug("IO thread shutting down");
}
-/* static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *msg, void *userdata) { */
-/* DBusMessageIter arg_i; */
-/* DBusError err; */
-/* const char *value; */
-/* struct userdata *u; */
-
-/* pa_assert(bus); */
-/* pa_assert(msg); */
-/* pa_assert(userdata); */
-/* u = userdata; */
-
-/* pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", */
-/* dbus_message_get_interface(msg), */
-/* dbus_message_get_path(msg), */
-/* dbus_message_get_member(msg)); */
-
-/* dbus_error_init(&err); */
-
-/* if (!dbus_message_has_path(msg, u->path)) */
-/* goto done; */
-
-/* if (dbus_message_is_signal(msg, "org.bluez.Headset", "PropertyChanged") || */
-/* dbus_message_is_signal(msg, "org.bluez.AudioSink", "PropertyChanged")) { */
-
-/* struct device *d; */
-/* const char *profile; */
-/* DBusMessageIter variant_i; */
-/* dbus_uint16_t gain; */
-
-/* if (!dbus_message_iter_init(msg, &arg_i)) { */
-/* pa_log("dbus: message has no parameters"); */
-/* goto done; */
-/* } */
+/* Run from main thread */
+static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
+ DBusError err;
+ struct userdata *u;
-/* if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_STRING) { */
-/* pa_log("Property name not a string."); */
-/* goto done; */
-/* } */
+ pa_assert(bus);
+ pa_assert(m);
+ pa_assert_se(u = userdata);
-/* dbus_message_iter_get_basic(&arg_i, &value); */
+ dbus_error_init(&err);
-/* if (!dbus_message_iter_next(&arg_i)) { */
-/* pa_log("Property value missing"); */
-/* goto done; */
-/* } */
+ pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
+ dbus_message_get_interface(m),
+ dbus_message_get_path(m),
+ dbus_message_get_member(m));
-/* if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_VARIANT) { */
-/* pa_log("Property value not a variant."); */
-/* goto done; */
-/* } */
+ if (!dbus_message_has_path(m, u->path))
+ goto fail;
-/* dbus_message_iter_recurse(&arg_i, &variant_i); */
+ if (dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged") ||
+ dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) {
-/* if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_UINT16) { */
-/* dbus_message_iter_get_basic(&variant_i, &gain); */
+ dbus_uint16_t gain;
+ pa_cvolume v;
-/* if (pa_streq(value, "SpeakerGain")) { */
-/* pa_log("spk gain: %d", gain); */
-/* pa_cvolume_set(&u->sink->virtual_volume, 1, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); */
-/* pa_subscription_post(u->sink->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, u->sink->index); */
-/* } else { */
-/* pa_log("mic gain: %d", gain); */
-/* if (!u->source) */
-/* goto done; */
+ if (!dbus_message_get_args(m, &err, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID) || gain > 15) {
+ pa_log("Failed to parse org.bluez.Headset.{Speaker|Microphone}GainChanged: %s", err.message);
+ goto fail;
+ }
-/* pa_cvolume_set(&u->source->virtual_volume, 1, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); */
-/* pa_subscription_post(u->source->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, u->source->index); */
-/* } */
-/* } */
-/* } */
+ if (u->profile == PROFILE_HSP) {
+ if (u->sink && dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged")) {
-/* done: */
-/* dbus_error_free(&err); */
-/* return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; */
-/* } */
+ pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
+ pa_sink_volume_changed(u->sink, &v, TRUE);
-/* static int sink_get_volume_cb(pa_sink *s) { */
-/* struct userdata *u = s->userdata; */
-/* pa_assert(u); */
+ } else if (u->source && dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) {
-/* /\* refresh? *\/ */
+ pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
+ pa_source_volume_changed(u->source, &v, TRUE);
+ }
+ }
+ }
-/* return 0; */
-/* } */
+fail:
+ dbus_error_free(&err);
-/* static int source_get_volume_cb(pa_source *s) { */
-/* struct userdata *u = s->userdata; */
-/* pa_assert(u); */
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
-/* /\* refresh? *\/ */
+/* Run from main thread */
+static void sink_set_volume_cb(pa_sink *s) {
+ struct userdata *u = s->userdata;
+ DBusMessage *m;
+ dbus_uint16_t gain;
-/* return 0; */
-/* } */
+ pa_assert(u);
-/* static int sink_set_volume_cb(pa_sink *s) { */
-/* DBusError e; */
-/* DBusMessage *m, *r; */
-/* DBusMessageIter it, itvar; */
-/* dbus_uint16_t vol; */
-/* const char *spkgain = "SpeakerGain"; */
-/* struct userdata *u = s->userdata; */
-/* pa_assert(u); */
+ if (u->profile != PROFILE_HSP)
+ return;
-/* dbus_error_init(&e); */
+ gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM;
-/* vol = ((float) pa_cvolume_max(&s->virtual_volume) / PA_VOLUME_NORM) * 15; */
-/* pa_log_debug("set headset volume: %d", vol); */
+ if (gain > 15)
+ gain = 15;
-/* pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetProperty")); */
-/* dbus_message_iter_init_append(m, &it); */
-/* dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &spkgain); */
-/* dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, DBUS_TYPE_UINT16_AS_STRING, &itvar); */
-/* dbus_message_iter_append_basic(&itvar, DBUS_TYPE_UINT16, &vol); */
-/* dbus_message_iter_close_container(&it, &itvar); */
+ pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
-/* r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->conn), m, -1, &e); */
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetSpeakerGain"));
+ pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID));
+ pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->connection), m, NULL));
+ dbus_message_unref(m);
+}
-/* finish: */
-/* if (m) */
-/* dbus_message_unref(m); */
-/* if (r) */
-/* dbus_message_unref(r); */
+/* Run from main thread */
+static void source_set_volume_cb(pa_source *s) {
+ struct userdata *u = s->userdata;
+ DBusMessage *m;
+ dbus_uint16_t gain;
-/* dbus_error_free(&e); */
+ pa_assert(u);
-/* return 0; */
-/* } */
+ if (u->profile != PROFILE_HSP)
+ return;
-/* static int source_set_volume_cb(pa_source *s) { */
-/* dbus_uint16_t vol; */
-/* struct userdata *u = s->userdata; */
-/* pa_assert(u); */
+ gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM;
-/* vol = ((float)pa_cvolume_max(&s->virtual_volume) / PA_VOLUME_NORM) * 15; */
+ if (gain > 15)
+ gain = 15;
-/* pa_log_debug("set headset mic volume: %d (not implemented yet)", vol); */
+ pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
-/* return 0; */
-/* } */
+ pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetMicrophoneGain"));
+ pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID));
+ pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->connection), m, NULL));
+ dbus_message_unref(m);
+}
+/* Run from main thread */
static char *get_name(const char *type, pa_modargs *ma, const char *device_id, pa_bool_t *namereg_fail) {
char *t;
const char *n;
@@ -1360,6 +1539,8 @@ static char *get_name(const char *type, pa_modargs *ma, const char *device_id, p
return pa_sprintf_malloc("bluez_%s.%s", type, n);
}
+#ifdef NOKIA
+
static void sco_over_pcm_state_update(struct userdata *u) {
pa_assert(u);
pa_assert(USE_SCO_OVER_PCM(u));
@@ -1414,8 +1595,12 @@ static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct
return PA_HOOK_OK;
}
+#endif
+
+/* Run from main thread */
static int add_sink(struct userdata *u) {
+#ifdef NOKIA
if (USE_SCO_OVER_PCM(u)) {
pa_proplist *p;
@@ -1428,7 +1613,10 @@ static int add_sink(struct userdata *u) {
if (!u->hsp.sink_state_changed_slot)
u->hsp.sink_state_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_cb, u);
- } else {
+ } else
+#endif
+
+ {
pa_sink_new_data data;
pa_bool_t b;
@@ -1437,11 +1625,19 @@ static int add_sink(struct userdata *u) {
data.module = u->module;
pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco");
+ if (u->profile == PROFILE_HSP)
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
data.card = u->card;
- data.name = get_name("sink", u->modargs, u->device->address, &b);
+ data.name = get_name("sink", u->modargs, u->address, &b);
data.namereg_fail = b;
- u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
+ if (pa_modargs_get_proplist(u->modargs, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_sink_new_data_done(&data);
+ return -1;
+ }
+
+ u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY | (u->profile == PROFILE_HSP ? PA_SINK_HW_VOLUME_CTRL : 0));
pa_sink_new_data_done(&data);
if (!u->sink) {
@@ -1451,28 +1647,36 @@ 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->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->block_size, &u->sample_spec));
}
-/* u->sink->get_volume = sink_get_volume_cb; */
-/* u->sink->set_volume = sink_set_volume_cb; */
+ if (u->profile == PROFILE_HSP) {
+ u->sink->set_volume = sink_set_volume_cb;
+ u->sink->n_volume_steps = 16;
+ }
return 0;
}
+/* Run from main thread */
static int add_source(struct userdata *u) {
- pa_proplist *p;
+#ifdef NOKIA
if (USE_SCO_OVER_PCM(u)) {
u->source = u->hsp.sco_source;
- p = pa_proplist_new();
- pa_proplist_sets(p, "bluetooth.protocol", "sco");
- pa_proplist_update(u->source->proplist, PA_UPDATE_MERGE, p);
- pa_proplist_free(p);
+ pa_proplist_sets(u->source->proplist, "bluetooth.protocol", "hsp");
if (!u->hsp.source_state_changed_slot)
u->hsp.source_state_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) source_state_changed_cb, u);
- } else {
+ } else
+#endif
+
+ {
pa_source_new_data data;
pa_bool_t b;
@@ -1480,12 +1684,20 @@ static int add_source(struct userdata *u) {
data.driver = __FILE__;
data.module = u->module;
pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
- pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco");
+ pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "hsp");
+ if (u->profile == PROFILE_HSP)
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
data.card = u->card;
- data.name = get_name("source", u->modargs, u->device->address, &b);
+ data.name = get_name("source", u->modargs, u->address, &b);
data.namereg_fail = b;
- u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
+ if (pa_modargs_get_proplist(u->modargs, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_source_new_data_done(&data);
+ return -1;
+ }
+
+ u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY | (u->profile == PROFILE_HSP ? PA_SOURCE_HW_VOLUME_CTRL : 0));
pa_source_new_data_done(&data);
if (!u->source) {
@@ -1495,39 +1707,51 @@ static int add_source(struct userdata *u) {
u->source->userdata = u;
u->source->parent.process_msg = source_process_msg;
- }
-/* u->source->get_volume = source_get_volume_cb; */
-/* u->source->set_volume = source_set_volume_cb; */
+ pa_source_set_fixed_latency(u->source,
+ (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) +
+ pa_bytes_to_usec(u->block_size, &u->sample_spec));
+ }
- p = pa_proplist_new();
- pa_proplist_sets(p, "bluetooth.nrec", pa_yes_no(u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC));
- pa_proplist_update(u->source->proplist, PA_UPDATE_MERGE, p);
- pa_proplist_free(p);
+ if (u->profile == PROFILE_HSP) {
+ pa_proplist_sets(u->source->proplist, "bluetooth.nrec", (u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC) ? "1" : "0");
+ u->source->set_volume = source_set_volume_cb;
+ u->source->n_volume_steps = 16;
+ }
return 0;
}
+/* Run from main thread */
static void shutdown_bt(struct userdata *u) {
pa_assert(u);
if (u->stream_fd >= 0) {
pa_close(u->stream_fd);
u->stream_fd = -1;
+
+ u->stream_write_type = 0;
}
if (u->service_fd >= 0) {
pa_close(u->service_fd);
u->service_fd = -1;
+ u->service_write_type = u->service_write_type = 0;
+ }
+
+ if (u->write_memchunk.memblock) {
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
}
}
+/* Run from main thread */
static int init_bt(struct userdata *u) {
pa_assert(u);
shutdown_bt(u);
- u->stream_write_type = u->stream_read_type = 0;
+ u->stream_write_type = 0;
u->service_write_type = u->service_write_type = 0;
if ((u->service_fd = bt_audio_service_open()) < 0) {
@@ -1540,10 +1764,11 @@ static int init_bt(struct userdata *u) {
return 0;
}
+/* Run from main thread */
static int setup_bt(struct userdata *u) {
pa_assert(u);
- if (get_caps(u) < 0)
+ if (get_caps(u, 0) < 0)
return -1;
pa_log_debug("Got device capabilities");
@@ -1553,16 +1778,19 @@ static int setup_bt(struct userdata *u) {
pa_log_debug("Connection to the device configured");
+#ifdef NOKIA
if (USE_SCO_OVER_PCM(u)) {
pa_log_debug("Configured to use SCO over PCM");
return 0;
}
+#endif
pa_log_debug("Got the stream socket");
return 0;
}
+/* Run from main thread */
static int init_profile(struct userdata *u) {
int r = 0;
pa_assert(u);
@@ -1583,6 +1811,7 @@ static int init_profile(struct userdata *u) {
return r;
}
+/* Run from main thread */
static void stop_thread(struct userdata *u) {
pa_assert(u);
@@ -1623,8 +1852,14 @@ static void stop_thread(struct userdata *u) {
pa_rtpoll_free(u->rtpoll);
u->rtpoll = NULL;
}
+
+ if (u->read_smoother) {
+ pa_smoother_free(u->read_smoother);
+ u->read_smoother = NULL;
+ }
}
+/* Run from main thread */
static int start_thread(struct userdata *u) {
pa_assert(u);
pa_assert(!u->thread);
@@ -1634,6 +1869,7 @@ static int start_thread(struct userdata *u) {
u->rtpoll = pa_rtpoll_new();
pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll);
+#ifdef NOKIA
if (USE_SCO_OVER_PCM(u)) {
if (start_stream_fd(u) < 0)
return -1;
@@ -1643,6 +1879,7 @@ static int start_thread(struct userdata *u) {
/* FIXME: monitor stream_fd error */
return 0;
}
+#endif
if (!(u->thread = pa_thread_new(thread_func, u))) {
pa_log_error("Failed to create IO thread");
@@ -1654,21 +1891,29 @@ static int start_thread(struct userdata *u) {
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
pa_sink_set_rtpoll(u->sink, u->rtpoll);
pa_sink_put(u->sink);
+
+ if (u->sink->set_volume)
+ u->sink->set_volume(u->sink);
}
if (u->source) {
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
pa_source_set_rtpoll(u->source, u->rtpoll);
pa_source_put(u->source);
+
+ if (u->source->set_volume)
+ u->source->set_volume(u->source);
}
return 0;
}
+/* Run from main thread */
static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
struct userdata *u;
enum profile *d;
pa_queue *inputs = NULL, *outputs = NULL;
+ const pa_bluetooth_device *device;
pa_assert(c);
pa_assert(new_profile);
@@ -1676,26 +1921,44 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
d = PA_CARD_PROFILE_DATA(new_profile);
+ if (!(device = pa_bluetooth_discovery_get_by_path(u->discovery, u->path))) {
+ pa_log_error("Failed to get device object.");
+ return -PA_ERR_IO;
+ }
+
+ /* The state signal is sent by bluez, so it is racy to check
+ strictly for CONNECTED, we should also accept STREAMING state
+ as being good enough. However, if the profile is used
+ concurrently (which is unlikely), ipc will fail later on, and
+ module will be unloaded. */
+ if (device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) {
+ pa_log_warn("HSP is not connected, refused to switch profile");
+ return -PA_ERR_IO;
+ }
+ else if (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) {
+ pa_log_warn("A2DP is not connected, refused to switch profile");
+ return -PA_ERR_IO;
+ }
+
if (u->sink) {
- inputs = pa_sink_move_all_start(u->sink);
+ inputs = pa_sink_move_all_start(u->sink, NULL);
+#ifdef NOKIA
if (!USE_SCO_OVER_PCM(u))
+#endif
pa_sink_unlink(u->sink);
}
if (u->source) {
- outputs = pa_source_move_all_start(u->source);
+ outputs = pa_source_move_all_start(u->source, NULL);
+#ifdef NOKIA
if (!USE_SCO_OVER_PCM(u))
+#endif
pa_source_unlink(u->source);
}
stop_thread(u);
shutdown_bt(u);
- if (u->write_memchunk.memblock) {
- pa_memblock_unref(u->write_memchunk.memblock);
- pa_memchunk_reset(&u->write_memchunk);
- }
-
u->profile = *d;
u->sample_spec = u->requested_sample_spec;
@@ -1724,36 +1987,51 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
return 0;
}
-static int add_card(struct userdata *u, const char * default_profile) {
+/* Run from main thread */
+static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
pa_card_new_data data;
pa_bool_t b;
pa_card_profile *p;
enum profile *d;
const char *ff;
char *n;
+ const char *default_profile;
+
+ pa_assert(u);
+ pa_assert(device);
pa_card_new_data_init(&data);
data.driver = __FILE__;
data.module = u->module;
- n = pa_bluetooth_cleanup_name(u->device->name);
+ n = pa_bluetooth_cleanup_name(device->name);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, n);
pa_xfree(n);
- pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device->address);
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, device->address);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "bluez");
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "sound");
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_BUS, "bluetooth");
- if ((ff = pa_bluetooth_get_form_factor(u->device->class)))
+ if ((ff = pa_bluetooth_get_form_factor(device->class)))
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, ff);
- pa_proplist_sets(data.proplist, "bluez.path", u->device->path);
- pa_proplist_setf(data.proplist, "bluez.class", "0x%06x", (unsigned) u->device->class);
- pa_proplist_sets(data.proplist, "bluez.name", u->device->name);
- data.name = get_name("card", u->modargs, u->device->address, &b);
+ pa_proplist_sets(data.proplist, "bluez.path", device->path);
+ pa_proplist_setf(data.proplist, "bluez.class", "0x%06x", (unsigned) device->class);
+ pa_proplist_sets(data.proplist, "bluez.name", device->name);
+ data.name = get_name("card", u->modargs, device->address, &b);
data.namereg_fail = b;
+ if (pa_modargs_get_proplist(u->modargs, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_card_new_data_done(&data);
+ return -1;
+ }
+
data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
- if (u->device->audio_sink_info_valid > 0) {
+ /* we base hsp/a2dp availability on UUIDs.
+ Ideally, it would be based on "Connected" state, but
+ we can't afford to wait for this information when
+ we are loaded with profile="hsp", for instance */
+ if (pa_bluetooth_uuid_has(device->uuids, A2DP_SINK_UUID)) {
p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(enum profile));
p->priority = 10;
p->n_sinks = 1;
@@ -1767,7 +2045,8 @@ static int add_card(struct userdata *u, const char * default_profile) {
pa_hashmap_put(data.profiles, p->name, p);
}
- if (u->device->headset_info_valid > 0) {
+ if (pa_bluetooth_uuid_has(device->uuids, HSP_HS_UUID) ||
+ pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) {
p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile));
p->priority = 20;
p->n_sinks = 1;
@@ -1788,7 +2067,7 @@ static int add_card(struct userdata *u, const char * default_profile) {
*d = PROFILE_OFF;
pa_hashmap_put(data.profiles, p->name, p);
- if (default_profile) {
+ if ((default_profile = pa_modargs_get_value(u->modargs, "profile", NULL))) {
if (pa_hashmap_get(data.profiles, default_profile))
pa_card_new_data_set_profile(&data, default_profile);
else
@@ -1807,51 +2086,71 @@ static int add_card(struct userdata *u, const char * default_profile) {
u->card->set_profile = card_set_profile;
d = PA_CARD_PROFILE_DATA(u->card->active_profile);
- u->profile = *d;
- return 0;
-}
-
-static int setup_dbus(struct userdata *u) {
- DBusError error;
-
- dbus_error_init(&error);
-
- u->connection = pa_dbus_bus_get(u->core, DBUS_BUS_SYSTEM, &error);
- if (dbus_error_is_set(&error) || (!u->connection)) {
- pa_log("Failed to get D-Bus connection: %s", error.message);
- dbus_error_free(&error);
- return -1;
+ if ((device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) ||
+ (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP)) {
+ pa_log_warn("Default profile not connected, selecting off profile");
+ u->card->active_profile = pa_hashmap_get(u->card->profiles, "off");
+ u->card->save_profile = FALSE;
}
+ d = PA_CARD_PROFILE_DATA(u->card->active_profile);
+ u->profile = *d;
+
return 0;
}
-static int find_device(struct userdata *u, const char *address, const char *path) {
+/* Run from main thread */
+static const pa_bluetooth_device* find_device(struct userdata *u, const char *address, const char *path) {
+ const pa_bluetooth_device *d = NULL;
+
pa_assert(u);
if (!address && !path) {
pa_log_error("Failed to get device address/path from module arguments.");
- return -1;
+ return NULL;
}
if (path) {
- if (!(u->device = pa_bluetooth_get_device(pa_dbus_connection_get(u->connection), path))) {
+ if (!(d = pa_bluetooth_discovery_get_by_path(u->discovery, path))) {
pa_log_error("%s is not a valid BlueZ audio device.", path);
- return -1;
+ return NULL;
}
- if (address && !(pa_streq(u->device->address, address))) {
+ if (address && !(pa_streq(d->address, address))) {
pa_log_error("Passed path %s and address %s don't match.", path, address);
- return -1;
+ return NULL;
}
+
} else {
- if (!(u->device = pa_bluetooth_find_device(pa_dbus_connection_get(u->connection), address))) {
+ if (!(d = pa_bluetooth_discovery_get_by_address(u->discovery, address))) {
pa_log_error("%s is not known.", address);
- return -1;
+ return NULL;
}
}
+ if (d) {
+ u->address = pa_xstrdup(d->address);
+ u->path = pa_xstrdup(d->path);
+ }
+
+ return d;
+}
+
+/* Run from main thread */
+static int setup_dbus(struct userdata *u) {
+ DBusError err;
+
+ dbus_error_init(&err);
+
+ u->connection = pa_dbus_bus_get(u->core, DBUS_BUS_SYSTEM, &err);
+
+ if (dbus_error_is_set(&err) || !u->connection) {
+ pa_log("Failed to get D-Bus connection: %s", err.message);
+ dbus_error_free(&err);
+ return -1;
+ }
+
return 0;
}
@@ -1860,9 +2159,14 @@ int pa__init(pa_module* m) {
uint32_t channels;
struct userdata *u;
const char *address, *path;
+ DBusError err;
+ char *mike, *speaker;
+ const pa_bluetooth_device *device;
pa_assert(m);
+ dbus_error_init(&err);
+
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log_error("Failed to parse module arguments");
goto fail;
@@ -1873,10 +2177,10 @@ int pa__init(pa_module* m) {
u->core = m->core;
u->service_fd = -1;
u->stream_fd = -1;
- u->read_smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10);
u->sample_spec = m->core->default_sample_spec;
u->modargs = ma;
+#ifdef NOKIA
if (pa_modargs_get_value(ma, "sco_sink", NULL) &&
!(u->hsp.sco_sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_sink", NULL), PA_NAMEREG_SINK))) {
pa_log("SCO sink not found");
@@ -1888,6 +2192,7 @@ int pa__init(pa_module* m) {
pa_log("SCO source not found");
goto fail;
}
+#endif
if (pa_modargs_get_value_u32(ma, "rate", &u->sample_spec.rate) < 0 ||
u->sample_spec.rate <= 0 || u->sample_spec.rate > PA_RATE_MAX) {
@@ -1904,65 +2209,54 @@ int pa__init(pa_module* m) {
u->sample_spec.channels = (uint8_t) channels;
u->requested_sample_spec = u->sample_spec;
- if (setup_dbus(u) < 0)
- goto fail;
-
address = pa_modargs_get_value(ma, "address", NULL);
path = pa_modargs_get_value(ma, "path", NULL);
- if (find_device(u, address, path) < 0)
+ if (setup_dbus(u) < 0)
+ goto fail;
+
+ if (!(u->discovery = pa_bluetooth_discovery_get(m->core)))
goto fail;
- pa_assert(u->device);
+ if (!(device = find_device(u, address, path)))
+ goto fail;
/* Add the card structure. This will also initialize the default profile */
- if (add_card(u, pa_modargs_get_value(ma, "profile", NULL)) < 0)
+ if (add_card(u, device) < 0)
goto fail;
/* Connect to the BT service and query capabilities */
if (init_bt(u) < 0)
goto fail;
+ 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;
+ }
+
+ 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);
+
+ if (pa_dbus_add_matches(
+ pa_dbus_connection_get(u->connection), &err,
+ speaker,
+ mike,
+ NULL) < 0) {
+
+ pa_xfree(speaker);
+ pa_xfree(mike);
+
+ pa_log("Failed to add D-Bus matches: %s", err.message);
+ goto fail;
+ }
+
+ pa_xfree(speaker);
+ pa_xfree(mike);
+
if (u->profile != PROFILE_OFF)
if (init_profile(u) < 0)
goto fail;
-/* if (u->path) { */
-/* DBusError err; */
-/* dbus_error_init(&err); */
-/* char *t; */
-
-
-/* if (!dbus_connection_add_filter(pa_dbus_connection_get(u->conn), filter_cb, u, NULL)) { */
-/* pa_log_error("Failed to add filter function"); */
-/* goto fail; */
-/* } */
-
-/* if (u->transport == BT_CAPABILITIES_TRANSPORT_SCO || */
-/* u->transport == BT_CAPABILITIES_TRANSPORT_ANY) { */
-/* t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged',path='%s'", u->path); */
-/* dbus_bus_add_match(pa_dbus_connection_get(u->conn), t, &err); */
-/* pa_xfree(t); */
-
-/* if (dbus_error_is_set(&err)) { */
-/* pa_log_error("Unable to subscribe to org.bluez.Headset signals: %s: %s", err.name, err.message); */
-/* goto fail; */
-/* } */
-/* } */
-
-/* if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP || */
-/* u->transport == BT_CAPABILITIES_TRANSPORT_ANY) { */
-/* t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged',path='%s'", u->path); */
-/* dbus_bus_add_match(pa_dbus_connection_get(u->conn), t, &err); */
-/* pa_xfree(t); */
-
-/* if (dbus_error_is_set(&err)) { */
-/* pa_log_error("Unable to subscribe to org.bluez.AudioSink signals: %s: %s", err.name, err.message); */
-/* goto fail; */
-/* } */
-/* } */
-/* } */
-
if (u->sink || u->source)
if (start_thread(u) < 0)
goto fail;
@@ -1970,7 +2264,11 @@ int pa__init(pa_module* m) {
return 0;
fail:
+
pa__done(m);
+
+ dbus_error_free(&err);
+
return -1;
}
@@ -1992,39 +2290,39 @@ void pa__done(pa_module *m) {
if (!(u = m->userdata))
return;
- if (u->sink && !USE_SCO_OVER_PCM(u))
+ if (u->sink
+#ifdef NOKIA
+ && !USE_SCO_OVER_PCM(u)
+#endif
+ )
pa_sink_unlink(u->sink);
- if (u->source && !USE_SCO_OVER_PCM(u))
+ if (u->source
+#ifdef NOKIA
+ && !USE_SCO_OVER_PCM(u)
+#endif
+ )
pa_source_unlink(u->source);
stop_thread(u);
if (u->connection) {
-/* DBusError error; */
-/* char *t; */
-
-/* if (u->transport == BT_CAPABILITIES_TRANSPORT_SCO || */
-/* u->transport == BT_CAPABILITIES_TRANSPORT_ANY) { */
-
-/* t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged',path='%s'", u->path); */
-/* dbus_error_init(&error); */
-/* dbus_bus_remove_match(pa_dbus_connection_get(u->conn), t, &error); */
-/* dbus_error_free(&error); */
-/* pa_xfree(t); */
-/* } */
-
-/* if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP || */
-/* u->transport == BT_CAPABILITIES_TRANSPORT_ANY) { */
-
-/* t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged',path='%s'", u->path); */
-/* dbus_error_init(&error); */
-/* dbus_bus_remove_match(pa_dbus_connection_get(u->conn), t, &error); */
-/* dbus_error_free(&error); */
-/* pa_xfree(t); */
-/* } */
-
-/* dbus_connection_remove_filter(pa_dbus_connection_get(u->conn), filter_cb, u); */
+
+ if (u->path) {
+ char *speaker, *mike;
+ 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_xfree(speaker);
+ pa_xfree(mike);
+ }
+
+ dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u);
pa_dbus_connection_unref(u->connection);
}
@@ -2036,12 +2334,6 @@ void pa__done(pa_module *m) {
shutdown_bt(u);
- if (u->device)
- pa_bluetooth_device_free(u->device);
-
- if (u->write_memchunk.memblock)
- pa_memblock_unref(u->write_memchunk.memblock);
-
if (u->a2dp.buffer)
pa_xfree(u->a2dp.buffer);
@@ -2050,5 +2342,11 @@ void pa__done(pa_module *m) {
if (u->modargs)
pa_modargs_free(u->modargs);
+ pa_xfree(u->address);
+ pa_xfree(u->path);
+
+ if (u->discovery)
+ pa_bluetooth_discovery_unref(u->discovery);
+
pa_xfree(u);
}
diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c
index 4586d8ca..788fee00 100644
--- a/src/modules/bluetooth/module-bluetooth-discover.c
+++ b/src/modules/bluetooth/module-bluetooth-discover.c
@@ -34,7 +34,7 @@
#include <pulsecore/macro.h>
#include <pulsecore/llist.h>
#include <pulsecore/core-util.h>
-#include <modules/dbus-util.h>
+#include <pulsecore/dbus-shared.h>
#include "module-bluetooth-discover-symdef.h"
#include "bluetooth-util.h"
@@ -42,13 +42,21 @@
PA_MODULE_AUTHOR("Joao Paulo Rechi Vita");
PA_MODULE_DESCRIPTION("Detect available bluetooth audio devices and load bluetooth audio drivers");
PA_MODULE_VERSION(PACKAGE_VERSION);
-PA_MODULE_USAGE("sco_sink=<name of sink> "
- "sco_source=<name of source>"
- "async=<Asynchronous initialization?>");
+PA_MODULE_USAGE("async=<Asynchronous initialization?>");
+PA_MODULE_LOAD_ONCE(TRUE);
+
+/*
+#ifdef NOKIA
+ "sco_sink=<name of sink> "
+ "sco_source=<name of source>"
+#endif
+*/
static const char* const valid_modargs[] = {
+#ifdef NOKIA
"sco_sink",
"sco_source",
+#endif
"async",
NULL
};
@@ -57,26 +65,45 @@ struct userdata {
pa_module *module;
pa_modargs *modargs;
pa_core *core;
- pa_dbus_connection *connection;
pa_bluetooth_discovery *discovery;
+ pa_hook_slot *slot;
+ pa_hashmap *hashmap;
};
-static void load_module_for_device(struct userdata *u, pa_bluetooth_device *d, pa_bool_t good) {
+struct module_info {
+ char *path;
+ uint32_t module;
+};
+
+static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) {
+ struct module_info *mi;
+
pa_assert(u);
pa_assert(d);
- if (good &&
- d->device_connected > 0 &&
- (d->audio_sink_connected > 0 || d->headset_connected > 0)) {
+ mi = pa_hashmap_get(u->hashmap, d->path);
+
+ if (!d->dead &&
+ d->device_connected > 0 && d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED) {
- if (((uint32_t) PA_PTR_TO_UINT(d->data))-1 == PA_INVALID_INDEX) {
+ if (!mi) {
pa_module *m = NULL;
char *args;
/* Oh, awesome, a new device has shown up and been connected! */
- args = pa_sprintf_malloc("address=\"%s\" path=\"%s\" profile=\"%s\"", d->address, d->path, d->headset_connected ? "hsp" : "a2dp");
+ args = pa_sprintf_malloc("address=\"%s\" path=\"%s\"", d->address, d->path);
+#if 0
+ /* This is in case we have to use hsp immediately, without waiting for .Audio.State = Connected */
+ if (d->headset_state >= PA_BT_AUDIO_STATE_CONNECTED && somecondition) {
+ char *tmp;
+ tmp = pa_sprintf_malloc("%s profile=\"hsp\"", args);
+ pa_xfree(args);
+ args = tmp;
+ }
+#endif
+#ifdef NOKIA
if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) &&
pa_modargs_get_value(u->modargs, "sco_source", NULL)) {
char *tmp;
@@ -87,44 +114,38 @@ static void load_module_for_device(struct userdata *u, pa_bluetooth_device *d, p
pa_xfree(args);
args = tmp;
}
+#endif
pa_log_debug("Loading module-bluetooth-device %s", args);
m = pa_module_load(u->module->core, "module-bluetooth-device", args);
pa_xfree(args);
- if (m)
- d->data = PA_UINT_TO_PTR((uint32_t) (m->index+1));
- else
+ if (m) {
+ mi = pa_xnew(struct module_info, 1);
+ mi->module = m->index;
+ mi->path = pa_xstrdup(d->path);
+
+ pa_hashmap_put(u->hashmap, mi->path, mi);
+ } else
pa_log_debug("Failed to load module for device %s", d->path);
}
} else {
- if (((uint32_t) PA_PTR_TO_UINT(d->data))-1 != PA_INVALID_INDEX) {
+ if (mi) {
/* Hmm, disconnection? Then let's unload our module */
pa_log_debug("Unloading module for %s", d->path);
- pa_module_unload_request_by_index(u->core, (uint32_t) (PA_PTR_TO_UINT(d->data))-1, TRUE);
- d->data = NULL;
- }
- }
-}
-
-static int setup_dbus(struct userdata *u) {
- DBusError err;
+ pa_module_unload_request_by_index(u->core, mi->module, TRUE);
- dbus_error_init(&err);
-
- u->connection = pa_dbus_bus_get(u->core, DBUS_BUS_SYSTEM, &err);
-
- if (dbus_error_is_set(&err) || !u->connection) {
- pa_log("Failed to get D-Bus connection: %s", err.message);
- dbus_error_free(&err);
- return -1;
+ pa_hashmap_remove(u->hashmap, mi->path);
+ pa_xfree(mi->path);
+ pa_xfree(mi);
+ }
}
- return 0;
+ return PA_HOOK_OK;
}
int pa__init(pa_module* m) {
@@ -149,12 +170,12 @@ int pa__init(pa_module* m) {
u->core = m->core;
u->modargs = ma;
ma = NULL;
+ u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
- if (setup_dbus(u) < 0)
+ if (!(u->discovery = pa_bluetooth_discovery_get(u->core)))
goto fail;
- if (!(u->discovery = pa_bluetooth_discovery_new(pa_dbus_connection_get(u->connection), load_module_for_device, u)))
- goto fail;
+ u->slot = pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery), PA_HOOK_NORMAL, (pa_hook_cb_t) load_module_for_device, u);
if (!async)
pa_bluetooth_discovery_sync(u->discovery);
@@ -178,11 +199,22 @@ void pa__done(pa_module* m) {
if (!(u = m->userdata))
return;
+ if (u->slot)
+ pa_hook_slot_free(u->slot);
+
if (u->discovery)
- pa_bluetooth_discovery_free(u->discovery);
+ pa_bluetooth_discovery_unref(u->discovery);
+
+ if (u->hashmap) {
+ struct module_info *mi;
- if (u->connection)
- pa_dbus_connection_unref(u->connection);
+ while ((mi = pa_hashmap_steal_first(u->hashmap))) {
+ pa_xfree(mi->path);
+ pa_xfree(mi);
+ }
+
+ pa_hashmap_free(u->hashmap, NULL, NULL);
+ }
if (u->modargs)
pa_modargs_free(u->modargs);
diff --git a/src/modules/bluetooth/module-bluetooth-proximity.c b/src/modules/bluetooth/module-bluetooth-proximity.c
index c8d7b4d9..c4cfd733 100644
--- a/src/modules/bluetooth/module-bluetooth-proximity.c
+++ b/src/modules/bluetooth/module-bluetooth-proximity.c
@@ -42,8 +42,8 @@
#include <pulsecore/core-util.h>
#include <pulsecore/core-error.h>
#include <pulsecore/start-child.h>
+#include <pulsecore/dbus-shared.h>
-#include "../dbus-util.h"
#include "module-bluetooth-proximity-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering");
@@ -109,7 +109,7 @@ static void update_volume(struct userdata *u) {
}
pa_log_info("Found %u BT devices, unmuting.", u->n_found);
- pa_sink_set_mute(s, FALSE);
+ pa_sink_set_mute(s, FALSE, FALSE);
} else if (!u->muted && (u->n_found+u->n_unknown) <= 0) {
pa_sink *s;
@@ -122,7 +122,7 @@ static void update_volume(struct userdata *u) {
}
pa_log_info("No BT devices found, muting.");
- pa_sink_set_mute(s, TRUE);
+ pa_sink_set_mute(s, TRUE, FALSE);
} else
pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown);
diff --git a/src/modules/bluetooth/sbc.c b/src/modules/bluetooth/sbc.c
index 6fa54796..779be4bd 100644
--- a/src/modules/bluetooth/sbc.c
+++ b/src/modules/bluetooth/sbc.c
@@ -973,13 +973,13 @@ int sbc_init(sbc_t *sbc, unsigned long flags)
return 0;
}
-int sbc_parse(sbc_t *sbc, void *input, int input_len)
+ssize_t sbc_parse(sbc_t *sbc, const void *input, size_t input_len)
{
return sbc_decode(sbc, input, input_len, NULL, 0, NULL);
}
-int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,
- int output_len, int *written)
+ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len,
+ void *output, size_t output_len, size_t *written)
{
struct sbc_priv *priv;
char *ptr;
@@ -1004,7 +1004,7 @@ int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,
sbc->bitpool = priv->frame.bitpool;
priv->frame.codesize = sbc_get_codesize(sbc);
- priv->frame.length = sbc_get_frame_length(sbc);
+ priv->frame.length = framelen;
}
if (!output)
@@ -1020,7 +1020,7 @@ int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,
ptr = output;
- if (output_len < samples * priv->frame.channels * 2)
+ if (output_len < (size_t) (samples * priv->frame.channels * 2))
samples = output_len / (priv->frame.channels * 2);
for (i = 0; i < samples; i++) {
@@ -1044,10 +1044,8 @@ int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,
return framelen;
}
-ssize_t sbc_encode(sbc_t *sbc,
- const void *input, size_t input_len,
- void *output, size_t output_len,
- size_t *written)
+ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len,
+ void *output, size_t output_len, size_t *written)
{
struct sbc_priv *priv;
int framelen, samples;
@@ -1138,30 +1136,25 @@ void sbc_finish(sbc_t *sbc)
size_t sbc_get_frame_length(sbc_t *sbc)
{
size_t ret;
- uint8_t subbands, channels, blocks, joint;
+ uint8_t subbands, channels, blocks, joint, bitpool;
struct sbc_priv *priv;
priv = sbc->priv;
- if (!priv->init) {
- subbands = sbc->subbands ? 8 : 4;
- blocks = 4 + (sbc->blocks * 4);
- channels = sbc->mode == SBC_MODE_MONO ? 1 : 2;
- joint = sbc->mode == SBC_MODE_JOINT_STEREO ? 1 : 0;
- } else {
- subbands = priv->frame.subbands;
- blocks = priv->frame.blocks;
- channels = priv->frame.channels;
- joint = priv->frame.joint;
- }
+ if (priv->init)
+ return priv->frame.length;
- ret = 4 + (4 * subbands * channels) / 8;
+ subbands = sbc->subbands ? 8 : 4;
+ blocks = 4 + (sbc->blocks * 4);
+ channels = sbc->mode == SBC_MODE_MONO ? 1 : 2;
+ joint = sbc->mode == SBC_MODE_JOINT_STEREO ? 1 : 0;
+ bitpool = sbc->bitpool;
+ ret = 4 + (4 * subbands * channels) / 8;
/* This term is not always evenly divide so we round it up */
if (channels == 1)
- ret += ((blocks * channels * sbc->bitpool) + 7) / 8;
+ ret += ((blocks * channels * bitpool) + 7) / 8;
else
- ret += (((joint ? subbands : 0) + blocks * sbc->bitpool) + 7)
- / 8;
+ ret += (((joint ? subbands : 0) + blocks * bitpool) + 7) / 8;
return ret;
}
diff --git a/src/modules/bluetooth/sbc.h b/src/modules/bluetooth/sbc.h
index 25a12885..65435884 100644
--- a/src/modules/bluetooth/sbc.h
+++ b/src/modules/bluetooth/sbc.h
@@ -82,15 +82,15 @@ typedef struct sbc_struct sbc_t;
int sbc_init(sbc_t *sbc, unsigned long flags);
int sbc_reinit(sbc_t *sbc, unsigned long flags);
-int sbc_parse(sbc_t *sbc, void *input, int input_len);
-int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,
- int output_len, int *len);
+
+ssize_t sbc_parse(sbc_t *sbc, const void *input, size_t input_len);
+
+ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len,
+ void *output, size_t output_len, size_t *written);
/* Encodes ONE input block into ONE output block */
-ssize_t sbc_encode(sbc_t *sbc,
- const void *input, size_t input_len,
- void *output, size_t output_len,
- size_t *written);
+ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len,
+ void *output, size_t output_len, size_t *written);
/* Returns the output block size in bytes */
size_t sbc_get_frame_length(sbc_t *sbc);