mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
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:
parent
5bb7a0f573
commit
e91fbd2721
4 changed files with 473 additions and 48 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue