More session manager work

pulseaudio card is mapped to device
pulseaudio sink/source is mapped to an endpoint
prepare to map streams to card profiles
Add Route param to implement the endpoint routing later (ports)
Create an alsa endpoint for each device
Create one stream for each endpoint (Playback/Capture)

Implement create_link on the endpoint. The idea is to call
create link on the peer endpoint to complete the link. Remove
create_link on the session.

Add stream-monitor to turn pw_stream nodes into endpoints

Add a policy manager that tries to link endpoints

Use enum pw_direction for the endpoint direction. We can use the
media_class to determine if this is a pw_stream or not but it should
not really matter, you can link any output to any input.

Add autoconnect property for endpoints to make the policy connect.
This commit is contained in:
Wim Taymans 2019-11-13 09:38:40 +01:00
parent edd011605d
commit 3f3dfbc67e
27 changed files with 2338 additions and 439 deletions

View file

@ -48,16 +48,21 @@ struct endpoint {
struct spa_list link;
struct pw_properties *props;
struct alsa_object *obj;
struct alsa_node *obj;
struct spa_hook listener;
struct pw_client_endpoint_proxy *client_endpoint;
struct spa_hook client_endpoint_listener;
struct pw_endpoint_info client_endpoint_info;
struct pw_endpoint_info info;
unsigned int use_ucm:1;
snd_use_case_mgr_t *ucm;
struct spa_list stream_list;
struct spa_audio_info format;
unsigned int active:1;
};
struct stream {
@ -65,28 +70,25 @@ struct stream {
struct pw_properties *props;
struct pw_endpoint_stream_info info;
unsigned int active:1;
};
static int client_endpoint_set_id(void *object, uint32_t id)
{
struct endpoint *endpoint = object;
struct alsa_object *obj = endpoint->obj;
endpoint->client_endpoint_info.id = id;
endpoint->client_endpoint_info.name = (char*)pw_properties_get(endpoint->props, PW_KEY_ENDPOINT_NAME);
endpoint->client_endpoint_info.media_class = (char*)pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS);
endpoint->info.id = id;
pw_client_endpoint_proxy_update(endpoint->client_endpoint,
PW_CLIENT_ENDPOINT_UPDATE_INFO,
0, NULL,
&endpoint->client_endpoint_info);
&endpoint->info);
return 0;
}
static int client_endpoint_set_session_id(void *object, uint32_t id)
{
struct endpoint *endpoint = object;
endpoint->client_endpoint_info.session_id = id;
endpoint->info.session_id = id;
return 0;
}
@ -103,6 +105,56 @@ static int client_endpoint_stream_set_param(void *object, uint32_t stream_id,
return -ENOTSUP;
}
static int client_endpoint_create_link(void *object, const struct spa_dict *props)
{
struct endpoint *endpoint = object;
struct impl *impl = endpoint->obj->monitor->impl;
struct pw_properties *p;
char buf[1024];
struct spa_pod_builder b = { 0, };
struct spa_pod *param;
p = pw_properties_new_dict(props);
if (endpoint->info.direction == PW_DIRECTION_OUTPUT) {
pw_properties_setf(p, PW_KEY_LINK_OUTPUT_NODE, "%d", endpoint->obj->info->id);
pw_properties_setf(p, PW_KEY_LINK_OUTPUT_PORT, "-1");
} else {
pw_properties_setf(p, PW_KEY_LINK_INPUT_NODE, "%d", endpoint->obj->info->id);
pw_properties_setf(p, PW_KEY_LINK_INPUT_PORT, "-1");
}
if (!endpoint->active) {
endpoint->format.info.raw.rate = 48000;
spa_pod_builder_init(&b, buf, sizeof(buf));
param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &endpoint->format.info.raw);
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig,
SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(endpoint->info.direction),
SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(true),
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param));
if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG))
spa_debug_pod(2, NULL, param);
pw_node_proxy_set_param((struct pw_node_proxy*)endpoint->obj->proxy,
SPA_PARAM_PortConfig, 0, param);
endpoint->active = true;
}
pw_core_proxy_create_object(impl->core_proxy,
"link-factory",
PW_TYPE_INTERFACE_Link,
PW_VERSION_LINK_PROXY,
&p->dict, 0);
pw_properties_free(p);
return 0;
}
static const struct pw_client_endpoint_proxy_events client_endpoint_events = {
PW_VERSION_CLIENT_ENDPOINT_PROXY_EVENTS,
@ -110,17 +162,133 @@ static const struct pw_client_endpoint_proxy_events client_endpoint_events = {
.set_session_id = client_endpoint_set_session_id,
.set_param = client_endpoint_set_param,
.stream_set_param = client_endpoint_stream_set_param,
.create_link = client_endpoint_create_link,
};
/** fallback, one stream for each node */
static int setup_alsa_fallback_endpoint(struct endpoint *endpoint)
static void node_event_param(void *object, int seq,
uint32_t id, uint32_t index, uint32_t next,
const struct spa_pod *param)
{
struct endpoint *endpoint = object;
struct alsa_node *n = endpoint->obj;
struct impl *impl = n->monitor->impl;
struct spa_audio_info info = { 0, };
pw_log_debug(NAME" %p: param for node %d, %d", impl, n->info->id, id);
if (id != SPA_PARAM_EnumFormat)
goto error;
if (spa_format_parse(param, &info.media_type, &info.media_subtype) < 0)
goto error;
if (info.media_type != SPA_MEDIA_TYPE_audio ||
info.media_subtype != SPA_MEDIA_SUBTYPE_raw)
return;
spa_pod_object_fixate((struct spa_pod_object*)param);
if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG))
spa_debug_pod(2, NULL, param);
if (spa_format_audio_raw_parse(param, &info.info.raw) < 0)
goto error;
if (endpoint->format.info.raw.channels < info.info.raw.channels)
endpoint->format = info;
return;
error:
pw_log_warn("unhandled param:");
if (pw_log_level_enabled(SPA_LOG_LEVEL_WARN))
spa_debug_pod(2, NULL, param);
return;
}
static const struct pw_node_proxy_events endpoint_node_events = {
PW_VERSION_NODE_PROXY_EVENTS,
.param = node_event_param,
};
static struct endpoint *make_endpoint(struct alsa_node *obj)
{
struct impl *impl = obj->monitor->impl;
struct pw_properties *props;
struct endpoint *endpoint;
struct pw_proxy *proxy;
const char *str, *media_class = NULL, *name = NULL;
props = pw_properties_new(NULL, NULL);
if (props == NULL)
return NULL;
if (obj->props) {
if ((media_class = pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS)) != NULL)
pw_properties_set(props, PW_KEY_MEDIA_CLASS, media_class);
if ((str = pw_properties_get(obj->props, PW_KEY_PRIORITY_SESSION)) != NULL)
pw_properties_set(props, PW_KEY_PRIORITY_SESSION, str);
if ((name = pw_properties_get(obj->props, PW_KEY_NODE_DESCRIPTION)) != NULL)
pw_properties_set(props, PW_KEY_ENDPOINT_NAME, name);
}
if (obj->object && obj->object->props) {
if ((str = pw_properties_get(obj->object->props, PW_KEY_DEVICE_ICON_NAME)) != NULL)
pw_properties_set(props, PW_KEY_ENDPOINT_ICON_NAME, str);
}
proxy = pw_core_proxy_create_object(impl->core_proxy,
"client-endpoint",
PW_TYPE_INTERFACE_ClientEndpoint,
PW_VERSION_CLIENT_ENDPOINT_PROXY,
&props->dict, sizeof(*endpoint));
if (proxy == NULL) {
pw_properties_free(props);
return NULL;
}
endpoint = pw_proxy_get_user_data(proxy);
endpoint->obj = obj;
endpoint->props = props;
endpoint->client_endpoint = (struct pw_client_endpoint_proxy *) proxy;
endpoint->info.version = PW_VERSION_ENDPOINT_INFO;
endpoint->info.name = (char*)pw_properties_get(endpoint->props, PW_KEY_ENDPOINT_NAME);
endpoint->info.media_class = (char*)pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS);
endpoint->info.session_id = impl->client_session_info.id;
endpoint->info.direction = obj->direction;
endpoint->info.flags = 0;
endpoint->info.change_mask =
PW_ENDPOINT_CHANGE_MASK_STREAMS |
PW_ENDPOINT_CHANGE_MASK_SESSION |
PW_ENDPOINT_CHANGE_MASK_PROPS;
endpoint->info.n_streams = 1;
endpoint->info.props = &endpoint->props->dict;
spa_list_init(&endpoint->stream_list);
pw_client_endpoint_proxy_add_listener(endpoint->client_endpoint,
&endpoint->client_endpoint_listener,
&client_endpoint_events,
endpoint);
pw_proxy_add_object_listener(obj->proxy, &endpoint->listener, &endpoint_node_events, endpoint);
pw_node_proxy_enum_params((struct pw_node_proxy*)obj->proxy,
0, SPA_PARAM_EnumFormat,
0, -1, NULL);
return endpoint;
}
/** fallback, one stream for each node */
static int setup_alsa_fallback_endpoint(struct alsa_object *obj)
{
struct alsa_object *obj = endpoint->obj;
struct alsa_node *n;
const char *str;
spa_list_for_each(n, &obj->node_list, link) {
struct stream *s;
struct endpoint *endpoint;
endpoint = make_endpoint(n);
if (endpoint == NULL)
return -errno;
s = calloc(1, sizeof(*s));
if (s == NULL)
@ -133,15 +301,15 @@ static int setup_alsa_fallback_endpoint(struct endpoint *endpoint)
pw_properties_set(s->props, PW_KEY_MEDIA_CLASS, str);
if ((str = pw_properties_get(n->props, PW_KEY_PRIORITY_SESSION)) != NULL)
pw_properties_set(s->props, PW_KEY_PRIORITY_SESSION, str);
if ((str = pw_properties_get(n->props, PW_KEY_NODE_NAME)) != NULL)
pw_properties_set(s->props, PW_KEY_STREAM_NAME, str);
if ((str = pw_properties_get(n->props, PW_KEY_NODE_DESCRIPTION)) != NULL)
pw_properties_set(s->props, PW_KEY_STREAM_DESCRIPTION, str);
if (n->direction == PW_DIRECTION_OUTPUT)
pw_properties_set(s->props, PW_KEY_STREAM_NAME, "Playback");
else
pw_properties_set(s->props, PW_KEY_STREAM_NAME, "Capture");
s->info.version = PW_VERSION_ENDPOINT_STREAM_INFO;
s->info.id = n->id;
s->info.endpoint_id = endpoint->client_endpoint_info.id;
s->info.name = (char*)pw_properties_get(s->props, PW_KEY_STREAM_DESCRIPTION);
s->info.endpoint_id = endpoint->info.id;
s->info.name = (char*)pw_properties_get(s->props, PW_KEY_STREAM_NAME);
s->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS;
s->info.props = &s->props->dict;
@ -159,13 +327,13 @@ static int setup_alsa_fallback_endpoint(struct endpoint *endpoint)
*
* We create 1 stream for each verb + modifier combination
*/
static int setup_alsa_ucm_endpoint(struct endpoint *endpoint)
static int setup_alsa_ucm_endpoint(struct alsa_object *obj)
{
struct alsa_object *obj = endpoint->obj;
const char *str, *card_name = NULL;
char *name_free = NULL;
int i, res, num_verbs;
const char **verb_list = NULL;
snd_use_case_mgr_t *ucm;
card_name = pw_properties_get(obj->props, SPA_KEY_API_ALSA_CARD_NAME);
if (card_name == NULL &&
@ -179,12 +347,12 @@ static int setup_alsa_ucm_endpoint(struct endpoint *endpoint)
goto exit;
}
if ((res = snd_use_case_mgr_open(&endpoint->ucm, card_name)) < 0) {
if ((res = snd_use_case_mgr_open(&ucm, card_name)) < 0) {
pw_log_error("can not open UCM for %s: %s", card_name, snd_strerror(res));
goto exit;
}
num_verbs = snd_use_case_verb_list(endpoint->ucm, &verb_list);
num_verbs = snd_use_case_verb_list(ucm, &verb_list);
if (num_verbs < 0) {
res = num_verbs;
pw_log_error("UCM verb list not found for %s: %s", card_name, snd_strerror(num_verbs));
@ -195,15 +363,13 @@ static int setup_alsa_ucm_endpoint(struct endpoint *endpoint)
pw_log_debug("verb: %s", verb_list[i]);
}
endpoint->use_ucm = true;
snd_use_case_free_list(verb_list, num_verbs);
return 0;
res = -ENOTSUP;
close_exit:
snd_use_case_mgr_close(endpoint->ucm);
snd_use_case_mgr_close(ucm);
exit:
endpoint->ucm = NULL;
free(name_free);
return res;
@ -211,50 +377,10 @@ exit:
static int setup_alsa_endpoint(struct alsa_object *obj)
{
struct impl *impl = obj->monitor->impl;
int res;
struct pw_proxy *proxy;
struct endpoint *endpoint;
struct pw_properties *props;
const char *str;
props = pw_properties_new(NULL, NULL);
if (props == NULL)
return -errno;
if ((str = pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS)) != NULL)
pw_properties_set(props, PW_KEY_MEDIA_CLASS, str);
if ((str = pw_properties_get(obj->props, PW_KEY_DEVICE_DESCRIPTION)) != NULL)
pw_properties_set(props, PW_KEY_ENDPOINT_NAME, str);
if ((str = pw_properties_get(obj->props, PW_KEY_DEVICE_ICON_NAME)) != NULL)
pw_properties_set(props, PW_KEY_ENDPOINT_ICON_NAME, str);
proxy = pw_core_proxy_create_object(impl->core_proxy,
"client-endpoint",
PW_TYPE_INTERFACE_ClientEndpoint,
PW_VERSION_CLIENT_ENDPOINT_PROXY,
&props->dict, sizeof(*endpoint));
if (proxy == NULL) {
pw_properties_free(props);
return -errno;
}
endpoint = pw_proxy_get_user_data(proxy);
endpoint->obj = obj;
endpoint->props = props;
endpoint->client_endpoint = (struct pw_client_endpoint_proxy *) proxy;
endpoint->client_endpoint_info.version = PW_VERSION_ENDPOINT_INFO;
endpoint->client_endpoint_info.name = "name";
endpoint->client_endpoint_info.media_class = "media-class";
spa_list_init(&endpoint->stream_list);
pw_client_endpoint_proxy_add_listener(endpoint->client_endpoint,
&endpoint->client_endpoint_listener,
&client_endpoint_events,
endpoint);
if ((res = setup_alsa_ucm_endpoint(endpoint)) < 0)
res = setup_alsa_fallback_endpoint(endpoint);
if ((res = setup_alsa_ucm_endpoint(obj)) < 0)
res = setup_alsa_fallback_endpoint(obj);
return res;
}