diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c index 74fdb6b30..8a8cfa13c 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -185,6 +185,75 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } +static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + const a2dp_sbc_t *conf; + + if (caps == NULL || caps_size < sizeof(conf)) + return -EINVAL; + + conf = caps; + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_S16; + + switch (conf->frequency) { + case SBC_SAMPLING_FREQ_16000: + info->info.raw.rate = 16000; + break; + case SBC_SAMPLING_FREQ_32000: + info->info.raw.rate = 32000; + break; + case SBC_SAMPLING_FREQ_44100: + info->info.raw.rate = 44100; + break; + case SBC_SAMPLING_FREQ_48000: + info->info.raw.rate = 48000; + break; + default: + return -EINVAL; + } + + switch (conf->channel_mode) { + case SBC_CHANNEL_MODE_MONO: + info->info.raw.channels = 1; + info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; + break; + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + info->info.raw.channels = 2; + info->info.raw.position[0] = SPA_AUDIO_CHANNEL_FL; + info->info.raw.position[1] = SPA_AUDIO_CHANNEL_FR; + break; + default: + return -EINVAL; + } + + switch (conf->subbands) { + case SBC_SUBBANDS_4: + case SBC_SUBBANDS_8: + break; + default: + return -EINVAL; + } + + switch (conf->block_length) { + case SBC_BLOCK_LENGTH_4: + case SBC_BLOCK_LENGTH_8: + case SBC_BLOCK_LENGTH_12: + case SBC_BLOCK_LENGTH_16: + break; + default: + return -EINVAL; + } + return 0; +} + static int codec_set_bitpool(struct impl *this, int bitpool) { this->sbc.bitpool = SPA_CLAMP(bitpool, this->min_bitpool, this->max_bitpool); @@ -494,6 +563,7 @@ const struct a2dp_codec a2dp_codec_sbc = { .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, diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 223f30a01..b92fa8e8b 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -1180,6 +1180,25 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, transport->a2dp_codec = codec; transport_update_props(transport, &it[1], NULL); + if (codec->validate_config) { + struct spa_audio_info info; + if (codec->validate_config(codec, 0, + transport->configuration, transport->configuration_len, + &info) < 0) { + spa_log_error(monitor->log, "invalid transport configuration"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + transport->n_channels = info.info.raw.channels; + memcpy(transport->channels, info.info.raw.position, + transport->n_channels * sizeof(uint32_t)); + } else { + transport->n_channels = 2; + transport->channels[0] = SPA_AUDIO_CHANNEL_FL; + transport->channels[1] = SPA_AUDIO_CHANNEL_FR; + } + spa_log_info(monitor->log, "%p: %s validate conf channels:%d", + monitor, path, transport->n_channels); + if (transport->device == NULL) { spa_log_warn(monitor->log, "no device found for transport"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 77bf77f0e..03e12be36 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -39,9 +39,11 @@ #include #include #include +#include #include #include #include +#include #include #include "defs.h" @@ -62,12 +64,29 @@ static void reset_props(struct props *props) strncpy(props->device, default_device, 64); } +struct node { + uint32_t id; + unsigned int active:1; + unsigned int mute:1; + uint32_t n_channels; + uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; + float volumes[SPA_AUDIO_MAX_CHANNELS]; +}; + struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; + uint32_t info_all; + struct spa_device_info info; +#define IDX_EnumProfile 0 +#define IDX_Profile 1 +#define IDX_EnumRoute 2 +#define IDX_Route 3 + struct spa_param_info params[4]; + struct spa_hook_list hooks; struct props props; @@ -75,28 +94,46 @@ struct impl { struct spa_bt_device *bt_dev; uint32_t profile; - uint32_t n_nodes; + struct node nodes[2]; }; -static void emit_node (struct impl *this, struct spa_bt_transport *t, uint32_t id, const char *factory_name) +static void init_node(struct impl *this, struct node *node, uint32_t id) { - struct spa_device_object_info info; - struct spa_dict_item items[3]; - char transport[32]; + uint32_t i; - snprintf(transport, sizeof(transport), "pointer:%p", t); - items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_TRANSPORT, transport); - items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PROFILE, spa_bt_profile_name(t->profile)); - items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CODEC, + spa_zero(*node); + node->id = id; + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + node->volumes[i] = 1.0; +} + +static void emit_node(struct impl *this, struct spa_bt_transport *t, + uint32_t id, const char *factory_name) +{ + struct spa_device_object_info info; + struct spa_dict_item items[4]; + char transport[32], str_id[32]; + + snprintf(transport, sizeof(transport), "pointer:%p", t); + items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_TRANSPORT, transport); + items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PROFILE, spa_bt_profile_name(t->profile)); + items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CODEC, t->a2dp_codec ? t->a2dp_codec->name : "unknown"); + snprintf(str_id, sizeof(str_id), "%d", id); + items[3] = SPA_DICT_ITEM_INIT("card.profile.device", str_id); - info = SPA_DEVICE_OBJECT_INFO_INIT(); - info.type = SPA_TYPE_INTERFACE_Node; - info.factory_name = factory_name; - info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; - info.props = &SPA_DICT_INIT_ARRAY(items); + info = SPA_DEVICE_OBJECT_INFO_INIT(); + info.type = SPA_TYPE_INTERFACE_Node; + info.factory_name = factory_name; + info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; + info.props = &SPA_DICT_INIT_ARRAY(items); - spa_device_emit_object_info(&this->hooks, id, &info); + spa_device_emit_object_info(&this->hooks, id, &info); + + this->nodes[id].active = true; + this->nodes[id].n_channels = t->n_channels; + memcpy(this->nodes[id].channels, t->channels, + t->n_channels * sizeof(uint32_t)); } static struct spa_bt_transport *find_transport(struct impl *this, int profile) @@ -115,7 +152,6 @@ static struct spa_bt_transport *find_transport(struct impl *this, int profile) static int emit_nodes(struct impl *this) { struct spa_bt_transport *t; - int index = 0; switch (this->profile) { case 0: @@ -124,13 +160,13 @@ static int emit_nodes(struct impl *this) if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE); if (t) - emit_node(this, t, index++, SPA_NAME_API_BLUEZ5_A2DP_SOURCE); + emit_node(this, t, 0, SPA_NAME_API_BLUEZ5_A2DP_SOURCE); } if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK); if (t) - emit_node(this, t, index++, SPA_NAME_API_BLUEZ5_A2DP_SINK); + emit_node(this, t, 1, SPA_NAME_API_BLUEZ5_A2DP_SINK); } break; case 2: @@ -145,17 +181,33 @@ static int emit_nodes(struct impl *this) } if (t == NULL) break; - emit_node(this, t, index++, SPA_NAME_API_BLUEZ5_SCO_SOURCE); - emit_node(this, t, index++, SPA_NAME_API_BLUEZ5_SCO_SINK); + emit_node(this, t, 0, SPA_NAME_API_BLUEZ5_SCO_SOURCE); + emit_node(this, t, 1, SPA_NAME_API_BLUEZ5_SCO_SINK); } break; default: return -EINVAL; } - this->n_nodes = index; return 0; } +static const struct spa_dict_item info_items[] = { + { SPA_KEY_DEVICE_API, "bluez5" }, + { SPA_KEY_MEDIA_CLASS, "Audio/Device" }, +}; + +static void emit_info(struct impl *this, bool full) +{ + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + this->info.props = &SPA_DICT_INIT_ARRAY(info_items); + + spa_device_emit_info(&this->hooks, &this->info); + this->info.change_mask = 0; + } +} + static int set_profile(struct impl *this, uint32_t profile) { uint32_t i; @@ -163,19 +215,24 @@ static int set_profile(struct impl *this, uint32_t profile) if (this->profile == profile) return 0; - for (i = 0; i < this->n_nodes; i++) - spa_device_emit_object_info(&this->hooks, i, NULL); - - this->n_nodes = 0; + for (i = 0; i < 2; i++) { + if (this->nodes[i].active) { + spa_device_emit_object_info(&this->hooks, i, NULL); + this->nodes[i].active = false; + } + } this->profile = profile; - return emit_nodes(this); -} + emit_nodes(this); -static const struct spa_dict_item info_items[] = { - { SPA_KEY_DEVICE_API, "bluez5" }, - { SPA_KEY_MEDIA_CLASS, "Audio/Device" }, -}; + this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; + this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL; + this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; + this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL; + emit_info(this, false); + + return 0; +} static int impl_add_listener(void *object, struct spa_hook *listener, @@ -190,23 +247,8 @@ static int impl_add_listener(void *object, spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - if (events->info) { - struct spa_device_info info; - struct spa_param_info params[2]; - - info = SPA_DEVICE_INFO_INIT(); - - info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS; - info.props = &SPA_DICT_INIT_ARRAY(info_items); - - info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; - params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); - params[1] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_READWRITE); - info.n_params = SPA_N_ELEMENTS(params); - info.params = params; - - spa_device_emit_info(&this->hooks, &info); - } + if (events->info) + emit_info(this, true); if (events->object_info) emit_nodes(this); @@ -227,6 +269,42 @@ static int impl_sync(void *object, int seq) return 0; } +static uint32_t profile_direction_mask(struct impl *this, uint32_t index) +{ + struct spa_bt_device *device = this->bt_dev; + uint32_t profile, mask; + bool have_output = false, have_input = false; + + switch (index) { + case 1: + profile = device->connected_profiles & + (SPA_BT_PROFILE_A2DP_SINK | + SPA_BT_PROFILE_A2DP_SOURCE); + if (profile == SPA_BT_PROFILE_A2DP_SINK) + have_output = true; + else if (profile == SPA_BT_PROFILE_A2DP_SOURCE) + have_input = true; + else + have_output = have_input = true; + break; + case 2: + profile = device->connected_profiles & + (SPA_BT_PROFILE_HEADSET_HEAD_UNIT | + SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); + if (profile == 0) + break; + have_output = have_input = true; + break; + } + + mask = 0; + if (have_output) + mask |= 1 << SPA_DIRECTION_OUTPUT; + if (have_input) + mask |= 1 << SPA_DIRECTION_INPUT; + return mask; +} + static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *b, uint32_t id, uint32_t index) { @@ -305,6 +383,89 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * return spa_pod_builder_pop(b, &f[0]); } +static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, + uint32_t id, uint32_t port, uint32_t dev, uint32_t profile) +{ + struct spa_pod_frame f[2]; + enum spa_direction direction; + const char *name, *description; + enum spa_param_availability available; + uint32_t i; + + switch (port) { + case 0: + direction = SPA_DIRECTION_INPUT; + name = "bluetooth-input"; + description = "Bluetooth input"; + break; + case 1: + direction = SPA_DIRECTION_OUTPUT; + name = "bluetooth-output"; + description = "Bluetooth Output"; + break; + default: + errno = -EINVAL; + return NULL; + } + + available = profile_direction_mask(this, this->profile) & (1 << direction) ? + SPA_PARAM_AVAILABILITY_yes : SPA_PARAM_AVAILABILITY_no; + if (dev != SPA_ID_INVALID && available == SPA_PARAM_AVAILABILITY_no) + return NULL; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamRoute, id); + spa_pod_builder_add(b, + SPA_PARAM_ROUTE_index, SPA_POD_Int(port), + SPA_PARAM_ROUTE_direction, SPA_POD_Id(direction), + SPA_PARAM_ROUTE_name, SPA_POD_String(name), + SPA_PARAM_ROUTE_description, SPA_POD_String(description), + SPA_PARAM_ROUTE_priority, SPA_POD_Int(0), + SPA_PARAM_ROUTE_available, SPA_POD_Id(available), + 0); + spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0); + spa_pod_builder_push_array(b, &f[1]); + for (i = 0; i < 3; i++) { + if (profile_direction_mask(this, i) & (1 << direction)) + spa_pod_builder_int(b, i); + } + spa_pod_builder_pop(b, &f[1]); + + if (dev != SPA_ID_INVALID) { + struct node *node = &this->nodes[dev]; + + spa_pod_builder_prop(b, SPA_PARAM_ROUTE_device, 0); + spa_pod_builder_int(b, dev); + + spa_pod_builder_prop(b, SPA_PARAM_ROUTE_props, 0); + spa_pod_builder_push_object(b, &f[1], SPA_TYPE_OBJECT_Props, id); + + spa_pod_builder_prop(b, SPA_PROP_mute, 0); + spa_pod_builder_bool(b, node->mute); + + spa_pod_builder_prop(b, SPA_PROP_channelVolumes, 0); + spa_pod_builder_array(b, sizeof(float), SPA_TYPE_Float, + node->n_channels, node->volumes); + + spa_pod_builder_prop(b, SPA_PROP_channelMap, 0); + spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id, + node->n_channels, node->channels); + + spa_pod_builder_pop(b, &f[1]); + } + + spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0); + spa_pod_builder_push_array(b, &f[1]); + /* port and device indexes are the same, 0=source, 1=sink */ + spa_pod_builder_int(b, port); + spa_pod_builder_pop(b, &f[1]); + + if (profile != SPA_ID_INVALID) { + spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profile, 0); + spa_pod_builder_int(b, profile); + } + return spa_pod_builder_pop(b, &f[0]); +} + static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) @@ -353,6 +514,32 @@ static int impl_enum_params(void *object, int seq, } break; } + case SPA_PARAM_EnumRoute: + { + switch (result.index) { + case 0: case 1: + param = build_route(this, &b, id, result.index, + SPA_ID_INVALID, SPA_ID_INVALID); + break; + default: + return 0; + } + break; + } + case SPA_PARAM_Route: + { + switch (result.index) { + case 0: case 1: + param = build_route(this, &b, id, result.index, + result.index, this->profile); + if (param == NULL) + goto next; + break; + default: + return 0; + } + break; + } default: return -ENOENT; } @@ -369,6 +556,111 @@ static int impl_enum_params(void *object, int seq, return 0; } +static int node_set_volume(struct impl *this, struct node *node, float volumes[], uint32_t n_volumes) +{ + struct spa_event *event; + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_frame f[1]; + + spa_log_info(this->log, "node %p volume %f", node, volumes[0]); + + node->n_channels = n_volumes; + memcpy(node->volumes, volumes, sizeof(float) * SPA_AUDIO_MAX_CHANNELS); + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_push_object(&b, &f[0], + SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); + spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); + spa_pod_builder_int(&b, node->id); + spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, + SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, n_volumes, volumes), + SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, node->n_channels, node->channels)); + event = spa_pod_builder_pop(&b, &f[0]); + + spa_device_emit_event(&this->hooks, event); + + return 0; +} + +static int node_set_mute(struct impl *this, struct node *node, bool mute) +{ + struct spa_event *event; + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_frame f[1]; + + spa_log_info(this->log, "node %p mute %d", node, mute); + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_push_object(&b, &f[0], + SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); + spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); + spa_pod_builder_int(&b, node->id); + spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); + + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, + SPA_PROP_mute, SPA_POD_Bool(mute)); + event = spa_pod_builder_pop(&b, &f[0]); + + spa_device_emit_event(&this->hooks, event); + + return 0; +} + +static int apply_device_props(struct impl *this, struct node *node, struct spa_pod *props) +{ + float volume = 0; + bool mute = 0; + struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *) props; + int changed = 0; + float volumes[SPA_AUDIO_MAX_CHANNELS]; + uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; + uint32_t n_volumes = 0, n_channels = 0; + + if (!spa_pod_is_object_type(props, SPA_TYPE_OBJECT_Props)) + return -EINVAL; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_volume: + if (spa_pod_get_float(&prop->value, &volume) == 0) { + node_set_volume(this, node, &volume, 1); + changed++; + } + break; + case SPA_PROP_mute: + if (spa_pod_get_bool(&prop->value, &mute) == 0) { + node_set_mute(this, node, mute); + changed++; + } + break; + case SPA_PROP_channelVolumes: + if ((n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + changed++; + } + break; + case SPA_PROP_channelMap: + if ((n_channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, + channels, SPA_AUDIO_MAX_CHANNELS)) > 0) { + changed++; + } + break; + } + } + if (n_volumes > 0) + node_set_volume(this, node, volumes, n_volumes); + + return changed; +} + static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) @@ -393,6 +685,33 @@ static int impl_set_param(void *object, set_profile(this, id); break; } + case SPA_PARAM_Route: + { + uint32_t id, device; + struct spa_pod *props = NULL; + struct node *node; + + if ((res = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamRoute, NULL, + SPA_PARAM_ROUTE_index, SPA_POD_Int(&id), + SPA_PARAM_ROUTE_device, SPA_POD_Int(&device), + SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&props))) < 0) { + spa_log_warn(this->log, "can't parse route"); + spa_debug_pod(0, NULL, param); + return res; + } + if (device > 2 || !this->nodes[device].active) + return -EINVAL; + + node = &this->nodes[device]; + if (props) { + apply_device_props(this, node, props); + this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; + this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; + emit_info(this, false); + } + break; + } default: return -ENOENT; } @@ -472,6 +791,20 @@ impl_init(const struct spa_handle_factory *factory, reset_props(&this->props); + init_node(this, &this->nodes[0], 0); + init_node(this, &this->nodes[1], 1); + + this->info = SPA_DEVICE_INFO_INIT(); + this->info_all = SPA_DEVICE_CHANGE_MASK_PROPS | + SPA_DEVICE_CHANGE_MASK_PARAMS; + + this->params[IDX_EnumProfile] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); + this->params[IDX_Profile] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_READWRITE); + this->params[IDX_EnumRoute] = SPA_PARAM_INFO(SPA_PARAM_EnumRoute, SPA_PARAM_INFO_READ); + this->params[IDX_Route] = SPA_PARAM_INFO(SPA_PARAM_Route, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = 4; + return 0; } diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index e5ed425c7..148965cfa 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -292,6 +292,9 @@ struct spa_bt_transport { void *configuration; int configuration_len; + uint32_t n_channels; + uint32_t channels[64]; + int acquire_refcount; int fd; uint16_t read_mtu;