bluez5: Implement routes

Implement routes on the device. This makes it possible for the
session manager to restore the device volumes.
Use validate_config to get the negotiated channels for the route
volumes.
This commit is contained in:
Wim Taymans 2021-01-07 18:10:22 +01:00
parent 5bb7a0f573
commit e91fbd2721
4 changed files with 473 additions and 48 deletions

View file

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

View file

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

View file

@ -39,9 +39,11 @@
#include <spa/support/plugin.h>
#include <spa/monitor/device.h>
#include <spa/monitor/utils.h>
#include <spa/monitor/event.h>
#include <spa/pod/filter.h>
#include <spa/pod/parser.h>
#include <spa/param/param.h>
#include <spa/param/audio/raw.h>
#include <spa/debug/pod.h>
#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;
}

View file

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