From af8272fe08fd95a5031736b4cbdffe3745876fb1 Mon Sep 17 00:00:00 2001 From: Huang-Huang Bao Date: Sun, 14 Mar 2021 14:06:50 +0800 Subject: [PATCH] bluez5: create device handle before profile negotiation started so that profile handler can retrieve per-device settings --- spa/include/spa/utils/keys.h | 1 + spa/plugins/bluez5/bluez5-dbus.c | 99 ++++++++++++++-------- spa/plugins/bluez5/bluez5-device.c | 19 ++++- spa/plugins/bluez5/defs.h | 4 + src/examples/media-session/bluez-monitor.c | 97 ++++++++++++--------- 5 files changed, 140 insertions(+), 80 deletions(-) diff --git a/spa/include/spa/utils/keys.h b/spa/include/spa/utils/keys.h index c1e0a1af4..6eeb59906 100644 --- a/spa/include/spa/utils/keys.h +++ b/spa/include/spa/utils/keys.h @@ -108,6 +108,7 @@ extern "C" { #define SPA_KEY_API_BLUEZ5 "api.bluez5" /**< key for the bluez5 api */ #define SPA_KEY_API_BLUEZ5_PATH "api.bluez5.path" /**< a bluez5 path */ #define SPA_KEY_API_BLUEZ5_DEVICE "api.bluez5.device" /**< an internal bluez5 device */ +#define SPA_KEY_API_BLUEZ5_CONNECTION "api.bluez5.connection" /**< bluez5 device connection status */ #define SPA_KEY_API_BLUEZ5_TRANSPORT "api.bluez5.transport" /**< an internal bluez5 transport */ #define SPA_KEY_API_BLUEZ5_PROFILE "api.bluez5.profile" /**< a bluetooth profile */ #define SPA_KEY_API_BLUEZ5_ADDRESS "api.bluez5.address" /**< a bluetooth address */ diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 1aef6ec4f..0717284be 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -136,6 +136,10 @@ struct spa_bt_a2dp_codec_switch { size_t num_paths; }; +#define BT_DEVICE_DISCONNECTED 0 +#define BT_DEVICE_CONNECTED 1 +#define BT_DEVICE_INIT -1 + /* * SCO socket connect may fail with ECONNABORTED if it is done too soon after * previous close. To avoid this in cases where nodes are toggled between @@ -640,8 +644,6 @@ static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const static int device_stop_timer(struct spa_bt_device *device); -static int device_remove(struct spa_bt_monitor *monitor, struct spa_bt_device *device); - static void a2dp_codec_switch_free(struct spa_bt_a2dp_codec_switch *sw); static void device_free(struct spa_bt_device *device) @@ -662,7 +664,10 @@ static void device_free(struct spa_bt_device *device) battery_remove(device); device_stop_timer(device); - device_remove(monitor, device); + + if (device->added) { + spa_device_emit_object_info(&monitor->hooks, device->id, NULL); + } spa_list_for_each_safe(ep, tep, &device->remote_endpoint_list, device_link) { if (ep->device == device) { @@ -691,15 +696,38 @@ static void device_free(struct spa_bt_device *device) free(device); } -static int device_add(struct spa_bt_monitor *monitor, struct spa_bt_device *device) +static int device_connected(struct spa_bt_monitor *monitor, struct spa_bt_device *device, int status) { struct spa_device_object_info info; char dev[32], name[128], class[16]; struct spa_dict_item items[20]; uint32_t n_items = 0; + bool connection_changed, init = status == BT_DEVICE_INIT; - if (device->connected_profiles == 0 || device->added) + status = init ? false : status; + connection_changed = status ^ device->connected; + device->connected = status; + + if (init) { + device->added = true; + } else if (!device->added || !connection_changed) { return 0; + } + + if ((device->connected_profiles != 0) ^ device->connected) { + spa_log_error(monitor->log, + "unexpected call, connected_profiles:%08x connected:%d", + device->connected_profiles, device->connected); + return -EINVAL; + } + + if (!init) { + spa_bt_device_emit_connected(device, device->connected); + if (!device->connected) { + battery_remove(device); + spa_bt_device_release_transports(device); + } + } info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Device; @@ -724,29 +752,20 @@ static int device_add(struct spa_bt_monitor *monitor, struct spa_bt_device *devi items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_DEVICE, dev); snprintf(class, sizeof(class), "0x%06x", device->bluetooth_class); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CLASS, class); + items[n_items++] = SPA_DICT_ITEM_INIT( + SPA_KEY_API_BLUEZ5_CONNECTION, + device->connected ? "connected": "disconnected"); info.props = &SPA_DICT_INIT(items, n_items); - - device->added = true; spa_device_emit_object_info(&monitor->hooks, device->id, &info); - return 0; -} - -static int device_remove(struct spa_bt_monitor *monitor, struct spa_bt_device *device) -{ - if (!device->added) - return 0; - - battery_remove(device); - - device->added = false; - spa_device_emit_object_info(&monitor->hooks, device->id, NULL); + if(!init && !device->connected) { + spa_device_emit_object_info(&monitor->hooks, device->id, NULL); + } return 0; } - #define DEVICE_PROFILE_TIMEOUT_SEC 3 static void device_timer_event(struct spa_source *source) @@ -761,7 +780,7 @@ static void device_timer_event(struct spa_source *source) spa_log_debug(monitor->log, "device %p: timeout %08x %08x", device, device->profiles, device->connected_profiles); - device_add(device->monitor, device); + device_connected(device->monitor, device, BT_DEVICE_CONNECTED); } static int device_start_timer(struct spa_bt_device *device) @@ -821,13 +840,11 @@ int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) device, device->profiles, connected_profiles, device->added); if (connected_profiles == 0 && spa_list_is_empty(&device->codec_switch_list)) { - if (device->added) { - device_stop_timer(device); - device_remove(monitor, device); - } + device_stop_timer(device); + device_connected(monitor, device, BT_DEVICE_DISCONNECTED); } else if (force || (device->profiles & connected_profiles) == device->profiles) { device_stop_timer(device); - device_add(monitor, device); + device_connected(monitor, device, BT_DEVICE_CONNECTED); } else { device_start_timer(device); } @@ -841,14 +858,11 @@ static void device_set_connected(struct spa_bt_device *device, int connected) if (device->connected && !connected) device->connected_profiles = 0; - device->connected = connected; - if (connected) spa_bt_device_check_profiles(device, false); else { device_stop_timer(device); - if (device->added) - device_remove(monitor, device); + device_connected(monitor, device, connected); } } @@ -1158,6 +1172,8 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en else if (strcmp(key, "Device") == 0) { struct spa_bt_device *device; device = spa_bt_device_find(monitor, value); + if (device == NULL) + goto next; spa_log_debug(monitor->log, "remote_endpoint %p: device -> %p", remote_endpoint, device); if (remote_endpoint->device != device) { @@ -2737,16 +2753,25 @@ static void interface_added(struct spa_bt_monitor *monitor, else if (strcmp(interface_name, BLUEZ_DEVICE_INTERFACE) == 0) { struct spa_bt_device *d; - d = spa_bt_device_find(monitor, object_path); + spa_assert(spa_bt_device_find(monitor, object_path) == NULL); + + d = device_create(monitor, object_path); if (d == NULL) { - d = device_create(monitor, object_path); - if (d == NULL) { - spa_log_warn(monitor->log, "can't create Bluetooth device %s: %m", - object_path); - return; - } + spa_log_warn(monitor->log, "can't create Bluetooth device %s: %m", + object_path); + return; } + device_update_props(d, props_iter, NULL); + /* We only care about audio devices. */ + if (d->profiles == 0) { + device_free(d); + return; + } + + /* Trigger bluez device creation before bluez profile negotiation started so that + * profile connection handlers can receive per-device settings during profile negotiation. */ + device_connected(monitor, d, BT_DEVICE_INIT); } else if (strcmp(interface_name, BLUEZ_MEDIA_ENDPOINT_INTERFACE) == 0) { struct spa_bt_remote_endpoint *ep; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index a3beaa862..82c1be2c0 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -410,8 +410,20 @@ static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t pr emit_info(this, false); } +static void set_initial_profile(struct impl *this); + +static void device_connected(void *userdata, bool connected) { + struct impl *this = userdata; + + spa_log_debug(this->log, "connected: %d", connected); + + if (connected ^ (this->profile != 0)) + set_initial_profile(this); +} + static const struct spa_bt_device_events bt_dev_events = { SPA_VERSION_BT_DEVICE_EVENTS, + .connected = device_connected, .codec_switched = codec_switched, .profiles_changed = profiles_changed, }; @@ -540,6 +552,11 @@ static void set_initial_profile(struct impl *this) struct spa_bt_transport *t; int i; + if (this->supported_codecs) + free(this->supported_codecs); + this->supported_codecs = spa_bt_device_get_supported_a2dp_codecs( + this->bt_dev, &this->supported_codec_count); + /* Prefer A2DP, then HFP, then null */ for (i = SPA_BT_PROFILE_A2DP_SINK; i <= SPA_BT_PROFILE_A2DP_SOURCE; i <<= 1) { @@ -1248,8 +1265,6 @@ impl_init(const struct spa_handle_factory *factory, this->info.params = this->params; this->info.n_params = 4; - this->supported_codecs = spa_bt_device_get_supported_a2dp_codecs(this->bt_dev, &this->supported_codec_count); - spa_bt_device_add_listener(this->bt_dev, &this->bt_dev_listener, &bt_dev_events, this); set_initial_profile(this); diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index c637c8c19..f49bb09e5 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -375,6 +375,9 @@ struct spa_bt_device_events { #define SPA_VERSION_BT_DEVICE_EVENTS 0 uint32_t version; + /** Device connection status */ + void (*connected) (void *data, bool connected); + /** Codec switching completed */ void (*codec_switched) (void *data, int status); @@ -430,6 +433,7 @@ int spa_bt_device_report_battery_level(struct spa_bt_device *device, uint8_t per #define spa_bt_device_emit(d,m,v,...) spa_hook_list_call(&(d)->listener_list, \ struct spa_bt_device_events, \ m, v, ##__VA_ARGS__) +#define spa_bt_device_emit_connected(d,...) spa_bt_device_emit(d, connected, 0, __VA_ARGS__) #define spa_bt_device_emit_codec_switched(d,...) spa_bt_device_emit(d, codec_switched, 0, __VA_ARGS__) #define spa_bt_device_emit_profiles_changed(d,...) spa_bt_device_emit(d, profiles_changed, 0, __VA_ARGS__) #define spa_bt_device_add_listener(d,listener,events,data) \ diff --git a/src/examples/media-session/bluez-monitor.c b/src/examples/media-session/bluez-monitor.c index 9afd259ce..ce5325367 100644 --- a/src/examples/media-session/bluez-monitor.c +++ b/src/examples/media-session/bluez-monitor.c @@ -344,18 +344,6 @@ static int update_device_props(struct device *device) return 0; } -static void bluez5_update_device(struct impl *impl, struct device *dev, - const struct spa_device_object_info *info) -{ - pw_log_debug("update device %u", dev->id); - - if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) - spa_debug_dict(0, info->props); - - pw_properties_update(dev->props, info->props); - update_device_props(dev); -} - static void device_destroy(void *data) { struct device *device = data; @@ -363,27 +351,15 @@ static void device_destroy(void *data) pw_log_debug("device %p destroy", device); + if (device->appeared) { + device->appeared = false; + spa_hook_remove(&device->device_listener); + } + spa_list_consume(node, &device->node_list, link) bluez5_remove_node(device, node); } -static void device_free(void *data) -{ - struct device *device = data; - - pw_log_debug("remove device %u", device->id); - spa_list_remove(&device->link); - spa_hook_remove(&device->listener); - if (device->appeared) - spa_hook_remove(&device->device_listener); - - sm_object_discard(&device->sdevice->obj); - - pw_unload_spa_handle(device->handle); - pw_properties_free(device->props); - free(device); -} - static void device_update(void *data) { struct device *device = data; @@ -406,7 +382,6 @@ static void device_update(void *data) static const struct sm_object_events device_events = { SM_VERSION_OBJECT_EVENTS, .destroy = device_destroy, - .free = device_free, .update = device_update, }; @@ -460,16 +435,6 @@ static struct device *bluez5_create_device(struct impl *impl, uint32_t id, device->handle = handle; device->device = iface; - device->sdevice = sm_media_session_export_device(impl->session, - &device->props->dict, device->device); - if (device->sdevice == NULL) { - res = -errno; - goto unload_handle; - } - - sm_object_add_listener(&device->sdevice->obj, - &device->listener, - &device_events, device); spa_list_append(&impl->device_list, &device->link); @@ -487,7 +452,53 @@ exit: static void bluez5_remove_device(struct impl *impl, struct device *device) { - sm_object_destroy(&device->sdevice->obj); + + pw_log_debug("remove device %u", device->id); + + if (device->sdevice) { + sm_object_destroy(&device->sdevice->obj); + device->sdevice = NULL; + } +} + +static void bluez5_update_device(struct impl *impl, struct device *device, + const struct spa_device_object_info *info) +{ + bool connected; + const char *str; + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_dict(0, info->props); + + pw_log_debug("update device %u", device->id); + + pw_properties_update(device->props, info->props); + update_device_props(device); + + str = spa_dict_lookup(info->props, SPA_KEY_API_BLUEZ5_CONNECTION); + connected = str != NULL && strcmp(str, "connected") == 0; + + /* Export device after bluez profiles get connected */ + if (device->sdevice == NULL && connected) { + device->sdevice = sm_media_session_export_device(impl->session, + &device->props->dict, device->device); + if (device->sdevice == NULL) { + bluez5_remove_device(impl, device); + return; + } + + sm_object_add_listener(&device->sdevice->obj, + &device->listener, + &device_events, device); + } +} + +static void bluez5_device_free(struct impl *impl, struct device *device) +{ + bluez5_remove_device(impl, device); + spa_list_remove(&device->link); + pw_unload_spa_handle(device->handle); + pw_properties_free(device->props); + free(device); } static void bluez5_enum_object_info(void *data, uint32_t id, @@ -519,6 +530,10 @@ static const struct spa_device_events bluez5_enum_callbacks = static void session_destroy(void *data) { struct impl *impl = data; + struct device *device; + spa_list_consume(device, &impl->device_list, link) + bluez5_device_free(impl, device); + spa_hook_remove(&impl->session_listener); spa_hook_remove(&impl->listener); pw_unload_spa_handle(impl->handle);