diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 8136e6a6c..44d221935 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -4582,6 +4582,26 @@ static bool codec_switch_clear_bap(struct spa_bt_codec_switch *sw, const char *p return true; } +static void codec_switch_emit_switching(struct spa_bt_monitor *monitor) +{ + struct spa_bt_device *d; + struct spa_bt_codec_switch *sw; + bool found = false; + + spa_list_for_each(d, &monitor->device_list, link) { + spa_list_for_each(sw, &d->codec_switch_list, link) { + if (sw->profiles & SPA_BT_PROFILE_BAP_AUDIO) { + found = true; + goto done; + } + } + } + +done: + spa_list_for_each(d, &monitor->device_list, link) + spa_bt_device_emit_codec_switch_other(d, found); +} + static bool codec_switch_process(struct spa_bt_codec_switch *sw) { if (sw->waiting) @@ -4596,6 +4616,9 @@ static bool codec_switch_process(struct spa_bt_codec_switch *sw) spa_log_info(sw->device->monitor->log, "media codec switch %p: success", sw); spa_bt_device_emit_codec_switched(sw->device, 0); spa_bt_device_check_profiles(sw->device, false); + + sw->profiles = 0; + codec_switch_emit_switching(sw->device->monitor); return true; } @@ -4610,17 +4633,8 @@ static bool codec_switch_process(struct spa_bt_codec_switch *sw) if (sw->path_idx == 0 && codec_switch_rate_limit(sw)) return false; - if (sw->path_idx == 0) { - struct spa_bt_transport *t, *t2; - - /* Force CIG inactive */ - spa_list_for_each(t, &sw->device->transport_list, link) { - spa_bt_transport_release_now(t); - spa_list_for_each(t2, &sw->device->monitor->transport_list, link) - if (t2->device != sw->device && transport_in_same_cig(t, t2)) - spa_bt_transport_release_now(t2); - } - } + if (sw->path_idx == 0) + codec_switch_emit_switching(sw->device->monitor); if (sw->paths[sw->path_idx].clear) { if (!codec_switch_clear_bap(sw, sw->paths[sw->path_idx].path)) @@ -4644,6 +4658,9 @@ fail: spa_log_info(sw->device->monitor->log, "media codec switch %p: failed", sw); spa_bt_device_emit_codec_switched(sw->device, -ENODEV); spa_bt_device_check_profiles(sw->device, false); + + sw->profiles = 0; + codec_switch_emit_switching(sw->device->monitor); return true; } diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index f0bbc6bb8..dde098ace 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -158,6 +158,7 @@ struct impl { uint32_t profile; unsigned int switching_codec:1; + unsigned int switching_codec_other:1; unsigned int save_profile:1; uint32_t prev_bt_connected_profiles; @@ -1161,6 +1162,15 @@ static int emit_nodes(struct impl *this) { struct spa_bt_transport *t; + switch (this->profile) { + case DEVICE_PROFILE_BAP: + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: + if (this->switching_codec_other) + return -EBUSY; + break; + } + this->props.codec = 0; device_set_update(this, &this->device_set, this->profile); @@ -1459,6 +1469,7 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a } this->switching_codec = false; + emit_nodes(this); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; @@ -1502,6 +1513,43 @@ static void codec_switched(void *userdata, int status) emit_info(this, false); } +static void codec_switch_other(void *userdata, bool switching) +{ + struct impl *this = userdata; + + this->switching_codec_other = switching; + + switch (this->profile) { + case DEVICE_PROFILE_BAP: + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: + break; + default: + return; + } + + spa_log_debug(this->log, "%p: BAP codec switching by another device, switching:%d", + this, (int)switching); + + /* + * In unicast BAP, output/input must be halted when another device is + * switching codec, because CIG must be torn down before it can be + * reconfigured. Easiest way to do this and to suspend output/input is to + * remove the nodes. + */ + if (!find_device_transport(this->bt_dev, SPA_BT_PROFILE_BAP_SINK) && + !find_device_transport(this->bt_dev, SPA_BT_PROFILE_BAP_SOURCE)) + return; + + if (switching) { + emit_remove_nodes(this); + spa_bt_device_release_transports(this->bt_dev); + } else { + emit_remove_nodes(this); + emit_nodes(this); + } +} + static bool device_set_needs_update(struct impl *this) { struct device_set dset = { .impl = this }; @@ -1655,6 +1703,7 @@ static const struct spa_bt_device_events bt_dev_events = { SPA_VERSION_BT_DEVICE_EVENTS, .connected = device_connected, .codec_switched = codec_switched, + .codec_switch_other = codec_switch_other, .profiles_changed = profiles_changed, .device_set_changed = device_set_changed, .switch_profile = device_switch_profile, diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index ae6544307..03ac283b2 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -489,6 +489,9 @@ struct spa_bt_device_events { /** Codec switching completed */ void (*codec_switched) (void *data, int status); + /** Codec switching initiated or completed by another device */ + void (*codec_switch_other) (void *data, bool switching); + /** Profile configuration changed */ void (*profiles_changed) (void *data, uint32_t connected_change); @@ -590,6 +593,7 @@ const struct media_codec *spa_bt_get_hfp_codec(struct spa_bt_monitor *monitor, u 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_codec_switch_other(d,...) spa_bt_device_emit(d, codec_switch_other, 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_emit_device_set_changed(d) spa_bt_device_emit(d, device_set_changed, 0) #define spa_bt_device_emit_switch_profile(d) spa_bt_device_emit(d, switch_profile, 0)