mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-30 21:37:53 -04:00
spa: alsa: Add a mechanism for external volume control
Currently enabled at device creation and delegated to an external entity via spa_device events.
This commit is contained in:
parent
122bfd712b
commit
ef6f5194e3
9 changed files with 867 additions and 86 deletions
|
|
@ -70,7 +70,8 @@ struct impl {
|
|||
#define IDX_Profile 1
|
||||
#define IDX_EnumRoute 2
|
||||
#define IDX_Route 3
|
||||
struct spa_param_info params[4];
|
||||
#define IDX_Props 4
|
||||
struct spa_param_info params[5];
|
||||
|
||||
struct spa_hook_list hooks;
|
||||
|
||||
|
|
@ -490,6 +491,11 @@ static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id,
|
|||
dev->n_codecs, dev->codecs);
|
||||
}
|
||||
|
||||
if (dev->ext_volume_flags) {
|
||||
spa_pod_builder_prop(b, SPA_PROP_volumeControlFlags, 0);
|
||||
spa_pod_builder_id(b, dev->ext_volume_flags);
|
||||
}
|
||||
|
||||
spa_pod_builder_pop(b, &f[1]);
|
||||
}
|
||||
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0);
|
||||
|
|
@ -507,6 +513,41 @@ static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id,
|
|||
return spa_pod_builder_pop(b, &f[0]);
|
||||
}
|
||||
|
||||
static struct spa_pod *build_props(struct spa_pod_builder *b, struct acp_card *card, struct acp_port *p)
|
||||
{
|
||||
struct spa_pod_frame f[4];
|
||||
|
||||
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
|
||||
spa_pod_builder_prop(b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(b, &f[1]);
|
||||
|
||||
spa_pod_builder_string(b, "ext-control-caps-reply");
|
||||
spa_pod_builder_push_struct(b, &f[2]);
|
||||
spa_pod_builder_int(b, card->ext_volume.caps_reply.res);
|
||||
spa_pod_builder_int(b, card->ext_volume.caps_reply.caps);
|
||||
spa_pod_builder_pop(b, &f[2]);
|
||||
|
||||
spa_pod_builder_string(b, "ext-control-vols-reply");
|
||||
spa_pod_builder_push_struct(b, &f[2]);
|
||||
spa_pod_builder_string(b, p->name);
|
||||
spa_pod_builder_int(b, card->ext_volume.vols_replies[p->index].res);
|
||||
spa_pod_builder_push_array(b, &f[3]);
|
||||
for (uint32_t i = 0; i < card->ext_volume.vols_replies[p->index].channels; i++)
|
||||
spa_pod_builder_double(b, card->ext_volume.vols_replies[p->index].values[i]);
|
||||
spa_pod_builder_pop(b, &f[3]);
|
||||
spa_pod_builder_pop(b, &f[2]);
|
||||
|
||||
spa_pod_builder_string(b, "ext-control-mute-reply");
|
||||
spa_pod_builder_push_struct(b, &f[2]);
|
||||
spa_pod_builder_string(b, p->name);
|
||||
spa_pod_builder_int(b, card->ext_volume.mute_replies[p->index].res);
|
||||
spa_pod_builder_bool(b, card->ext_volume.mute_replies[p->index].mute);
|
||||
spa_pod_builder_pop(b, &f[2]);
|
||||
|
||||
spa_pod_builder_pop(b, &f[1]);
|
||||
return spa_pod_builder_pop(b, &f[0]);
|
||||
}
|
||||
|
||||
static struct acp_port *find_port_for_device(struct acp_card *card, struct acp_device *dev)
|
||||
{
|
||||
uint32_t i;
|
||||
|
|
@ -601,6 +642,16 @@ static int impl_enum_params(void *object, int seq,
|
|||
return -errno;
|
||||
break;
|
||||
|
||||
case SPA_PARAM_Props:
|
||||
if (result.index >= card->n_ports)
|
||||
return 0;
|
||||
|
||||
p = card->ports[result.index];
|
||||
if (SPA_FLAG_IS_SET(p->flags, ACP_PORT_HIDDEN))
|
||||
goto next;
|
||||
param = build_props(&b.b, card, p);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
|
@ -793,6 +844,165 @@ static bool check_active_profile_port(struct impl *this, uint32_t device, uint32
|
|||
return true;
|
||||
}
|
||||
|
||||
static int parse_ext_control_caps_reply (struct acp_card *card, struct spa_pod_struct *reply)
|
||||
{
|
||||
int changed = 0;
|
||||
struct spa_pod *it;
|
||||
bool res_parsed = false, caps_parsed = false;
|
||||
int res = 0, caps = 0;
|
||||
int field = 0;
|
||||
|
||||
SPA_POD_STRUCT_FOREACH(reply, it) {
|
||||
switch (field++) {
|
||||
case 0:
|
||||
if (spa_pod_get_int(it, &res) >= 0)
|
||||
res_parsed = true;
|
||||
break;
|
||||
case 1:
|
||||
if (spa_pod_get_int(it, &caps) >= 0)
|
||||
caps_parsed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (res_parsed && caps_parsed) {
|
||||
card->ext_volume.caps_reply.res = res;
|
||||
card->ext_volume.caps_reply.caps = caps;
|
||||
changed++;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static int parse_ext_control_vols_reply (struct acp_card *card, struct spa_pod_struct *reply)
|
||||
{
|
||||
int changed = 0;
|
||||
struct spa_pod *it;
|
||||
const char *route_name = NULL;
|
||||
bool res_parsed = false, vols_parsed = false;
|
||||
int res = 0;
|
||||
uint32_t channels;
|
||||
int64_t *vols;
|
||||
uint32_t port_index = ACP_INVALID_INDEX;
|
||||
int field = 0;
|
||||
|
||||
SPA_POD_STRUCT_FOREACH(reply, it) {
|
||||
switch (field++) {
|
||||
case 0:
|
||||
spa_pod_get_string(it, &route_name);
|
||||
break;
|
||||
case 1:
|
||||
if (spa_pod_get_int(it, &res) >= 0)
|
||||
res_parsed = true;
|
||||
break;
|
||||
case 2:
|
||||
vols_parsed = true;
|
||||
channels = SPA_POD_ARRAY_N_VALUES(SPA_POD_BODY(it));
|
||||
vols = SPA_POD_ARRAY_VALUES(SPA_POD_BODY(it));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
port_index = find_route_by_name(card, route_name);
|
||||
|
||||
if (port_index != ACP_INVALID_INDEX && res_parsed && vols_parsed) {
|
||||
card->ext_volume.vols_replies[port_index].res = res;
|
||||
card->ext_volume.vols_replies[port_index].channels = channels;
|
||||
for (uint32_t i = 0; i < channels; i++)
|
||||
card->ext_volume.vols_replies[port_index].values[i] = vols[i];
|
||||
changed++;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static int parse_ext_control_mute_reply (struct acp_card *card, struct spa_pod_struct *reply)
|
||||
{
|
||||
int changed = 0;
|
||||
struct spa_pod *it;
|
||||
const char *route_name = NULL;
|
||||
bool res_parsed = false, mute_parsed = false, mute = false;
|
||||
int res = 0;
|
||||
uint32_t port_index = ACP_INVALID_INDEX;
|
||||
int field = 0;
|
||||
|
||||
SPA_POD_STRUCT_FOREACH(reply, it) {
|
||||
switch (field++) {
|
||||
case 0:
|
||||
spa_pod_get_string(it, &route_name);
|
||||
break;
|
||||
case 1:
|
||||
if (spa_pod_get_int(it, &res) >= 0)
|
||||
res_parsed = true;
|
||||
break;
|
||||
case 2:
|
||||
if (spa_pod_get_bool(it, &mute) >= 0)
|
||||
mute_parsed = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
port_index = find_route_by_name(card, route_name);
|
||||
|
||||
if (port_index != ACP_INVALID_INDEX && res_parsed && mute_parsed) {
|
||||
card->ext_volume.mute_replies[port_index].res = res;
|
||||
card->ext_volume.mute_replies[port_index].mute = mute;
|
||||
changed++;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static int parse_prop_params(struct impl *this, struct spa_pod *params)
|
||||
{
|
||||
struct acp_card *card = this->card;
|
||||
struct spa_pod_parser prs;
|
||||
struct spa_pod_frame f;
|
||||
int changed = 0;
|
||||
|
||||
if (params == NULL)
|
||||
return 0;
|
||||
|
||||
spa_pod_parser_pod(&prs, params);
|
||||
if (spa_pod_parser_push_struct(&prs, &f) < 0)
|
||||
return 0;
|
||||
|
||||
while (true) {
|
||||
const char *name;
|
||||
struct spa_pod *pod;
|
||||
|
||||
if (spa_pod_parser_get_string(&prs, &name) < 0)
|
||||
break;
|
||||
|
||||
if (spa_pod_parser_get_pod(&prs, &pod) < 0)
|
||||
break;
|
||||
|
||||
if (spa_streq(name, "ext-control-caps-reply") && spa_pod_is_struct(pod)) {
|
||||
parse_ext_control_caps_reply(card, (struct spa_pod_struct *)pod);
|
||||
} else if (spa_streq(name, "ext-control-vols-reply") && spa_pod_is_struct(pod)) {
|
||||
parse_ext_control_vols_reply(card, (struct spa_pod_struct *)pod);
|
||||
} else if (spa_streq(name, "ext-control-mute-reply") && spa_pod_is_struct(pod)) {
|
||||
parse_ext_control_mute_reply(card, (struct spa_pod_struct *)pod);
|
||||
} else
|
||||
continue;
|
||||
|
||||
changed++;
|
||||
}
|
||||
|
||||
if (changed > 0) {
|
||||
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
|
||||
this->params[IDX_Props].user++;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static int impl_set_param(void *object,
|
||||
uint32_t id, uint32_t flags,
|
||||
const struct spa_pod *param)
|
||||
|
|
@ -879,6 +1089,25 @@ static int impl_set_param(void *object,
|
|||
emit_info(this, false);
|
||||
break;
|
||||
}
|
||||
case SPA_PARAM_Props:
|
||||
{
|
||||
struct spa_pod *params = NULL;
|
||||
|
||||
if (param == NULL)
|
||||
return 0;
|
||||
|
||||
if ((res = spa_pod_parse_object(param,
|
||||
SPA_TYPE_OBJECT_Props, NULL,
|
||||
SPA_PROP_params, SPA_POD_OPT_Pod(¶ms))) < 0) {
|
||||
spa_log_warn(this->log, "can't parse props");
|
||||
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
|
||||
return res;
|
||||
}
|
||||
|
||||
parse_prop_params(this, params);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
|
@ -1083,6 +1312,12 @@ static void on_mute_changed(void *data, struct acp_device *dev)
|
|||
spa_device_emit_event(&this->hooks, event);
|
||||
}
|
||||
|
||||
static void on_ext_vol_event_available(void *data, struct spa_event *event)
|
||||
{
|
||||
struct impl *this = data;
|
||||
spa_device_emit_event(&this->hooks, event);
|
||||
}
|
||||
|
||||
static const struct acp_card_events card_events = {
|
||||
ACP_VERSION_CARD_EVENTS,
|
||||
.props_changed = card_props_changed,
|
||||
|
|
@ -1092,6 +1327,7 @@ static const struct acp_card_events card_events = {
|
|||
.port_available = card_port_available,
|
||||
.volume_changed = on_volume_changed,
|
||||
.mute_changed = on_mute_changed,
|
||||
.ext_vol_event_available = on_ext_vol_event_available,
|
||||
};
|
||||
|
||||
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue