bluez5: create device handle before profile negotiation started so that profile handler can retrieve per-device settings

This commit is contained in:
Huang-Huang Bao 2021-03-14 14:06:50 +08:00 committed by Wim Taymans
parent e7cecaaea6
commit af8272fe08
5 changed files with 140 additions and 80 deletions

View file

@ -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 */

View file

@ -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;

View file

@ -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);

View file

@ -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) \

View file

@ -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);