mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-14 08:56:37 -05: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);
|
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)
|
static int codec_set_bitpool(struct impl *this, int bitpool)
|
||||||
{
|
{
|
||||||
this->sbc.bitpool = SPA_CLAMP(bitpool, this->min_bitpool, this->max_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,
|
.fill_caps = codec_fill_caps,
|
||||||
.select_config = codec_select_config,
|
.select_config = codec_select_config,
|
||||||
.enum_config = codec_enum_config,
|
.enum_config = codec_enum_config,
|
||||||
|
.validate_config = codec_validate_config,
|
||||||
.init = codec_init,
|
.init = codec_init,
|
||||||
.deinit = codec_deinit,
|
.deinit = codec_deinit,
|
||||||
.get_block_size = codec_get_block_size,
|
.get_block_size = codec_get_block_size,
|
||||||
|
|
|
||||||
|
|
@ -1180,6 +1180,25 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn,
|
||||||
transport->a2dp_codec = codec;
|
transport->a2dp_codec = codec;
|
||||||
transport_update_props(transport, &it[1], NULL);
|
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) {
|
if (transport->device == NULL) {
|
||||||
spa_log_warn(monitor->log, "no device found for transport");
|
spa_log_warn(monitor->log, "no device found for transport");
|
||||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,11 @@
|
||||||
#include <spa/support/plugin.h>
|
#include <spa/support/plugin.h>
|
||||||
#include <spa/monitor/device.h>
|
#include <spa/monitor/device.h>
|
||||||
#include <spa/monitor/utils.h>
|
#include <spa/monitor/utils.h>
|
||||||
|
#include <spa/monitor/event.h>
|
||||||
#include <spa/pod/filter.h>
|
#include <spa/pod/filter.h>
|
||||||
#include <spa/pod/parser.h>
|
#include <spa/pod/parser.h>
|
||||||
#include <spa/param/param.h>
|
#include <spa/param/param.h>
|
||||||
|
#include <spa/param/audio/raw.h>
|
||||||
#include <spa/debug/pod.h>
|
#include <spa/debug/pod.h>
|
||||||
|
|
||||||
#include "defs.h"
|
#include "defs.h"
|
||||||
|
|
@ -62,12 +64,29 @@ static void reset_props(struct props *props)
|
||||||
strncpy(props->device, default_device, 64);
|
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 impl {
|
||||||
struct spa_handle handle;
|
struct spa_handle handle;
|
||||||
struct spa_device device;
|
struct spa_device device;
|
||||||
|
|
||||||
struct spa_log *log;
|
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 spa_hook_list hooks;
|
||||||
|
|
||||||
struct props props;
|
struct props props;
|
||||||
|
|
@ -75,28 +94,46 @@ struct impl {
|
||||||
struct spa_bt_device *bt_dev;
|
struct spa_bt_device *bt_dev;
|
||||||
|
|
||||||
uint32_t profile;
|
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;
|
uint32_t i;
|
||||||
struct spa_dict_item items[3];
|
|
||||||
char transport[32];
|
|
||||||
|
|
||||||
snprintf(transport, sizeof(transport), "pointer:%p", t);
|
spa_zero(*node);
|
||||||
items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_TRANSPORT, transport);
|
node->id = id;
|
||||||
items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PROFILE, spa_bt_profile_name(t->profile));
|
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
|
||||||
items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CODEC,
|
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");
|
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 = SPA_DEVICE_OBJECT_INFO_INIT();
|
||||||
info.type = SPA_TYPE_INTERFACE_Node;
|
info.type = SPA_TYPE_INTERFACE_Node;
|
||||||
info.factory_name = factory_name;
|
info.factory_name = factory_name;
|
||||||
info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
|
info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
|
||||||
info.props = &SPA_DICT_INIT_ARRAY(items);
|
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)
|
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)
|
static int emit_nodes(struct impl *this)
|
||||||
{
|
{
|
||||||
struct spa_bt_transport *t;
|
struct spa_bt_transport *t;
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
switch (this->profile) {
|
switch (this->profile) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|
@ -124,13 +160,13 @@ static int emit_nodes(struct impl *this)
|
||||||
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
|
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
|
||||||
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE);
|
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE);
|
||||||
if (t)
|
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) {
|
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) {
|
||||||
t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK);
|
t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK);
|
||||||
if (t)
|
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;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
|
|
@ -145,17 +181,33 @@ static int emit_nodes(struct impl *this)
|
||||||
}
|
}
|
||||||
if (t == NULL)
|
if (t == NULL)
|
||||||
break;
|
break;
|
||||||
emit_node(this, t, index++, SPA_NAME_API_BLUEZ5_SCO_SOURCE);
|
emit_node(this, t, 0, SPA_NAME_API_BLUEZ5_SCO_SOURCE);
|
||||||
emit_node(this, t, index++, SPA_NAME_API_BLUEZ5_SCO_SINK);
|
emit_node(this, t, 1, SPA_NAME_API_BLUEZ5_SCO_SINK);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
this->n_nodes = index;
|
|
||||||
return 0;
|
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)
|
static int set_profile(struct impl *this, uint32_t profile)
|
||||||
{
|
{
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
|
|
@ -163,19 +215,24 @@ static int set_profile(struct impl *this, uint32_t profile)
|
||||||
if (this->profile == profile)
|
if (this->profile == profile)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
for (i = 0; i < this->n_nodes; i++)
|
for (i = 0; i < 2; i++) {
|
||||||
spa_device_emit_object_info(&this->hooks, i, NULL);
|
if (this->nodes[i].active) {
|
||||||
|
spa_device_emit_object_info(&this->hooks, i, NULL);
|
||||||
this->n_nodes = 0;
|
this->nodes[i].active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
this->profile = profile;
|
this->profile = profile;
|
||||||
|
|
||||||
return emit_nodes(this);
|
emit_nodes(this);
|
||||||
}
|
|
||||||
|
|
||||||
static const struct spa_dict_item info_items[] = {
|
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||||
{ SPA_KEY_DEVICE_API, "bluez5" },
|
this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||||
{ SPA_KEY_MEDIA_CLASS, "Audio/Device" },
|
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,
|
static int impl_add_listener(void *object,
|
||||||
struct spa_hook *listener,
|
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);
|
spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
|
||||||
|
|
||||||
if (events->info) {
|
if (events->info)
|
||||||
struct spa_device_info info;
|
emit_info(this, true);
|
||||||
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->object_info)
|
if (events->object_info)
|
||||||
emit_nodes(this);
|
emit_nodes(this);
|
||||||
|
|
@ -227,6 +269,42 @@ static int impl_sync(void *object, int seq)
|
||||||
return 0;
|
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,
|
static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *b,
|
||||||
uint32_t id, uint32_t index)
|
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]);
|
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,
|
static int impl_enum_params(void *object, int seq,
|
||||||
uint32_t id, uint32_t start, uint32_t num,
|
uint32_t id, uint32_t start, uint32_t num,
|
||||||
const struct spa_pod *filter)
|
const struct spa_pod *filter)
|
||||||
|
|
@ -353,6 +514,32 @@ static int impl_enum_params(void *object, int seq,
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
@ -369,6 +556,111 @@ static int impl_enum_params(void *object, int seq,
|
||||||
return 0;
|
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,
|
static int impl_set_param(void *object,
|
||||||
uint32_t id, uint32_t flags,
|
uint32_t id, uint32_t flags,
|
||||||
const struct spa_pod *param)
|
const struct spa_pod *param)
|
||||||
|
|
@ -393,6 +685,33 @@ static int impl_set_param(void *object,
|
||||||
set_profile(this, id);
|
set_profile(this, id);
|
||||||
break;
|
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:
|
default:
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
@ -472,6 +791,20 @@ impl_init(const struct spa_handle_factory *factory,
|
||||||
|
|
||||||
reset_props(&this->props);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,9 @@ struct spa_bt_transport {
|
||||||
void *configuration;
|
void *configuration;
|
||||||
int configuration_len;
|
int configuration_len;
|
||||||
|
|
||||||
|
uint32_t n_channels;
|
||||||
|
uint32_t channels[64];
|
||||||
|
|
||||||
int acquire_refcount;
|
int acquire_refcount;
|
||||||
int fd;
|
int fd;
|
||||||
uint16_t read_mtu;
|
uint16_t read_mtu;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue