From: Jo?o Paulo Rechi Vita <jprvita@xxxxxxxxxxxxx> --- src/Makefile.am | 43 +- src/modules/bluetooth/bluetooth-util.c | 1905 --------------- src/modules/bluetooth/bluetooth-util.h | 178 -- src/modules/bluetooth/bluez4-util.c | 1905 +++++++++++++++ src/modules/bluetooth/bluez4-util.h | 178 ++ src/modules/bluetooth/module-bluetooth-device.c | 2623 --------------------- src/modules/bluetooth/module-bluetooth-discover.c | 194 -- src/modules/bluetooth/module-bluez4-device.c | 2623 +++++++++++++++++++++ src/modules/bluetooth/module-bluez4-discover.c | 194 ++ 9 files changed, 4921 insertions(+), 4922 deletions(-) delete mode 100644 src/modules/bluetooth/bluetooth-util.c delete mode 100644 src/modules/bluetooth/bluetooth-util.h create mode 100644 src/modules/bluetooth/bluez4-util.c create mode 100644 src/modules/bluetooth/bluez4-util.h delete mode 100644 src/modules/bluetooth/module-bluetooth-device.c delete mode 100644 src/modules/bluetooth/module-bluetooth-discover.c create mode 100644 src/modules/bluetooth/module-bluez4-device.c create mode 100644 src/modules/bluetooth/module-bluez4-discover.c diff --git a/src/Makefile.am b/src/Makefile.am index 6de6e96..ff37877 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1320,11 +1320,11 @@ endif if HAVE_BLUEZ modlibexec_LTLIBRARIES += \ - libbluetooth-util.la \ module-bluetooth-proximity.la \ - module-bluetooth-discover.la \ module-bluetooth-policy.la \ - module-bluetooth-device.la + libbluez4-util.la \ + module-bluez4-discover.la \ + module-bluez4-device.la pulselibexec_PROGRAMS += \ proximity-helper @@ -1415,9 +1415,9 @@ SYMDEF_FILES = \ module-udev-detect-symdef.h \ module-systemd-login-symdef.h \ module-bluetooth-proximity-symdef.h \ - module-bluetooth-discover-symdef.h \ module-bluetooth-policy-symdef.h \ - module-bluetooth-device-symdef.h \ + module-bluez4-discover-symdef.h \ + module-bluez4-device-symdef.h \ module-raop-sink-symdef.h \ module-raop-discover-symdef.h \ module-gconf-symdef.h \ @@ -2016,25 +2016,24 @@ proximity_helper_LDADD = $(AM_LDADD) $(BLUEZ_LIBS) proximity_helper_CFLAGS = $(AM_CFLAGS) $(BLUEZ_CFLAGS) proximity_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -# Bluetooth sink / source -module_bluetooth_discover_la_SOURCES = modules/bluetooth/module-bluetooth-discover.c -module_bluetooth_discover_la_LDFLAGS = $(MODULE_LDFLAGS) -module_bluetooth_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libbluetooth-util.la -module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +# Bluetooth BlueZ 4 sink / source +module_bluez4_discover_la_SOURCES = modules/bluetooth/module-bluez4-discover.c +module_bluez4_discover_la_LDFLAGS = $(MODULE_LDFLAGS) +module_bluez4_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libbluez4-util.la +module_bluez4_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) - -libbluetooth_util_la_SOURCES = \ +libbluez4_util_la_SOURCES = \ modules/bluetooth/a2dp-codecs.h \ - modules/bluetooth/bluetooth-util.c \ - modules/bluetooth/bluetooth-util.h -libbluetooth_util_la_LDFLAGS = -avoid-version -libbluetooth_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) -libbluetooth_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) - -module_bluetooth_device_la_SOURCES = modules/bluetooth/module-bluetooth-device.c modules/bluetooth/rtp.h -module_bluetooth_device_la_LDFLAGS = $(MODULE_LDFLAGS) -module_bluetooth_device_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) libbluetooth-util.la -module_bluetooth_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS) + modules/bluetooth/bluez4-util.c \ + modules/bluetooth/bluez4-util.h +libbluez4_util_la_LDFLAGS = -avoid-version +libbluez4_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) +libbluez4_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) + +module_bluez4_device_la_SOURCES = modules/bluetooth/module-bluez4-device.c modules/bluetooth/rtp.h +module_bluez4_device_la_LDFLAGS = $(MODULE_LDFLAGS) +module_bluez4_device_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) $(SBC_LIBS) libbluez4-util.la +module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS) module_bluetooth_policy_la_SOURCES = modules/bluetooth/module-bluetooth-policy.c module_bluetooth_policy_la_LDFLAGS = $(MODULE_LDFLAGS) diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c deleted file mode 100644 index 2342068..0000000 --- a/src/modules/bluetooth/bluetooth-util.c +++ /dev/null @@ -1,1905 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2008-2013 Jo?o Paulo Rechi Vita - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 2.1 of the - License, or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <pulse/xmalloc.h> - -#include <pulsecore/core-util.h> -#include <pulsecore/shared.h> -#include <pulsecore/dbus-shared.h> - -#include "bluetooth-util.h" -#include "a2dp-codecs.h" - -#define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG" -#define HFP_HS_ENDPOINT "/MediaEndpoint/HFPHS" -#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource" -#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink" - -#define ENDPOINT_INTROSPECT_XML \ - DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ - "<node>" \ - " <interface name=\"org.bluez.MediaEndpoint\">" \ - " <method name=\"SetConfiguration\">" \ - " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \ - " <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>" \ - " </method>" \ - " <method name=\"SelectConfiguration\">" \ - " <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \ - " <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \ - " </method>" \ - " <method name=\"ClearConfiguration\">" \ - " </method>" \ - " <method name=\"Release\">" \ - " </method>" \ - " </interface>" \ - " <interface name=\"org.freedesktop.DBus.Introspectable\">" \ - " <method name=\"Introspect\">" \ - " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \ - " </method>" \ - " </interface>" \ - "</node>" - -struct pa_bluetooth_discovery { - PA_REFCNT_DECLARE; - - pa_core *core; - pa_dbus_connection *connection; - PA_LLIST_HEAD(pa_dbus_pending, pending); - bool adapters_listed; - pa_hashmap *devices; - pa_hashmap *transports; - pa_hook hooks[PA_BLUETOOTH_HOOK_MAX]; - bool filter_added; -}; - -static void get_properties_reply(DBusPendingCall *pending, void *userdata); -static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, - void *call_data); -static void found_adapter(pa_bluetooth_discovery *y, const char *path); -static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path); - -static pa_bt_audio_state_t audio_state_from_string(const char* value) { - pa_assert(value); - - 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; -} - -const char *pa_bt_profile_to_string(enum profile profile) { - switch(profile) { - case PROFILE_A2DP: - return "a2dp"; - case PROFILE_A2DP_SOURCE: - return "a2dp_source"; - case PROFILE_HSP: - return "hsp"; - case PROFILE_HFGW: - return "hfgw"; - case PROFILE_OFF: - pa_assert_not_reached(); - } - - pa_assert_not_reached(); -} - -static int profile_from_interface(const char *interface, enum profile *p) { - pa_assert(interface); - pa_assert(p); - - if (pa_streq(interface, "org.bluez.AudioSink")) { - *p = PROFILE_A2DP; - return 0; - } else if (pa_streq(interface, "org.bluez.AudioSource")) { - *p = PROFILE_A2DP_SOURCE; - return 0; - } else if (pa_streq(interface, "org.bluez.Headset")) { - *p = PROFILE_HSP; - return 0; - } else if (pa_streq(interface, "org.bluez.HandsfreeGateway")) { - *p = PROFILE_HFGW; - return 0; - } - - return -1; -} - -static pa_bluetooth_transport_state_t audio_state_to_transport_state(pa_bt_audio_state_t state) { - switch (state) { - case PA_BT_AUDIO_STATE_INVALID: /* Typically if state hasn't been received yet */ - case PA_BT_AUDIO_STATE_DISCONNECTED: - case PA_BT_AUDIO_STATE_CONNECTING: - return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED; - case PA_BT_AUDIO_STATE_CONNECTED: - return PA_BLUETOOTH_TRANSPORT_STATE_IDLE; - case PA_BT_AUDIO_STATE_PLAYING: - return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING; - } - - pa_assert_not_reached(); -} - -static pa_bluetooth_uuid *uuid_new(const char *uuid) { - pa_bluetooth_uuid *u; - - u = pa_xnew(pa_bluetooth_uuid, 1); - u->uuid = pa_xstrdup(uuid); - PA_LLIST_INIT(pa_bluetooth_uuid, u); - - return u; -} - -static void uuid_free(pa_bluetooth_uuid *u) { - pa_assert(u); - - pa_xfree(u->uuid); - pa_xfree(u); -} - -static pa_bluetooth_device* device_new(pa_bluetooth_discovery *discovery, const char *path) { - pa_bluetooth_device *d; - unsigned i; - - pa_assert(discovery); - pa_assert(path); - - d = pa_xnew0(pa_bluetooth_device, 1); - - d->discovery = discovery; - d->dead = false; - - d->device_info_valid = 0; - - d->name = NULL; - d->path = pa_xstrdup(path); - d->paired = -1; - d->alias = NULL; - PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids); - d->address = NULL; - d->class = -1; - d->trusted = -1; - - d->audio_state = PA_BT_AUDIO_STATE_INVALID; - - for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) - d->profile_state[i] = PA_BT_AUDIO_STATE_INVALID; - - return d; -} - -static void transport_free(pa_bluetooth_transport *t) { - pa_assert(t); - - pa_xfree(t->owner); - pa_xfree(t->path); - pa_xfree(t->config); - pa_xfree(t); -} - -static void device_free(pa_bluetooth_device *d) { - pa_bluetooth_uuid *u; - pa_bluetooth_transport *t; - unsigned i; - - pa_assert(d); - - for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) { - if (!(t = d->transports[i])) - continue; - - d->transports[i] = NULL; - pa_hashmap_remove(d->discovery->transports, t->path); - t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED; - pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t); - transport_free(t); - } - - while ((u = d->uuids)) { - PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u); - uuid_free(u); - } - - pa_xfree(d->name); - pa_xfree(d->path); - pa_xfree(d->alias); - pa_xfree(d->address); - pa_xfree(d); -} - -static const char *check_variant_property(DBusMessageIter *i) { - const char *key; - - pa_assert(i); - - if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { - pa_log("Property name not a string."); - return NULL; - } - - dbus_message_iter_get_basic(i, &key); - - if (!dbus_message_iter_next(i)) { - pa_log("Property value missing"); - return NULL; - } - - if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) { - pa_log("Property value not a variant."); - return NULL; - } - - return key; -} - -static int parse_manager_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) { - const char *key; - DBusMessageIter variant_i; - - pa_assert(y); - - key = check_variant_property(i); - if (key == NULL) - return -1; - - dbus_message_iter_recurse(i, &variant_i); - - switch (dbus_message_iter_get_arg_type(&variant_i)) { - - case DBUS_TYPE_ARRAY: { - - DBusMessageIter ai; - dbus_message_iter_recurse(&variant_i, &ai); - - if (pa_streq(key, "Adapters")) { - y->adapters_listed = true; - - if (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_OBJECT_PATH) - break; - - while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) { - const char *value; - - dbus_message_iter_get_basic(&ai, &value); - - found_adapter(y, value); - - dbus_message_iter_next(&ai); - } - } - - break; - } - } - - return 0; -} - -static int parse_adapter_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) { - const char *key; - DBusMessageIter variant_i; - - pa_assert(y); - - key = check_variant_property(i); - if (key == NULL) - return -1; - - dbus_message_iter_recurse(i, &variant_i); - - switch (dbus_message_iter_get_arg_type(&variant_i)) { - - case DBUS_TYPE_ARRAY: { - - DBusMessageIter ai; - dbus_message_iter_recurse(&variant_i, &ai); - - if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_OBJECT_PATH && - pa_streq(key, "Devices")) { - - while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) { - const char *value; - - dbus_message_iter_get_basic(&ai, &value); - - found_device(y, value); - - dbus_message_iter_next(&ai); - } - } - - break; - } - } - - return 0; -} - -static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) { - const char *key; - DBusMessageIter variant_i; - - pa_assert(d); - - key = check_variant_property(i); - if (key == NULL) - return -1; - - dbus_message_iter_recurse(i, &variant_i); - -/* pa_log_debug("Parsing property org.bluez.Device.%s", key); */ - - switch (dbus_message_iter_get_arg_type(&variant_i)) { - - case DBUS_TYPE_STRING: { - - const char *value; - dbus_message_iter_get_basic(&variant_i, &value); - - if (pa_streq(key, "Name")) { - pa_xfree(d->name); - d->name = pa_xstrdup(value); - } else if (pa_streq(key, "Alias")) { - pa_xfree(d->alias); - d->alias = pa_xstrdup(value); - } else if (pa_streq(key, "Address")) { - if (is_property_change) { - pa_log("Device property 'Address' expected to be constant but changed for %s", d->path); - return -1; - } - - if (d->address) { - pa_log("Device %s: Received a duplicate Address property.", d->path); - return -1; - } - - d->address = pa_xstrdup(value); - } - -/* pa_log_debug("Value %s", value); */ - - break; - } - - case DBUS_TYPE_BOOLEAN: { - - dbus_bool_t value; - dbus_message_iter_get_basic(&variant_i, &value); - - if (pa_streq(key, "Paired")) - d->paired = !!value; - else if (pa_streq(key, "Trusted")) - d->trusted = !!value; - -/* pa_log_debug("Value %s", pa_yes_no(value)); */ - - break; - } - - case DBUS_TYPE_UINT32: { - - uint32_t value; - dbus_message_iter_get_basic(&variant_i, &value); - - if (pa_streq(key, "Class")) - d->class = (int) value; - -/* pa_log_debug("Value %u", (unsigned) value); */ - - break; - } - - case DBUS_TYPE_ARRAY: { - - DBusMessageIter ai; - dbus_message_iter_recurse(&variant_i, &ai); - - if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING && pa_streq(key, "UUIDs")) { - DBusMessage *m; - bool has_audio = false; - - while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) { - pa_bluetooth_uuid *node; - const char *value; - struct pa_bluetooth_hook_uuid_data uuiddata; - - dbus_message_iter_get_basic(&ai, &value); - - if (pa_bluetooth_uuid_has(d->uuids, value)) { - dbus_message_iter_next(&ai); - continue; - } - - node = uuid_new(value); - PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node); - - uuiddata.device = d; - uuiddata.uuid = value; - pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED], &uuiddata); - - /* Vudentz said the interfaces are here when the UUIDs are announced */ - if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) { - pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway", - "GetProperties")); - send_and_add_to_pending(d->discovery, m, get_properties_reply, d); - has_audio = true; - } else 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(d->discovery, m, get_properties_reply, d); - has_audio = true; - } 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(d->discovery, m, get_properties_reply, d); - has_audio = true; - } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) { - pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", - "GetProperties")); - send_and_add_to_pending(d->discovery, m, get_properties_reply, d); - has_audio = true; - } - - dbus_message_iter_next(&ai); - } - - /* 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 */ - if (has_audio) { - pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties")); - send_and_add_to_pending(d->discovery, m, get_properties_reply, d); - } - } - - break; - } - } - - return 0; -} - -static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) { - switch (state) { - case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED: - return "disconnected"; - case PA_BLUETOOTH_TRANSPORT_STATE_IDLE: - return "idle"; - case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING: - return "playing"; - } - - pa_assert_not_reached(); -} - -static int parse_audio_property(pa_bluetooth_device *d, const char *interface, DBusMessageIter *i, bool is_property_change) { - pa_bluetooth_transport *transport; - const char *key; - DBusMessageIter variant_i; - bool is_audio_interface; - enum profile p = PROFILE_OFF; - - pa_assert(d); - pa_assert(interface); - pa_assert(i); - - if (!(is_audio_interface = pa_streq(interface, "org.bluez.Audio"))) - if (profile_from_interface(interface, &p) < 0) - return 0; /* Interface not known so silently ignore property */ - - key = check_variant_property(i); - if (key == NULL) - return -1; - - transport = p == PROFILE_OFF ? NULL : d->transports[p]; - - dbus_message_iter_recurse(i, &variant_i); - -/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */ - - switch (dbus_message_iter_get_arg_type(&variant_i)) { - - case DBUS_TYPE_STRING: { - - const char *value; - dbus_message_iter_get_basic(&variant_i, &value); - - if (pa_streq(key, "State")) { - pa_bt_audio_state_t state = audio_state_from_string(value); - pa_bluetooth_transport_state_t old_state; - - pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d->path, interface, value); - - if (state == PA_BT_AUDIO_STATE_INVALID) - return -1; - - if (is_audio_interface) { - d->audio_state = state; - break; - } - - pa_assert(p != PROFILE_OFF); - - d->profile_state[p] = state; - - if (!transport) - break; - - old_state = transport->state; - transport->state = audio_state_to_transport_state(state); - - if (transport->state != old_state) { - pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport->path, - pa_bt_profile_to_string(transport->profile), transport_state_to_string(old_state), - transport_state_to_string(transport->state)); - - pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], transport); - } - } - - break; - } - - case DBUS_TYPE_UINT16: { - uint16_t value; - - dbus_message_iter_get_basic(&variant_i, &value); - - if (pa_streq(key, "MicrophoneGain")) { - uint16_t gain; - - pa_log_debug("dbus: property '%s' changed to value '%u'", key, value); - - if (!transport) { - pa_log("Volume change does not have an associated transport"); - return -1; - } - - if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->microphone_gain) - break; - - transport->microphone_gain = gain; - pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED], transport); - } else if (pa_streq(key, "SpeakerGain")) { - uint16_t gain; - - pa_log_debug("dbus: property '%s' changed to value '%u'", key, value); - - if (!transport) { - pa_log("Volume change does not have an associated transport"); - return -1; - } - - if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->speaker_gain) - break; - - transport->speaker_gain = gain; - pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED], transport); - } - - break; - } - } - - return 0; -} - -static void run_callback(pa_bluetooth_device *d, bool dead) { - pa_assert(d); - - if (d->device_info_valid != 1) - return; - - d->dead = dead; - pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], d); -} - -static void remove_all_devices(pa_bluetooth_discovery *y) { - pa_bluetooth_device *d; - - pa_assert(y); - - while ((d = pa_hashmap_steal_first(y->devices))) { - run_callback(d, true); - device_free(d); - } -} - -static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) { - DBusMessage *m; - pa_bluetooth_device *d; - - pa_assert(y); - pa_assert(path); - - d = pa_hashmap_get(y->devices, path); - if (d) - return d; - - d = device_new(y, path); - - 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, m, get_properties_reply, d); - - /* Before we read the other properties (Audio, AudioSink, AudioSource, - * Headset) we wait that the UUID is read */ - return d; -} - -static void get_properties_reply(DBusPendingCall *pending, void *userdata) { - DBusMessage *r; - DBusMessageIter arg_i, element_i; - pa_dbus_pending *p; - pa_bluetooth_device *d; - pa_bluetooth_discovery *y; - int valid; - bool old_any_connected; - - pa_assert_se(p = userdata); - pa_assert_se(y = p->context_data); - pa_assert_se(r = dbus_pending_call_steal_reply(pending)); - -/* pa_log_debug("Got %s.GetProperties response for %s", */ -/* dbus_message_get_interface(p->message), */ -/* dbus_message_get_path(p->message)); */ - - /* We don't use p->call_data here right-away since the device - * might already be invalidated at this point */ - - if (dbus_message_has_interface(p->message, "org.bluez.Manager") || - dbus_message_has_interface(p->message, "org.bluez.Adapter")) - d = NULL; - else if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message)))) { - pa_log_warn("Received GetProperties() reply from unknown device: %s (device removed?)", dbus_message_get_path(p->message)); - goto finish2; - } - - pa_assert(p->call_data == d); - - if (d != NULL) - old_any_connected = pa_bluetooth_device_any_audio_connected(d); - - valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1; - - if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties")) - d->device_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) { - pa_log("%s.GetProperties() failed: %s: %s", dbus_message_get_interface(p->message), dbus_message_get_error_name(r), - pa_dbus_get_error_message(r)); - goto finish; - } - - if (!dbus_message_iter_init(r, &arg_i)) { - pa_log("GetProperties reply has no arguments."); - goto finish; - } - - if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) { - pa_log("GetProperties argument is not an array."); - goto finish; - } - - dbus_message_iter_recurse(&arg_i, &element_i); - while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) { - - if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { - DBusMessageIter dict_i; - - dbus_message_iter_recurse(&element_i, &dict_i); - - if (dbus_message_has_interface(p->message, "org.bluez.Manager")) { - if (parse_manager_property(y, &dict_i, false) < 0) - goto finish; - - } else if (dbus_message_has_interface(p->message, "org.bluez.Adapter")) { - if (parse_adapter_property(y, &dict_i, false) < 0) - goto finish; - - } else if (dbus_message_has_interface(p->message, "org.bluez.Device")) { - if (parse_device_property(d, &dict_i, false) < 0) - goto finish; - - } else if (parse_audio_property(d, dbus_message_get_interface(p->message), &dict_i, false) < 0) - goto finish; - - } - - dbus_message_iter_next(&element_i); - } - -finish: - if (d != NULL && old_any_connected != pa_bluetooth_device_any_audio_connected(d)) - run_callback(d, false); - -finish2: - dbus_message_unref(r); - - PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); - pa_dbus_pending_free(p); -} - -static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, - void *call_data) { - pa_dbus_pending *p; - DBusPendingCall *call; - - pa_assert(y); - pa_assert(m); - - pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1)); - - p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data); - PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p); - dbus_pending_call_set_notify(call, func, p, NULL); - - return p; -} - -static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) { - DBusMessage *r; - pa_dbus_pending *p; - pa_bluetooth_discovery *y; - char *endpoint; - - pa_assert(pending); - pa_assert_se(p = userdata); - pa_assert_se(y = p->context_data); - pa_assert_se(endpoint = p->call_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_is_error(r, PA_BLUETOOTH_ERROR_NOT_SUPPORTED)) { - pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint); - goto finish; - } - - if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { - pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r), - pa_dbus_get_error_message(r)); - goto finish; - } - -finish: - dbus_message_unref(r); - - PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); - pa_dbus_pending_free(p); - - pa_xfree(endpoint); -} - -static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) { - DBusMessage *m; - DBusMessageIter i, d; - uint8_t codec = 0; - - pa_log_debug("Registering %s on adapter %s.", endpoint, path); - - pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint")); - - dbus_message_iter_init_append(m, &i); - - dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint); - - dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING - DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, - &d); - - pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid); - - pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec); - - if (pa_streq(uuid, HFP_AG_UUID) || pa_streq(uuid, HFP_HS_UUID)) { - uint8_t capability = 0; - pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capability, 1); - } else { - a2dp_sbc_t capabilities; - - capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | - SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO; - capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | - SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000; - capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS; - capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8; - capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | - SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16; - capabilities.min_bitpool = MIN_BITPOOL; - capabilities.max_bitpool = MAX_BITPOOL; - - pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities)); - } - - dbus_message_iter_close_container(&i, &d); - - send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint)); -} - -static void found_adapter(pa_bluetooth_discovery *y, const char *path) { - DBusMessage *m; - - pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties")); - send_and_add_to_pending(y, m, get_properties_reply, NULL); - - register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID); - register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID); - register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID); - register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID); -} - -static void list_adapters(pa_bluetooth_discovery *y) { - DBusMessage *m; - pa_assert(y); - - pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "GetProperties")); - send_and_add_to_pending(y, m, get_properties_reply, NULL); -} - -static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) { - const char *key; - DBusMessageIter variant_i; - - key = check_variant_property(i); - if (key == NULL) - return -1; - - dbus_message_iter_recurse(i, &variant_i); - - switch (dbus_message_iter_get_arg_type(&variant_i)) { - - case DBUS_TYPE_BOOLEAN: { - - dbus_bool_t value; - dbus_message_iter_get_basic(&variant_i, &value); - - if (pa_streq(key, "NREC") && t->nrec != value) { - t->nrec = value; - pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t->path, t->nrec ? "True" : "False"); - pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED], t); - } - - break; - } - } - - return 0; -} - -static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) { - DBusError err; - pa_bluetooth_discovery *y; - - pa_assert(bus); - pa_assert(m); - - pa_assert_se(y = userdata); - - dbus_error_init(&err); - - 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_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) { - const char *path; - pa_bluetooth_device *d; - - if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { - pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message); - goto fail; - } - - pa_log_debug("Device %s removed", path); - - if ((d = pa_hashmap_remove(y->devices, path))) { - run_callback(d, true); - device_free(d); - } - - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - - } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) { - const char *path; - - if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { - pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err.message); - goto fail; - } - - pa_log_debug("Device %s created", path); - - found_device(y, path); - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - - } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) { - const char *path; - - if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { - pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message); - goto fail; - } - - if (!y->adapters_listed) { - pa_log_debug("Ignoring 'AdapterAdded' because initial adapter list has not been received yet."); - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - } - - pa_log_debug("Adapter %s created", path); - - found_adapter(y, path); - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - - } 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.AudioSource", "PropertyChanged") || - dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") || - dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) { - - pa_bluetooth_device *d; - - if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) { - DBusMessageIter arg_i; - bool old_any_connected = pa_bluetooth_device_any_audio_connected(d); - - if (!dbus_message_iter_init(m, &arg_i)) { - pa_log("Failed to parse PropertyChanged for device %s", d->path); - goto fail; - } - - if (dbus_message_has_interface(m, "org.bluez.Device")) { - if (parse_device_property(d, &arg_i, true) < 0) - goto fail; - - } else if (parse_audio_property(d, dbus_message_get_interface(m), &arg_i, true) < 0) - goto fail; - - if (old_any_connected != pa_bluetooth_device_any_audio_connected(d)) - run_callback(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); - y->adapters_listed = false; - } - - if (new_owner && *new_owner) { - pa_log_debug("Bluetooth daemon appeared."); - list_adapters(y); - } - } - - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) { - pa_bluetooth_transport *t; - DBusMessageIter arg_i; - - if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m)))) - goto fail; - - if (!dbus_message_iter_init(m, &arg_i)) { - pa_log("Failed to parse PropertyChanged for transport %s", t->path); - goto fail; - } - - if (transport_parse_property(t, &arg_i) < 0) - goto fail; - - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - } - -fail: - dbus_error_free(&err); - - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; -} - -pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) { - pa_bluetooth_device *d; - void *state = NULL; - - pa_assert(y); - pa_assert(PA_REFCNT_VALUE(y) > 0); - pa_assert(address); - - while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) - if (pa_streq(d->address, address)) - return d->device_info_valid == 1 ? d : NULL; - - return NULL; -} - -pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) { - pa_bluetooth_device *d; - - pa_assert(y); - pa_assert(PA_REFCNT_VALUE(y) > 0); - pa_assert(path); - - if ((d = pa_hashmap_get(y->devices, path))) - if (d->device_info_valid == 1) - return d; - - return NULL; -} - -bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) { - unsigned i; - - pa_assert(d); - - if (d->dead || d->device_info_valid != 1) - return false; - - if (d->audio_state == PA_BT_AUDIO_STATE_INVALID) - return false; - - /* Make sure audio_state is *not* in CONNECTING state before we fire the - * hook to report the new device state. This is actually very important in - * order to make module-card-restore work well with headsets: if the headset - * supports both HSP and A2DP, one of those profiles is connected first and - * then the other, and lastly the Audio interface becomes connected. - * Checking only audio_state means that this function will return false at - * the time when only the first connection has been made. This is good, - * because otherwise, if the first connection is for HSP and we would - * already load a new device module instance, and module-card-restore tries - * to restore the A2DP profile, that would fail because A2DP is not yet - * connected. Waiting until the Audio interface gets connected means that - * both headset profiles will be connected when the device module is - * loaded. */ - if (d->audio_state == PA_BT_AUDIO_STATE_CONNECTING) - return false; - - for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) - if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) - return true; - - return false; -} - -int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) { - const char *accesstype = "rw"; - DBusMessage *m, *r; - DBusError err; - int ret; - uint16_t i, o; - - pa_assert(t); - pa_assert(t->device); - pa_assert(t->device->discovery); - - if (optional) { - /* FIXME: we are trying to acquire the transport only if the stream is - playing, without actually initiating the stream request from our side - (which is typically undesireable specially for hfgw use-cases. - However this approach is racy, since the stream could have been - suspended in the meantime, so we can't really guarantee that the - stream will not be requested until BlueZ's API supports this - atomically. */ - if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) { - pa_log_info("Failed optional acquire of transport %s", t->path); - return -1; - } - } - - dbus_error_init(&err); - - pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Acquire")); - pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); - r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err); - - if (!r) { - dbus_error_free(&err); - return -1; - } - - if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o, - DBUS_TYPE_INVALID)) { - pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message); - ret = -1; - dbus_error_free(&err); - goto fail; - } - - if (imtu) - *imtu = i; - - if (omtu) - *omtu = o; - -fail: - dbus_message_unref(r); - return ret; -} - -void pa_bluetooth_transport_release(pa_bluetooth_transport *t) { - const char *accesstype = "rw"; - DBusMessage *m; - DBusError err; - - pa_assert(t); - pa_assert(t->device); - pa_assert(t->device->discovery); - - dbus_error_init(&err); - - pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release")); - pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); - dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err); - - if (dbus_error_is_set(&err)) { - pa_log("Failed to release transport %s: %s", t->path, err.message); - dbus_error_free(&err); - } else - pa_log_info("Transport %s released", t->path); -} - -static void set_property(pa_bluetooth_discovery *y, const char *bus, const char *path, const char *interface, - const char *prop_name, int prop_type, void *prop_value) { - DBusMessage *m; - DBusMessageIter i; - - pa_assert(y); - pa_assert(path); - pa_assert(interface); - pa_assert(prop_name); - - pa_assert_se(m = dbus_message_new_method_call(bus, path, interface, "SetProperty")); - dbus_message_iter_init_append(m, &i); - dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name); - pa_dbus_append_basic_variant(&i, prop_type, prop_value); - - dbus_message_set_no_reply(m, true); - pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), m, NULL)); - dbus_message_unref(m); -} - -void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value) { - dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN); - - pa_assert(t); - pa_assert(t->profile == PROFILE_HSP); - - set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset", - "MicrophoneGain", DBUS_TYPE_UINT16, &gain); -} - -void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value) { - dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN); - - pa_assert(t); - pa_assert(t->profile == PROFILE_HSP); - - set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset", - "SpeakerGain", DBUS_TYPE_UINT16, &gain); -} - -static int setup_dbus(pa_bluetooth_discovery *y) { - DBusError err; - - dbus_error_init(&err); - - if (!(y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err))) { - pa_log("Failed to get D-Bus connection: %s", err.message); - dbus_error_free(&err); - return -1; - } - - return 0; -} - -static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char *owner, const char *path, enum profile p, - const uint8_t *config, int size) { - pa_bluetooth_transport *t; - - t = pa_xnew0(pa_bluetooth_transport, 1); - t->device = d; - t->owner = pa_xstrdup(owner); - t->path = pa_xstrdup(path); - t->profile = p; - t->config_size = size; - - if (size > 0) { - t->config = pa_xnew(uint8_t, size); - memcpy(t->config, config, size); - } - - t->state = audio_state_to_transport_state(d->profile_state[p]); - - return t; -} - -static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { - pa_bluetooth_discovery *y = userdata; - pa_bluetooth_device *d; - pa_bluetooth_transport *t; - const char *sender, *path, *dev_path = NULL, *uuid = NULL; - uint8_t *config = NULL; - int size = 0; - bool nrec = false; - enum profile p; - DBusMessageIter args, props; - DBusMessage *r; - bool old_any_connected; - - if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) { - pa_log("Invalid signature for method SetConfiguration"); - goto fail2; - } - - dbus_message_iter_get_basic(&args, &path); - - if (pa_hashmap_get(y->transports, path)) { - pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path); - goto fail2; - } - - pa_assert_se(dbus_message_iter_next(&args)); - - dbus_message_iter_recurse(&args, &props); - if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) - goto fail; - - /* Read transport properties */ - while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) { - const char *key; - DBusMessageIter value, entry; - int var; - - dbus_message_iter_recurse(&props, &entry); - dbus_message_iter_get_basic(&entry, &key); - - dbus_message_iter_next(&entry); - dbus_message_iter_recurse(&entry, &value); - - var = dbus_message_iter_get_arg_type(&value); - - if (strcasecmp(key, "UUID") == 0) { - if (var != DBUS_TYPE_STRING) - goto fail; - - dbus_message_iter_get_basic(&value, &uuid); - } else if (strcasecmp(key, "Device") == 0) { - if (var != DBUS_TYPE_OBJECT_PATH) - goto fail; - - dbus_message_iter_get_basic(&value, &dev_path); - } else if (strcasecmp(key, "NREC") == 0) { - dbus_bool_t tmp_boolean; - if (var != DBUS_TYPE_BOOLEAN) - goto fail; - - dbus_message_iter_get_basic(&value, &tmp_boolean); - nrec = tmp_boolean; - } else if (strcasecmp(key, "Configuration") == 0) { - DBusMessageIter array; - if (var != DBUS_TYPE_ARRAY) - goto fail; - - dbus_message_iter_recurse(&value, &array); - dbus_message_iter_get_fixed_array(&array, &config, &size); - } - - dbus_message_iter_next(&props); - } - - d = found_device(y, dev_path); - if (!d) - goto fail; - - if (dbus_message_has_path(m, HFP_AG_ENDPOINT)) - p = PROFILE_HSP; - else if (dbus_message_has_path(m, HFP_HS_ENDPOINT)) - p = PROFILE_HFGW; - else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT)) - p = PROFILE_A2DP; - else - p = PROFILE_A2DP_SOURCE; - - if (d->transports[p] != NULL) { - pa_log("Cannot configure transport %s because profile %d is already used", path, p); - goto fail2; - } - - old_any_connected = pa_bluetooth_device_any_audio_connected(d); - - sender = dbus_message_get_sender(m); - - t = transport_new(d, sender, path, p, config, size); - if (nrec) - t->nrec = nrec; - - d->transports[p] = t; - pa_assert_se(pa_hashmap_put(y->transports, t->path, t) >= 0); - - pa_log_debug("Transport %s profile %d available", t->path, t->profile); - - pa_assert_se(r = dbus_message_new_method_return(m)); - pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL)); - dbus_message_unref(r); - - if (old_any_connected != pa_bluetooth_device_any_audio_connected(d)) - run_callback(d, false); - - return NULL; - -fail: - pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments"); - -fail2: - pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", - "Unable to set configuration")); - return r; -} - -static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { - pa_bluetooth_discovery *y = userdata; - pa_bluetooth_transport *t; - DBusMessage *r; - DBusError e; - const char *path; - - dbus_error_init(&e); - - if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { - pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message); - dbus_error_free(&e); - goto fail; - } - - if ((t = pa_hashmap_get(y->transports, path))) { - bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device); - - pa_log_debug("Clearing transport %s profile %d", t->path, t->profile); - t->device->transports[t->profile] = NULL; - pa_hashmap_remove(y->transports, t->path); - t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED; - pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t); - - if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device)) - run_callback(t->device, false); - - transport_free(t); - } - - pa_assert_se(r = dbus_message_new_method_return(m)); - - return r; - -fail: - pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", - "Unable to clear configuration")); - return r; -} - -static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) { - - switch (freq) { - case SBC_SAMPLING_FREQ_16000: - case SBC_SAMPLING_FREQ_32000: - return 53; - - case SBC_SAMPLING_FREQ_44100: - - switch (mode) { - case SBC_CHANNEL_MODE_MONO: - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - return 31; - - case SBC_CHANNEL_MODE_STEREO: - case SBC_CHANNEL_MODE_JOINT_STEREO: - return 53; - - default: - pa_log_warn("Invalid channel mode %u", mode); - return 53; - } - - case SBC_SAMPLING_FREQ_48000: - - switch (mode) { - case SBC_CHANNEL_MODE_MONO: - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - return 29; - - case SBC_CHANNEL_MODE_STEREO: - case SBC_CHANNEL_MODE_JOINT_STEREO: - return 51; - - default: - pa_log_warn("Invalid channel mode %u", mode); - return 51; - } - - default: - pa_log_warn("Invalid sampling freq %u", freq); - return 53; - } -} - -static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { - pa_bluetooth_discovery *y = userdata; - a2dp_sbc_t *cap, config; - uint8_t *pconf = (uint8_t *) &config; - int i, size; - DBusMessage *r; - DBusError e; - - static const struct { - uint32_t rate; - uint8_t cap; - } freq_table[] = { - { 16000U, SBC_SAMPLING_FREQ_16000 }, - { 32000U, SBC_SAMPLING_FREQ_32000 }, - { 44100U, SBC_SAMPLING_FREQ_44100 }, - { 48000U, SBC_SAMPLING_FREQ_48000 } - }; - - dbus_error_init(&e); - - if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) { - pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message); - dbus_error_free(&e); - goto fail; - } - - if (dbus_message_has_path(m, HFP_AG_ENDPOINT) || dbus_message_has_path(m, HFP_HS_ENDPOINT)) - goto done; - - pa_assert(size == sizeof(config)); - - memset(&config, 0, sizeof(config)); - - /* Find the lowest freq that is at least as high as the requested - * sampling rate */ - for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) - if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) { - config.frequency = freq_table[i].cap; - break; - } - - if ((unsigned) i == PA_ELEMENTSOF(freq_table)) { - for (--i; i >= 0; i--) { - if (cap->frequency & freq_table[i].cap) { - config.frequency = freq_table[i].cap; - break; - } - } - - if (i < 0) { - pa_log("Not suitable sample rate"); - goto fail; - } - } - - pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table)); - - if (y->core->default_sample_spec.channels <= 1) { - if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) - config.channel_mode = SBC_CHANNEL_MODE_MONO; - } - - if (y->core->default_sample_spec.channels >= 2) { - if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) - config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; - else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO) - config.channel_mode = SBC_CHANNEL_MODE_STEREO; - else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) - config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; - else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) { - config.channel_mode = SBC_CHANNEL_MODE_MONO; - } else { - pa_log("No supported channel modes"); - goto fail; - } - } - - if (cap->block_length & SBC_BLOCK_LENGTH_16) - config.block_length = SBC_BLOCK_LENGTH_16; - else if (cap->block_length & SBC_BLOCK_LENGTH_12) - config.block_length = SBC_BLOCK_LENGTH_12; - else if (cap->block_length & SBC_BLOCK_LENGTH_8) - config.block_length = SBC_BLOCK_LENGTH_8; - else if (cap->block_length & SBC_BLOCK_LENGTH_4) - config.block_length = SBC_BLOCK_LENGTH_4; - else { - pa_log_error("No supported block lengths"); - goto fail; - } - - if (cap->subbands & SBC_SUBBANDS_8) - config.subbands = SBC_SUBBANDS_8; - else if (cap->subbands & SBC_SUBBANDS_4) - config.subbands = SBC_SUBBANDS_4; - else { - pa_log_error("No supported subbands"); - goto fail; - } - - if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS) - config.allocation_method = SBC_ALLOCATION_LOUDNESS; - else if (cap->allocation_method & SBC_ALLOCATION_SNR) - config.allocation_method = SBC_ALLOCATION_SNR; - - config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool); - config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool); - -done: - pa_assert_se(r = dbus_message_new_method_return(m)); - - pa_assert_se(dbus_message_append_args( - r, - DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, - DBUS_TYPE_INVALID)); - - return r; - -fail: - pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", - "Unable to select configuration")); - return r; -} - -static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) { - struct pa_bluetooth_discovery *y = userdata; - DBusMessage *r = NULL; - DBusError e; - const char *path, *interface, *member; - - pa_assert(y); - - path = dbus_message_get_path(m); - interface = dbus_message_get_interface(m); - member = dbus_message_get_member(m); - - pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); - - dbus_error_init(&e); - - if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT) && - !pa_streq(path, HFP_HS_ENDPOINT)) - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - - if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { - const char *xml = ENDPOINT_INTROSPECT_XML; - - pa_assert_se(r = dbus_message_new_method_return(m)); - pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)); - - } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration")) - r = endpoint_set_configuration(c, m, userdata); - else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration")) - r = endpoint_select_configuration(c, m, userdata); - else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration")) - r = endpoint_clear_configuration(c, m, userdata); - else - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - - if (r) { - pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL)); - dbus_message_unref(r); - } - - return DBUS_HANDLER_RESULT_HANDLED; -} - -pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { - DBusError err; - pa_bluetooth_discovery *y; - DBusConnection *conn; - unsigned i; - static const DBusObjectPathVTable vtable_endpoint = { - .message_function = endpoint_handler, - }; - - pa_assert(c); - - 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); - PA_REFCNT_INIT(y); - y->core = c; - y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending); - - for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++) - pa_hook_init(&y->hooks[i], y); - - pa_shared_set(c, "bluetooth-discovery", y); - - if (setup_dbus(y) < 0) - goto fail; - - conn = pa_dbus_connection_get(y->connection); - - /* dynamic detection of bluetooth audio devices */ - if (!dbus_connection_add_filter(conn, filter_cb, y, NULL)) { - pa_log_error("Failed to add filter function"); - goto fail; - } - - y->filter_added = true; - - if (pa_dbus_add_matches( - conn, &err, - "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" - ",arg0='org.bluez'", - "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'", - "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'", - NULL) < 0) { - pa_log("Failed to add D-Bus matches: %s", err.message); - goto fail; - } - - pa_assert_se(dbus_connection_register_object_path(conn, HFP_AG_ENDPOINT, &vtable_endpoint, y)); - pa_assert_se(dbus_connection_register_object_path(conn, HFP_HS_ENDPOINT, &vtable_endpoint, y)); - pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y)); - pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SINK_ENDPOINT, &vtable_endpoint, y)); - - list_adapters(y); - - return y; - -fail: - if (y) - pa_bluetooth_discovery_unref(y); - - dbus_error_free(&err); - - return NULL; -} - -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) { - unsigned i; - - 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) { - remove_all_devices(y); - pa_hashmap_free(y->devices, NULL); - } - - if (y->transports) { - pa_assert(pa_hashmap_isempty(y->transports)); - pa_hashmap_free(y->transports, NULL); - } - - if (y->connection) { - dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT); - dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT); - dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT); - dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT); - pa_dbus_remove_matches( - pa_dbus_connection_get(y->connection), - "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" - ",arg0='org.bluez'", - "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'", - "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'", - "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'", - NULL); - - if (y->filter_added) - dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); - - pa_dbus_connection_unref(y->connection); - } - - for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++) - pa_hook_done(&y->hooks[i]); - - if (y->core) - pa_shared_remove(y->core, "bluetooth-discovery"); - - pa_xfree(y); -} - -pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) { - pa_assert(y); - pa_assert(PA_REFCNT_VALUE(y) > 0); - - return &y->hooks[hook]; -} - -pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) { - unsigned major, minor; - pa_bt_form_factor_t r; - - static const pa_bt_form_factor_t table[] = { - [1] = PA_BT_FORM_FACTOR_HEADSET, - [2] = PA_BT_FORM_FACTOR_HANDSFREE, - [4] = PA_BT_FORM_FACTOR_MICROPHONE, - [5] = PA_BT_FORM_FACTOR_SPEAKER, - [6] = PA_BT_FORM_FACTOR_HEADPHONE, - [7] = PA_BT_FORM_FACTOR_PORTABLE, - [8] = PA_BT_FORM_FACTOR_CAR, - [10] = PA_BT_FORM_FACTOR_HIFI - }; - - /* - * See Bluetooth Assigned Numbers: - * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm - */ - major = (class >> 8) & 0x1F; - minor = (class >> 2) & 0x3F; - - switch (major) { - case 2: - return PA_BT_FORM_FACTOR_PHONE; - case 4: - break; - default: - pa_log_debug("Unknown Bluetooth major device class %u", major); - return PA_BT_FORM_FACTOR_UNKNOWN; - } - - r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BT_FORM_FACTOR_UNKNOWN; - - if (!r) - pa_log_debug("Unknown Bluetooth minor device class %u", minor); - - return r; -} - -const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff) { - switch (ff) { - case PA_BT_FORM_FACTOR_UNKNOWN: - return "unknown"; - case PA_BT_FORM_FACTOR_HEADSET: - return "headset"; - case PA_BT_FORM_FACTOR_HANDSFREE: - return "hands-free"; - case PA_BT_FORM_FACTOR_MICROPHONE: - return "microphone"; - case PA_BT_FORM_FACTOR_SPEAKER: - return "speaker"; - case PA_BT_FORM_FACTOR_HEADPHONE: - return "headphone"; - case PA_BT_FORM_FACTOR_PORTABLE: - return "portable"; - case PA_BT_FORM_FACTOR_CAR: - return "car"; - case PA_BT_FORM_FACTOR_HIFI: - return "hifi"; - case PA_BT_FORM_FACTOR_PHONE: - return "phone"; - } - - pa_assert_not_reached(); -} - -char *pa_bluetooth_cleanup_name(const char *name) { - char *t, *s, *d; - bool space = false; - - pa_assert(name); - - while ((*name >= 1 && *name <= 32) || *name >= 127) - name++; - - t = pa_xstrdup(name); - - for (s = d = t; *s; s++) { - - if (*s <= 32 || *s >= 127 || *s == '_') { - space = true; - continue; - } - - if (space) { - *(d++) = ' '; - space = false; - } - - *(d++) = *s; - } - - *d = 0; - - return t; -} - -bool 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 deleted file mode 100644 index c69ba71..0000000 --- a/src/modules/bluetooth/bluetooth-util.h +++ /dev/null @@ -1,178 +0,0 @@ -#ifndef foobluetoothutilhfoo -#define foobluetoothutilhfoo - -/*** - This file is part of PulseAudio. - - Copyright 2008-2013 Jo?o Paulo Rechi Vita - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 2.1 of the - License, or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#include <dbus/dbus.h> - -#include <pulsecore/llist.h> -#include <pulsecore/macro.h> - -#define PA_BLUETOOTH_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" - -/* 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" - -#define HSP_MAX_GAIN 15 - -typedef struct pa_bluetooth_uuid pa_bluetooth_uuid; -typedef struct pa_bluetooth_device pa_bluetooth_device; -typedef struct pa_bluetooth_discovery pa_bluetooth_discovery; -typedef struct pa_bluetooth_transport pa_bluetooth_transport; - -struct userdata; - -struct pa_bluetooth_uuid { - char *uuid; - PA_LLIST_FIELDS(pa_bluetooth_uuid); -}; - -enum profile { - PROFILE_A2DP, - PROFILE_A2DP_SOURCE, - PROFILE_HSP, - PROFILE_HFGW, - PROFILE_OFF -}; - -#define PA_BLUETOOTH_PROFILE_COUNT PROFILE_OFF - -struct pa_bluetooth_hook_uuid_data { - pa_bluetooth_device *device; - const char *uuid; -}; - -/* Hook data: pa_bluetooth_discovery pointer. */ -typedef enum pa_bluetooth_hook { - PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */ - PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED, /* Call data: pa_bluetooth_hook_uuid_data */ - PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */ - PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED, /* Call data: pa_bluetooth_transport */ - PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */ - PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */ - PA_BLUETOOTH_HOOK_MAX -} pa_bluetooth_hook_t; - -typedef enum pa_bluetooth_transport_state { - PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED, - PA_BLUETOOTH_TRANSPORT_STATE_IDLE, /* Connected but not playing */ - PA_BLUETOOTH_TRANSPORT_STATE_PLAYING -} pa_bluetooth_transport_state_t; - -struct pa_bluetooth_transport { - pa_bluetooth_device *device; - char *owner; - char *path; - enum profile profile; - uint8_t codec; - uint8_t *config; - int config_size; - - pa_bluetooth_transport_state_t state; - bool nrec; - uint16_t microphone_gain; /* Used for HSP/HFP */ - uint16_t speaker_gain; /* Used for HSP/HFP */ -}; - -/* This enum is shared among Audio, Headset, AudioSink, and AudioSource, 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_t; - -struct pa_bluetooth_device { - pa_bluetooth_discovery *discovery; - bool dead; - - int device_info_valid; /* 0: no results yet; 1: good results; -1: bad results ... */ - - /* Device information */ - char *name; - char *path; - pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT]; - int paired; - char *alias; - PA_LLIST_HEAD(pa_bluetooth_uuid, uuids); - char *address; - int class; - int trusted; - - /* Audio state */ - pa_bt_audio_state_t audio_state; - - /* AudioSink, AudioSource, Headset and HandsfreeGateway states */ - pa_bt_audio_state_t profile_state[PA_BLUETOOTH_PROFILE_COUNT]; -}; - -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); - -pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path); -pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address); - -bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d); - -int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu); -void pa_bluetooth_transport_release(pa_bluetooth_transport *t); - -void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value); -void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value); - -pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook); - -typedef enum pa_bt_form_factor { - PA_BT_FORM_FACTOR_UNKNOWN, - PA_BT_FORM_FACTOR_HEADSET, - PA_BT_FORM_FACTOR_HANDSFREE, - PA_BT_FORM_FACTOR_MICROPHONE, - PA_BT_FORM_FACTOR_SPEAKER, - PA_BT_FORM_FACTOR_HEADPHONE, - PA_BT_FORM_FACTOR_PORTABLE, - PA_BT_FORM_FACTOR_CAR, - PA_BT_FORM_FACTOR_HIFI, - PA_BT_FORM_FACTOR_PHONE, -} pa_bt_form_factor_t; - -pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class); -const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff); - -char *pa_bluetooth_cleanup_name(const char *name); - -bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid); -const char *pa_bt_profile_to_string(enum profile profile); - -#endif diff --git a/src/modules/bluetooth/bluez4-util.c b/src/modules/bluetooth/bluez4-util.c new file mode 100644 index 0000000..29b237b --- /dev/null +++ b/src/modules/bluetooth/bluez4-util.c @@ -0,0 +1,1905 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008-2013 Jo?o Paulo Rechi Vita + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/shared.h> +#include <pulsecore/dbus-shared.h> + +#include "bluez4-util.h" +#include "a2dp-codecs.h" + +#define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG" +#define HFP_HS_ENDPOINT "/MediaEndpoint/HFPHS" +#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource" +#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink" + +#define ENDPOINT_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <interface name=\"org.bluez.MediaEndpoint\">" \ + " <method name=\"SetConfiguration\">" \ + " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \ + " <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>" \ + " </method>" \ + " <method name=\"SelectConfiguration\">" \ + " <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \ + " <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \ + " </method>" \ + " <method name=\"ClearConfiguration\">" \ + " </method>" \ + " <method name=\"Release\">" \ + " </method>" \ + " </interface>" \ + " <interface name=\"org.freedesktop.DBus.Introspectable\">" \ + " <method name=\"Introspect\">" \ + " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \ + " </method>" \ + " </interface>" \ + "</node>" + +struct pa_bluetooth_discovery { + PA_REFCNT_DECLARE; + + pa_core *core; + pa_dbus_connection *connection; + PA_LLIST_HEAD(pa_dbus_pending, pending); + bool adapters_listed; + pa_hashmap *devices; + pa_hashmap *transports; + pa_hook hooks[PA_BLUETOOTH_HOOK_MAX]; + bool filter_added; +}; + +static void get_properties_reply(DBusPendingCall *pending, void *userdata); +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, + void *call_data); +static void found_adapter(pa_bluetooth_discovery *y, const char *path); +static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path); + +static pa_bt_audio_state_t audio_state_from_string(const char* value) { + pa_assert(value); + + 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; +} + +const char *pa_bt_profile_to_string(enum profile profile) { + switch(profile) { + case PROFILE_A2DP: + return "a2dp"; + case PROFILE_A2DP_SOURCE: + return "a2dp_source"; + case PROFILE_HSP: + return "hsp"; + case PROFILE_HFGW: + return "hfgw"; + case PROFILE_OFF: + pa_assert_not_reached(); + } + + pa_assert_not_reached(); +} + +static int profile_from_interface(const char *interface, enum profile *p) { + pa_assert(interface); + pa_assert(p); + + if (pa_streq(interface, "org.bluez.AudioSink")) { + *p = PROFILE_A2DP; + return 0; + } else if (pa_streq(interface, "org.bluez.AudioSource")) { + *p = PROFILE_A2DP_SOURCE; + return 0; + } else if (pa_streq(interface, "org.bluez.Headset")) { + *p = PROFILE_HSP; + return 0; + } else if (pa_streq(interface, "org.bluez.HandsfreeGateway")) { + *p = PROFILE_HFGW; + return 0; + } + + return -1; +} + +static pa_bluetooth_transport_state_t audio_state_to_transport_state(pa_bt_audio_state_t state) { + switch (state) { + case PA_BT_AUDIO_STATE_INVALID: /* Typically if state hasn't been received yet */ + case PA_BT_AUDIO_STATE_DISCONNECTED: + case PA_BT_AUDIO_STATE_CONNECTING: + return PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED; + case PA_BT_AUDIO_STATE_CONNECTED: + return PA_BLUETOOTH_TRANSPORT_STATE_IDLE; + case PA_BT_AUDIO_STATE_PLAYING: + return PA_BLUETOOTH_TRANSPORT_STATE_PLAYING; + } + + pa_assert_not_reached(); +} + +static pa_bluetooth_uuid *uuid_new(const char *uuid) { + pa_bluetooth_uuid *u; + + u = pa_xnew(pa_bluetooth_uuid, 1); + u->uuid = pa_xstrdup(uuid); + PA_LLIST_INIT(pa_bluetooth_uuid, u); + + return u; +} + +static void uuid_free(pa_bluetooth_uuid *u) { + pa_assert(u); + + pa_xfree(u->uuid); + pa_xfree(u); +} + +static pa_bluetooth_device* device_new(pa_bluetooth_discovery *discovery, const char *path) { + pa_bluetooth_device *d; + unsigned i; + + pa_assert(discovery); + pa_assert(path); + + d = pa_xnew0(pa_bluetooth_device, 1); + + d->discovery = discovery; + d->dead = false; + + d->device_info_valid = 0; + + d->name = NULL; + d->path = pa_xstrdup(path); + d->paired = -1; + d->alias = NULL; + PA_LLIST_HEAD_INIT(pa_bluetooth_uuid, d->uuids); + d->address = NULL; + d->class = -1; + d->trusted = -1; + + d->audio_state = PA_BT_AUDIO_STATE_INVALID; + + for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) + d->profile_state[i] = PA_BT_AUDIO_STATE_INVALID; + + return d; +} + +static void transport_free(pa_bluetooth_transport *t) { + pa_assert(t); + + pa_xfree(t->owner); + pa_xfree(t->path); + pa_xfree(t->config); + pa_xfree(t); +} + +static void device_free(pa_bluetooth_device *d) { + pa_bluetooth_uuid *u; + pa_bluetooth_transport *t; + unsigned i; + + pa_assert(d); + + for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) { + if (!(t = d->transports[i])) + continue; + + d->transports[i] = NULL; + pa_hashmap_remove(d->discovery->transports, t->path); + t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED; + pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t); + transport_free(t); + } + + while ((u = d->uuids)) { + PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u); + uuid_free(u); + } + + pa_xfree(d->name); + pa_xfree(d->path); + pa_xfree(d->alias); + pa_xfree(d->address); + pa_xfree(d); +} + +static const char *check_variant_property(DBusMessageIter *i) { + const char *key; + + pa_assert(i); + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { + pa_log("Property name not a string."); + return NULL; + } + + dbus_message_iter_get_basic(i, &key); + + if (!dbus_message_iter_next(i)) { + pa_log("Property value missing"); + return NULL; + } + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) { + pa_log("Property value not a variant."); + return NULL; + } + + return key; +} + +static int parse_manager_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) { + const char *key; + DBusMessageIter variant_i; + + pa_assert(y); + + key = check_variant_property(i); + if (key == NULL) + return -1; + + dbus_message_iter_recurse(i, &variant_i); + + switch (dbus_message_iter_get_arg_type(&variant_i)) { + + case DBUS_TYPE_ARRAY: { + + DBusMessageIter ai; + dbus_message_iter_recurse(&variant_i, &ai); + + if (pa_streq(key, "Adapters")) { + y->adapters_listed = true; + + if (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_OBJECT_PATH) + break; + + while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) { + const char *value; + + dbus_message_iter_get_basic(&ai, &value); + + found_adapter(y, value); + + dbus_message_iter_next(&ai); + } + } + + break; + } + } + + return 0; +} + +static int parse_adapter_property(pa_bluetooth_discovery *y, DBusMessageIter *i, bool is_property_change) { + const char *key; + DBusMessageIter variant_i; + + pa_assert(y); + + key = check_variant_property(i); + if (key == NULL) + return -1; + + dbus_message_iter_recurse(i, &variant_i); + + switch (dbus_message_iter_get_arg_type(&variant_i)) { + + case DBUS_TYPE_ARRAY: { + + DBusMessageIter ai; + dbus_message_iter_recurse(&variant_i, &ai); + + if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_OBJECT_PATH && + pa_streq(key, "Devices")) { + + while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) { + const char *value; + + dbus_message_iter_get_basic(&ai, &value); + + found_device(y, value); + + dbus_message_iter_next(&ai); + } + } + + break; + } + } + + return 0; +} + +static int parse_device_property(pa_bluetooth_device *d, DBusMessageIter *i, bool is_property_change) { + const char *key; + DBusMessageIter variant_i; + + pa_assert(d); + + key = check_variant_property(i); + if (key == NULL) + return -1; + + dbus_message_iter_recurse(i, &variant_i); + +/* pa_log_debug("Parsing property org.bluez.Device.%s", key); */ + + switch (dbus_message_iter_get_arg_type(&variant_i)) { + + case DBUS_TYPE_STRING: { + + const char *value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "Name")) { + pa_xfree(d->name); + d->name = pa_xstrdup(value); + } else if (pa_streq(key, "Alias")) { + pa_xfree(d->alias); + d->alias = pa_xstrdup(value); + } else if (pa_streq(key, "Address")) { + if (is_property_change) { + pa_log("Device property 'Address' expected to be constant but changed for %s", d->path); + return -1; + } + + if (d->address) { + pa_log("Device %s: Received a duplicate Address property.", d->path); + return -1; + } + + d->address = pa_xstrdup(value); + } + +/* pa_log_debug("Value %s", value); */ + + break; + } + + case DBUS_TYPE_BOOLEAN: { + + dbus_bool_t value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "Paired")) + d->paired = !!value; + else if (pa_streq(key, "Trusted")) + d->trusted = !!value; + +/* pa_log_debug("Value %s", pa_yes_no(value)); */ + + break; + } + + case DBUS_TYPE_UINT32: { + + uint32_t value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "Class")) + d->class = (int) value; + +/* pa_log_debug("Value %u", (unsigned) value); */ + + break; + } + + case DBUS_TYPE_ARRAY: { + + DBusMessageIter ai; + dbus_message_iter_recurse(&variant_i, &ai); + + if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING && pa_streq(key, "UUIDs")) { + DBusMessage *m; + bool has_audio = false; + + while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) { + pa_bluetooth_uuid *node; + const char *value; + struct pa_bluetooth_hook_uuid_data uuiddata; + + dbus_message_iter_get_basic(&ai, &value); + + if (pa_bluetooth_uuid_has(d->uuids, value)) { + dbus_message_iter_next(&ai); + continue; + } + + node = uuid_new(value); + PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node); + + uuiddata.device = d; + uuiddata.uuid = value; + pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED], &uuiddata); + + /* Vudentz said the interfaces are here when the UUIDs are announced */ + if (strcasecmp(HSP_AG_UUID, value) == 0 || strcasecmp(HFP_AG_UUID, value) == 0) { + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.HandsfreeGateway", + "GetProperties")); + send_and_add_to_pending(d->discovery, m, get_properties_reply, d); + has_audio = true; + } else 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(d->discovery, m, get_properties_reply, d); + has_audio = true; + } 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(d->discovery, m, get_properties_reply, d); + has_audio = true; + } else if (strcasecmp(A2DP_SOURCE_UUID, value) == 0) { + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSource", + "GetProperties")); + send_and_add_to_pending(d->discovery, m, get_properties_reply, d); + has_audio = true; + } + + dbus_message_iter_next(&ai); + } + + /* 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 */ + if (has_audio) { + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties")); + send_and_add_to_pending(d->discovery, m, get_properties_reply, d); + } + } + + break; + } + } + + return 0; +} + +static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) { + switch (state) { + case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED: + return "disconnected"; + case PA_BLUETOOTH_TRANSPORT_STATE_IDLE: + return "idle"; + case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING: + return "playing"; + } + + pa_assert_not_reached(); +} + +static int parse_audio_property(pa_bluetooth_device *d, const char *interface, DBusMessageIter *i, bool is_property_change) { + pa_bluetooth_transport *transport; + const char *key; + DBusMessageIter variant_i; + bool is_audio_interface; + enum profile p = PROFILE_OFF; + + pa_assert(d); + pa_assert(interface); + pa_assert(i); + + if (!(is_audio_interface = pa_streq(interface, "org.bluez.Audio"))) + if (profile_from_interface(interface, &p) < 0) + return 0; /* Interface not known so silently ignore property */ + + key = check_variant_property(i); + if (key == NULL) + return -1; + + transport = p == PROFILE_OFF ? NULL : d->transports[p]; + + dbus_message_iter_recurse(i, &variant_i); + +/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|AudioSource|Headset}.%s", key); */ + + switch (dbus_message_iter_get_arg_type(&variant_i)) { + + case DBUS_TYPE_STRING: { + + const char *value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "State")) { + pa_bt_audio_state_t state = audio_state_from_string(value); + pa_bluetooth_transport_state_t old_state; + + pa_log_debug("Device %s interface %s property 'State' changed to value '%s'", d->path, interface, value); + + if (state == PA_BT_AUDIO_STATE_INVALID) + return -1; + + if (is_audio_interface) { + d->audio_state = state; + break; + } + + pa_assert(p != PROFILE_OFF); + + d->profile_state[p] = state; + + if (!transport) + break; + + old_state = transport->state; + transport->state = audio_state_to_transport_state(state); + + if (transport->state != old_state) { + pa_log_debug("Transport %s (profile %s) changed state from %s to %s.", transport->path, + pa_bt_profile_to_string(transport->profile), transport_state_to_string(old_state), + transport_state_to_string(transport->state)); + + pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], transport); + } + } + + break; + } + + case DBUS_TYPE_UINT16: { + uint16_t value; + + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "MicrophoneGain")) { + uint16_t gain; + + pa_log_debug("dbus: property '%s' changed to value '%u'", key, value); + + if (!transport) { + pa_log("Volume change does not have an associated transport"); + return -1; + } + + if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->microphone_gain) + break; + + transport->microphone_gain = gain; + pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED], transport); + } else if (pa_streq(key, "SpeakerGain")) { + uint16_t gain; + + pa_log_debug("dbus: property '%s' changed to value '%u'", key, value); + + if (!transport) { + pa_log("Volume change does not have an associated transport"); + return -1; + } + + if ((gain = PA_MIN(value, HSP_MAX_GAIN)) == transport->speaker_gain) + break; + + transport->speaker_gain = gain; + pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED], transport); + } + + break; + } + } + + return 0; +} + +static void run_callback(pa_bluetooth_device *d, bool dead) { + pa_assert(d); + + if (d->device_info_valid != 1) + return; + + d->dead = dead; + pa_hook_fire(&d->discovery->hooks[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED], d); +} + +static void remove_all_devices(pa_bluetooth_discovery *y) { + pa_bluetooth_device *d; + + pa_assert(y); + + while ((d = pa_hashmap_steal_first(y->devices))) { + run_callback(d, true); + device_free(d); + } +} + +static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) { + DBusMessage *m; + pa_bluetooth_device *d; + + pa_assert(y); + pa_assert(path); + + d = pa_hashmap_get(y->devices, path); + if (d) + return d; + + d = device_new(y, path); + + 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, m, get_properties_reply, d); + + /* Before we read the other properties (Audio, AudioSink, AudioSource, + * Headset) we wait that the UUID is read */ + return d; +} + +static void get_properties_reply(DBusPendingCall *pending, void *userdata) { + DBusMessage *r; + DBusMessageIter arg_i, element_i; + pa_dbus_pending *p; + pa_bluetooth_device *d; + pa_bluetooth_discovery *y; + int valid; + bool old_any_connected; + + pa_assert_se(p = userdata); + pa_assert_se(y = p->context_data); + pa_assert_se(r = dbus_pending_call_steal_reply(pending)); + +/* pa_log_debug("Got %s.GetProperties response for %s", */ +/* dbus_message_get_interface(p->message), */ +/* dbus_message_get_path(p->message)); */ + + /* We don't use p->call_data here right-away since the device + * might already be invalidated at this point */ + + if (dbus_message_has_interface(p->message, "org.bluez.Manager") || + dbus_message_has_interface(p->message, "org.bluez.Adapter")) + d = NULL; + else if (!(d = pa_hashmap_get(y->devices, dbus_message_get_path(p->message)))) { + pa_log_warn("Received GetProperties() reply from unknown device: %s (device removed?)", dbus_message_get_path(p->message)); + goto finish2; + } + + pa_assert(p->call_data == d); + + if (d != NULL) + old_any_connected = pa_bluetooth_device_any_audio_connected(d); + + valid = dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR ? -1 : 1; + + if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties")) + d->device_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) { + pa_log("%s.GetProperties() failed: %s: %s", dbus_message_get_interface(p->message), dbus_message_get_error_name(r), + pa_dbus_get_error_message(r)); + goto finish; + } + + if (!dbus_message_iter_init(r, &arg_i)) { + pa_log("GetProperties reply has no arguments."); + goto finish; + } + + if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) { + pa_log("GetProperties argument is not an array."); + goto finish; + } + + dbus_message_iter_recurse(&arg_i, &element_i); + while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) { + + if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter dict_i; + + dbus_message_iter_recurse(&element_i, &dict_i); + + if (dbus_message_has_interface(p->message, "org.bluez.Manager")) { + if (parse_manager_property(y, &dict_i, false) < 0) + goto finish; + + } else if (dbus_message_has_interface(p->message, "org.bluez.Adapter")) { + if (parse_adapter_property(y, &dict_i, false) < 0) + goto finish; + + } else if (dbus_message_has_interface(p->message, "org.bluez.Device")) { + if (parse_device_property(d, &dict_i, false) < 0) + goto finish; + + } else if (parse_audio_property(d, dbus_message_get_interface(p->message), &dict_i, false) < 0) + goto finish; + + } + + dbus_message_iter_next(&element_i); + } + +finish: + if (d != NULL && old_any_connected != pa_bluetooth_device_any_audio_connected(d)) + run_callback(d, false); + +finish2: + dbus_message_unref(r); + + PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); + pa_dbus_pending_free(p); +} + +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m, DBusPendingCallNotifyFunction func, + void *call_data) { + pa_dbus_pending *p; + DBusPendingCall *call; + + pa_assert(y); + pa_assert(m); + + pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(y->connection), m, &call, -1)); + + p = pa_dbus_pending_new(pa_dbus_connection_get(y->connection), m, call, y, call_data); + PA_LLIST_PREPEND(pa_dbus_pending, y->pending, p); + dbus_pending_call_set_notify(call, func, p, NULL); + + return p; +} + +static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) { + DBusMessage *r; + pa_dbus_pending *p; + pa_bluetooth_discovery *y; + char *endpoint; + + pa_assert(pending); + pa_assert_se(p = userdata); + pa_assert_se(y = p->context_data); + pa_assert_se(endpoint = p->call_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_is_error(r, PA_BLUETOOTH_ERROR_NOT_SUPPORTED)) { + pa_log_info("Couldn't register endpoint %s, because BlueZ is configured to disable the endpoint type.", endpoint); + goto finish; + } + + if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + pa_log("org.bluez.Media.RegisterEndpoint() failed: %s: %s", dbus_message_get_error_name(r), + pa_dbus_get_error_message(r)); + goto finish; + } + +finish: + dbus_message_unref(r); + + PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); + pa_dbus_pending_free(p); + + pa_xfree(endpoint); +} + +static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) { + DBusMessage *m; + DBusMessageIter i, d; + uint8_t codec = 0; + + pa_log_debug("Registering %s on adapter %s.", endpoint, path); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint")); + + dbus_message_iter_init_append(m, &i); + + dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint); + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &d); + + pa_dbus_append_basic_variant_dict_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid); + + pa_dbus_append_basic_variant_dict_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec); + + if (pa_streq(uuid, HFP_AG_UUID) || pa_streq(uuid, HFP_HS_UUID)) { + uint8_t capability = 0; + pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capability, 1); + } else { + a2dp_sbc_t capabilities; + + capabilities.channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | + SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO; + capabilities.frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | + SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000; + capabilities.allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS; + capabilities.subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8; + capabilities.block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | + SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16; + capabilities.min_bitpool = MIN_BITPOOL; + capabilities.max_bitpool = MAX_BITPOOL; + + pa_dbus_append_basic_array_variant_dict_entry(&d, "Capabilities", DBUS_TYPE_BYTE, &capabilities, sizeof(capabilities)); + } + + dbus_message_iter_close_container(&i, &d); + + send_and_add_to_pending(y, m, register_endpoint_reply, pa_xstrdup(endpoint)); +} + +static void found_adapter(pa_bluetooth_discovery *y, const char *path) { + DBusMessage *m; + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "GetProperties")); + send_and_add_to_pending(y, m, get_properties_reply, NULL); + + register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID); + register_endpoint(y, path, HFP_HS_ENDPOINT, HFP_HS_UUID); + register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID); + register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID); +} + +static void list_adapters(pa_bluetooth_discovery *y) { + DBusMessage *m; + pa_assert(y); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "GetProperties")); + send_and_add_to_pending(y, m, get_properties_reply, NULL); +} + +static int transport_parse_property(pa_bluetooth_transport *t, DBusMessageIter *i) { + const char *key; + DBusMessageIter variant_i; + + key = check_variant_property(i); + if (key == NULL) + return -1; + + dbus_message_iter_recurse(i, &variant_i); + + switch (dbus_message_iter_get_arg_type(&variant_i)) { + + case DBUS_TYPE_BOOLEAN: { + + dbus_bool_t value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "NREC") && t->nrec != value) { + t->nrec = value; + pa_log_debug("Transport %s: Property 'NREC' changed to %s.", t->path, t->nrec ? "True" : "False"); + pa_hook_fire(&t->device->discovery->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED], t); + } + + break; + } + } + + return 0; +} + +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) { + DBusError err; + pa_bluetooth_discovery *y; + + pa_assert(bus); + pa_assert(m); + + pa_assert_se(y = userdata); + + dbus_error_init(&err); + + 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_is_signal(m, "org.bluez.Adapter", "DeviceRemoved")) { + const char *path; + pa_bluetooth_device *d; + + if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log("Failed to parse org.bluez.Adapter.DeviceRemoved: %s", err.message); + goto fail; + } + + pa_log_debug("Device %s removed", path); + + if ((d = pa_hashmap_remove(y->devices, path))) { + run_callback(d, true); + device_free(d); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "DeviceCreated")) { + const char *path; + + if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log("Failed to parse org.bluez.Adapter.DeviceCreated: %s", err.message); + goto fail; + } + + pa_log_debug("Device %s created", path); + + found_device(y, path); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + } else if (dbus_message_is_signal(m, "org.bluez.Manager", "AdapterAdded")) { + const char *path; + + if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log("Failed to parse org.bluez.Manager.AdapterAdded: %s", err.message); + goto fail; + } + + if (!y->adapters_listed) { + pa_log_debug("Ignoring 'AdapterAdded' because initial adapter list has not been received yet."); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + pa_log_debug("Adapter %s created", path); + + found_adapter(y, path); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + } 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.AudioSource", "PropertyChanged") || + dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged") || + dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) { + + pa_bluetooth_device *d; + + if ((d = pa_hashmap_get(y->devices, dbus_message_get_path(m)))) { + DBusMessageIter arg_i; + bool old_any_connected = pa_bluetooth_device_any_audio_connected(d); + + if (!dbus_message_iter_init(m, &arg_i)) { + pa_log("Failed to parse PropertyChanged for device %s", d->path); + goto fail; + } + + if (dbus_message_has_interface(m, "org.bluez.Device")) { + if (parse_device_property(d, &arg_i, true) < 0) + goto fail; + + } else if (parse_audio_property(d, dbus_message_get_interface(m), &arg_i, true) < 0) + goto fail; + + if (old_any_connected != pa_bluetooth_device_any_audio_connected(d)) + run_callback(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); + y->adapters_listed = false; + } + + if (new_owner && *new_owner) { + pa_log_debug("Bluetooth daemon appeared."); + list_adapters(y); + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) { + pa_bluetooth_transport *t; + DBusMessageIter arg_i; + + if (!(t = pa_hashmap_get(y->transports, dbus_message_get_path(m)))) + goto fail; + + if (!dbus_message_iter_init(m, &arg_i)) { + pa_log("Failed to parse PropertyChanged for transport %s", t->path); + goto fail; + } + + if (transport_parse_property(t, &arg_i) < 0) + goto fail; + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + +fail: + dbus_error_free(&err); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *y, const char* address) { + pa_bluetooth_device *d; + void *state = NULL; + + pa_assert(y); + pa_assert(PA_REFCNT_VALUE(y) > 0); + pa_assert(address); + + while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) + if (pa_streq(d->address, address)) + return d->device_info_valid == 1 ? d : NULL; + + return NULL; +} + +pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *y, const char* path) { + pa_bluetooth_device *d; + + pa_assert(y); + pa_assert(PA_REFCNT_VALUE(y) > 0); + pa_assert(path); + + if ((d = pa_hashmap_get(y->devices, path))) + if (d->device_info_valid == 1) + return d; + + return NULL; +} + +bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d) { + unsigned i; + + pa_assert(d); + + if (d->dead || d->device_info_valid != 1) + return false; + + if (d->audio_state == PA_BT_AUDIO_STATE_INVALID) + return false; + + /* Make sure audio_state is *not* in CONNECTING state before we fire the + * hook to report the new device state. This is actually very important in + * order to make module-card-restore work well with headsets: if the headset + * supports both HSP and A2DP, one of those profiles is connected first and + * then the other, and lastly the Audio interface becomes connected. + * Checking only audio_state means that this function will return false at + * the time when only the first connection has been made. This is good, + * because otherwise, if the first connection is for HSP and we would + * already load a new device module instance, and module-card-restore tries + * to restore the A2DP profile, that would fail because A2DP is not yet + * connected. Waiting until the Audio interface gets connected means that + * both headset profiles will be connected when the device module is + * loaded. */ + if (d->audio_state == PA_BT_AUDIO_STATE_CONNECTING) + return false; + + for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) + if (d->transports[i] && d->transports[i]->state != PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) + return true; + + return false; +} + +int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) { + const char *accesstype = "rw"; + DBusMessage *m, *r; + DBusError err; + int ret; + uint16_t i, o; + + pa_assert(t); + pa_assert(t->device); + pa_assert(t->device->discovery); + + if (optional) { + /* FIXME: we are trying to acquire the transport only if the stream is + playing, without actually initiating the stream request from our side + (which is typically undesireable specially for hfgw use-cases. + However this approach is racy, since the stream could have been + suspended in the meantime, so we can't really guarantee that the + stream will not be requested until BlueZ's API supports this + atomically. */ + if (t->state < PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) { + pa_log_info("Failed optional acquire of transport %s", t->path); + return -1; + } + } + + dbus_error_init(&err); + + pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Acquire")); + pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); + r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err); + + if (!r) { + dbus_error_free(&err); + return -1; + } + + if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_UINT16, &i, DBUS_TYPE_UINT16, &o, + DBUS_TYPE_INVALID)) { + pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message); + ret = -1; + dbus_error_free(&err); + goto fail; + } + + if (imtu) + *imtu = i; + + if (omtu) + *omtu = o; + +fail: + dbus_message_unref(r); + return ret; +} + +void pa_bluetooth_transport_release(pa_bluetooth_transport *t) { + const char *accesstype = "rw"; + DBusMessage *m; + DBusError err; + + pa_assert(t); + pa_assert(t->device); + pa_assert(t->device->discovery); + + dbus_error_init(&err); + + pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.bluez.MediaTransport", "Release")); + pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); + dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err); + + if (dbus_error_is_set(&err)) { + pa_log("Failed to release transport %s: %s", t->path, err.message); + dbus_error_free(&err); + } else + pa_log_info("Transport %s released", t->path); +} + +static void set_property(pa_bluetooth_discovery *y, const char *bus, const char *path, const char *interface, + const char *prop_name, int prop_type, void *prop_value) { + DBusMessage *m; + DBusMessageIter i; + + pa_assert(y); + pa_assert(path); + pa_assert(interface); + pa_assert(prop_name); + + pa_assert_se(m = dbus_message_new_method_call(bus, path, interface, "SetProperty")); + dbus_message_iter_init_append(m, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name); + pa_dbus_append_basic_variant(&i, prop_type, prop_value); + + dbus_message_set_no_reply(m, true); + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), m, NULL)); + dbus_message_unref(m); +} + +void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value) { + dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN); + + pa_assert(t); + pa_assert(t->profile == PROFILE_HSP); + + set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset", + "MicrophoneGain", DBUS_TYPE_UINT16, &gain); +} + +void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value) { + dbus_uint16_t gain = PA_MIN(value, HSP_MAX_GAIN); + + pa_assert(t); + pa_assert(t->profile == PROFILE_HSP); + + set_property(t->device->discovery, "org.bluez", t->device->path, "org.bluez.Headset", + "SpeakerGain", DBUS_TYPE_UINT16, &gain); +} + +static int setup_dbus(pa_bluetooth_discovery *y) { + DBusError err; + + dbus_error_init(&err); + + if (!(y->connection = pa_dbus_bus_get(y->core, DBUS_BUS_SYSTEM, &err))) { + pa_log("Failed to get D-Bus connection: %s", err.message); + dbus_error_free(&err); + return -1; + } + + return 0; +} + +static pa_bluetooth_transport *transport_new(pa_bluetooth_device *d, const char *owner, const char *path, enum profile p, + const uint8_t *config, int size) { + pa_bluetooth_transport *t; + + t = pa_xnew0(pa_bluetooth_transport, 1); + t->device = d; + t->owner = pa_xstrdup(owner); + t->path = pa_xstrdup(path); + t->profile = p; + t->config_size = size; + + if (size > 0) { + t->config = pa_xnew(uint8_t, size); + memcpy(t->config, config, size); + } + + t->state = audio_state_to_transport_state(d->profile_state[p]); + + return t; +} + +static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { + pa_bluetooth_discovery *y = userdata; + pa_bluetooth_device *d; + pa_bluetooth_transport *t; + const char *sender, *path, *dev_path = NULL, *uuid = NULL; + uint8_t *config = NULL; + int size = 0; + bool nrec = false; + enum profile p; + DBusMessageIter args, props; + DBusMessage *r; + bool old_any_connected; + + if (!dbus_message_iter_init(m, &args) || !pa_streq(dbus_message_get_signature(m), "oa{sv}")) { + pa_log("Invalid signature for method SetConfiguration"); + goto fail2; + } + + dbus_message_iter_get_basic(&args, &path); + + if (pa_hashmap_get(y->transports, path)) { + pa_log("org.bluez.MediaEndpoint.SetConfiguration: Transport %s is already configured.", path); + goto fail2; + } + + pa_assert_se(dbus_message_iter_next(&args)); + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + goto fail; + + /* Read transport properties */ + while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(&props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + + if (strcasecmp(key, "UUID") == 0) { + if (var != DBUS_TYPE_STRING) + goto fail; + + dbus_message_iter_get_basic(&value, &uuid); + } else if (strcasecmp(key, "Device") == 0) { + if (var != DBUS_TYPE_OBJECT_PATH) + goto fail; + + dbus_message_iter_get_basic(&value, &dev_path); + } else if (strcasecmp(key, "NREC") == 0) { + dbus_bool_t tmp_boolean; + if (var != DBUS_TYPE_BOOLEAN) + goto fail; + + dbus_message_iter_get_basic(&value, &tmp_boolean); + nrec = tmp_boolean; + } else if (strcasecmp(key, "Configuration") == 0) { + DBusMessageIter array; + if (var != DBUS_TYPE_ARRAY) + goto fail; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, &config, &size); + } + + dbus_message_iter_next(&props); + } + + d = found_device(y, dev_path); + if (!d) + goto fail; + + if (dbus_message_has_path(m, HFP_AG_ENDPOINT)) + p = PROFILE_HSP; + else if (dbus_message_has_path(m, HFP_HS_ENDPOINT)) + p = PROFILE_HFGW; + else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT)) + p = PROFILE_A2DP; + else + p = PROFILE_A2DP_SOURCE; + + if (d->transports[p] != NULL) { + pa_log("Cannot configure transport %s because profile %d is already used", path, p); + goto fail2; + } + + old_any_connected = pa_bluetooth_device_any_audio_connected(d); + + sender = dbus_message_get_sender(m); + + t = transport_new(d, sender, path, p, config, size); + if (nrec) + t->nrec = nrec; + + d->transports[p] = t; + pa_assert_se(pa_hashmap_put(y->transports, t->path, t) >= 0); + + pa_log_debug("Transport %s profile %d available", t->path, t->profile); + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL)); + dbus_message_unref(r); + + if (old_any_connected != pa_bluetooth_device_any_audio_connected(d)) + run_callback(d, false); + + return NULL; + +fail: + pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments"); + +fail2: + pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", + "Unable to set configuration")); + return r; +} + +static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { + pa_bluetooth_discovery *y = userdata; + pa_bluetooth_transport *t; + DBusMessage *r; + DBusError e; + const char *path; + + dbus_error_init(&e); + + if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message); + dbus_error_free(&e); + goto fail; + } + + if ((t = pa_hashmap_get(y->transports, path))) { + bool old_any_connected = pa_bluetooth_device_any_audio_connected(t->device); + + pa_log_debug("Clearing transport %s profile %d", t->path, t->profile); + t->device->transports[t->profile] = NULL; + pa_hashmap_remove(y->transports, t->path); + t->state = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED; + pa_hook_fire(&y->hooks[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED], t); + + if (old_any_connected != pa_bluetooth_device_any_audio_connected(t->device)) + run_callback(t->device, false); + + transport_free(t); + } + + pa_assert_se(r = dbus_message_new_method_return(m)); + + return r; + +fail: + pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", + "Unable to clear configuration")); + return r; +} + +static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) { + + switch (freq) { + case SBC_SAMPLING_FREQ_16000: + case SBC_SAMPLING_FREQ_32000: + return 53; + + case SBC_SAMPLING_FREQ_44100: + + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 53; + + default: + pa_log_warn("Invalid channel mode %u", mode); + return 53; + } + + case SBC_SAMPLING_FREQ_48000: + + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 51; + + default: + pa_log_warn("Invalid channel mode %u", mode); + return 51; + } + + default: + pa_log_warn("Invalid sampling freq %u", freq); + return 53; + } +} + +static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { + pa_bluetooth_discovery *y = userdata; + a2dp_sbc_t *cap, config; + uint8_t *pconf = (uint8_t *) &config; + int i, size; + DBusMessage *r; + DBusError e; + + static const struct { + uint32_t rate; + uint8_t cap; + } freq_table[] = { + { 16000U, SBC_SAMPLING_FREQ_16000 }, + { 32000U, SBC_SAMPLING_FREQ_32000 }, + { 44100U, SBC_SAMPLING_FREQ_44100 }, + { 48000U, SBC_SAMPLING_FREQ_48000 } + }; + + dbus_error_init(&e); + + if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) { + pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message); + dbus_error_free(&e); + goto fail; + } + + if (dbus_message_has_path(m, HFP_AG_ENDPOINT) || dbus_message_has_path(m, HFP_HS_ENDPOINT)) + goto done; + + pa_assert(size == sizeof(config)); + + memset(&config, 0, sizeof(config)); + + /* Find the lowest freq that is at least as high as the requested + * sampling rate */ + for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) + if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) { + config.frequency = freq_table[i].cap; + break; + } + + if ((unsigned) i == PA_ELEMENTSOF(freq_table)) { + for (--i; i >= 0; i--) { + if (cap->frequency & freq_table[i].cap) { + config.frequency = freq_table[i].cap; + break; + } + } + + if (i < 0) { + pa_log("Not suitable sample rate"); + goto fail; + } + } + + pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table)); + + if (y->core->default_sample_spec.channels <= 1) { + if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) + config.channel_mode = SBC_CHANNEL_MODE_MONO; + } + + if (y->core->default_sample_spec.channels >= 2) { + if (cap->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) + config.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & SBC_CHANNEL_MODE_STEREO) + config.channel_mode = SBC_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) + config.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; + else if (cap->channel_mode & SBC_CHANNEL_MODE_MONO) { + config.channel_mode = SBC_CHANNEL_MODE_MONO; + } else { + pa_log("No supported channel modes"); + goto fail; + } + } + + if (cap->block_length & SBC_BLOCK_LENGTH_16) + config.block_length = SBC_BLOCK_LENGTH_16; + else if (cap->block_length & SBC_BLOCK_LENGTH_12) + config.block_length = SBC_BLOCK_LENGTH_12; + else if (cap->block_length & SBC_BLOCK_LENGTH_8) + config.block_length = SBC_BLOCK_LENGTH_8; + else if (cap->block_length & SBC_BLOCK_LENGTH_4) + config.block_length = SBC_BLOCK_LENGTH_4; + else { + pa_log_error("No supported block lengths"); + goto fail; + } + + if (cap->subbands & SBC_SUBBANDS_8) + config.subbands = SBC_SUBBANDS_8; + else if (cap->subbands & SBC_SUBBANDS_4) + config.subbands = SBC_SUBBANDS_4; + else { + pa_log_error("No supported subbands"); + goto fail; + } + + if (cap->allocation_method & SBC_ALLOCATION_LOUDNESS) + config.allocation_method = SBC_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & SBC_ALLOCATION_SNR) + config.allocation_method = SBC_ALLOCATION_SNR; + + config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool); + config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool); + +done: + pa_assert_se(r = dbus_message_new_method_return(m)); + + pa_assert_se(dbus_message_append_args( + r, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, + DBUS_TYPE_INVALID)); + + return r; + +fail: + pa_assert_se(r = dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", + "Unable to select configuration")); + return r; +} + +static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) { + struct pa_bluetooth_discovery *y = userdata; + DBusMessage *r = NULL; + DBusError e; + const char *path, *interface, *member; + + pa_assert(y); + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member); + + dbus_error_init(&e); + + if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT) && + !pa_streq(path, HFP_HS_ENDPOINT)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + const char *xml = ENDPOINT_INTROSPECT_XML; + + pa_assert_se(r = dbus_message_new_method_return(m)); + pa_assert_se(dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)); + + } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration")) + r = endpoint_set_configuration(c, m, userdata); + else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration")) + r = endpoint_select_configuration(c, m, userdata); + else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration")) + r = endpoint_clear_configuration(c, m, userdata); + else + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (r) { + pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL)); + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) { + DBusError err; + pa_bluetooth_discovery *y; + DBusConnection *conn; + unsigned i; + static const DBusObjectPathVTable vtable_endpoint = { + .message_function = endpoint_handler, + }; + + pa_assert(c); + + 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); + PA_REFCNT_INIT(y); + y->core = c; + y->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + y->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + PA_LLIST_HEAD_INIT(pa_dbus_pending, y->pending); + + for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++) + pa_hook_init(&y->hooks[i], y); + + pa_shared_set(c, "bluetooth-discovery", y); + + if (setup_dbus(y) < 0) + goto fail; + + conn = pa_dbus_connection_get(y->connection); + + /* dynamic detection of bluetooth audio devices */ + if (!dbus_connection_add_filter(conn, filter_cb, y, NULL)) { + pa_log_error("Failed to add filter function"); + goto fail; + } + + y->filter_added = true; + + if (pa_dbus_add_matches( + conn, &err, + "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" + ",arg0='org.bluez'", + "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'", + "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'", + NULL) < 0) { + pa_log("Failed to add D-Bus matches: %s", err.message); + goto fail; + } + + pa_assert_se(dbus_connection_register_object_path(conn, HFP_AG_ENDPOINT, &vtable_endpoint, y)); + pa_assert_se(dbus_connection_register_object_path(conn, HFP_HS_ENDPOINT, &vtable_endpoint, y)); + pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y)); + pa_assert_se(dbus_connection_register_object_path(conn, A2DP_SINK_ENDPOINT, &vtable_endpoint, y)); + + list_adapters(y); + + return y; + +fail: + if (y) + pa_bluetooth_discovery_unref(y); + + dbus_error_free(&err); + + return NULL; +} + +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) { + unsigned i; + + 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) { + remove_all_devices(y); + pa_hashmap_free(y->devices, NULL); + } + + if (y->transports) { + pa_assert(pa_hashmap_isempty(y->transports)); + pa_hashmap_free(y->transports, NULL); + } + + if (y->connection) { + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT); + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_HS_ENDPOINT); + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT); + dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT); + pa_dbus_remove_matches( + pa_dbus_connection_get(y->connection), + "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'" + ",arg0='org.bluez'", + "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'", + "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'", + "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'", + NULL); + + if (y->filter_added) + dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); + + pa_dbus_connection_unref(y->connection); + } + + for (i = 0; i < PA_BLUETOOTH_HOOK_MAX; i++) + pa_hook_done(&y->hooks[i]); + + if (y->core) + pa_shared_remove(y->core, "bluetooth-discovery"); + + pa_xfree(y); +} + +pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook) { + pa_assert(y); + pa_assert(PA_REFCNT_VALUE(y) > 0); + + return &y->hooks[hook]; +} + +pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class) { + unsigned major, minor; + pa_bt_form_factor_t r; + + static const pa_bt_form_factor_t table[] = { + [1] = PA_BT_FORM_FACTOR_HEADSET, + [2] = PA_BT_FORM_FACTOR_HANDSFREE, + [4] = PA_BT_FORM_FACTOR_MICROPHONE, + [5] = PA_BT_FORM_FACTOR_SPEAKER, + [6] = PA_BT_FORM_FACTOR_HEADPHONE, + [7] = PA_BT_FORM_FACTOR_PORTABLE, + [8] = PA_BT_FORM_FACTOR_CAR, + [10] = PA_BT_FORM_FACTOR_HIFI + }; + + /* + * See Bluetooth Assigned Numbers: + * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm + */ + major = (class >> 8) & 0x1F; + minor = (class >> 2) & 0x3F; + + switch (major) { + case 2: + return PA_BT_FORM_FACTOR_PHONE; + case 4: + break; + default: + pa_log_debug("Unknown Bluetooth major device class %u", major); + return PA_BT_FORM_FACTOR_UNKNOWN; + } + + r = minor < PA_ELEMENTSOF(table) ? table[minor] : PA_BT_FORM_FACTOR_UNKNOWN; + + if (!r) + pa_log_debug("Unknown Bluetooth minor device class %u", minor); + + return r; +} + +const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff) { + switch (ff) { + case PA_BT_FORM_FACTOR_UNKNOWN: + return "unknown"; + case PA_BT_FORM_FACTOR_HEADSET: + return "headset"; + case PA_BT_FORM_FACTOR_HANDSFREE: + return "hands-free"; + case PA_BT_FORM_FACTOR_MICROPHONE: + return "microphone"; + case PA_BT_FORM_FACTOR_SPEAKER: + return "speaker"; + case PA_BT_FORM_FACTOR_HEADPHONE: + return "headphone"; + case PA_BT_FORM_FACTOR_PORTABLE: + return "portable"; + case PA_BT_FORM_FACTOR_CAR: + return "car"; + case PA_BT_FORM_FACTOR_HIFI: + return "hifi"; + case PA_BT_FORM_FACTOR_PHONE: + return "phone"; + } + + pa_assert_not_reached(); +} + +char *pa_bluetooth_cleanup_name(const char *name) { + char *t, *s, *d; + bool space = false; + + pa_assert(name); + + while ((*name >= 1 && *name <= 32) || *name >= 127) + name++; + + t = pa_xstrdup(name); + + for (s = d = t; *s; s++) { + + if (*s <= 32 || *s >= 127 || *s == '_') { + space = true; + continue; + } + + if (space) { + *(d++) = ' '; + space = false; + } + + *(d++) = *s; + } + + *d = 0; + + return t; +} + +bool 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/bluez4-util.h b/src/modules/bluetooth/bluez4-util.h new file mode 100644 index 0000000..4570a10 --- /dev/null +++ b/src/modules/bluetooth/bluez4-util.h @@ -0,0 +1,178 @@ +#ifndef foobluez4utilhfoo +#define foobluez4utilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008-2013 Jo?o Paulo Rechi Vita + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <dbus/dbus.h> + +#include <pulsecore/llist.h> +#include <pulsecore/macro.h> + +#define PA_BLUETOOTH_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" + +/* 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" + +#define HSP_MAX_GAIN 15 + +typedef struct pa_bluetooth_uuid pa_bluetooth_uuid; +typedef struct pa_bluetooth_device pa_bluetooth_device; +typedef struct pa_bluetooth_discovery pa_bluetooth_discovery; +typedef struct pa_bluetooth_transport pa_bluetooth_transport; + +struct userdata; + +struct pa_bluetooth_uuid { + char *uuid; + PA_LLIST_FIELDS(pa_bluetooth_uuid); +}; + +enum profile { + PROFILE_A2DP, + PROFILE_A2DP_SOURCE, + PROFILE_HSP, + PROFILE_HFGW, + PROFILE_OFF +}; + +#define PA_BLUETOOTH_PROFILE_COUNT PROFILE_OFF + +struct pa_bluetooth_hook_uuid_data { + pa_bluetooth_device *device; + const char *uuid; +}; + +/* Hook data: pa_bluetooth_discovery pointer. */ +typedef enum pa_bluetooth_hook { + PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED, /* Call data: pa_bluetooth_device */ + PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED, /* Call data: pa_bluetooth_hook_uuid_data */ + PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED, /* Call data: pa_bluetooth_transport */ + PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED, /* Call data: pa_bluetooth_transport */ + PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */ + PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED, /* Call data: pa_bluetooth_transport */ + PA_BLUETOOTH_HOOK_MAX +} pa_bluetooth_hook_t; + +typedef enum pa_bluetooth_transport_state { + PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED, + PA_BLUETOOTH_TRANSPORT_STATE_IDLE, /* Connected but not playing */ + PA_BLUETOOTH_TRANSPORT_STATE_PLAYING +} pa_bluetooth_transport_state_t; + +struct pa_bluetooth_transport { + pa_bluetooth_device *device; + char *owner; + char *path; + enum profile profile; + uint8_t codec; + uint8_t *config; + int config_size; + + pa_bluetooth_transport_state_t state; + bool nrec; + uint16_t microphone_gain; /* Used for HSP/HFP */ + uint16_t speaker_gain; /* Used for HSP/HFP */ +}; + +/* This enum is shared among Audio, Headset, AudioSink, and AudioSource, 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_t; + +struct pa_bluetooth_device { + pa_bluetooth_discovery *discovery; + bool dead; + + int device_info_valid; /* 0: no results yet; 1: good results; -1: bad results ... */ + + /* Device information */ + char *name; + char *path; + pa_bluetooth_transport *transports[PA_BLUETOOTH_PROFILE_COUNT]; + int paired; + char *alias; + PA_LLIST_HEAD(pa_bluetooth_uuid, uuids); + char *address; + int class; + int trusted; + + /* Audio state */ + pa_bt_audio_state_t audio_state; + + /* AudioSink, AudioSource, Headset and HandsfreeGateway states */ + pa_bt_audio_state_t profile_state[PA_BLUETOOTH_PROFILE_COUNT]; +}; + +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); + +pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path); +pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address); + +bool pa_bluetooth_device_any_audio_connected(const pa_bluetooth_device *d); + +int pa_bluetooth_transport_acquire(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu); +void pa_bluetooth_transport_release(pa_bluetooth_transport *t); + +void pa_bluetooth_transport_set_microphone_gain(pa_bluetooth_transport *t, uint16_t value); +void pa_bluetooth_transport_set_speaker_gain(pa_bluetooth_transport *t, uint16_t value); + +pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hook_t hook); + +typedef enum pa_bt_form_factor { + PA_BT_FORM_FACTOR_UNKNOWN, + PA_BT_FORM_FACTOR_HEADSET, + PA_BT_FORM_FACTOR_HANDSFREE, + PA_BT_FORM_FACTOR_MICROPHONE, + PA_BT_FORM_FACTOR_SPEAKER, + PA_BT_FORM_FACTOR_HEADPHONE, + PA_BT_FORM_FACTOR_PORTABLE, + PA_BT_FORM_FACTOR_CAR, + PA_BT_FORM_FACTOR_HIFI, + PA_BT_FORM_FACTOR_PHONE, +} pa_bt_form_factor_t; + +pa_bt_form_factor_t pa_bluetooth_get_form_factor(uint32_t class); +const char *pa_bt_form_factor_to_string(pa_bt_form_factor_t ff); + +char *pa_bluetooth_cleanup_name(const char *name); + +bool pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid); +const char *pa_bt_profile_to_string(enum profile profile); + +#endif diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c deleted file mode 100644 index 5da197b..0000000 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ /dev/null @@ -1,2623 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2008-2013 Jo?o Paulo Rechi Vita - Copyright 2011-2013 BMW Car IT GmbH. - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 2.1 of the - License, or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <string.h> -#include <errno.h> -#include <math.h> -#include <linux/sockios.h> -#include <arpa/inet.h> - -#include <pulse/rtclock.h> -#include <pulse/sample.h> -#include <pulse/timeval.h> -#include <pulse/xmalloc.h> - -#include <pulsecore/i18n.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/shared.h> -#include <pulsecore/socket-util.h> -#include <pulsecore/thread.h> -#include <pulsecore/thread-mq.h> -#include <pulsecore/poll.h> -#include <pulsecore/rtpoll.h> -#include <pulsecore/time-smoother.h> -#include <pulsecore/namereg.h> - -#include <sbc/sbc.h> - -#include "module-bluetooth-device-symdef.h" -#include "a2dp-codecs.h" -#include "rtp.h" -#include "bluetooth-util.h" - -#define BITPOOL_DEC_LIMIT 32 -#define BITPOOL_DEC_STEP 5 - -PA_MODULE_AUTHOR("Jo?o Paulo Rechi Vita"); -PA_MODULE_DESCRIPTION("BlueZ 4 Bluetooth audio sink and source"); -PA_MODULE_VERSION(PACKAGE_VERSION); -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|hfgw> " - "rate=<sample rate> " - "channels=<number of channels> " - "path=<device object path> " - "auto_connect=<automatically connect?> " - "sco_sink=<SCO over PCM sink name> " - "sco_source=<SCO over PCM source name>"); - -/* TODO: not close fd when entering suspend mode in a2dp */ - -static const char* const valid_modargs[] = { - "name", - "card_name", - "card_properties", - "sink_name", - "sink_properties", - "source_name", - "source_properties", - "address", - "profile", - "rate", - "channels", - "path", - "auto_connect", - "sco_sink", - "sco_source", - NULL -}; - -struct a2dp_info { - sbc_t sbc; /* Codec data */ - bool sbc_initialized; /* Keep track if the encoder is initialized */ - 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 */ - - uint16_t seq_num; /* Cumulative packet sequence */ - uint8_t min_bitpool; - uint8_t max_bitpool; -}; - -struct hsp_info { - pa_sink *sco_sink; - void (*sco_sink_set_volume)(pa_sink *s); - pa_source *sco_source; - void (*sco_source_set_volume)(pa_source *s); -}; - -struct bluetooth_msg { - pa_msgobject parent; - pa_card *card; -}; - -typedef struct bluetooth_msg bluetooth_msg; -PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject); -#define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o)) - -struct userdata { - pa_core *core; - pa_module *module; - - pa_bluetooth_device *device; - pa_hook_slot *uuid_added_slot; - char *address; - char *path; - pa_bluetooth_transport *transport; - bool transport_acquired; - pa_hook_slot *discovery_slot; - pa_hook_slot *sink_state_changed_slot; - pa_hook_slot *source_state_changed_slot; - pa_hook_slot *transport_state_changed_slot; - pa_hook_slot *transport_nrec_changed_slot; - pa_hook_slot *transport_microphone_changed_slot; - pa_hook_slot *transport_speaker_changed_slot; - - pa_bluetooth_discovery *discovery; - bool auto_connect; - - char *output_port_name; - char *input_port_name; - - pa_card *card; - pa_sink *sink; - pa_source *source; - - pa_thread_mq thread_mq; - pa_rtpoll *rtpoll; - pa_rtpoll_item *rtpoll_item; - pa_thread *thread; - bluetooth_msg *msg; - - uint64_t read_index, write_index; - pa_usec_t started_at; - pa_smoother *read_smoother; - - pa_memchunk write_memchunk; - - pa_sample_spec sample_spec, requested_sample_spec; - - int stream_fd; - - size_t read_link_mtu; - size_t read_block_size; - - size_t write_link_mtu; - size_t write_block_size; - - struct a2dp_info a2dp; - struct hsp_info hsp; - - enum profile profile; - - pa_modargs *modargs; - - int stream_write_type; -}; - -enum { - BLUETOOTH_MESSAGE_IO_THREAD_FAILED, - BLUETOOTH_MESSAGE_MAX -}; - -#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC) -#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC) -#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC) -#define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC) - -#define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC) - -#define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source)) - -static int init_profile(struct userdata *u); - -/* from IO thread */ -static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) { - struct a2dp_info *a2dp; - - pa_assert(u); - - a2dp = &u->a2dp; - - if (a2dp->sbc.bitpool == bitpool) - return; - - if (bitpool > a2dp->max_bitpool) - bitpool = a2dp->max_bitpool; - else if (bitpool < a2dp->min_bitpool) - bitpool = a2dp->min_bitpool; - - a2dp->sbc.bitpool = bitpool; - - a2dp->codesize = sbc_get_codesize(&a2dp->sbc); - a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); - - pa_log_debug("Bitpool has changed to %u", a2dp->sbc.bitpool); - - u->read_block_size = - (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) - / a2dp->frame_length * a2dp->codesize; - - u->write_block_size = - (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) - / a2dp->frame_length * a2dp->codesize; - - pa_sink_set_max_request_within_thread(u->sink, u->write_block_size); - pa_sink_set_fixed_latency_within_thread(u->sink, - FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec)); -} - -/* from IO thread, except in SCO over PCM */ -static void bt_transport_config_mtu(struct userdata *u) { - /* Calculate block sizes */ - if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) { - u->read_block_size = u->read_link_mtu; - u->write_block_size = u->write_link_mtu; - } else { - u->read_block_size = - (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) - / u->a2dp.frame_length * u->a2dp.codesize; - - u->write_block_size = - (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) - / u->a2dp.frame_length * u->a2dp.codesize; - } - - if (USE_SCO_OVER_PCM(u)) - return; - - if (u->sink) { - pa_sink_set_max_request_within_thread(u->sink, u->write_block_size); - pa_sink_set_fixed_latency_within_thread(u->sink, - (u->profile == PROFILE_A2DP ? - FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) + - pa_bytes_to_usec(u->write_block_size, &u->sample_spec)); - } - - if (u->source) - pa_source_set_fixed_latency_within_thread(u->source, - (u->profile == PROFILE_A2DP_SOURCE ? - FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) + - pa_bytes_to_usec(u->read_block_size, &u->sample_spec)); -} - -/* from IO thread, except in SCO over PCM */ - -static void setup_stream(struct userdata *u) { - struct pollfd *pollfd; - int one; - - pa_log_info("Transport %s resuming", u->transport->path); - - bt_transport_config_mtu(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!"); - - if (u->profile == PROFILE_A2DP) - a2dp_set_bitpool(u, u->a2dp.max_bitpool); - - 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 = 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); -} - -static void teardown_stream(struct userdata *u) { - if (u->rtpoll_item) { - pa_rtpoll_item_free(u->rtpoll_item); - u->rtpoll_item = NULL; - } - - if (u->stream_fd >= 0) { - pa_close(u->stream_fd); - u->stream_fd = -1; - } - - if (u->read_smoother) { - pa_smoother_free(u->read_smoother); - u->read_smoother = NULL; - } - - if (u->write_memchunk.memblock) { - pa_memblock_unref(u->write_memchunk.memblock); - pa_memchunk_reset(&u->write_memchunk); - } - - pa_log_debug("Audio stream torn down"); -} - -static void bt_transport_release(struct userdata *u) { - pa_assert(u->transport); - - /* Ignore if already released */ - if (!u->transport_acquired) - return; - - pa_log_debug("Releasing transport %s", u->transport->path); - - pa_bluetooth_transport_release(u->transport); - - u->transport_acquired = false; - - teardown_stream(u); -} - -static int bt_transport_acquire(struct userdata *u, bool optional) { - pa_assert(u->transport); - - if (u->transport_acquired) - return 0; - - pa_log_debug("Acquiring transport %s", u->transport->path); - - u->stream_fd = pa_bluetooth_transport_acquire(u->transport, optional, &u->read_link_mtu, &u->write_link_mtu); - if (u->stream_fd < 0) { - if (!optional) - pa_log("Failed to acquire transport %s", u->transport->path); - else - pa_log_info("Failed optional acquire of transport %s", u->transport->path); - - return -1; - } - - u->transport_acquired = true; - pa_log_info("Transport %s acquired: fd %d", u->transport->path, u->stream_fd); - - return 0; -} - -/* 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; - bool failed = false; - int r; - - pa_assert(u->sink == PA_SINK(o)); - pa_assert(u->transport); - - switch (code) { - - case PA_SINK_MESSAGE_SET_STATE: - - switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { - - case PA_SINK_SUSPENDED: - /* Ignore if transition is PA_SINK_INIT->PA_SINK_SUSPENDED */ - if (!PA_SINK_IS_OPENED(u->sink->thread_info.state)) - break; - - /* Stop the device if the source is suspended as well */ - if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) - /* We deliberately ignore whether stopping - * actually worked. Since the stream_fd is - * closed it doesn't really matter */ - bt_transport_release(u); - - break; - - case PA_SINK_IDLE: - case PA_SINK_RUNNING: - if (u->sink->thread_info.state != PA_SINK_SUSPENDED) - break; - - /* Resume the device if the source was suspended as well */ - if (!u->source || !PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { - if (bt_transport_acquire(u, false) < 0) - failed = true; - else - setup_stream(u); - } - break; - - case PA_SINK_UNLINKED: - case PA_SINK_INIT: - case PA_SINK_INVALID_STATE: - ; - } - break; - - case PA_SINK_MESSAGE_GET_LATENCY: { - - 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->write_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->thread_info.fixed_latency; - return 0; - } - } - - r = pa_sink_process_msg(o, code, data, offset, chunk); - - 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; - bool failed = false; - int r; - - pa_assert(u->source == PA_SOURCE(o)); - pa_assert(u->transport); - - switch (code) { - - case PA_SOURCE_MESSAGE_SET_STATE: - - switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { - - case PA_SOURCE_SUSPENDED: - /* Ignore if transition is PA_SOURCE_INIT->PA_SOURCE_SUSPENDED */ - if (!PA_SOURCE_IS_OPENED(u->source->thread_info.state)) - break; - - /* Stop the device if the sink is suspended as well */ - if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) - bt_transport_release(u); - - if (u->read_smoother) - pa_smoother_pause(u->read_smoother, pa_rtclock_now()); - break; - - case PA_SOURCE_IDLE: - case PA_SOURCE_RUNNING: - if (u->source->thread_info.state != PA_SOURCE_SUSPENDED) - break; - - /* Resume the device if the sink was suspended as well */ - if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - if (bt_transport_acquire(u, false) < 0) - failed = true; - else - setup_stream(u); - } - /* We don't resume the smoother here. Instead we - * wait until the first packet arrives */ - break; - - case PA_SOURCE_UNLINKED: - case PA_SOURCE_INIT: - case PA_SOURCE_INVALID_STATE: - ; - } - break; - - case PA_SOURCE_MESSAGE_GET_LATENCY: { - pa_usec_t wi, ri; - - if (u->read_smoother) { - 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->thread_info.fixed_latency; - } else - *((pa_usec_t*) data) = 0; - - return 0; - } - - } - - r = pa_source_process_msg(o, code, data, offset, chunk); - - return (r < 0 || !failed) ? r : -1; -} - -/* Called from main thread context */ -static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct bluetooth_msg *u = BLUETOOTH_MSG(obj); - - switch (code) { - case BLUETOOTH_MESSAGE_IO_THREAD_FAILED: { - if (u->card->module->unload_requested) - break; - - pa_log_debug("Switching the profile to off due to IO thread failure."); - - pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0); - break; - } - } - return 0; -} - -/* Run from IO thread */ -static int hsp_process_render(struct userdata *u) { - int ret = 0; - - pa_assert(u); - pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW); - pa_assert(u->sink); - - /* First, render some data */ - if (!u->write_memchunk.memblock) - pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk); - - pa_assert(u->write_memchunk.length == u->write_block_size); - - for (;;) { - ssize_t l; - const void *p; - - /* 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. */ - - p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk); - 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) - /* Retry right away if we got interrupted */ - continue; - - else if (errno == EAGAIN) - /* Hmm, apparently the socket was not writable, give up for now */ - break; - - pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno)); - ret = -1; - break; - } - - pa_assert((size_t) l <= u->write_memchunk.length); - - 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; - } - - 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; - - pa_assert(u); - pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW); - pa_assert(u->source); - pa_assert(u->read_smoother); - - memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size); - memchunk.index = memchunk.length = 0; - - for (;;) { - ssize_t l; - void *p; - struct msghdr m; - struct cmsghdr *cm; - uint8_t aux[1024]; - struct iovec iov; - bool 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); - 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) - /* Retry right away if we got interrupted */ - continue; - - else if (l < 0 && errno == EAGAIN) - /* Hmm, apparently the socket was not readable, give up for now. */ - break; - - 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)); - - /* In some rare occasions, we might receive packets of a very strange - * size. This could potentially be possible if the SCO packet was - * received partially over-the-air, or more probably due to hardware - * issues in our Bluetooth adapter. In these cases, in order to avoid - * an assertion failure due to unaligned data, just discard the whole - * packet */ - if (!pa_frame_aligned(l, &u->sample_spec)) { - pa_log_warn("SCO packet received of unaligned size: %zu", l); - break; - } - - 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 = l; - break; - } - - pa_memblock_unref(memchunk.memblock); - - return ret; -} - -/* Run from IO thread */ -static void a2dp_prepare_buffer(struct userdata *u) { - size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu); - - pa_assert(u); - - if (u->a2dp.buffer_size >= min_buffer_size) - return; - - u->a2dp.buffer_size = 2 * min_buffer_size; - 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) { - struct a2dp_info *a2dp; - struct rtp_header *header; - struct rtp_payload *payload; - size_t nbytes; - void *d; - const void *p; - size_t to_write, to_encode; - unsigned frame_count; - int ret = 0; - - pa_assert(u); - pa_assert(u->profile == PROFILE_A2DP); - pa_assert(u->sink); - - /* First, render some data */ - if (!u->write_memchunk.memblock) - pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk); - - pa_assert(u->write_memchunk.length == u->write_block_size); - - a2dp_prepare_buffer(u); - - a2dp = &u->a2dp; - header = a2dp->buffer; - payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header)); - - frame_count = 0; - - /* Try to create a packet of the full MTU */ - - p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk); - to_encode = u->write_memchunk.length; - - 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)) { - ssize_t written; - ssize_t encoded; - - encoded = sbc_encode(&a2dp->sbc, - p, to_encode, - d, to_write, - &written); - - if (PA_UNLIKELY(encoded <= 0)) { - pa_log_error("SBC encoding error (%li)", (long) encoded); - pa_memblock_release(u->write_memchunk.memblock); - return -1; - } - -/* 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_fp((size_t) encoded <= to_encode); - pa_assert_fp((size_t) encoded == a2dp->codesize); - - pa_assert_fp((size_t) written <= to_write); - pa_assert_fp((size_t) written == a2dp->frame_length); - - p = (const uint8_t*) p + encoded; - to_encode -= encoded; - - d = (uint8_t*) d + written; - to_write -= written; - - frame_count++; - } - - 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)); - header->v = 2; - header->pt = 1; - header->sequence_number = htons(a2dp->seq_num++); - header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec)); - header->ssrc = htonl(1); - payload->frame_count = frame_count; - - nbytes = (uint8_t*) d - (uint8_t*) a2dp->buffer; - - for (;;) { - ssize_t l; - - l = pa_write(u->stream_fd, a2dp->buffer, nbytes, &u->stream_write_type); - - pa_assert(l != 0); - - if (l < 0) { - - if (errno == EINTR) - /* Retry right away if we got interrupted */ - continue; - - 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 ret; -} - -static int a2dp_process_push(struct userdata *u) { - int ret = 0; - pa_memchunk memchunk; - - pa_assert(u); - pa_assert(u->profile == PROFILE_A2DP_SOURCE); - pa_assert(u->source); - pa_assert(u->read_smoother); - - memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size); - memchunk.index = memchunk.length = 0; - - for (;;) { - bool found_tstamp = false; - pa_usec_t tstamp; - struct a2dp_info *a2dp; - struct rtp_header *header; - struct rtp_payload *payload; - const void *p; - void *d; - ssize_t l; - size_t to_write, to_decode; - - a2dp_prepare_buffer(u); - - a2dp = &u->a2dp; - header = a2dp->buffer; - payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header)); - - l = pa_read(u->stream_fd, a2dp->buffer, a2dp->buffer_size, &u->stream_write_type); - - if (l <= 0) { - - if (l < 0 && errno == EINTR) - /* Retry right away if we got interrupted */ - continue; - - else if (l < 0 && errno == EAGAIN) - /* Hmm, apparently the socket was not readable, give up for now. */ - break; - - pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF"); - ret = -1; - break; - } - - pa_assert((size_t) l <= a2dp->buffer_size); - - u->read_index += (uint64_t) l; - - /* TODO: get timestamp from rtp */ - 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); - - p = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload); - to_decode = l - sizeof(*header) - sizeof(*payload); - - d = pa_memblock_acquire(memchunk.memblock); - to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock); - - while (PA_LIKELY(to_decode > 0)) { - size_t written; - ssize_t decoded; - - decoded = sbc_decode(&a2dp->sbc, - p, to_decode, - d, to_write, - &written); - - if (PA_UNLIKELY(decoded <= 0)) { - pa_log_error("SBC decoding error (%li)", (long) decoded); - pa_memblock_release(memchunk.memblock); - pa_memblock_unref(memchunk.memblock); - return -1; - } - -/* pa_log_debug("SBC: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written); */ -/* pa_log_debug("SBC: frame_length: %lu; codesize: %lu", (unsigned long) a2dp->frame_length, (unsigned long) a2dp->codesize); */ - - /* Reset frame length, it can be changed due to bitpool change */ - a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); - - pa_assert_fp((size_t) decoded <= to_decode); - pa_assert_fp((size_t) decoded == a2dp->frame_length); - - pa_assert_fp((size_t) written == a2dp->codesize); - - p = (const uint8_t*) p + decoded; - to_decode -= decoded; - - d = (uint8_t*) d + written; - to_write -= written; - } - - memchunk.length -= to_write; - - pa_memblock_release(memchunk.memblock); - - pa_source_post(u->source, &memchunk); - - ret = l; - break; - } - - pa_memblock_unref(memchunk.memblock); - - return ret; -} - -static void a2dp_reduce_bitpool(struct userdata *u) { - struct a2dp_info *a2dp; - uint8_t bitpool; - - pa_assert(u); - - a2dp = &u->a2dp; - - /* Check if bitpool is already at its limit */ - if (a2dp->sbc.bitpool <= BITPOOL_DEC_LIMIT) - return; - - bitpool = a2dp->sbc.bitpool - BITPOOL_DEC_STEP; - - if (bitpool < BITPOOL_DEC_LIMIT) - bitpool = BITPOOL_DEC_LIMIT; - - a2dp_set_bitpool(u, bitpool); -} - -static void thread_func(void *userdata) { - struct userdata *u = userdata; - unsigned do_write = 0; - unsigned pending_read_bytes = 0; - bool writable = false; - - pa_assert(u); - pa_assert(u->transport); - - pa_log_debug("IO Thread starting up"); - - if (u->core->realtime_scheduling) - pa_make_realtime(u->core->realtime_priority); - - pa_thread_mq_install(&u->thread_mq); - - /* Setup the stream only if the transport was already acquired */ - if (u->transport_acquired) - setup_stream(u); - - for (;;) { - struct pollfd *pollfd; - int ret; - bool disable_timer = true; - - pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL; - - 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 (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) - n_read = hsp_process_push(u); - else - n_read = a2dp_process_push(u); - - if (n_read < 0) - goto io_fail; - - /* We just read something, so we are supposed to write something, too */ - pending_read_bytes += n_read; - do_write += pending_read_bytes / u->write_block_size; - pending_read_bytes = pending_read_bytes % u->write_block_size; - } - } - - if (u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state)) { - - if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) - pa_sink_process_rewind(u->sink, 0); - - if (pollfd) { - if (pollfd->revents & POLLOUT) - writable = true; - - if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) { - pa_usec_t time_passed; - 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_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; - - skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC; - skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec); - - if (skip_bytes > 0) { - pa_memchunk tmp; - - 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; - - if (u->profile == PROFILE_A2DP) - a2dp_reduce_bitpool(u); - } - } - - do_write = 1; - pending_read_bytes = 0; - } - } - - 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 ((n_written = a2dp_process_render(u)) < 0) - goto io_fail; - } else { - if ((n_written = hsp_process_render(u)) < 0) - goto io_fail; - } - - 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 <= 0) { - pa_usec_t sleep_for; - pa_usec_t time_passed, next_write_at; - - if (writable) { - /* 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_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); */ - } else - /* drop stream every 500 ms */ - sleep_for = PA_USEC_PER_MSEC * 500; - - pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for); - disable_timer = false; - } - } - } - - if (disable_timer) - pa_rtpoll_set_timer_disabled(u->rtpoll); - - /* Hmm, nothing to do. Let's sleep */ - if (pollfd) - pollfd->events = (short) (((u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state) && !writable) ? POLLOUT : 0) | - (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state) ? POLLIN : 0)); - - if ((ret = pa_rtpoll_run(u->rtpoll, true)) < 0) { - pa_log_debug("pa_rtpoll_run failed with: %d", ret); - goto fail; - } - if (ret == 0) { - pa_log_debug("IO thread shutdown requested, stopping cleanly"); - bt_transport_release(u); - goto finish; - } - - pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL; - - if (pollfd && (pollfd->revents & ~(POLLOUT|POLLIN))) { - 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 io_fail; - } - - continue; - -io_fail: - /* In case of HUP, just tear down the streams */ - if (!pollfd || (pollfd->revents & POLLHUP) == 0) - goto fail; - - do_write = 0; - pending_read_bytes = 0; - writable = false; - - teardown_stream(u); - } - -fail: - /* If this was no regular exit from the loop we have to continue processing messages until we receive PA_MESSAGE_SHUTDOWN */ - pa_log_debug("IO thread failed"); - pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_IO_THREAD_FAILED, NULL, 0, NULL, NULL); - pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - -finish: - pa_log_debug("IO thread shutting down"); -} - -static pa_available_t transport_state_to_availability(pa_bluetooth_transport_state_t state) { - if (state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) - return PA_AVAILABLE_NO; - else if (state >= PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) - return PA_AVAILABLE_YES; - else - return PA_AVAILABLE_UNKNOWN; -} - -static pa_direction_t get_profile_direction(enum profile p) { - static const pa_direction_t profile_direction[] = { - [PROFILE_A2DP] = PA_DIRECTION_OUTPUT, - [PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, - [PROFILE_HSP] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, - [PROFILE_HFGW] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, - [PROFILE_OFF] = 0 - }; - - return profile_direction[p]; -} - -/* Run from main thread */ -static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) { - pa_available_t result = PA_AVAILABLE_NO; - unsigned i; - - pa_assert(u); - pa_assert(u->device); - - for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) { - pa_bluetooth_transport *transport; - - if (!(get_profile_direction(i) & direction)) - continue; - - if (!(transport = u->device->transports[i])) - continue; - - switch(transport->state) { - case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED: - continue; - - case PA_BLUETOOTH_TRANSPORT_STATE_IDLE: - if (result == PA_AVAILABLE_NO) - result = PA_AVAILABLE_UNKNOWN; - - break; - - case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING: - return PA_AVAILABLE_YES; - } - } - - return result; -} - -/* Run from main thread */ -static void handle_transport_state_change(struct userdata *u, struct pa_bluetooth_transport *transport) { - bool acquire = false; - bool release = false; - enum profile profile; - pa_card_profile *cp; - pa_bluetooth_transport_state_t state; - pa_device_port *port; - - pa_assert(u); - pa_assert(transport); - - profile = transport->profile; - state = transport->state; - - /* Update profile availability */ - if (!(cp = pa_hashmap_get(u->card->profiles, pa_bt_profile_to_string(profile)))) - return; - - pa_card_profile_set_available(cp, transport_state_to_availability(state)); - - /* Update port availability */ - pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name)); - pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_OUTPUT)); - - pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name)); - pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_INPUT)); - - /* Acquire or release transport as needed */ - acquire = (state == PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile); - release = (state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile); - - if (acquire) - if (bt_transport_acquire(u, true) >= 0) { - if (u->source) { - pa_log_debug("Resuming source %s, because the bluetooth audio state changed to 'playing'.", u->source->name); - pa_source_suspend(u->source, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER); - } - - if (u->sink) { - pa_log_debug("Resuming sink %s, because the bluetooth audio state changed to 'playing'.", u->sink->name); - pa_sink_suspend(u->sink, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER); - } - } - - if (release && u->transport_acquired) { - /* FIXME: this release is racy, since the audio stream might have - been set up again in the meantime (but not processed yet by PA). - BlueZ should probably release the transport automatically, and - in that case we would just mark the transport as released */ - - /* Remote side closed the stream so we consider it PA_SUSPEND_USER */ - if (u->source) { - pa_log_debug("Suspending source %s, because the remote end closed the stream.", u->source->name); - pa_source_suspend(u->source, true, PA_SUSPEND_USER); - } - - if (u->sink) { - pa_log_debug("Suspending sink %s, because the remote end closed the stream.", u->sink->name); - pa_sink_suspend(u->sink, true, PA_SUSPEND_USER); - } - } -} - -/* Run from main thread */ -static void sink_set_volume_cb(pa_sink *s) { - uint16_t gain; - pa_volume_t volume; - struct userdata *u; - char *k; - - pa_assert(s); - pa_assert(s->core); - - k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s); - u = pa_shared_get(s->core, k); - pa_xfree(k); - - pa_assert(u); - pa_assert(u->sink == s); - pa_assert(u->profile == PROFILE_HSP); - pa_assert(u->transport); - - gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM); - volume = (pa_volume_t) round((double) gain * PA_VOLUME_NORM / HSP_MAX_GAIN); - - pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume); - - pa_bluetooth_transport_set_speaker_gain(u->transport, gain); -} - -/* Run from main thread */ -static void source_set_volume_cb(pa_source *s) { - uint16_t gain; - pa_volume_t volume; - struct userdata *u; - char *k; - - pa_assert(s); - pa_assert(s->core); - - k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s); - u = pa_shared_get(s->core, k); - pa_xfree(k); - - pa_assert(u); - pa_assert(u->source == s); - pa_assert(u->profile == PROFILE_HSP); - pa_assert(u->transport); - - gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM); - volume = (pa_volume_t) round((double) gain * PA_VOLUME_NORM / HSP_MAX_GAIN); - - pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume); - - pa_bluetooth_transport_set_microphone_gain(u->transport, gain); -} - -/* Run from main thread */ -static char *get_name(const char *type, pa_modargs *ma, const char *device_id, bool *namereg_fail) { - char *t; - const char *n; - - pa_assert(type); - pa_assert(ma); - pa_assert(device_id); - pa_assert(namereg_fail); - - t = pa_sprintf_malloc("%s_name", type); - n = pa_modargs_get_value(ma, t, NULL); - pa_xfree(t); - - if (n) { - *namereg_fail = true; - return pa_xstrdup(n); - } - - if ((n = pa_modargs_get_value(ma, "name", NULL))) - *namereg_fail = true; - else { - n = device_id; - *namereg_fail = false; - } - - return pa_sprintf_malloc("bluez_%s.%s", type, n); -} - -static int sco_over_pcm_state_update(struct userdata *u, bool changed) { - pa_assert(u); - pa_assert(USE_SCO_OVER_PCM(u)); - - if (PA_SINK_IS_OPENED(pa_sink_get_state(u->hsp.sco_sink)) || - PA_SOURCE_IS_OPENED(pa_source_get_state(u->hsp.sco_source))) { - - if (u->stream_fd >= 0) - return 0; - - pa_log_debug("Resuming SCO over PCM"); - if (init_profile(u) < 0) { - pa_log("Can't resume SCO over PCM"); - return -1; - } - - if (bt_transport_acquire(u, false) < 0) - return -1; - - setup_stream(u); - - return 0; - } - - if (changed) { - if (u->stream_fd < 0) - return 0; - - pa_log_debug("Closing SCO over PCM"); - - bt_transport_release(u); - } - - return 0; -} - -static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, struct userdata *u) { - pa_assert(c); - pa_sink_assert_ref(s); - pa_assert(u); - - if (!USE_SCO_OVER_PCM(u) || s != u->hsp.sco_sink) - return PA_HOOK_OK; - - sco_over_pcm_state_update(u, true); - - return PA_HOOK_OK; -} - -static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct userdata *u) { - pa_assert(c); - pa_source_assert_ref(s); - pa_assert(u); - - if (!USE_SCO_OVER_PCM(u) || s != u->hsp.sco_source) - return PA_HOOK_OK; - - sco_over_pcm_state_update(u, true); - - return PA_HOOK_OK; -} - -static pa_hook_result_t transport_nrec_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) { - pa_proplist *p; - - pa_assert(t); - pa_assert(u); - - if (t != u->transport) - return PA_HOOK_OK; - - p = pa_proplist_new(); - pa_proplist_sets(p, "bluetooth.nrec", t->nrec ? "1" : "0"); - pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, p); - pa_proplist_free(p); - - return PA_HOOK_OK; -} - -static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, - struct userdata *u) { - pa_cvolume v; - - pa_assert(t); - pa_assert(u); - - if (t != u->transport) - return PA_HOOK_OK; - - pa_assert(u->source); - - pa_cvolume_set(&v, u->sample_spec.channels, - (pa_volume_t) round((double) t->microphone_gain * PA_VOLUME_NORM / HSP_MAX_GAIN)); - pa_source_volume_changed(u->source, &v); - - return PA_HOOK_OK; -} - -static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, - struct userdata *u) { - pa_cvolume v; - - pa_assert(t); - pa_assert(u); - - if (t != u->transport) - return PA_HOOK_OK; - - pa_assert(u->sink); - - pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) round((double) t->speaker_gain * PA_VOLUME_NORM / HSP_MAX_GAIN)); - pa_sink_volume_changed(u->sink, &v); - - return PA_HOOK_OK; -} - -static void connect_ports(struct userdata *u, void *sink_or_source_new_data, pa_direction_t direction) { - pa_device_port *port; - - if (direction == PA_DIRECTION_OUTPUT) { - pa_sink_new_data *sink_new_data = sink_or_source_new_data; - - pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name)); - pa_assert_se(pa_hashmap_put(sink_new_data->ports, port->name, port) >= 0); - pa_device_port_ref(port); - } else { - pa_source_new_data *source_new_data = sink_or_source_new_data; - - pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name)); - pa_assert_se(pa_hashmap_put(source_new_data->ports, port->name, port) >= 0); - pa_device_port_ref(port); - } -} - -static int sink_set_port_cb(pa_sink *s, pa_device_port *p) { - return 0; -} - -static int source_set_port_cb(pa_source *s, pa_device_port *p) { - return 0; -} - -/* Run from main thread */ -static int add_sink(struct userdata *u) { - char *k; - - pa_assert(u->transport); - - if (USE_SCO_OVER_PCM(u)) { - pa_proplist *p; - - u->sink = u->hsp.sco_sink; - p = pa_proplist_new(); - pa_proplist_sets(p, "bluetooth.protocol", pa_bt_profile_to_string(u->profile)); - pa_proplist_update(u->sink->proplist, PA_UPDATE_MERGE, p); - pa_proplist_free(p); - } else { - pa_sink_new_data data; - bool b; - - pa_sink_new_data_init(&data); - data.driver = __FILE__; - data.module = u->module; - pa_sink_new_data_set_sample_spec(&data, &u->sample_spec); - pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile)); - 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->address, &b); - data.namereg_fail = b; - - 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; - } - connect_ports(u, &data, PA_DIRECTION_OUTPUT); - - if (!u->transport_acquired) - switch (u->profile) { - case PROFILE_A2DP: - case PROFILE_HSP: - pa_assert_not_reached(); /* Profile switch should have failed */ - break; - case PROFILE_HFGW: - data.suspend_cause = PA_SUSPEND_USER; - break; - case PROFILE_A2DP_SOURCE: - case PROFILE_OFF: - pa_assert_not_reached(); - } - - u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); - pa_sink_new_data_done(&data); - - if (!u->sink) { - pa_log_error("Failed to create sink"); - return -1; - } - - u->sink->userdata = u; - u->sink->parent.process_msg = sink_process_msg; - u->sink->set_port = sink_set_port_cb; - } - - if (u->profile == PROFILE_HSP) { - pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); - u->sink->n_volume_steps = 16; - - k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink); - pa_shared_set(u->core, k, u); - pa_xfree(k); - } - - return 0; -} - -/* Run from main thread */ -static int add_source(struct userdata *u) { - char *k; - - pa_assert(u->transport); - - if (USE_SCO_OVER_PCM(u)) { - u->source = u->hsp.sco_source; - pa_proplist_sets(u->source->proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile)); - } else { - pa_source_new_data data; - bool b; - - pa_source_new_data_init(&data); - 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", pa_bt_profile_to_string(u->profile)); - 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->address, &b); - data.namereg_fail = b; - - 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; - } - - connect_ports(u, &data, PA_DIRECTION_INPUT); - - if (!u->transport_acquired) - switch (u->profile) { - case PROFILE_HSP: - pa_assert_not_reached(); /* Profile switch should have failed */ - break; - case PROFILE_A2DP_SOURCE: - case PROFILE_HFGW: - data.suspend_cause = PA_SUSPEND_USER; - break; - case PROFILE_A2DP: - case PROFILE_OFF: - pa_assert_not_reached(); - } - - u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); - pa_source_new_data_done(&data); - - if (!u->source) { - pa_log_error("Failed to create source"); - return -1; - } - - u->source->userdata = u; - u->source->parent.process_msg = source_process_msg; - u->source->set_port = source_set_port_cb; - } - - if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) { - pa_bluetooth_transport *t = u->transport; - pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0"); - } - - if (u->profile == PROFILE_HSP) { - pa_source_set_set_volume_callback(u->source, source_set_volume_cb); - u->source->n_volume_steps = 16; - - k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source); - pa_shared_set(u->core, k, u); - pa_xfree(k); - } - - return 0; -} - -static void bt_transport_config_a2dp(struct userdata *u) { - const pa_bluetooth_transport *t; - struct a2dp_info *a2dp = &u->a2dp; - a2dp_sbc_t *config; - - t = u->transport; - pa_assert(t); - - config = (a2dp_sbc_t *) t->config; - - u->sample_spec.format = PA_SAMPLE_S16LE; - - if (a2dp->sbc_initialized) - sbc_reinit(&a2dp->sbc, 0); - else - sbc_init(&a2dp->sbc, 0); - a2dp->sbc_initialized = true; - - switch (config->frequency) { - case SBC_SAMPLING_FREQ_16000: - a2dp->sbc.frequency = SBC_FREQ_16000; - u->sample_spec.rate = 16000U; - break; - case SBC_SAMPLING_FREQ_32000: - a2dp->sbc.frequency = SBC_FREQ_32000; - u->sample_spec.rate = 32000U; - break; - case SBC_SAMPLING_FREQ_44100: - a2dp->sbc.frequency = SBC_FREQ_44100; - u->sample_spec.rate = 44100U; - break; - case SBC_SAMPLING_FREQ_48000: - a2dp->sbc.frequency = SBC_FREQ_48000; - u->sample_spec.rate = 48000U; - break; - default: - pa_assert_not_reached(); - } - - switch (config->channel_mode) { - case SBC_CHANNEL_MODE_MONO: - a2dp->sbc.mode = SBC_MODE_MONO; - u->sample_spec.channels = 1; - break; - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; - u->sample_spec.channels = 2; - break; - case SBC_CHANNEL_MODE_STEREO: - a2dp->sbc.mode = SBC_MODE_STEREO; - u->sample_spec.channels = 2; - break; - case SBC_CHANNEL_MODE_JOINT_STEREO: - a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; - u->sample_spec.channels = 2; - break; - default: - pa_assert_not_reached(); - } - - switch (config->allocation_method) { - case SBC_ALLOCATION_SNR: - a2dp->sbc.allocation = SBC_AM_SNR; - break; - case SBC_ALLOCATION_LOUDNESS: - a2dp->sbc.allocation = SBC_AM_LOUDNESS; - break; - default: - pa_assert_not_reached(); - } - - switch (config->subbands) { - case SBC_SUBBANDS_4: - a2dp->sbc.subbands = SBC_SB_4; - break; - case SBC_SUBBANDS_8: - a2dp->sbc.subbands = SBC_SB_8; - break; - default: - pa_assert_not_reached(); - } - - switch (config->block_length) { - case SBC_BLOCK_LENGTH_4: - a2dp->sbc.blocks = SBC_BLK_4; - break; - case SBC_BLOCK_LENGTH_8: - a2dp->sbc.blocks = SBC_BLK_8; - break; - case SBC_BLOCK_LENGTH_12: - a2dp->sbc.blocks = SBC_BLK_12; - break; - case SBC_BLOCK_LENGTH_16: - a2dp->sbc.blocks = SBC_BLK_16; - break; - default: - pa_assert_not_reached(); - } - - a2dp->min_bitpool = config->min_bitpool; - a2dp->max_bitpool = config->max_bitpool; - - /* Set minimum bitpool for source to get the maximum possible block_size */ - a2dp->sbc.bitpool = u->profile == PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool; - a2dp->codesize = sbc_get_codesize(&a2dp->sbc); - a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); - - pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", - a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, a2dp->sbc.bitpool); -} - -static void bt_transport_config(struct userdata *u) { - if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) { - u->sample_spec.format = PA_SAMPLE_S16LE; - u->sample_spec.channels = 1; - u->sample_spec.rate = 8000; - } else - bt_transport_config_a2dp(u); -} - -/* Run from main thread */ -static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) { - pa_assert(t); - pa_assert(u); - - if (t == u->transport && t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) - pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0); - - if (t->device == u->device) - handle_transport_state_change(u, t); - - return PA_HOOK_OK; -} - -/* Run from main thread */ -static int setup_transport(struct userdata *u) { - pa_bluetooth_transport *t; - - pa_assert(u); - pa_assert(!u->transport); - pa_assert(u->profile != PROFILE_OFF); - - /* check if profile has a transport */ - t = u->device->transports[u->profile]; - if (!t || t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) { - pa_log_warn("Profile has no transport"); - return -1; - } - - u->transport = t; - - if (u->profile == PROFILE_A2DP_SOURCE || u->profile == PROFILE_HFGW) - bt_transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */ - else if (bt_transport_acquire(u, false) < 0) - return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */ - - bt_transport_config(u); - - return 0; -} - -/* Run from main thread */ -static int init_profile(struct userdata *u) { - int r = 0; - pa_assert(u); - pa_assert(u->profile != PROFILE_OFF); - - if (setup_transport(u) < 0) - return -1; - - pa_assert(u->transport); - - if (u->profile == PROFILE_A2DP || - u->profile == PROFILE_HSP || - u->profile == PROFILE_HFGW) - if (add_sink(u) < 0) - r = -1; - - if (u->profile == PROFILE_HSP || - u->profile == PROFILE_A2DP_SOURCE || - u->profile == PROFILE_HFGW) - if (add_source(u) < 0) - r = -1; - - return r; -} - -/* Run from main thread */ -static void stop_thread(struct userdata *u) { - char *k; - - pa_assert(u); - - if (u->sink && !USE_SCO_OVER_PCM(u)) - pa_sink_unlink(u->sink); - - if (u->source && !USE_SCO_OVER_PCM(u)) - pa_source_unlink(u->source); - - if (u->thread) { - pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); - pa_thread_free(u->thread); - u->thread = NULL; - } - - if (u->rtpoll_item) { - pa_rtpoll_item_free(u->rtpoll_item); - u->rtpoll_item = NULL; - } - - if (u->rtpoll) { - pa_thread_mq_done(&u->thread_mq); - - pa_rtpoll_free(u->rtpoll); - u->rtpoll = NULL; - } - - if (u->transport) { - bt_transport_release(u); - u->transport = NULL; - } - - if (u->sink) { - if (u->profile == PROFILE_HSP) { - k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink); - pa_shared_remove(u->core, k); - pa_xfree(k); - } - - pa_sink_unref(u->sink); - u->sink = NULL; - } - - if (u->source) { - if (u->profile == PROFILE_HSP) { - k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source); - pa_shared_remove(u->core, k); - pa_xfree(k); - } - - pa_source_unref(u->source); - u->source = 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); - pa_assert(!u->rtpoll); - pa_assert(!u->rtpoll_item); - - u->rtpoll = pa_rtpoll_new(); - pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll); - - if (USE_SCO_OVER_PCM(u)) { - if (sco_over_pcm_state_update(u, false) < 0) { - char *k; - - if (u->sink) { - k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink); - pa_shared_remove(u->core, k); - pa_xfree(k); - u->sink = NULL; - } - if (u->source) { - k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source); - pa_shared_remove(u->core, k); - pa_xfree(k); - u->source = NULL; - } - return -1; - } - - pa_sink_ref(u->sink); - pa_source_ref(u->source); - /* FIXME: monitor stream_fd error */ - return 0; - } - - if (!(u->thread = pa_thread_new("bluetooth", thread_func, u))) { - pa_log_error("Failed to create IO thread"); - return -1; - } - - if (u->sink) { - 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; -} - -static void save_sco_volume_callbacks(struct userdata *u) { - pa_assert(u); - pa_assert(USE_SCO_OVER_PCM(u)); - - u->hsp.sco_sink_set_volume = u->hsp.sco_sink->set_volume; - u->hsp.sco_source_set_volume = u->hsp.sco_source->set_volume; -} - -static void restore_sco_volume_callbacks(struct userdata *u) { - pa_assert(u); - pa_assert(USE_SCO_OVER_PCM(u)); - - pa_sink_set_set_volume_callback(u->hsp.sco_sink, u->hsp.sco_sink_set_volume); - pa_source_set_set_volume_callback(u->hsp.sco_source, u->hsp.sco_source_set_volume); -} - -/* Run from main thread */ -static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { - struct userdata *u; - enum profile *d; - - pa_assert(c); - pa_assert(new_profile); - pa_assert_se(u = c->userdata); - - d = PA_CARD_PROFILE_DATA(new_profile); - - if (*d != PROFILE_OFF) { - const pa_bluetooth_device *device = u->device; - - if (!device->transports[*d] || device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) { - pa_log_warn("Profile not connected, refused to switch profile to %s", new_profile->name); - return -PA_ERR_IO; - } - } - - stop_thread(u); - - if (USE_SCO_OVER_PCM(u)) - restore_sco_volume_callbacks(u); - - u->profile = *d; - u->sample_spec = u->requested_sample_spec; - - if (USE_SCO_OVER_PCM(u)) - save_sco_volume_callbacks(u); - - if (u->profile != PROFILE_OFF) - if (init_profile(u) < 0) - goto off; - - if (u->sink || u->source) - if (start_thread(u) < 0) - goto off; - - return 0; - -off: - stop_thread(u); - - pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0); - - return -PA_ERR_IO; -} - -/* Run from main thread */ -static void create_card_ports(struct userdata *u, pa_hashmap *ports) { - pa_device_port *port; - pa_device_port_new_data port_data; - - const char *name_prefix = NULL; - const char *input_description = NULL; - const char *output_description = NULL; - - pa_assert(u); - pa_assert(ports); - pa_assert(u->device); - - switch (pa_bluetooth_get_form_factor(u->device->class)) { - case PA_BT_FORM_FACTOR_UNKNOWN: - break; - - case PA_BT_FORM_FACTOR_HEADSET: - name_prefix = "headset"; - input_description = output_description = _("Headset"); - break; - - case PA_BT_FORM_FACTOR_HANDSFREE: - name_prefix = "handsfree"; - input_description = output_description = _("Handsfree"); - break; - - case PA_BT_FORM_FACTOR_MICROPHONE: - name_prefix = "microphone"; - input_description = _("Microphone"); - break; - - case PA_BT_FORM_FACTOR_SPEAKER: - name_prefix = "speaker"; - output_description = _("Speaker"); - break; - - case PA_BT_FORM_FACTOR_HEADPHONE: - name_prefix = "headphone"; - output_description = _("Headphone"); - break; - - case PA_BT_FORM_FACTOR_PORTABLE: - name_prefix = "portable"; - input_description = output_description = _("Portable"); - break; - - case PA_BT_FORM_FACTOR_CAR: - name_prefix = "car"; - input_description = output_description = _("Car"); - break; - - case PA_BT_FORM_FACTOR_HIFI: - name_prefix = "hifi"; - input_description = output_description = _("HiFi"); - break; - - case PA_BT_FORM_FACTOR_PHONE: - name_prefix = "phone"; - input_description = output_description = _("Phone"); - break; - } - - if (!name_prefix) - name_prefix = "unknown"; - - if (!output_description) - output_description = _("Bluetooth Output"); - - if (!input_description) - input_description = _("Bluetooth Input"); - - u->output_port_name = pa_sprintf_malloc("%s-output", name_prefix); - u->input_port_name = pa_sprintf_malloc("%s-input", name_prefix); - - pa_device_port_new_data_init(&port_data); - pa_device_port_new_data_set_name(&port_data, u->output_port_name); - pa_device_port_new_data_set_description(&port_data, output_description); - pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_OUTPUT); - pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_OUTPUT)); - pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0)); - pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0); - pa_device_port_new_data_done(&port_data); - - pa_device_port_new_data_init(&port_data); - pa_device_port_new_data_set_name(&port_data, u->input_port_name); - pa_device_port_new_data_set_description(&port_data, input_description); - pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_INPUT); - pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_INPUT)); - pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0)); - pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0); - pa_device_port_new_data_done(&port_data); -} - -/* Run from main thread */ -static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid, pa_hashmap *ports) { - pa_device_port *input_port, *output_port; - pa_card_profile *p = NULL; - enum profile *d; - - pa_assert(u->input_port_name); - pa_assert(u->output_port_name); - pa_assert_se(input_port = pa_hashmap_get(ports, u->input_port_name)); - pa_assert_se(output_port = pa_hashmap_get(ports, u->output_port_name)); - - if (pa_streq(uuid, A2DP_SINK_UUID)) { - p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(enum profile)); - p->priority = 10; - p->n_sinks = 1; - p->n_sources = 0; - p->max_sink_channels = 2; - p->max_source_channels = 0; - pa_hashmap_put(output_port->profiles, p->name, p); - - d = PA_CARD_PROFILE_DATA(p); - *d = PROFILE_A2DP; - } else if (pa_streq(uuid, A2DP_SOURCE_UUID)) { - p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(enum profile)); - p->priority = 10; - p->n_sinks = 0; - p->n_sources = 1; - p->max_sink_channels = 0; - p->max_source_channels = 2; - pa_hashmap_put(input_port->profiles, p->name, p); - - d = PA_CARD_PROFILE_DATA(p); - *d = PROFILE_A2DP_SOURCE; - } else if (pa_streq(uuid, HSP_HS_UUID) || pa_streq(uuid, HFP_HS_UUID)) { - p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile)); - p->priority = 20; - p->n_sinks = 1; - p->n_sources = 1; - p->max_sink_channels = 1; - p->max_source_channels = 1; - pa_hashmap_put(input_port->profiles, p->name, p); - pa_hashmap_put(output_port->profiles, p->name, p); - - d = PA_CARD_PROFILE_DATA(p); - *d = PROFILE_HSP; - } else if (pa_streq(uuid, HFP_AG_UUID)) { - p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(enum profile)); - p->priority = 20; - p->n_sinks = 1; - p->n_sources = 1; - p->max_sink_channels = 1; - p->max_source_channels = 1; - pa_hashmap_put(input_port->profiles, p->name, p); - pa_hashmap_put(output_port->profiles, p->name, p); - - d = PA_CARD_PROFILE_DATA(p); - *d = PROFILE_HFGW; - } - - if (p) { - pa_bluetooth_transport *t; - - if ((t = u->device->transports[*d])) - p->available = transport_state_to_availability(t->state); - } - - return p; -} - -/* Run from main thread */ -static int add_card(struct userdata *u) { - pa_card_new_data data; - bool b; - pa_card_profile *p; - enum profile *d; - pa_bt_form_factor_t ff; - char *n; - const char *default_profile; - const pa_bluetooth_device *device; - const pa_bluetooth_uuid *uuid; - - pa_assert(u); - pa_assert(u->device); - - device = u->device; - - pa_card_new_data_init(&data); - data.driver = __FILE__; - data.module = u->module; - - n = pa_bluetooth_cleanup_name(device->alias); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, n); - pa_xfree(n); - 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(device->class)) != PA_BT_FORM_FACTOR_UNKNOWN) - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, pa_bt_form_factor_to_string(ff)); - - 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.alias", device->alias); - 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; - } - - create_card_ports(u, data.ports); - - PA_LLIST_FOREACH(uuid, device->uuids) { - p = create_card_profile(u, uuid->uuid, data.ports); - - if (!p) - continue; - - if (pa_hashmap_get(data.profiles, p->name)) { - pa_card_profile_free(p); - continue; - } - - pa_hashmap_put(data.profiles, p->name, p); - } - - pa_assert(!pa_hashmap_isempty(data.profiles)); - - p = pa_card_profile_new("off", _("Off"), sizeof(enum profile)); - p->available = PA_AVAILABLE_YES; - d = PA_CARD_PROFILE_DATA(p); - *d = PROFILE_OFF; - pa_hashmap_put(data.profiles, p->name, p); - - 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 - pa_log_warn("Profile '%s' not valid or not supported by device.", default_profile); - } - - u->card = pa_card_new(u->core, &data); - pa_card_new_data_done(&data); - - if (!u->card) { - pa_log("Failed to allocate card."); - return -1; - } - - u->card->userdata = u; - u->card->set_profile = card_set_profile; - - d = PA_CARD_PROFILE_DATA(u->card->active_profile); - - if (*d != PROFILE_OFF && (!device->transports[*d] || - device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)) { - 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; - - if (USE_SCO_OVER_PCM(u)) - save_sco_volume_callbacks(u); - - return 0; -} - -/* Run from main thread */ -static pa_bluetooth_device* find_device(struct userdata *u, const char *address, const char *path) { - pa_bluetooth_device *d = NULL; - - pa_assert(u); - - if (!address && !path) { - pa_log_error("Failed to get device address/path from module arguments."); - return NULL; - } - - if (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 NULL; - } - - if (address && !(pa_streq(d->address, address))) { - pa_log_error("Passed path %s address %s != %s don't match.", path, d->address, address); - return NULL; - } - - } else { - if (!(d = pa_bluetooth_discovery_get_by_address(u->discovery, address))) { - pa_log_error("%s is not known.", address); - return NULL; - } - } - - if (d) { - u->address = pa_xstrdup(d->address); - u->path = pa_xstrdup(d->path); - } - - return d; -} - -/* Run from main thread */ -static pa_hook_result_t uuid_added_cb(pa_bluetooth_discovery *y, const struct pa_bluetooth_hook_uuid_data *data, - struct userdata *u) { - pa_card_profile *p; - - pa_assert(data); - pa_assert(data->device); - pa_assert(data->uuid); - pa_assert(u); - - if (data->device != u->device) - return PA_HOOK_OK; - - p = create_card_profile(u, data->uuid, u->card->ports); - - if (!p) - return PA_HOOK_OK; - - if (pa_hashmap_get(u->card->profiles, p->name)) { - pa_card_profile_free(p); - return PA_HOOK_OK; - } - - pa_card_add_profile(u->card, p); - - return PA_HOOK_OK; -} - -/* Run from main thread */ -static pa_hook_result_t discovery_hook_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) { - pa_assert(u); - pa_assert(d); - - if (d != u->device) - return PA_HOOK_OK; - - if (d->dead) - pa_log_debug("Device %s removed: unloading module", d->path); - else if (!pa_bluetooth_device_any_audio_connected(d)) - pa_log_debug("Unloading module, because device %s doesn't have any audio profiles connected anymore.", d->path); - else - return PA_HOOK_OK; - - pa_module_unload(u->core, u->module, true); - - return PA_HOOK_OK; -} - -int pa__init(pa_module *m) { - pa_modargs *ma; - uint32_t channels; - struct userdata *u; - const char *address, *path; - pa_bluetooth_device *device; - - pa_assert(m); - - if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log_error("Failed to parse module arguments"); - goto fail; - } - - m->userdata = u = pa_xnew0(struct userdata, 1); - u->module = m; - u->core = m->core; - u->stream_fd = -1; - u->sample_spec = m->core->default_sample_spec; - u->modargs = ma; - - 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"); - goto fail; - } - - if (pa_modargs_get_value(ma, "sco_source", NULL) && - !(u->hsp.sco_source = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_source", NULL), PA_NAMEREG_SOURCE))) { - pa_log("SCO source not found"); - goto fail; - } - - 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) { - pa_log_error("Failed to get rate from module arguments"); - goto fail; - } - - u->auto_connect = true; - if (pa_modargs_get_value_boolean(ma, "auto_connect", &u->auto_connect)) { - pa_log("Failed to parse auto_connect= argument"); - goto fail; - } - - channels = u->sample_spec.channels; - if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || - channels <= 0 || channels > PA_CHANNELS_MAX) { - pa_log_error("Failed to get channels from module arguments"); - goto fail; - } - u->sample_spec.channels = (uint8_t) channels; - u->requested_sample_spec = u->sample_spec; - - address = pa_modargs_get_value(ma, "address", NULL); - path = pa_modargs_get_value(ma, "path", NULL); - - if (!(u->discovery = pa_bluetooth_discovery_get(m->core))) - goto fail; - - if (!(device = find_device(u, address, path))) - goto fail; - - u->device = device; - - u->discovery_slot = - pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED), - PA_HOOK_NORMAL, (pa_hook_cb_t) discovery_hook_cb, u); - - u->uuid_added_slot = - pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED), - PA_HOOK_NORMAL, (pa_hook_cb_t) uuid_added_cb, u); - - u->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); - - u->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); - - u->transport_state_changed_slot = - pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED), - PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u); - - u->transport_nrec_changed_slot = - pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED), - PA_HOOK_NORMAL, (pa_hook_cb_t) transport_nrec_changed_cb, u); - - u->transport_microphone_changed_slot = - pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), - PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u); - - u->transport_speaker_changed_slot = - pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), - PA_HOOK_NORMAL, (pa_hook_cb_t) transport_speaker_gain_changed_cb, u); - - /* Add the card structure. This will also initialize the default profile */ - if (add_card(u) < 0) - goto fail; - - if (!(u->msg = pa_msgobject_new(bluetooth_msg))) - goto fail; - - u->msg->parent.process_msg = device_process_msg; - u->msg->card = u->card; - - if (u->profile != PROFILE_OFF) - if (init_profile(u) < 0) - goto off; - - if (u->sink || u->source) - if (start_thread(u) < 0) - goto off; - - return 0; - -off: - stop_thread(u); - - pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0); - - return 0; - -fail: - - pa__done(m); - - return -1; -} - -int pa__get_n_used(pa_module *m) { - struct userdata *u; - - pa_assert(m); - pa_assert_se(u = m->userdata); - - return - (u->sink ? pa_sink_linked_by(u->sink) : 0) + - (u->source ? pa_source_linked_by(u->source) : 0); -} - -void pa__done(pa_module *m) { - struct userdata *u; - - pa_assert(m); - - if (!(u = m->userdata)) - return; - - stop_thread(u); - - if (u->discovery_slot) - pa_hook_slot_free(u->discovery_slot); - - if (u->uuid_added_slot) - pa_hook_slot_free(u->uuid_added_slot); - - if (u->sink_state_changed_slot) - pa_hook_slot_free(u->sink_state_changed_slot); - - if (u->source_state_changed_slot) - pa_hook_slot_free(u->source_state_changed_slot); - - if (u->transport_state_changed_slot) - pa_hook_slot_free(u->transport_state_changed_slot); - - if (u->transport_nrec_changed_slot) - pa_hook_slot_free(u->transport_nrec_changed_slot); - - if (u->transport_microphone_changed_slot) - pa_hook_slot_free(u->transport_microphone_changed_slot); - - if (u->transport_speaker_changed_slot) - pa_hook_slot_free(u->transport_speaker_changed_slot); - - if (USE_SCO_OVER_PCM(u)) - restore_sco_volume_callbacks(u); - - if (u->msg) - pa_xfree(u->msg); - - if (u->card) - pa_card_free(u->card); - - if (u->a2dp.buffer) - pa_xfree(u->a2dp.buffer); - - sbc_finish(&u->a2dp.sbc); - - if (u->modargs) - pa_modargs_free(u->modargs); - - pa_xfree(u->output_port_name); - pa_xfree(u->input_port_name); - - 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 deleted file mode 100644 index f3d4d55..0000000 --- a/src/modules/bluetooth/module-bluetooth-discover.c +++ /dev/null @@ -1,194 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2008-2013 Jo?o Paulo Rechi Vita - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation; either version 2.1 of the - License, or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdio.h> -#include <stdlib.h> - -#include <pulse/xmalloc.h> -#include <pulsecore/module.h> -#include <pulsecore/core-util.h> -#include <pulsecore/modargs.h> -#include <pulsecore/macro.h> -#include <pulsecore/core-util.h> -#include <pulsecore/dbus-shared.h> - -#include "module-bluetooth-discover-symdef.h" -#include "bluetooth-util.h" - -PA_MODULE_AUTHOR("Jo?o Paulo Rechi Vita"); -PA_MODULE_DESCRIPTION("Detect available BlueZ 4 Bluetooth audio devices and load BlueZ 4 Bluetooth audio drivers"); -PA_MODULE_VERSION(PACKAGE_VERSION); -PA_MODULE_USAGE("sco_sink=<name of sink> " - "sco_source=<name of source> "); -PA_MODULE_LOAD_ONCE(true); - -static const char* const valid_modargs[] = { - "sco_sink", - "sco_source", - "async", /* deprecated */ - NULL -}; - -struct userdata { - pa_module *module; - pa_modargs *modargs; - pa_core *core; - pa_bluetooth_discovery *discovery; - pa_hook_slot *slot; - pa_hashmap *hashmap; -}; - -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); - - mi = pa_hashmap_get(u->hashmap, d->path); - - if (pa_bluetooth_device_any_audio_connected(d)) { - - 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\"", d->address, d->path); - - if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) && - pa_modargs_get_value(u->modargs, "sco_source", NULL)) { - char *tmp; - - tmp = pa_sprintf_malloc("%s sco_sink=\"%s\" sco_source=\"%s\"", args, - pa_modargs_get_value(u->modargs, "sco_sink", NULL), - pa_modargs_get_value(u->modargs, "sco_source", NULL)); - pa_xfree(args); - args = tmp; - } - - 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) { - 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 (mi) { - - /* Hmm, disconnection? Then the module unloads itself */ - - pa_log_debug("Unregistering module for %s", d->path); - pa_hashmap_remove(u->hashmap, mi->path); - pa_xfree(mi->path); - pa_xfree(mi); - } - } - - return PA_HOOK_OK; -} - -int pa__init(pa_module* m) { - struct userdata *u; - pa_modargs *ma = NULL; - - pa_assert(m); - - if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("Failed to parse module arguments"); - goto fail; - } - - if (pa_modargs_get_value(ma, "async", NULL)) - pa_log_warn("The 'async' argument is deprecated and does nothing."); - - m->userdata = u = pa_xnew0(struct userdata, 1); - u->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 (!(u->discovery = pa_bluetooth_discovery_get(u->core))) - goto fail; - - u->slot = pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED), - PA_HOOK_NORMAL, (pa_hook_cb_t) load_module_for_device, u); - - return 0; - -fail: - pa__done(m); - - if (ma) - pa_modargs_free(ma); - - return -1; -} - -void pa__done(pa_module* m) { - struct userdata *u; - - pa_assert(m); - - if (!(u = m->userdata)) - return; - - if (u->slot) - pa_hook_slot_free(u->slot); - - if (u->discovery) - pa_bluetooth_discovery_unref(u->discovery); - - if (u->hashmap) { - struct module_info *mi; - - while ((mi = pa_hashmap_steal_first(u->hashmap))) { - pa_xfree(mi->path); - pa_xfree(mi); - } - - pa_hashmap_free(u->hashmap, NULL); - } - - if (u->modargs) - pa_modargs_free(u->modargs); - - pa_xfree(u); -} diff --git a/src/modules/bluetooth/module-bluez4-device.c b/src/modules/bluetooth/module-bluez4-device.c new file mode 100644 index 0000000..2d1224f --- /dev/null +++ b/src/modules/bluetooth/module-bluez4-device.c @@ -0,0 +1,2623 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008-2013 Jo?o Paulo Rechi Vita + Copyright 2011-2013 BMW Car IT GmbH. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <errno.h> +#include <math.h> +#include <linux/sockios.h> +#include <arpa/inet.h> + +#include <pulse/rtclock.h> +#include <pulse/sample.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/i18n.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/shared.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/poll.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/time-smoother.h> +#include <pulsecore/namereg.h> + +#include <sbc/sbc.h> + +#include "module-bluez4-device-symdef.h" +#include "a2dp-codecs.h" +#include "rtp.h" +#include "bluez4-util.h" + +#define BITPOOL_DEC_LIMIT 32 +#define BITPOOL_DEC_STEP 5 + +PA_MODULE_AUTHOR("Jo?o Paulo Rechi Vita"); +PA_MODULE_DESCRIPTION("BlueZ 4 Bluetooth audio sink and source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +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|hfgw> " + "rate=<sample rate> " + "channels=<number of channels> " + "path=<device object path> " + "auto_connect=<automatically connect?> " + "sco_sink=<SCO over PCM sink name> " + "sco_source=<SCO over PCM source name>"); + +/* TODO: not close fd when entering suspend mode in a2dp */ + +static const char* const valid_modargs[] = { + "name", + "card_name", + "card_properties", + "sink_name", + "sink_properties", + "source_name", + "source_properties", + "address", + "profile", + "rate", + "channels", + "path", + "auto_connect", + "sco_sink", + "sco_source", + NULL +}; + +struct a2dp_info { + sbc_t sbc; /* Codec data */ + bool sbc_initialized; /* Keep track if the encoder is initialized */ + 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 */ + + uint16_t seq_num; /* Cumulative packet sequence */ + uint8_t min_bitpool; + uint8_t max_bitpool; +}; + +struct hsp_info { + pa_sink *sco_sink; + void (*sco_sink_set_volume)(pa_sink *s); + pa_source *sco_source; + void (*sco_source_set_volume)(pa_source *s); +}; + +struct bluetooth_msg { + pa_msgobject parent; + pa_card *card; +}; + +typedef struct bluetooth_msg bluetooth_msg; +PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject); +#define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o)) + +struct userdata { + pa_core *core; + pa_module *module; + + pa_bluetooth_device *device; + pa_hook_slot *uuid_added_slot; + char *address; + char *path; + pa_bluetooth_transport *transport; + bool transport_acquired; + pa_hook_slot *discovery_slot; + pa_hook_slot *sink_state_changed_slot; + pa_hook_slot *source_state_changed_slot; + pa_hook_slot *transport_state_changed_slot; + pa_hook_slot *transport_nrec_changed_slot; + pa_hook_slot *transport_microphone_changed_slot; + pa_hook_slot *transport_speaker_changed_slot; + + pa_bluetooth_discovery *discovery; + bool auto_connect; + + char *output_port_name; + char *input_port_name; + + pa_card *card; + pa_sink *sink; + pa_source *source; + + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; + pa_thread *thread; + bluetooth_msg *msg; + + uint64_t read_index, write_index; + pa_usec_t started_at; + pa_smoother *read_smoother; + + pa_memchunk write_memchunk; + + pa_sample_spec sample_spec, requested_sample_spec; + + int stream_fd; + + size_t read_link_mtu; + size_t read_block_size; + + size_t write_link_mtu; + size_t write_block_size; + + struct a2dp_info a2dp; + struct hsp_info hsp; + + enum profile profile; + + pa_modargs *modargs; + + int stream_write_type; +}; + +enum { + BLUETOOTH_MESSAGE_IO_THREAD_FAILED, + BLUETOOTH_MESSAGE_MAX +}; + +#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC) +#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC) +#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC) +#define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC) + +#define MAX_PLAYBACK_CATCH_UP_USEC (100*PA_USEC_PER_MSEC) + +#define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source)) + +static int init_profile(struct userdata *u); + +/* from IO thread */ +static void a2dp_set_bitpool(struct userdata *u, uint8_t bitpool) { + struct a2dp_info *a2dp; + + pa_assert(u); + + a2dp = &u->a2dp; + + if (a2dp->sbc.bitpool == bitpool) + return; + + if (bitpool > a2dp->max_bitpool) + bitpool = a2dp->max_bitpool; + else if (bitpool < a2dp->min_bitpool) + bitpool = a2dp->min_bitpool; + + a2dp->sbc.bitpool = bitpool; + + a2dp->codesize = sbc_get_codesize(&a2dp->sbc); + a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); + + pa_log_debug("Bitpool has changed to %u", a2dp->sbc.bitpool); + + u->read_block_size = + (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) + / a2dp->frame_length * a2dp->codesize; + + u->write_block_size = + (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) + / a2dp->frame_length * a2dp->codesize; + + pa_sink_set_max_request_within_thread(u->sink, u->write_block_size); + pa_sink_set_fixed_latency_within_thread(u->sink, + FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec(u->write_block_size, &u->sample_spec)); +} + +/* from IO thread, except in SCO over PCM */ +static void bt_transport_config_mtu(struct userdata *u) { + /* Calculate block sizes */ + if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) { + u->read_block_size = u->read_link_mtu; + u->write_block_size = u->write_link_mtu; + } else { + u->read_block_size = + (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) + / u->a2dp.frame_length * u->a2dp.codesize; + + u->write_block_size = + (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) + / u->a2dp.frame_length * u->a2dp.codesize; + } + + if (USE_SCO_OVER_PCM(u)) + return; + + if (u->sink) { + pa_sink_set_max_request_within_thread(u->sink, u->write_block_size); + pa_sink_set_fixed_latency_within_thread(u->sink, + (u->profile == PROFILE_A2DP ? + FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) + + pa_bytes_to_usec(u->write_block_size, &u->sample_spec)); + } + + if (u->source) + pa_source_set_fixed_latency_within_thread(u->source, + (u->profile == PROFILE_A2DP_SOURCE ? + FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) + + pa_bytes_to_usec(u->read_block_size, &u->sample_spec)); +} + +/* from IO thread, except in SCO over PCM */ + +static void setup_stream(struct userdata *u) { + struct pollfd *pollfd; + int one; + + pa_log_info("Transport %s resuming", u->transport->path); + + bt_transport_config_mtu(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!"); + + if (u->profile == PROFILE_A2DP) + a2dp_set_bitpool(u, u->a2dp.max_bitpool); + + 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 = 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); +} + +static void teardown_stream(struct userdata *u) { + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + if (u->stream_fd >= 0) { + pa_close(u->stream_fd); + u->stream_fd = -1; + } + + if (u->read_smoother) { + pa_smoother_free(u->read_smoother); + u->read_smoother = NULL; + } + + if (u->write_memchunk.memblock) { + pa_memblock_unref(u->write_memchunk.memblock); + pa_memchunk_reset(&u->write_memchunk); + } + + pa_log_debug("Audio stream torn down"); +} + +static void bt_transport_release(struct userdata *u) { + pa_assert(u->transport); + + /* Ignore if already released */ + if (!u->transport_acquired) + return; + + pa_log_debug("Releasing transport %s", u->transport->path); + + pa_bluetooth_transport_release(u->transport); + + u->transport_acquired = false; + + teardown_stream(u); +} + +static int bt_transport_acquire(struct userdata *u, bool optional) { + pa_assert(u->transport); + + if (u->transport_acquired) + return 0; + + pa_log_debug("Acquiring transport %s", u->transport->path); + + u->stream_fd = pa_bluetooth_transport_acquire(u->transport, optional, &u->read_link_mtu, &u->write_link_mtu); + if (u->stream_fd < 0) { + if (!optional) + pa_log("Failed to acquire transport %s", u->transport->path); + else + pa_log_info("Failed optional acquire of transport %s", u->transport->path); + + return -1; + } + + u->transport_acquired = true; + pa_log_info("Transport %s acquired: fd %d", u->transport->path, u->stream_fd); + + return 0; +} + +/* 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; + bool failed = false; + int r; + + pa_assert(u->sink == PA_SINK(o)); + pa_assert(u->transport); + + switch (code) { + + case PA_SINK_MESSAGE_SET_STATE: + + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + /* Ignore if transition is PA_SINK_INIT->PA_SINK_SUSPENDED */ + if (!PA_SINK_IS_OPENED(u->sink->thread_info.state)) + break; + + /* Stop the device if the source is suspended as well */ + if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) + /* We deliberately ignore whether stopping + * actually worked. Since the stream_fd is + * closed it doesn't really matter */ + bt_transport_release(u); + + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + if (u->sink->thread_info.state != PA_SINK_SUSPENDED) + break; + + /* Resume the device if the source was suspended as well */ + if (!u->source || !PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + if (bt_transport_acquire(u, false) < 0) + failed = true; + else + setup_stream(u); + } + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + case PA_SINK_INVALID_STATE: + ; + } + break; + + case PA_SINK_MESSAGE_GET_LATENCY: { + + 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->write_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->thread_info.fixed_latency; + return 0; + } + } + + r = pa_sink_process_msg(o, code, data, offset, chunk); + + 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; + bool failed = false; + int r; + + pa_assert(u->source == PA_SOURCE(o)); + pa_assert(u->transport); + + switch (code) { + + case PA_SOURCE_MESSAGE_SET_STATE: + + switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SOURCE_SUSPENDED: + /* Ignore if transition is PA_SOURCE_INIT->PA_SOURCE_SUSPENDED */ + if (!PA_SOURCE_IS_OPENED(u->source->thread_info.state)) + break; + + /* Stop the device if the sink is suspended as well */ + if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) + bt_transport_release(u); + + if (u->read_smoother) + pa_smoother_pause(u->read_smoother, pa_rtclock_now()); + break; + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: + if (u->source->thread_info.state != PA_SOURCE_SUSPENDED) + break; + + /* Resume the device if the sink was suspended as well */ + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + if (bt_transport_acquire(u, false) < 0) + failed = true; + else + setup_stream(u); + } + /* We don't resume the smoother here. Instead we + * wait until the first packet arrives */ + break; + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + case PA_SOURCE_INVALID_STATE: + ; + } + break; + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t wi, ri; + + if (u->read_smoother) { + 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->thread_info.fixed_latency; + } else + *((pa_usec_t*) data) = 0; + + return 0; + } + + } + + r = pa_source_process_msg(o, code, data, offset, chunk); + + return (r < 0 || !failed) ? r : -1; +} + +/* Called from main thread context */ +static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct bluetooth_msg *u = BLUETOOTH_MSG(obj); + + switch (code) { + case BLUETOOTH_MESSAGE_IO_THREAD_FAILED: { + if (u->card->module->unload_requested) + break; + + pa_log_debug("Switching the profile to off due to IO thread failure."); + + pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0); + break; + } + } + return 0; +} + +/* Run from IO thread */ +static int hsp_process_render(struct userdata *u) { + int ret = 0; + + pa_assert(u); + pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW); + pa_assert(u->sink); + + /* First, render some data */ + if (!u->write_memchunk.memblock) + pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk); + + pa_assert(u->write_memchunk.length == u->write_block_size); + + for (;;) { + ssize_t l; + const void *p; + + /* 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. */ + + p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk); + 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) + /* Retry right away if we got interrupted */ + continue; + + else if (errno == EAGAIN) + /* Hmm, apparently the socket was not writable, give up for now */ + break; + + pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno)); + ret = -1; + break; + } + + pa_assert((size_t) l <= u->write_memchunk.length); + + 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; + } + + 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; + + pa_assert(u); + pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW); + pa_assert(u->source); + pa_assert(u->read_smoother); + + memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size); + memchunk.index = memchunk.length = 0; + + for (;;) { + ssize_t l; + void *p; + struct msghdr m; + struct cmsghdr *cm; + uint8_t aux[1024]; + struct iovec iov; + bool 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); + 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) + /* Retry right away if we got interrupted */ + continue; + + else if (l < 0 && errno == EAGAIN) + /* Hmm, apparently the socket was not readable, give up for now. */ + break; + + 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)); + + /* In some rare occasions, we might receive packets of a very strange + * size. This could potentially be possible if the SCO packet was + * received partially over-the-air, or more probably due to hardware + * issues in our Bluetooth adapter. In these cases, in order to avoid + * an assertion failure due to unaligned data, just discard the whole + * packet */ + if (!pa_frame_aligned(l, &u->sample_spec)) { + pa_log_warn("SCO packet received of unaligned size: %zu", l); + break; + } + + 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 = l; + break; + } + + pa_memblock_unref(memchunk.memblock); + + return ret; +} + +/* Run from IO thread */ +static void a2dp_prepare_buffer(struct userdata *u) { + size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu); + + pa_assert(u); + + if (u->a2dp.buffer_size >= min_buffer_size) + return; + + u->a2dp.buffer_size = 2 * min_buffer_size; + 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) { + struct a2dp_info *a2dp; + struct rtp_header *header; + struct rtp_payload *payload; + size_t nbytes; + void *d; + const void *p; + size_t to_write, to_encode; + unsigned frame_count; + int ret = 0; + + pa_assert(u); + pa_assert(u->profile == PROFILE_A2DP); + pa_assert(u->sink); + + /* First, render some data */ + if (!u->write_memchunk.memblock) + pa_sink_render_full(u->sink, u->write_block_size, &u->write_memchunk); + + pa_assert(u->write_memchunk.length == u->write_block_size); + + a2dp_prepare_buffer(u); + + a2dp = &u->a2dp; + header = a2dp->buffer; + payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header)); + + frame_count = 0; + + /* Try to create a packet of the full MTU */ + + p = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk); + to_encode = u->write_memchunk.length; + + 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)) { + ssize_t written; + ssize_t encoded; + + encoded = sbc_encode(&a2dp->sbc, + p, to_encode, + d, to_write, + &written); + + if (PA_UNLIKELY(encoded <= 0)) { + pa_log_error("SBC encoding error (%li)", (long) encoded); + pa_memblock_release(u->write_memchunk.memblock); + return -1; + } + +/* 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_fp((size_t) encoded <= to_encode); + pa_assert_fp((size_t) encoded == a2dp->codesize); + + pa_assert_fp((size_t) written <= to_write); + pa_assert_fp((size_t) written == a2dp->frame_length); + + p = (const uint8_t*) p + encoded; + to_encode -= encoded; + + d = (uint8_t*) d + written; + to_write -= written; + + frame_count++; + } + + 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)); + header->v = 2; + header->pt = 1; + header->sequence_number = htons(a2dp->seq_num++); + header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec)); + header->ssrc = htonl(1); + payload->frame_count = frame_count; + + nbytes = (uint8_t*) d - (uint8_t*) a2dp->buffer; + + for (;;) { + ssize_t l; + + l = pa_write(u->stream_fd, a2dp->buffer, nbytes, &u->stream_write_type); + + pa_assert(l != 0); + + if (l < 0) { + + if (errno == EINTR) + /* Retry right away if we got interrupted */ + continue; + + 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 ret; +} + +static int a2dp_process_push(struct userdata *u) { + int ret = 0; + pa_memchunk memchunk; + + pa_assert(u); + pa_assert(u->profile == PROFILE_A2DP_SOURCE); + pa_assert(u->source); + pa_assert(u->read_smoother); + + memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size); + memchunk.index = memchunk.length = 0; + + for (;;) { + bool found_tstamp = false; + pa_usec_t tstamp; + struct a2dp_info *a2dp; + struct rtp_header *header; + struct rtp_payload *payload; + const void *p; + void *d; + ssize_t l; + size_t to_write, to_decode; + + a2dp_prepare_buffer(u); + + a2dp = &u->a2dp; + header = a2dp->buffer; + payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header)); + + l = pa_read(u->stream_fd, a2dp->buffer, a2dp->buffer_size, &u->stream_write_type); + + if (l <= 0) { + + if (l < 0 && errno == EINTR) + /* Retry right away if we got interrupted */ + continue; + + else if (l < 0 && errno == EAGAIN) + /* Hmm, apparently the socket was not readable, give up for now. */ + break; + + pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF"); + ret = -1; + break; + } + + pa_assert((size_t) l <= a2dp->buffer_size); + + u->read_index += (uint64_t) l; + + /* TODO: get timestamp from rtp */ + 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); + + p = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload); + to_decode = l - sizeof(*header) - sizeof(*payload); + + d = pa_memblock_acquire(memchunk.memblock); + to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock); + + while (PA_LIKELY(to_decode > 0)) { + size_t written; + ssize_t decoded; + + decoded = sbc_decode(&a2dp->sbc, + p, to_decode, + d, to_write, + &written); + + if (PA_UNLIKELY(decoded <= 0)) { + pa_log_error("SBC decoding error (%li)", (long) decoded); + pa_memblock_release(memchunk.memblock); + pa_memblock_unref(memchunk.memblock); + return -1; + } + +/* pa_log_debug("SBC: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written); */ +/* pa_log_debug("SBC: frame_length: %lu; codesize: %lu", (unsigned long) a2dp->frame_length, (unsigned long) a2dp->codesize); */ + + /* Reset frame length, it can be changed due to bitpool change */ + a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); + + pa_assert_fp((size_t) decoded <= to_decode); + pa_assert_fp((size_t) decoded == a2dp->frame_length); + + pa_assert_fp((size_t) written == a2dp->codesize); + + p = (const uint8_t*) p + decoded; + to_decode -= decoded; + + d = (uint8_t*) d + written; + to_write -= written; + } + + memchunk.length -= to_write; + + pa_memblock_release(memchunk.memblock); + + pa_source_post(u->source, &memchunk); + + ret = l; + break; + } + + pa_memblock_unref(memchunk.memblock); + + return ret; +} + +static void a2dp_reduce_bitpool(struct userdata *u) { + struct a2dp_info *a2dp; + uint8_t bitpool; + + pa_assert(u); + + a2dp = &u->a2dp; + + /* Check if bitpool is already at its limit */ + if (a2dp->sbc.bitpool <= BITPOOL_DEC_LIMIT) + return; + + bitpool = a2dp->sbc.bitpool - BITPOOL_DEC_STEP; + + if (bitpool < BITPOOL_DEC_LIMIT) + bitpool = BITPOOL_DEC_LIMIT; + + a2dp_set_bitpool(u, bitpool); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned do_write = 0; + unsigned pending_read_bytes = 0; + bool writable = false; + + pa_assert(u); + pa_assert(u->transport); + + pa_log_debug("IO Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + + /* Setup the stream only if the transport was already acquired */ + if (u->transport_acquired) + setup_stream(u); + + for (;;) { + struct pollfd *pollfd; + int ret; + bool disable_timer = true; + + pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL; + + 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 (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) + n_read = hsp_process_push(u); + else + n_read = a2dp_process_push(u); + + if (n_read < 0) + goto io_fail; + + /* We just read something, so we are supposed to write something, too */ + pending_read_bytes += n_read; + do_write += pending_read_bytes / u->write_block_size; + pending_read_bytes = pending_read_bytes % u->write_block_size; + } + } + + if (u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state)) { + + if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) + pa_sink_process_rewind(u->sink, 0); + + if (pollfd) { + if (pollfd->revents & POLLOUT) + writable = true; + + if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) { + pa_usec_t time_passed; + 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_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; + + skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC; + skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec); + + if (skip_bytes > 0) { + pa_memchunk tmp; + + 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; + + if (u->profile == PROFILE_A2DP) + a2dp_reduce_bitpool(u); + } + } + + do_write = 1; + pending_read_bytes = 0; + } + } + + 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 ((n_written = a2dp_process_render(u)) < 0) + goto io_fail; + } else { + if ((n_written = hsp_process_render(u)) < 0) + goto io_fail; + } + + 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 <= 0) { + pa_usec_t sleep_for; + pa_usec_t time_passed, next_write_at; + + if (writable) { + /* 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_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); */ + } else + /* drop stream every 500 ms */ + sleep_for = PA_USEC_PER_MSEC * 500; + + pa_rtpoll_set_timer_relative(u->rtpoll, sleep_for); + disable_timer = false; + } + } + } + + if (disable_timer) + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Hmm, nothing to do. Let's sleep */ + if (pollfd) + pollfd->events = (short) (((u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state) && !writable) ? POLLOUT : 0) | + (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state) ? POLLIN : 0)); + + if ((ret = pa_rtpoll_run(u->rtpoll, true)) < 0) { + pa_log_debug("pa_rtpoll_run failed with: %d", ret); + goto fail; + } + if (ret == 0) { + pa_log_debug("IO thread shutdown requested, stopping cleanly"); + bt_transport_release(u); + goto finish; + } + + pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL; + + if (pollfd && (pollfd->revents & ~(POLLOUT|POLLIN))) { + 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 io_fail; + } + + continue; + +io_fail: + /* In case of HUP, just tear down the streams */ + if (!pollfd || (pollfd->revents & POLLHUP) == 0) + goto fail; + + do_write = 0; + pending_read_bytes = 0; + writable = false; + + teardown_stream(u); + } + +fail: + /* If this was no regular exit from the loop we have to continue processing messages until we receive PA_MESSAGE_SHUTDOWN */ + pa_log_debug("IO thread failed"); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_IO_THREAD_FAILED, NULL, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("IO thread shutting down"); +} + +static pa_available_t transport_state_to_availability(pa_bluetooth_transport_state_t state) { + if (state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) + return PA_AVAILABLE_NO; + else if (state >= PA_BLUETOOTH_TRANSPORT_STATE_PLAYING) + return PA_AVAILABLE_YES; + else + return PA_AVAILABLE_UNKNOWN; +} + +static pa_direction_t get_profile_direction(enum profile p) { + static const pa_direction_t profile_direction[] = { + [PROFILE_A2DP] = PA_DIRECTION_OUTPUT, + [PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT, + [PROFILE_HSP] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, + [PROFILE_HFGW] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT, + [PROFILE_OFF] = 0 + }; + + return profile_direction[p]; +} + +/* Run from main thread */ +static pa_available_t get_port_availability(struct userdata *u, pa_direction_t direction) { + pa_available_t result = PA_AVAILABLE_NO; + unsigned i; + + pa_assert(u); + pa_assert(u->device); + + for (i = 0; i < PA_BLUETOOTH_PROFILE_COUNT; i++) { + pa_bluetooth_transport *transport; + + if (!(get_profile_direction(i) & direction)) + continue; + + if (!(transport = u->device->transports[i])) + continue; + + switch(transport->state) { + case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED: + continue; + + case PA_BLUETOOTH_TRANSPORT_STATE_IDLE: + if (result == PA_AVAILABLE_NO) + result = PA_AVAILABLE_UNKNOWN; + + break; + + case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING: + return PA_AVAILABLE_YES; + } + } + + return result; +} + +/* Run from main thread */ +static void handle_transport_state_change(struct userdata *u, struct pa_bluetooth_transport *transport) { + bool acquire = false; + bool release = false; + enum profile profile; + pa_card_profile *cp; + pa_bluetooth_transport_state_t state; + pa_device_port *port; + + pa_assert(u); + pa_assert(transport); + + profile = transport->profile; + state = transport->state; + + /* Update profile availability */ + if (!(cp = pa_hashmap_get(u->card->profiles, pa_bt_profile_to_string(profile)))) + return; + + pa_card_profile_set_available(cp, transport_state_to_availability(state)); + + /* Update port availability */ + pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name)); + pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_OUTPUT)); + + pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name)); + pa_device_port_set_available(port, get_port_availability(u, PA_DIRECTION_INPUT)); + + /* Acquire or release transport as needed */ + acquire = (state == PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile); + release = (state != PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile == profile); + + if (acquire) + if (bt_transport_acquire(u, true) >= 0) { + if (u->source) { + pa_log_debug("Resuming source %s, because the bluetooth audio state changed to 'playing'.", u->source->name); + pa_source_suspend(u->source, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER); + } + + if (u->sink) { + pa_log_debug("Resuming sink %s, because the bluetooth audio state changed to 'playing'.", u->sink->name); + pa_sink_suspend(u->sink, false, PA_SUSPEND_IDLE|PA_SUSPEND_USER); + } + } + + if (release && u->transport_acquired) { + /* FIXME: this release is racy, since the audio stream might have + been set up again in the meantime (but not processed yet by PA). + BlueZ should probably release the transport automatically, and + in that case we would just mark the transport as released */ + + /* Remote side closed the stream so we consider it PA_SUSPEND_USER */ + if (u->source) { + pa_log_debug("Suspending source %s, because the remote end closed the stream.", u->source->name); + pa_source_suspend(u->source, true, PA_SUSPEND_USER); + } + + if (u->sink) { + pa_log_debug("Suspending sink %s, because the remote end closed the stream.", u->sink->name); + pa_sink_suspend(u->sink, true, PA_SUSPEND_USER); + } + } +} + +/* Run from main thread */ +static void sink_set_volume_cb(pa_sink *s) { + uint16_t gain; + pa_volume_t volume; + struct userdata *u; + char *k; + + pa_assert(s); + pa_assert(s->core); + + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s); + u = pa_shared_get(s->core, k); + pa_xfree(k); + + pa_assert(u); + pa_assert(u->sink == s); + pa_assert(u->profile == PROFILE_HSP); + pa_assert(u->transport); + + gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM); + volume = (pa_volume_t) round((double) gain * PA_VOLUME_NORM / HSP_MAX_GAIN); + + pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume); + + pa_bluetooth_transport_set_speaker_gain(u->transport, gain); +} + +/* Run from main thread */ +static void source_set_volume_cb(pa_source *s) { + uint16_t gain; + pa_volume_t volume; + struct userdata *u; + char *k; + + pa_assert(s); + pa_assert(s->core); + + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) s); + u = pa_shared_get(s->core, k); + pa_xfree(k); + + pa_assert(u); + pa_assert(u->source == s); + pa_assert(u->profile == PROFILE_HSP); + pa_assert(u->transport); + + gain = (dbus_uint16_t) round((double) pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN / PA_VOLUME_NORM); + volume = (pa_volume_t) round((double) gain * PA_VOLUME_NORM / HSP_MAX_GAIN); + + pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume); + + pa_bluetooth_transport_set_microphone_gain(u->transport, gain); +} + +/* Run from main thread */ +static char *get_name(const char *type, pa_modargs *ma, const char *device_id, bool *namereg_fail) { + char *t; + const char *n; + + pa_assert(type); + pa_assert(ma); + pa_assert(device_id); + pa_assert(namereg_fail); + + t = pa_sprintf_malloc("%s_name", type); + n = pa_modargs_get_value(ma, t, NULL); + pa_xfree(t); + + if (n) { + *namereg_fail = true; + return pa_xstrdup(n); + } + + if ((n = pa_modargs_get_value(ma, "name", NULL))) + *namereg_fail = true; + else { + n = device_id; + *namereg_fail = false; + } + + return pa_sprintf_malloc("bluez_%s.%s", type, n); +} + +static int sco_over_pcm_state_update(struct userdata *u, bool changed) { + pa_assert(u); + pa_assert(USE_SCO_OVER_PCM(u)); + + if (PA_SINK_IS_OPENED(pa_sink_get_state(u->hsp.sco_sink)) || + PA_SOURCE_IS_OPENED(pa_source_get_state(u->hsp.sco_source))) { + + if (u->stream_fd >= 0) + return 0; + + pa_log_debug("Resuming SCO over PCM"); + if (init_profile(u) < 0) { + pa_log("Can't resume SCO over PCM"); + return -1; + } + + if (bt_transport_acquire(u, false) < 0) + return -1; + + setup_stream(u); + + return 0; + } + + if (changed) { + if (u->stream_fd < 0) + return 0; + + pa_log_debug("Closing SCO over PCM"); + + bt_transport_release(u); + } + + return 0; +} + +static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, struct userdata *u) { + pa_assert(c); + pa_sink_assert_ref(s); + pa_assert(u); + + if (!USE_SCO_OVER_PCM(u) || s != u->hsp.sco_sink) + return PA_HOOK_OK; + + sco_over_pcm_state_update(u, true); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct userdata *u) { + pa_assert(c); + pa_source_assert_ref(s); + pa_assert(u); + + if (!USE_SCO_OVER_PCM(u) || s != u->hsp.sco_source) + return PA_HOOK_OK; + + sco_over_pcm_state_update(u, true); + + return PA_HOOK_OK; +} + +static pa_hook_result_t transport_nrec_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) { + pa_proplist *p; + + pa_assert(t); + pa_assert(u); + + if (t != u->transport) + return PA_HOOK_OK; + + p = pa_proplist_new(); + pa_proplist_sets(p, "bluetooth.nrec", t->nrec ? "1" : "0"); + pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, p); + pa_proplist_free(p); + + return PA_HOOK_OK; +} + +static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, + struct userdata *u) { + pa_cvolume v; + + pa_assert(t); + pa_assert(u); + + if (t != u->transport) + return PA_HOOK_OK; + + pa_assert(u->source); + + pa_cvolume_set(&v, u->sample_spec.channels, + (pa_volume_t) round((double) t->microphone_gain * PA_VOLUME_NORM / HSP_MAX_GAIN)); + pa_source_volume_changed(u->source, &v); + + return PA_HOOK_OK; +} + +static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, + struct userdata *u) { + pa_cvolume v; + + pa_assert(t); + pa_assert(u); + + if (t != u->transport) + return PA_HOOK_OK; + + pa_assert(u->sink); + + pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) round((double) t->speaker_gain * PA_VOLUME_NORM / HSP_MAX_GAIN)); + pa_sink_volume_changed(u->sink, &v); + + return PA_HOOK_OK; +} + +static void connect_ports(struct userdata *u, void *sink_or_source_new_data, pa_direction_t direction) { + pa_device_port *port; + + if (direction == PA_DIRECTION_OUTPUT) { + pa_sink_new_data *sink_new_data = sink_or_source_new_data; + + pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name)); + pa_assert_se(pa_hashmap_put(sink_new_data->ports, port->name, port) >= 0); + pa_device_port_ref(port); + } else { + pa_source_new_data *source_new_data = sink_or_source_new_data; + + pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name)); + pa_assert_se(pa_hashmap_put(source_new_data->ports, port->name, port) >= 0); + pa_device_port_ref(port); + } +} + +static int sink_set_port_cb(pa_sink *s, pa_device_port *p) { + return 0; +} + +static int source_set_port_cb(pa_source *s, pa_device_port *p) { + return 0; +} + +/* Run from main thread */ +static int add_sink(struct userdata *u) { + char *k; + + pa_assert(u->transport); + + if (USE_SCO_OVER_PCM(u)) { + pa_proplist *p; + + u->sink = u->hsp.sco_sink; + p = pa_proplist_new(); + pa_proplist_sets(p, "bluetooth.protocol", pa_bt_profile_to_string(u->profile)); + pa_proplist_update(u->sink->proplist, PA_UPDATE_MERGE, p); + pa_proplist_free(p); + } else { + pa_sink_new_data data; + bool b; + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = u->module; + pa_sink_new_data_set_sample_spec(&data, &u->sample_spec); + pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile)); + 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->address, &b); + data.namereg_fail = b; + + 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; + } + connect_ports(u, &data, PA_DIRECTION_OUTPUT); + + if (!u->transport_acquired) + switch (u->profile) { + case PROFILE_A2DP: + case PROFILE_HSP: + pa_assert_not_reached(); /* Profile switch should have failed */ + break; + case PROFILE_HFGW: + data.suspend_cause = PA_SUSPEND_USER; + break; + case PROFILE_A2DP_SOURCE: + case PROFILE_OFF: + pa_assert_not_reached(); + } + + u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log_error("Failed to create sink"); + return -1; + } + + u->sink->userdata = u; + u->sink->parent.process_msg = sink_process_msg; + u->sink->set_port = sink_set_port_cb; + } + + if (u->profile == PROFILE_HSP) { + pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb); + u->sink->n_volume_steps = 16; + + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink); + pa_shared_set(u->core, k, u); + pa_xfree(k); + } + + return 0; +} + +/* Run from main thread */ +static int add_source(struct userdata *u) { + char *k; + + pa_assert(u->transport); + + if (USE_SCO_OVER_PCM(u)) { + u->source = u->hsp.sco_source; + pa_proplist_sets(u->source->proplist, "bluetooth.protocol", pa_bt_profile_to_string(u->profile)); + } else { + pa_source_new_data data; + bool b; + + pa_source_new_data_init(&data); + 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", pa_bt_profile_to_string(u->profile)); + 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->address, &b); + data.namereg_fail = b; + + 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; + } + + connect_ports(u, &data, PA_DIRECTION_INPUT); + + if (!u->transport_acquired) + switch (u->profile) { + case PROFILE_HSP: + pa_assert_not_reached(); /* Profile switch should have failed */ + break; + case PROFILE_A2DP_SOURCE: + case PROFILE_HFGW: + data.suspend_cause = PA_SUSPEND_USER; + break; + case PROFILE_A2DP: + case PROFILE_OFF: + pa_assert_not_reached(); + } + + u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log_error("Failed to create source"); + return -1; + } + + u->source->userdata = u; + u->source->parent.process_msg = source_process_msg; + u->source->set_port = source_set_port_cb; + } + + if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) { + pa_bluetooth_transport *t = u->transport; + pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0"); + } + + if (u->profile == PROFILE_HSP) { + pa_source_set_set_volume_callback(u->source, source_set_volume_cb); + u->source->n_volume_steps = 16; + + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source); + pa_shared_set(u->core, k, u); + pa_xfree(k); + } + + return 0; +} + +static void bt_transport_config_a2dp(struct userdata *u) { + const pa_bluetooth_transport *t; + struct a2dp_info *a2dp = &u->a2dp; + a2dp_sbc_t *config; + + t = u->transport; + pa_assert(t); + + config = (a2dp_sbc_t *) t->config; + + u->sample_spec.format = PA_SAMPLE_S16LE; + + if (a2dp->sbc_initialized) + sbc_reinit(&a2dp->sbc, 0); + else + sbc_init(&a2dp->sbc, 0); + a2dp->sbc_initialized = true; + + switch (config->frequency) { + case SBC_SAMPLING_FREQ_16000: + a2dp->sbc.frequency = SBC_FREQ_16000; + u->sample_spec.rate = 16000U; + break; + case SBC_SAMPLING_FREQ_32000: + a2dp->sbc.frequency = SBC_FREQ_32000; + u->sample_spec.rate = 32000U; + break; + case SBC_SAMPLING_FREQ_44100: + a2dp->sbc.frequency = SBC_FREQ_44100; + u->sample_spec.rate = 44100U; + break; + case SBC_SAMPLING_FREQ_48000: + a2dp->sbc.frequency = SBC_FREQ_48000; + u->sample_spec.rate = 48000U; + break; + default: + pa_assert_not_reached(); + } + + switch (config->channel_mode) { + case SBC_CHANNEL_MODE_MONO: + a2dp->sbc.mode = SBC_MODE_MONO; + u->sample_spec.channels = 1; + break; + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; + u->sample_spec.channels = 2; + break; + case SBC_CHANNEL_MODE_STEREO: + a2dp->sbc.mode = SBC_MODE_STEREO; + u->sample_spec.channels = 2; + break; + case SBC_CHANNEL_MODE_JOINT_STEREO: + a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; + u->sample_spec.channels = 2; + break; + default: + pa_assert_not_reached(); + } + + switch (config->allocation_method) { + case SBC_ALLOCATION_SNR: + a2dp->sbc.allocation = SBC_AM_SNR; + break; + case SBC_ALLOCATION_LOUDNESS: + a2dp->sbc.allocation = SBC_AM_LOUDNESS; + break; + default: + pa_assert_not_reached(); + } + + switch (config->subbands) { + case SBC_SUBBANDS_4: + a2dp->sbc.subbands = SBC_SB_4; + break; + case SBC_SUBBANDS_8: + a2dp->sbc.subbands = SBC_SB_8; + break; + default: + pa_assert_not_reached(); + } + + switch (config->block_length) { + case SBC_BLOCK_LENGTH_4: + a2dp->sbc.blocks = SBC_BLK_4; + break; + case SBC_BLOCK_LENGTH_8: + a2dp->sbc.blocks = SBC_BLK_8; + break; + case SBC_BLOCK_LENGTH_12: + a2dp->sbc.blocks = SBC_BLK_12; + break; + case SBC_BLOCK_LENGTH_16: + a2dp->sbc.blocks = SBC_BLK_16; + break; + default: + pa_assert_not_reached(); + } + + a2dp->min_bitpool = config->min_bitpool; + a2dp->max_bitpool = config->max_bitpool; + + /* Set minimum bitpool for source to get the maximum possible block_size */ + a2dp->sbc.bitpool = u->profile == PROFILE_A2DP ? a2dp->max_bitpool : a2dp->min_bitpool; + a2dp->codesize = sbc_get_codesize(&a2dp->sbc); + a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); + + pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", + a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, a2dp->sbc.bitpool); +} + +static void bt_transport_config(struct userdata *u) { + if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) { + u->sample_spec.format = PA_SAMPLE_S16LE; + u->sample_spec.channels = 1; + u->sample_spec.rate = 8000; + } else + bt_transport_config_a2dp(u); +} + +/* Run from main thread */ +static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) { + pa_assert(t); + pa_assert(u); + + if (t == u->transport && t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) + pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0); + + if (t->device == u->device) + handle_transport_state_change(u, t); + + return PA_HOOK_OK; +} + +/* Run from main thread */ +static int setup_transport(struct userdata *u) { + pa_bluetooth_transport *t; + + pa_assert(u); + pa_assert(!u->transport); + pa_assert(u->profile != PROFILE_OFF); + + /* check if profile has a transport */ + t = u->device->transports[u->profile]; + if (!t || t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) { + pa_log_warn("Profile has no transport"); + return -1; + } + + u->transport = t; + + if (u->profile == PROFILE_A2DP_SOURCE || u->profile == PROFILE_HFGW) + bt_transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */ + else if (bt_transport_acquire(u, false) < 0) + return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */ + + bt_transport_config(u); + + return 0; +} + +/* Run from main thread */ +static int init_profile(struct userdata *u) { + int r = 0; + pa_assert(u); + pa_assert(u->profile != PROFILE_OFF); + + if (setup_transport(u) < 0) + return -1; + + pa_assert(u->transport); + + if (u->profile == PROFILE_A2DP || + u->profile == PROFILE_HSP || + u->profile == PROFILE_HFGW) + if (add_sink(u) < 0) + r = -1; + + if (u->profile == PROFILE_HSP || + u->profile == PROFILE_A2DP_SOURCE || + u->profile == PROFILE_HFGW) + if (add_source(u) < 0) + r = -1; + + return r; +} + +/* Run from main thread */ +static void stop_thread(struct userdata *u) { + char *k; + + pa_assert(u); + + if (u->sink && !USE_SCO_OVER_PCM(u)) + pa_sink_unlink(u->sink); + + if (u->source && !USE_SCO_OVER_PCM(u)) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + u->thread = NULL; + } + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + if (u->rtpoll) { + pa_thread_mq_done(&u->thread_mq); + + pa_rtpoll_free(u->rtpoll); + u->rtpoll = NULL; + } + + if (u->transport) { + bt_transport_release(u); + u->transport = NULL; + } + + if (u->sink) { + if (u->profile == PROFILE_HSP) { + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink); + pa_shared_remove(u->core, k); + pa_xfree(k); + } + + pa_sink_unref(u->sink); + u->sink = NULL; + } + + if (u->source) { + if (u->profile == PROFILE_HSP) { + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source); + pa_shared_remove(u->core, k); + pa_xfree(k); + } + + pa_source_unref(u->source); + u->source = 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); + pa_assert(!u->rtpoll); + pa_assert(!u->rtpoll_item); + + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll); + + if (USE_SCO_OVER_PCM(u)) { + if (sco_over_pcm_state_update(u, false) < 0) { + char *k; + + if (u->sink) { + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink); + pa_shared_remove(u->core, k); + pa_xfree(k); + u->sink = NULL; + } + if (u->source) { + k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source); + pa_shared_remove(u->core, k); + pa_xfree(k); + u->source = NULL; + } + return -1; + } + + pa_sink_ref(u->sink); + pa_source_ref(u->source); + /* FIXME: monitor stream_fd error */ + return 0; + } + + if (!(u->thread = pa_thread_new("bluetooth", thread_func, u))) { + pa_log_error("Failed to create IO thread"); + return -1; + } + + if (u->sink) { + 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; +} + +static void save_sco_volume_callbacks(struct userdata *u) { + pa_assert(u); + pa_assert(USE_SCO_OVER_PCM(u)); + + u->hsp.sco_sink_set_volume = u->hsp.sco_sink->set_volume; + u->hsp.sco_source_set_volume = u->hsp.sco_source->set_volume; +} + +static void restore_sco_volume_callbacks(struct userdata *u) { + pa_assert(u); + pa_assert(USE_SCO_OVER_PCM(u)); + + pa_sink_set_set_volume_callback(u->hsp.sco_sink, u->hsp.sco_sink_set_volume); + pa_source_set_set_volume_callback(u->hsp.sco_source, u->hsp.sco_source_set_volume); +} + +/* Run from main thread */ +static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { + struct userdata *u; + enum profile *d; + + pa_assert(c); + pa_assert(new_profile); + pa_assert_se(u = c->userdata); + + d = PA_CARD_PROFILE_DATA(new_profile); + + if (*d != PROFILE_OFF) { + const pa_bluetooth_device *device = u->device; + + if (!device->transports[*d] || device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) { + pa_log_warn("Profile not connected, refused to switch profile to %s", new_profile->name); + return -PA_ERR_IO; + } + } + + stop_thread(u); + + if (USE_SCO_OVER_PCM(u)) + restore_sco_volume_callbacks(u); + + u->profile = *d; + u->sample_spec = u->requested_sample_spec; + + if (USE_SCO_OVER_PCM(u)) + save_sco_volume_callbacks(u); + + if (u->profile != PROFILE_OFF) + if (init_profile(u) < 0) + goto off; + + if (u->sink || u->source) + if (start_thread(u) < 0) + goto off; + + return 0; + +off: + stop_thread(u); + + pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0); + + return -PA_ERR_IO; +} + +/* Run from main thread */ +static void create_card_ports(struct userdata *u, pa_hashmap *ports) { + pa_device_port *port; + pa_device_port_new_data port_data; + + const char *name_prefix = NULL; + const char *input_description = NULL; + const char *output_description = NULL; + + pa_assert(u); + pa_assert(ports); + pa_assert(u->device); + + switch (pa_bluetooth_get_form_factor(u->device->class)) { + case PA_BT_FORM_FACTOR_UNKNOWN: + break; + + case PA_BT_FORM_FACTOR_HEADSET: + name_prefix = "headset"; + input_description = output_description = _("Headset"); + break; + + case PA_BT_FORM_FACTOR_HANDSFREE: + name_prefix = "handsfree"; + input_description = output_description = _("Handsfree"); + break; + + case PA_BT_FORM_FACTOR_MICROPHONE: + name_prefix = "microphone"; + input_description = _("Microphone"); + break; + + case PA_BT_FORM_FACTOR_SPEAKER: + name_prefix = "speaker"; + output_description = _("Speaker"); + break; + + case PA_BT_FORM_FACTOR_HEADPHONE: + name_prefix = "headphone"; + output_description = _("Headphone"); + break; + + case PA_BT_FORM_FACTOR_PORTABLE: + name_prefix = "portable"; + input_description = output_description = _("Portable"); + break; + + case PA_BT_FORM_FACTOR_CAR: + name_prefix = "car"; + input_description = output_description = _("Car"); + break; + + case PA_BT_FORM_FACTOR_HIFI: + name_prefix = "hifi"; + input_description = output_description = _("HiFi"); + break; + + case PA_BT_FORM_FACTOR_PHONE: + name_prefix = "phone"; + input_description = output_description = _("Phone"); + break; + } + + if (!name_prefix) + name_prefix = "unknown"; + + if (!output_description) + output_description = _("Bluetooth Output"); + + if (!input_description) + input_description = _("Bluetooth Input"); + + u->output_port_name = pa_sprintf_malloc("%s-output", name_prefix); + u->input_port_name = pa_sprintf_malloc("%s-input", name_prefix); + + pa_device_port_new_data_init(&port_data); + pa_device_port_new_data_set_name(&port_data, u->output_port_name); + pa_device_port_new_data_set_description(&port_data, output_description); + pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_OUTPUT); + pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_OUTPUT)); + pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0)); + pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0); + pa_device_port_new_data_done(&port_data); + + pa_device_port_new_data_init(&port_data); + pa_device_port_new_data_set_name(&port_data, u->input_port_name); + pa_device_port_new_data_set_description(&port_data, input_description); + pa_device_port_new_data_set_direction(&port_data, PA_DIRECTION_INPUT); + pa_device_port_new_data_set_available(&port_data, get_port_availability(u, PA_DIRECTION_INPUT)); + pa_assert_se(port = pa_device_port_new(u->core, &port_data, 0)); + pa_assert_se(pa_hashmap_put(ports, port->name, port) >= 0); + pa_device_port_new_data_done(&port_data); +} + +/* Run from main thread */ +static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid, pa_hashmap *ports) { + pa_device_port *input_port, *output_port; + pa_card_profile *p = NULL; + enum profile *d; + + pa_assert(u->input_port_name); + pa_assert(u->output_port_name); + pa_assert_se(input_port = pa_hashmap_get(ports, u->input_port_name)); + pa_assert_se(output_port = pa_hashmap_get(ports, u->output_port_name)); + + if (pa_streq(uuid, A2DP_SINK_UUID)) { + p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(enum profile)); + p->priority = 10; + p->n_sinks = 1; + p->n_sources = 0; + p->max_sink_channels = 2; + p->max_source_channels = 0; + pa_hashmap_put(output_port->profiles, p->name, p); + + d = PA_CARD_PROFILE_DATA(p); + *d = PROFILE_A2DP; + } else if (pa_streq(uuid, A2DP_SOURCE_UUID)) { + p = pa_card_profile_new("a2dp_source", _("High Fidelity Capture (A2DP)"), sizeof(enum profile)); + p->priority = 10; + p->n_sinks = 0; + p->n_sources = 1; + p->max_sink_channels = 0; + p->max_source_channels = 2; + pa_hashmap_put(input_port->profiles, p->name, p); + + d = PA_CARD_PROFILE_DATA(p); + *d = PROFILE_A2DP_SOURCE; + } else if (pa_streq(uuid, HSP_HS_UUID) || pa_streq(uuid, HFP_HS_UUID)) { + p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile)); + p->priority = 20; + p->n_sinks = 1; + p->n_sources = 1; + p->max_sink_channels = 1; + p->max_source_channels = 1; + pa_hashmap_put(input_port->profiles, p->name, p); + pa_hashmap_put(output_port->profiles, p->name, p); + + d = PA_CARD_PROFILE_DATA(p); + *d = PROFILE_HSP; + } else if (pa_streq(uuid, HFP_AG_UUID)) { + p = pa_card_profile_new("hfgw", _("Handsfree Gateway"), sizeof(enum profile)); + p->priority = 20; + p->n_sinks = 1; + p->n_sources = 1; + p->max_sink_channels = 1; + p->max_source_channels = 1; + pa_hashmap_put(input_port->profiles, p->name, p); + pa_hashmap_put(output_port->profiles, p->name, p); + + d = PA_CARD_PROFILE_DATA(p); + *d = PROFILE_HFGW; + } + + if (p) { + pa_bluetooth_transport *t; + + if ((t = u->device->transports[*d])) + p->available = transport_state_to_availability(t->state); + } + + return p; +} + +/* Run from main thread */ +static int add_card(struct userdata *u) { + pa_card_new_data data; + bool b; + pa_card_profile *p; + enum profile *d; + pa_bt_form_factor_t ff; + char *n; + const char *default_profile; + const pa_bluetooth_device *device; + const pa_bluetooth_uuid *uuid; + + pa_assert(u); + pa_assert(u->device); + + device = u->device; + + pa_card_new_data_init(&data); + data.driver = __FILE__; + data.module = u->module; + + n = pa_bluetooth_cleanup_name(device->alias); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, n); + pa_xfree(n); + 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(device->class)) != PA_BT_FORM_FACTOR_UNKNOWN) + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, pa_bt_form_factor_to_string(ff)); + + 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.alias", device->alias); + 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; + } + + create_card_ports(u, data.ports); + + PA_LLIST_FOREACH(uuid, device->uuids) { + p = create_card_profile(u, uuid->uuid, data.ports); + + if (!p) + continue; + + if (pa_hashmap_get(data.profiles, p->name)) { + pa_card_profile_free(p); + continue; + } + + pa_hashmap_put(data.profiles, p->name, p); + } + + pa_assert(!pa_hashmap_isempty(data.profiles)); + + p = pa_card_profile_new("off", _("Off"), sizeof(enum profile)); + p->available = PA_AVAILABLE_YES; + d = PA_CARD_PROFILE_DATA(p); + *d = PROFILE_OFF; + pa_hashmap_put(data.profiles, p->name, p); + + 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 + pa_log_warn("Profile '%s' not valid or not supported by device.", default_profile); + } + + u->card = pa_card_new(u->core, &data); + pa_card_new_data_done(&data); + + if (!u->card) { + pa_log("Failed to allocate card."); + return -1; + } + + u->card->userdata = u; + u->card->set_profile = card_set_profile; + + d = PA_CARD_PROFILE_DATA(u->card->active_profile); + + if (*d != PROFILE_OFF && (!device->transports[*d] || + device->transports[*d]->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED)) { + 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; + + if (USE_SCO_OVER_PCM(u)) + save_sco_volume_callbacks(u); + + return 0; +} + +/* Run from main thread */ +static pa_bluetooth_device* find_device(struct userdata *u, const char *address, const char *path) { + pa_bluetooth_device *d = NULL; + + pa_assert(u); + + if (!address && !path) { + pa_log_error("Failed to get device address/path from module arguments."); + return NULL; + } + + if (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 NULL; + } + + if (address && !(pa_streq(d->address, address))) { + pa_log_error("Passed path %s address %s != %s don't match.", path, d->address, address); + return NULL; + } + + } else { + if (!(d = pa_bluetooth_discovery_get_by_address(u->discovery, address))) { + pa_log_error("%s is not known.", address); + return NULL; + } + } + + if (d) { + u->address = pa_xstrdup(d->address); + u->path = pa_xstrdup(d->path); + } + + return d; +} + +/* Run from main thread */ +static pa_hook_result_t uuid_added_cb(pa_bluetooth_discovery *y, const struct pa_bluetooth_hook_uuid_data *data, + struct userdata *u) { + pa_card_profile *p; + + pa_assert(data); + pa_assert(data->device); + pa_assert(data->uuid); + pa_assert(u); + + if (data->device != u->device) + return PA_HOOK_OK; + + p = create_card_profile(u, data->uuid, u->card->ports); + + if (!p) + return PA_HOOK_OK; + + if (pa_hashmap_get(u->card->profiles, p->name)) { + pa_card_profile_free(p); + return PA_HOOK_OK; + } + + pa_card_add_profile(u->card, p); + + return PA_HOOK_OK; +} + +/* Run from main thread */ +static pa_hook_result_t discovery_hook_cb(pa_bluetooth_discovery *y, const pa_bluetooth_device *d, struct userdata *u) { + pa_assert(u); + pa_assert(d); + + if (d != u->device) + return PA_HOOK_OK; + + if (d->dead) + pa_log_debug("Device %s removed: unloading module", d->path); + else if (!pa_bluetooth_device_any_audio_connected(d)) + pa_log_debug("Unloading module, because device %s doesn't have any audio profiles connected anymore.", d->path); + else + return PA_HOOK_OK; + + pa_module_unload(u->core, u->module, true); + + return PA_HOOK_OK; +} + +int pa__init(pa_module *m) { + pa_modargs *ma; + uint32_t channels; + struct userdata *u; + const char *address, *path; + pa_bluetooth_device *device; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log_error("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->module = m; + u->core = m->core; + u->stream_fd = -1; + u->sample_spec = m->core->default_sample_spec; + u->modargs = ma; + + 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"); + goto fail; + } + + if (pa_modargs_get_value(ma, "sco_source", NULL) && + !(u->hsp.sco_source = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_source", NULL), PA_NAMEREG_SOURCE))) { + pa_log("SCO source not found"); + goto fail; + } + + 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) { + pa_log_error("Failed to get rate from module arguments"); + goto fail; + } + + u->auto_connect = true; + if (pa_modargs_get_value_boolean(ma, "auto_connect", &u->auto_connect)) { + pa_log("Failed to parse auto_connect= argument"); + goto fail; + } + + channels = u->sample_spec.channels; + if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || + channels <= 0 || channels > PA_CHANNELS_MAX) { + pa_log_error("Failed to get channels from module arguments"); + goto fail; + } + u->sample_spec.channels = (uint8_t) channels; + u->requested_sample_spec = u->sample_spec; + + address = pa_modargs_get_value(ma, "address", NULL); + path = pa_modargs_get_value(ma, "path", NULL); + + if (!(u->discovery = pa_bluetooth_discovery_get(m->core))) + goto fail; + + if (!(device = find_device(u, address, path))) + goto fail; + + u->device = device; + + u->discovery_slot = + pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED), + PA_HOOK_NORMAL, (pa_hook_cb_t) discovery_hook_cb, u); + + u->uuid_added_slot = + pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_UUID_ADDED), + PA_HOOK_NORMAL, (pa_hook_cb_t) uuid_added_cb, u); + + u->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); + + u->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); + + u->transport_state_changed_slot = + pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED), + PA_HOOK_NORMAL, (pa_hook_cb_t) transport_state_changed_cb, u); + + u->transport_nrec_changed_slot = + pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_NREC_CHANGED), + PA_HOOK_NORMAL, (pa_hook_cb_t) transport_nrec_changed_cb, u); + + u->transport_microphone_changed_slot = + pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), + PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u); + + u->transport_speaker_changed_slot = + pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), + PA_HOOK_NORMAL, (pa_hook_cb_t) transport_speaker_gain_changed_cb, u); + + /* Add the card structure. This will also initialize the default profile */ + if (add_card(u) < 0) + goto fail; + + if (!(u->msg = pa_msgobject_new(bluetooth_msg))) + goto fail; + + u->msg->parent.process_msg = device_process_msg; + u->msg->card = u->card; + + if (u->profile != PROFILE_OFF) + if (init_profile(u) < 0) + goto off; + + if (u->sink || u->source) + if (start_thread(u) < 0) + goto off; + + return 0; + +off: + stop_thread(u); + + pa_assert_se(pa_card_set_profile(u->card, "off", false) >= 0); + + return 0; + +fail: + + pa__done(m); + + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return + (u->sink ? pa_sink_linked_by(u->sink) : 0) + + (u->source ? pa_source_linked_by(u->source) : 0); +} + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + stop_thread(u); + + if (u->discovery_slot) + pa_hook_slot_free(u->discovery_slot); + + if (u->uuid_added_slot) + pa_hook_slot_free(u->uuid_added_slot); + + if (u->sink_state_changed_slot) + pa_hook_slot_free(u->sink_state_changed_slot); + + if (u->source_state_changed_slot) + pa_hook_slot_free(u->source_state_changed_slot); + + if (u->transport_state_changed_slot) + pa_hook_slot_free(u->transport_state_changed_slot); + + if (u->transport_nrec_changed_slot) + pa_hook_slot_free(u->transport_nrec_changed_slot); + + if (u->transport_microphone_changed_slot) + pa_hook_slot_free(u->transport_microphone_changed_slot); + + if (u->transport_speaker_changed_slot) + pa_hook_slot_free(u->transport_speaker_changed_slot); + + if (USE_SCO_OVER_PCM(u)) + restore_sco_volume_callbacks(u); + + if (u->msg) + pa_xfree(u->msg); + + if (u->card) + pa_card_free(u->card); + + if (u->a2dp.buffer) + pa_xfree(u->a2dp.buffer); + + sbc_finish(&u->a2dp.sbc); + + if (u->modargs) + pa_modargs_free(u->modargs); + + pa_xfree(u->output_port_name); + pa_xfree(u->input_port_name); + + 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-bluez4-discover.c b/src/modules/bluetooth/module-bluez4-discover.c new file mode 100644 index 0000000..e6ae931 --- /dev/null +++ b/src/modules/bluetooth/module-bluez4-discover.c @@ -0,0 +1,194 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008-2013 Jo?o Paulo Rechi Vita + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-shared.h> + +#include "module-bluez4-discover-symdef.h" +#include "bluez4-util.h" + +PA_MODULE_AUTHOR("Jo?o Paulo Rechi Vita"); +PA_MODULE_DESCRIPTION("Detect available BlueZ 4 Bluetooth audio devices and load BlueZ 4 Bluetooth audio drivers"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_USAGE("sco_sink=<name of sink> " + "sco_source=<name of source> "); +PA_MODULE_LOAD_ONCE(true); + +static const char* const valid_modargs[] = { + "sco_sink", + "sco_source", + "async", /* deprecated */ + NULL +}; + +struct userdata { + pa_module *module; + pa_modargs *modargs; + pa_core *core; + pa_bluetooth_discovery *discovery; + pa_hook_slot *slot; + pa_hashmap *hashmap; +}; + +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); + + mi = pa_hashmap_get(u->hashmap, d->path); + + if (pa_bluetooth_device_any_audio_connected(d)) { + + 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\"", d->address, d->path); + + if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) && + pa_modargs_get_value(u->modargs, "sco_source", NULL)) { + char *tmp; + + tmp = pa_sprintf_malloc("%s sco_sink=\"%s\" sco_source=\"%s\"", args, + pa_modargs_get_value(u->modargs, "sco_sink", NULL), + pa_modargs_get_value(u->modargs, "sco_source", NULL)); + pa_xfree(args); + args = tmp; + } + + pa_log_debug("Loading module-bluez4-device %s", args); + m = pa_module_load(u->module->core, "module-bluez4-device", args); + pa_xfree(args); + + 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 (mi) { + + /* Hmm, disconnection? Then the module unloads itself */ + + pa_log_debug("Unregistering module for %s", d->path); + pa_hashmap_remove(u->hashmap, mi->path); + pa_xfree(mi->path); + pa_xfree(mi); + } + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module* m) { + struct userdata *u; + pa_modargs *ma = NULL; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (pa_modargs_get_value(ma, "async", NULL)) + pa_log_warn("The 'async' argument is deprecated and does nothing."); + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->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 (!(u->discovery = pa_bluetooth_discovery_get(u->core))) + goto fail; + + u->slot = pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED), + PA_HOOK_NORMAL, (pa_hook_cb_t) load_module_for_device, u); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module* m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->slot) + pa_hook_slot_free(u->slot); + + if (u->discovery) + pa_bluetooth_discovery_unref(u->discovery); + + if (u->hashmap) { + struct module_info *mi; + + while ((mi = pa_hashmap_steal_first(u->hashmap))) { + pa_xfree(mi->path); + pa_xfree(mi); + } + + pa_hashmap_free(u->hashmap, NULL); + } + + if (u->modargs) + pa_modargs_free(u->modargs); + + pa_xfree(u); +} -- 1.7.11.7