diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c index 9cce8e083..0676f6153 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -125,16 +125,11 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, a2dp_sbc_t conf; int bitpool; bool xq = false; - const char *str; if (caps_size < sizeof(conf)) return -EINVAL; - if (settings) { - if ((str = spa_dict_lookup(settings, "codec.sbc.enable-xq")) != NULL && - (strcmp(str, "true") == 0 || atoi(str))) - xq = true; - } + xq = (strcmp(codec->name, "sbc_xq") == 0); memcpy(&conf, caps, sizeof(conf)); @@ -587,3 +582,25 @@ const struct a2dp_codec a2dp_codec_sbc = { .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, }; + +const struct a2dp_codec a2dp_codec_sbc_xq = { + .codec_id = A2DP_CODEC_SBC, + .name = "sbc_xq", + .description = "SBC-XQ", + .fill_caps = codec_fill_caps, + .select_config = codec_select_config, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .get_num_blocks = codec_get_num_blocks, + .abr_process = codec_abr_process, + .start_encode = codec_start_encode, + .encode = codec_encode, + .start_decode = codec_start_decode, + .decode = codec_decode, + .reduce_bitpool = codec_reduce_bitpool, + .increase_bitpool = codec_increase_bitpool, + .feature_flag = "sbc-xq", +}; diff --git a/spa/plugins/bluez5/a2dp-codecs.c b/spa/plugins/bluez5/a2dp-codecs.c index e2de0bb21..0f4587dc9 100644 --- a/spa/plugins/bluez5/a2dp-codecs.c +++ b/spa/plugins/bluez5/a2dp-codecs.c @@ -69,6 +69,7 @@ const a2dp_mpeg_t bluez_a2dp_mpeg = { #endif extern struct a2dp_codec a2dp_codec_sbc; +extern struct a2dp_codec a2dp_codec_sbc_xq; #if ENABLE_LDAC extern struct a2dp_codec a2dp_codec_ldac; #endif @@ -97,6 +98,7 @@ const struct a2dp_codec *a2dp_codec_list[] = { #if ENABLE_MP3 &a2dp_codec_mpeg, #endif + &a2dp_codec_sbc_xq, &a2dp_codec_sbc, NULL, }; diff --git a/spa/plugins/bluez5/a2dp-codecs.h b/spa/plugins/bluez5/a2dp-codecs.h index af6776b12..79407ce70 100644 --- a/spa/plugins/bluez5/a2dp-codecs.h +++ b/spa/plugins/bluez5/a2dp-codecs.h @@ -334,6 +334,8 @@ struct a2dp_codec { const size_t send_buf_size; + const char *feature_flag; + int (*fill_caps) (const struct a2dp_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]); int (*select_config) (const struct a2dp_codec *codec, uint32_t flags, diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index df022a449..0c602968d 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -214,13 +214,8 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu spa_log_debug(monitor->log, " %d: %02x", i, cap[i]); codec = a2dp_endpoint_to_codec(path); - if (codec != NULL) { - struct spa_dict_item items[1]; - - items[0] = SPA_DICT_ITEM_INIT("codec.sbc.enable-xq", monitor->enable_sbc_xq ? "true" : "false"); - - res = codec->select_config(codec, 0, cap, size, &SPA_DICT_INIT_ARRAY(items), config); - } + if (codec != NULL) + res = codec->select_config(codec, 0, cap, size, NULL, config); else res = -ENOTSUP; @@ -770,6 +765,22 @@ static int device_update_props(struct spa_bt_device *device, return 0; } +static bool device_can_accept_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec *codec) +{ + struct spa_bt_monitor *monitor = device->monitor; + + /* Device capability checks in addition to A2DP Capabilities. */ + + if (!is_a2dp_codec_enabled(device->monitor, codec)) + return false; + + if (codec->feature_flag != NULL && strcmp(codec->feature_flag, "sbc-xq") == 0) + return monitor->enable_sbc_xq; + + /* The rest is determined by A2DP caps */ + return true; +} + static int device_try_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile, const char *profile_uuid) @@ -1641,23 +1652,28 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a struct spa_bt_a2dp_codec_switch *sw; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; - size_t i, num_codecs, num_eps; + const struct a2dp_codec *preferred_codec = NULL; + size_t i, j, num_codecs, num_eps; if (!device->adapter->application_registered) { /* Codec switching not supported */ return -ENOTSUP; } - if (codecs[0] == NULL) - return -ENODEV; + for (i = 0; codecs[i] != NULL; ++i) { + if (spa_bt_device_supports_a2dp_codec(device, codecs[i])) { + preferred_codec = codecs[i]; + break; + } + } /* Check if we already have an enabled transport for the most preferred codec. * However, if there already was a codec switch running, these transports may * disapper soon. In that case, we have to do the full thing. */ - if (device->active_codec_switch == NULL) { + if (device->active_codec_switch == NULL && preferred_codec != NULL) { spa_list_for_each(t, &device->transport_list, device_link) { - if (t->a2dp_codec != codecs[0] || !t->enabled) + if (t->a2dp_codec != preferred_codec || !t->enabled) continue; if ((device->connected_profiles & t->profile) != t->profile) @@ -1691,7 +1707,13 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a return -ENOMEM; } - memcpy(sw->codecs, codecs, num_codecs * sizeof(const struct a2dp_codec *)); + for (i = 0, j = 0; i < num_codecs; ++i) { + if (device_can_accept_a2dp_codec(device, codecs[i])) { + sw->codecs[j] = codecs[i]; + ++j; + } + } + sw->codecs[j] = NULL; i = 0; spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { @@ -1702,9 +1724,7 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a } ++i; } - - sw->codecs[num_codecs] = NULL; - sw->paths[num_eps] = NULL; + sw->paths[i] = NULL; sw->codec_iter = sw->codecs; sw->path_iter = sw->paths; @@ -1800,7 +1820,15 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, spa_log_info(monitor->log, "%p: %s validate conf channels:%d", monitor, path, transport->n_channels); - transport->enabled = true; + /* + * Per-device determination of which codecs are allowed needs to be done also + * here. The A2DP codec switching process does this filtering, but before that + * BlueZ may autoconnect to any endpoint with compatible caps. In case it got it + * wrong, mark the transport disabled, and we'll switch to a better one later when + * needed. (We don't want to fail the connection, because we want to keep the + * profile connected.) + */ + transport->enabled = device_can_accept_a2dp_codec(transport->device, codec); spa_bt_device_connect_profile(transport->device, transport->profile); @@ -2060,7 +2088,7 @@ static int adapter_register_endpoints(struct spa_bt_adapter *a) if (!is_a2dp_codec_enabled(monitor, codec)) continue; - if (codec->codec_id != A2DP_CODEC_SBC) + if (!(codec->codec_id == A2DP_CODEC_SBC && strcmp(codec->name, "sbc") == 0)) continue; if ((err = bluez_register_endpoint(monitor, a->path,