mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-05 13:30:02 -05:00
1004 lines
25 KiB
C
1004 lines
25 KiB
C
|
|
/* PipeWire
|
||
|
|
*
|
||
|
|
* Copyright © 2019 Wim Taymans
|
||
|
|
*
|
||
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
|
* copy of this software and associated documentation files (the "Software"),
|
||
|
|
* to deal in the Software without restriction, including without limitation
|
||
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
||
|
|
* Software is furnished to do so, subject to the following conditions:
|
||
|
|
*
|
||
|
|
* The above copyright notice and this permission notice (including the next
|
||
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
||
|
|
* Software.
|
||
|
|
*
|
||
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||
|
|
* DEALINGS IN THE SOFTWARE.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <string.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#include <math.h>
|
||
|
|
#include <time.h>
|
||
|
|
|
||
|
|
#include "config.h"
|
||
|
|
|
||
|
|
#include <spa/node/node.h>
|
||
|
|
#include <spa/utils/hook.h>
|
||
|
|
#include <spa/param/audio/format-utils.h>
|
||
|
|
#include <spa/param/props.h>
|
||
|
|
#include <spa/debug/pod.h>
|
||
|
|
|
||
|
|
#include "pipewire/pipewire.h"
|
||
|
|
#include "pipewire/private.h"
|
||
|
|
#include "extensions/session-manager.h"
|
||
|
|
|
||
|
|
#define NAME "policy-ep"
|
||
|
|
|
||
|
|
#define DEFAULT_CHANNELS 2
|
||
|
|
#define DEFAULT_SAMPLERATE 48000
|
||
|
|
|
||
|
|
#define DEFAULT_IDLE_SECONDS 3
|
||
|
|
|
||
|
|
struct impl;
|
||
|
|
|
||
|
|
struct impl {
|
||
|
|
struct timespec now;
|
||
|
|
|
||
|
|
struct pw_core *core;
|
||
|
|
struct pw_remote *remote;
|
||
|
|
struct spa_hook remote_listener;
|
||
|
|
|
||
|
|
struct pw_core_proxy *core_proxy;
|
||
|
|
struct spa_hook core_listener;
|
||
|
|
|
||
|
|
struct pw_registry_proxy *registry_proxy;
|
||
|
|
struct spa_hook registry_listener;
|
||
|
|
|
||
|
|
struct pw_map globals;
|
||
|
|
|
||
|
|
struct spa_list client_list;
|
||
|
|
struct spa_list endpoint_list;
|
||
|
|
struct spa_list session_list;
|
||
|
|
int seq;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct object {
|
||
|
|
struct impl *impl;
|
||
|
|
uint32_t id;
|
||
|
|
uint32_t type;
|
||
|
|
struct pw_proxy *proxy;
|
||
|
|
struct spa_hook listener;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct client {
|
||
|
|
struct object obj;
|
||
|
|
|
||
|
|
struct spa_list l;
|
||
|
|
|
||
|
|
struct spa_hook listener;
|
||
|
|
struct pw_client_info *info;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct session {
|
||
|
|
struct object obj;
|
||
|
|
|
||
|
|
struct spa_list l;
|
||
|
|
|
||
|
|
struct spa_hook listener;
|
||
|
|
struct pw_session_info *info;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct endpoint {
|
||
|
|
struct object obj;
|
||
|
|
|
||
|
|
struct spa_list l;
|
||
|
|
|
||
|
|
struct spa_hook listener;
|
||
|
|
struct pw_endpoint_info *info;
|
||
|
|
|
||
|
|
struct endpoint *peer;
|
||
|
|
struct session *session;
|
||
|
|
|
||
|
|
uint32_t client_id;
|
||
|
|
int32_t priority;
|
||
|
|
|
||
|
|
struct spa_list stream_list;
|
||
|
|
|
||
|
|
enum pw_direction direction;
|
||
|
|
#define ENDPOINT_TYPE_UNKNOWN 0
|
||
|
|
#define ENDPOINT_TYPE_STREAM 1
|
||
|
|
#define ENDPOINT_TYPE_DEVICE 2
|
||
|
|
uint32_t type;
|
||
|
|
char *media;
|
||
|
|
|
||
|
|
uint32_t media_type;
|
||
|
|
uint32_t media_subtype;
|
||
|
|
struct spa_audio_info_raw format;
|
||
|
|
|
||
|
|
uint64_t plugged;
|
||
|
|
unsigned int exclusive:1;
|
||
|
|
unsigned int enabled:1;
|
||
|
|
unsigned int busy:1;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct stream {
|
||
|
|
struct object obj;
|
||
|
|
|
||
|
|
struct spa_list l;
|
||
|
|
enum pw_direction direction;
|
||
|
|
struct pw_endpoint_stream_info *info;
|
||
|
|
struct endpoint *endpoint;
|
||
|
|
#define STREAM_FLAG_NONE 0
|
||
|
|
#define STREAM_FLAG_DSP (1<<0)
|
||
|
|
#define STREAM_FLAG_SKIP (1<<1)
|
||
|
|
uint32_t flags;
|
||
|
|
|
||
|
|
struct spa_hook listener;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct link {
|
||
|
|
struct object obj;
|
||
|
|
struct stream *out;
|
||
|
|
struct stream *in;
|
||
|
|
};
|
||
|
|
|
||
|
|
static void add_object(struct impl *impl, struct object *obj)
|
||
|
|
{
|
||
|
|
size_t size = pw_map_get_size(&impl->globals);
|
||
|
|
while (obj->id > size)
|
||
|
|
pw_map_insert_at(&impl->globals, size++, NULL);
|
||
|
|
pw_map_insert_at(&impl->globals, obj->id, obj);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void remove_object(struct impl *impl, struct object *obj)
|
||
|
|
{
|
||
|
|
pw_map_insert_at(&impl->globals, obj->id, NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void *find_object(struct impl *impl, uint32_t id)
|
||
|
|
{
|
||
|
|
void *obj;
|
||
|
|
if ((obj = pw_map_lookup(&impl->globals, id)) != NULL)
|
||
|
|
return obj;
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void schedule_rescan(struct impl *impl)
|
||
|
|
{
|
||
|
|
if (impl->core_proxy)
|
||
|
|
impl->seq = pw_core_proxy_sync(impl->core_proxy, 0, impl->seq);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void endpoint_event_info(void *object, const struct pw_endpoint_info *update)
|
||
|
|
{
|
||
|
|
struct endpoint *e = object;
|
||
|
|
struct impl *impl = e->obj.impl;
|
||
|
|
struct pw_endpoint_info *info = e->info;
|
||
|
|
const char *str;
|
||
|
|
|
||
|
|
pw_log_debug(NAME" %p: info for endpoint %d type %d", impl, e->obj.id, e->type);
|
||
|
|
|
||
|
|
if (info == NULL && update) {
|
||
|
|
info = e->info = calloc(1, sizeof(*info));
|
||
|
|
info->id = update->id;
|
||
|
|
info->name = update->name ? strdup(update->name) : NULL;
|
||
|
|
info->media_class = update->media_class ? strdup(update->media_class) : NULL;
|
||
|
|
info->direction = update->direction;
|
||
|
|
info->flags = update->flags;
|
||
|
|
}
|
||
|
|
info->change_mask = update->change_mask;
|
||
|
|
if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION) {
|
||
|
|
info->session_id = update->session_id;
|
||
|
|
e->session = find_object(impl, info->session_id);
|
||
|
|
}
|
||
|
|
if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) {
|
||
|
|
if (info->props)
|
||
|
|
pw_properties_free ((struct pw_properties *)info->props);
|
||
|
|
info->props = (struct spa_dict *) pw_properties_new_dict (update->props);
|
||
|
|
if ((str = spa_dict_lookup(info->props, PW_KEY_PRIORITY_SESSION)) != NULL)
|
||
|
|
e->priority = pw_properties_parse_int(str);
|
||
|
|
}
|
||
|
|
e->enabled = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void endpoint_event_param(void *object, int seq,
|
||
|
|
uint32_t id, uint32_t index, uint32_t next,
|
||
|
|
const struct spa_pod *param)
|
||
|
|
{
|
||
|
|
struct endpoint *e = object;
|
||
|
|
struct impl *impl = e->obj.impl;
|
||
|
|
pw_log_debug(NAME" %p: param for endpoint %d, %d", impl, e->obj.id, id);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_endpoint_proxy_events endpoint_events = {
|
||
|
|
PW_VERSION_ENDPOINT_PROXY_EVENTS,
|
||
|
|
.info = endpoint_event_info,
|
||
|
|
.param = endpoint_event_param,
|
||
|
|
};
|
||
|
|
|
||
|
|
static void endpoint_proxy_destroy(void *data)
|
||
|
|
{
|
||
|
|
struct endpoint *e = data;
|
||
|
|
struct impl *impl = e->obj.impl;
|
||
|
|
struct stream *s, *t;
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: proxy destroy endpoint %d", impl, e->obj.id);
|
||
|
|
|
||
|
|
spa_list_remove(&e->l);
|
||
|
|
|
||
|
|
spa_list_for_each_safe(s, t, &e->stream_list, l) {
|
||
|
|
spa_list_remove(&s->l);
|
||
|
|
s->endpoint = NULL;
|
||
|
|
}
|
||
|
|
free(e->media);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_proxy_events endpoint_proxy_events = {
|
||
|
|
PW_VERSION_PROXY_EVENTS,
|
||
|
|
.destroy = endpoint_proxy_destroy,
|
||
|
|
};
|
||
|
|
|
||
|
|
static int
|
||
|
|
handle_endpoint(struct impl *impl, uint32_t id,
|
||
|
|
uint32_t type, const struct spa_dict *props)
|
||
|
|
{
|
||
|
|
const char *str, *media_class;
|
||
|
|
enum pw_direction direction;
|
||
|
|
struct pw_proxy *p;
|
||
|
|
struct endpoint *ep;
|
||
|
|
uint32_t client_id = SPA_ID_INVALID;
|
||
|
|
|
||
|
|
if (props) {
|
||
|
|
if ((str = spa_dict_lookup(props, PW_KEY_CLIENT_ID)) != NULL)
|
||
|
|
client_id = atoi(str);
|
||
|
|
}
|
||
|
|
|
||
|
|
p = pw_registry_proxy_bind(impl->registry_proxy,
|
||
|
|
id, type, PW_VERSION_ENDPOINT_PROXY,
|
||
|
|
sizeof(struct endpoint));
|
||
|
|
|
||
|
|
ep = pw_proxy_get_user_data(p);
|
||
|
|
ep->obj.impl = impl;
|
||
|
|
ep->obj.id = id;
|
||
|
|
ep->obj.type = type;
|
||
|
|
ep->obj.proxy = p;
|
||
|
|
ep->client_id = client_id;
|
||
|
|
spa_list_init(&ep->stream_list);
|
||
|
|
pw_proxy_add_listener(p, &ep->obj.listener, &endpoint_proxy_events, ep);
|
||
|
|
pw_proxy_add_object_listener(p, &ep->listener, &endpoint_events, ep);
|
||
|
|
add_object(impl, &ep->obj);
|
||
|
|
spa_list_append(&impl->endpoint_list, &ep->l);
|
||
|
|
ep->type = ENDPOINT_TYPE_UNKNOWN;
|
||
|
|
|
||
|
|
media_class = props ? spa_dict_lookup(props, PW_KEY_MEDIA_CLASS) : NULL;
|
||
|
|
|
||
|
|
pw_log_debug(NAME" %p: endpoint "PW_KEY_MEDIA_CLASS" %s", impl, media_class);
|
||
|
|
|
||
|
|
if (media_class == NULL)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (strstr(media_class, "Stream/") == media_class) {
|
||
|
|
media_class += strlen("Stream/");
|
||
|
|
|
||
|
|
if (strstr(media_class, "Output/") == media_class) {
|
||
|
|
direction = PW_DIRECTION_OUTPUT;
|
||
|
|
media_class += strlen("Output/");
|
||
|
|
}
|
||
|
|
else if (strstr(media_class, "Input/") == media_class) {
|
||
|
|
direction = PW_DIRECTION_INPUT;
|
||
|
|
media_class += strlen("Input/");
|
||
|
|
}
|
||
|
|
else
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
ep->direction = direction;
|
||
|
|
ep->type = ENDPOINT_TYPE_STREAM;
|
||
|
|
ep->media = strdup(media_class);
|
||
|
|
pw_log_debug(NAME "%p: endpoint %d is stream %s", impl, id, ep->media);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
if (strstr(media_class, "Audio/") == media_class) {
|
||
|
|
media_class += strlen("Audio/");
|
||
|
|
}
|
||
|
|
else if (strstr(media_class, "Video/") == media_class) {
|
||
|
|
media_class += strlen("Video/");
|
||
|
|
}
|
||
|
|
else
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (strcmp(media_class, "Sink") == 0)
|
||
|
|
direction = PW_DIRECTION_OUTPUT;
|
||
|
|
else if (strcmp(media_class, "Source") == 0)
|
||
|
|
direction = PW_DIRECTION_INPUT;
|
||
|
|
else
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
ep->direction = direction;
|
||
|
|
ep->type = ENDPOINT_TYPE_DEVICE;
|
||
|
|
|
||
|
|
pw_log_debug(NAME" %p: endpoint %d prio:%d", impl, id, ep->priority);
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void stream_event_info(void *object, const struct pw_endpoint_stream_info *info)
|
||
|
|
{
|
||
|
|
struct stream *s = object;
|
||
|
|
pw_log_debug(NAME" %p: info for stream %d", s->obj.impl, s->obj.id);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void stream_event_param(void *object, int seq,
|
||
|
|
uint32_t id, uint32_t index, uint32_t next,
|
||
|
|
const struct spa_pod *param)
|
||
|
|
{
|
||
|
|
struct stream *s = object;
|
||
|
|
struct endpoint *ep = s->endpoint;
|
||
|
|
struct spa_audio_info_raw info = { 0, };
|
||
|
|
|
||
|
|
pw_log_debug(NAME" %p: param for stream %d", s->obj.impl, s->obj.id);
|
||
|
|
|
||
|
|
if (ep == NULL)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (id != SPA_PARAM_EnumFormat)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (spa_format_parse(param, &ep->media_type, &ep->media_subtype) < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (ep->media_type != SPA_MEDIA_TYPE_audio ||
|
||
|
|
ep->media_subtype != SPA_MEDIA_SUBTYPE_raw)
|
||
|
|
return;
|
||
|
|
|
||
|
|
spa_pod_fixate((struct spa_pod*)param);
|
||
|
|
|
||
|
|
if (spa_format_audio_raw_parse(param, &info) < 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (info.channels > ep->format.channels)
|
||
|
|
ep->format = info;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_endpoint_stream_proxy_events stream_events = {
|
||
|
|
PW_VERSION_ENDPOINT_STREAM_PROXY_EVENTS,
|
||
|
|
.info = stream_event_info,
|
||
|
|
.param = stream_event_param,
|
||
|
|
};
|
||
|
|
|
||
|
|
static void stream_proxy_destroy(void *data)
|
||
|
|
{
|
||
|
|
struct stream *s = data;
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: proxy destroy stream %d", s->obj.impl, s->obj.id);
|
||
|
|
|
||
|
|
if (s->endpoint) {
|
||
|
|
spa_list_remove(&s->l);
|
||
|
|
s->endpoint = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_proxy_events stream_proxy_events = {
|
||
|
|
PW_VERSION_PROXY_EVENTS,
|
||
|
|
.destroy = stream_proxy_destroy,
|
||
|
|
};
|
||
|
|
|
||
|
|
static int
|
||
|
|
handle_stream(struct impl *impl, uint32_t id, uint32_t type,
|
||
|
|
const struct spa_dict *props)
|
||
|
|
{
|
||
|
|
struct stream *s;
|
||
|
|
struct pw_proxy *p;
|
||
|
|
struct endpoint *ep;
|
||
|
|
const char *str;
|
||
|
|
uint32_t endpoint_id;
|
||
|
|
|
||
|
|
if (props == NULL || (str = spa_dict_lookup(props, PW_KEY_ENDPOINT_ID)) == NULL)
|
||
|
|
return -EINVAL;
|
||
|
|
|
||
|
|
endpoint_id = atoi(str);
|
||
|
|
|
||
|
|
if ((ep = find_object(impl, endpoint_id)) == NULL)
|
||
|
|
return -ESRCH;
|
||
|
|
|
||
|
|
p = pw_registry_proxy_bind(impl->registry_proxy,
|
||
|
|
id, type, PW_VERSION_ENDPOINT_STREAM_PROXY,
|
||
|
|
sizeof(struct stream));
|
||
|
|
|
||
|
|
s = pw_proxy_get_user_data(p);
|
||
|
|
s->obj.impl = impl;
|
||
|
|
s->obj.id = id;
|
||
|
|
s->obj.type = type;
|
||
|
|
s->obj.proxy = p;
|
||
|
|
s->endpoint = ep;
|
||
|
|
s->direction = ep->direction;
|
||
|
|
|
||
|
|
pw_proxy_add_listener(p, &s->obj.listener, &stream_proxy_events, s);
|
||
|
|
pw_proxy_add_object_listener(p, &s->listener, &stream_events, s);
|
||
|
|
add_object(impl, &s->obj);
|
||
|
|
|
||
|
|
spa_list_append(&ep->stream_list, &s->l);
|
||
|
|
|
||
|
|
pw_log_debug(NAME" %p: new stream %d for endpoint %d type %d %08x", impl, id, endpoint_id,
|
||
|
|
ep->type, s->flags);
|
||
|
|
|
||
|
|
if (ep->type == ENDPOINT_TYPE_DEVICE) {
|
||
|
|
pw_endpoint_stream_proxy_enum_params((struct pw_endpoint_stream_proxy*)p,
|
||
|
|
0, SPA_PARAM_EnumFormat,
|
||
|
|
0, -1, NULL);
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void client_event_info(void *object, const struct pw_client_info *info)
|
||
|
|
{
|
||
|
|
struct client *c = object;
|
||
|
|
uint32_t i;
|
||
|
|
|
||
|
|
pw_log_debug(NAME" %p: info for client %d", c->obj.impl, c->obj.id);
|
||
|
|
c->info = pw_client_info_update(c->info, info);
|
||
|
|
for (i = 0; i < info->props->n_items; i++)
|
||
|
|
pw_log_debug(NAME" %p: %s = %s", c,
|
||
|
|
info->props->items[i].key,
|
||
|
|
info->props->items[i].value);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_client_proxy_events client_events = {
|
||
|
|
PW_VERSION_CLIENT_PROXY_EVENTS,
|
||
|
|
.info = client_event_info,
|
||
|
|
};
|
||
|
|
|
||
|
|
static void client_proxy_destroy(void *data)
|
||
|
|
{
|
||
|
|
struct client *c = data;
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: proxy destroy client %d", c->obj.impl, c->obj.id);
|
||
|
|
|
||
|
|
spa_list_remove(&c->l);
|
||
|
|
if (c->info)
|
||
|
|
pw_client_info_free(c->info);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_proxy_events client_proxy_events = {
|
||
|
|
PW_VERSION_PROXY_EVENTS,
|
||
|
|
.destroy = client_proxy_destroy,
|
||
|
|
};
|
||
|
|
|
||
|
|
static int
|
||
|
|
handle_client(struct impl *impl, uint32_t id,
|
||
|
|
uint32_t type, const struct spa_dict *props)
|
||
|
|
{
|
||
|
|
struct pw_proxy *p;
|
||
|
|
struct client *client;
|
||
|
|
struct pw_permission perms[2];
|
||
|
|
const char *str;
|
||
|
|
|
||
|
|
p = pw_registry_proxy_bind(impl->registry_proxy,
|
||
|
|
id, type, PW_VERSION_CLIENT_PROXY,
|
||
|
|
sizeof(struct client));
|
||
|
|
|
||
|
|
client = pw_proxy_get_user_data(p);
|
||
|
|
client->obj.impl = impl;
|
||
|
|
client->obj.id = id;
|
||
|
|
client->obj.type = type;
|
||
|
|
client->obj.proxy = p;
|
||
|
|
|
||
|
|
pw_proxy_add_listener(p, &client->obj.listener, &client_proxy_events, client);
|
||
|
|
pw_proxy_add_object_listener(p, &client->listener, &client_events, client);
|
||
|
|
add_object(impl, &client->obj);
|
||
|
|
spa_list_append(&impl->client_list, &client->l);
|
||
|
|
|
||
|
|
if (props == NULL)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
str = spa_dict_lookup(props, PW_KEY_ACCESS);
|
||
|
|
if (str == NULL)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (strcmp(str, "restricted") == 0) {
|
||
|
|
perms[0] = PW_PERMISSION_INIT(-1, PW_PERM_RWX);
|
||
|
|
pw_client_proxy_update_permissions((struct pw_client_proxy*)p,
|
||
|
|
1, perms);
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void session_event_info(void *object, const struct pw_session_info *info)
|
||
|
|
{
|
||
|
|
struct session *c = object;
|
||
|
|
pw_log_debug(NAME" %p: info for session %d", c->obj.impl, c->obj.id);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_session_proxy_events session_events = {
|
||
|
|
PW_VERSION_SESSION_PROXY_EVENTS,
|
||
|
|
.info = session_event_info,
|
||
|
|
};
|
||
|
|
|
||
|
|
static void session_proxy_destroy(void *data)
|
||
|
|
{
|
||
|
|
struct session *c = data;
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: proxy destroy session %d", c->obj.impl, c->obj.id);
|
||
|
|
|
||
|
|
spa_list_remove(&c->l);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_proxy_events session_proxy_events = {
|
||
|
|
PW_VERSION_PROXY_EVENTS,
|
||
|
|
.destroy = session_proxy_destroy,
|
||
|
|
};
|
||
|
|
|
||
|
|
static int
|
||
|
|
handle_session(struct impl *impl, uint32_t id,
|
||
|
|
uint32_t type, const struct spa_dict *props)
|
||
|
|
{
|
||
|
|
struct pw_proxy *p;
|
||
|
|
struct session *session;
|
||
|
|
|
||
|
|
p = pw_registry_proxy_bind(impl->registry_proxy,
|
||
|
|
id, type, PW_VERSION_SESSION_PROXY,
|
||
|
|
sizeof(struct session));
|
||
|
|
|
||
|
|
session = pw_proxy_get_user_data(p);
|
||
|
|
session->obj.impl = impl;
|
||
|
|
session->obj.id = id;
|
||
|
|
session->obj.type = type;
|
||
|
|
session->obj.proxy = p;
|
||
|
|
|
||
|
|
pw_proxy_add_listener(p, &session->obj.listener, &session_proxy_events, session);
|
||
|
|
pw_proxy_add_object_listener(p, &session->listener, &session_events, session);
|
||
|
|
add_object(impl, &session->obj);
|
||
|
|
spa_list_append(&impl->session_list, &session->l);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
registry_global(void *data,uint32_t id,
|
||
|
|
uint32_t permissions, uint32_t type, uint32_t version,
|
||
|
|
const struct spa_dict *props)
|
||
|
|
{
|
||
|
|
struct impl *impl = data;
|
||
|
|
int res;
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: new global '%d' %d", impl, id, type);
|
||
|
|
|
||
|
|
switch (type) {
|
||
|
|
case PW_TYPE_INTERFACE_Client:
|
||
|
|
res = handle_client(impl, id, type, props);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PW_TYPE_INTERFACE_Session:
|
||
|
|
res = handle_session(impl, id, type, props);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PW_TYPE_INTERFACE_Endpoint:
|
||
|
|
res = handle_endpoint(impl, id, type, props);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PW_TYPE_INTERFACE_EndpointStream:
|
||
|
|
res = handle_stream(impl, id, type, props);
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
res = 0;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (res < 0) {
|
||
|
|
pw_log_warn(NAME" %p: can't handle global %d", impl, id);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
schedule_rescan(impl);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
registry_global_remove(void *data, uint32_t id)
|
||
|
|
{
|
||
|
|
struct impl *impl = data;
|
||
|
|
struct object *obj;
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: remove global '%d'", impl, id);
|
||
|
|
|
||
|
|
if ((obj = find_object(impl, id)) == NULL)
|
||
|
|
return;
|
||
|
|
|
||
|
|
remove_object(impl, obj);
|
||
|
|
schedule_rescan(impl);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_registry_proxy_events registry_events = {
|
||
|
|
PW_VERSION_REGISTRY_PROXY_EVENTS,
|
||
|
|
.global = registry_global,
|
||
|
|
.global_remove = registry_global_remove,
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
struct find_data {
|
||
|
|
struct impl *impl;
|
||
|
|
uint32_t path_id;
|
||
|
|
const char *media_class;
|
||
|
|
struct endpoint *endpoint;
|
||
|
|
bool exclusive;
|
||
|
|
int priority;
|
||
|
|
uint64_t plugged;
|
||
|
|
};
|
||
|
|
|
||
|
|
static int find_endpoint(void *data, struct endpoint *endpoint)
|
||
|
|
{
|
||
|
|
struct find_data *find = data;
|
||
|
|
struct impl *impl = find->impl;
|
||
|
|
const struct spa_dict *props;
|
||
|
|
const char *str;
|
||
|
|
int priority = 0;
|
||
|
|
uint64_t plugged = 0;
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: looking at endpoint '%d' enabled:%d busy:%d exclusive:%d",
|
||
|
|
impl, endpoint->obj.id, endpoint->enabled, endpoint->busy, endpoint->exclusive);
|
||
|
|
|
||
|
|
if (!endpoint->enabled)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (find->path_id != SPA_ID_INVALID && endpoint->obj.id != find->path_id)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (find->path_id == SPA_ID_INVALID) {
|
||
|
|
if (endpoint->info == NULL ||
|
||
|
|
(props = endpoint->info->props) == NULL)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if ((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) == NULL)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (strcmp(str, find->media_class) != 0)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
plugged = endpoint->plugged;
|
||
|
|
priority = endpoint->priority;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((find->exclusive && endpoint->busy) || endpoint->exclusive) {
|
||
|
|
pw_log_debug(NAME " %p: endpoint '%d' in use", impl, endpoint->obj.id);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: found endpoint '%d' %"PRIu64" prio:%d", impl,
|
||
|
|
endpoint->obj.id, plugged, priority);
|
||
|
|
|
||
|
|
if (find->endpoint == NULL ||
|
||
|
|
priority > find->priority ||
|
||
|
|
(priority == find->priority && plugged > find->plugged)) {
|
||
|
|
pw_log_debug(NAME " %p: new best %d %" PRIu64, impl, priority, plugged);
|
||
|
|
find->endpoint = endpoint;
|
||
|
|
find->priority = priority;
|
||
|
|
find->plugged = plugged;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int link_endpoints(struct endpoint *endpoint, enum pw_direction direction, struct endpoint *peer, int max)
|
||
|
|
{
|
||
|
|
struct impl *impl = peer->obj.impl;
|
||
|
|
struct stream *s;
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: link endpoints %d %d %d", impl, max, endpoint->obj.id, peer->obj.id);
|
||
|
|
|
||
|
|
if (endpoint->session == NULL) {
|
||
|
|
pw_log_debug(NAME " %p: endpoint has no session", impl);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
spa_list_for_each(s, &endpoint->stream_list, l) {
|
||
|
|
struct pw_properties *props;
|
||
|
|
|
||
|
|
pw_log_debug(NAME " %p: stream %p: %d %d", impl, s, s->direction, s->flags);
|
||
|
|
|
||
|
|
if (s->direction == direction)
|
||
|
|
continue;
|
||
|
|
if (s->flags & STREAM_FLAG_SKIP)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if (max-- == 0)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
props = pw_properties_new(NULL, NULL);
|
||
|
|
if (s->direction == PW_DIRECTION_OUTPUT) {
|
||
|
|
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", endpoint->obj.id);
|
||
|
|
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", s->obj.id);
|
||
|
|
pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", peer->obj.id);
|
||
|
|
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", -1);
|
||
|
|
pw_log_debug(NAME " %p: stream %d:%d -> endpoint %d", impl,
|
||
|
|
endpoint->obj.id, s->obj.id, peer->obj.id);
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", peer->obj.id);
|
||
|
|
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", -1);
|
||
|
|
pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", endpoint->obj.id);
|
||
|
|
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", s->obj.id);
|
||
|
|
pw_log_debug(NAME " %p: endpoint %d -> stream %d:%d", impl,
|
||
|
|
peer->obj.id, endpoint->obj.id, s->obj.id);
|
||
|
|
}
|
||
|
|
|
||
|
|
pw_endpoint_proxy_create_link((struct pw_endpoint_proxy*)endpoint->obj.proxy,
|
||
|
|
&props->dict);
|
||
|
|
|
||
|
|
pw_properties_free(props);
|
||
|
|
}
|
||
|
|
endpoint->peer = peer;
|
||
|
|
peer->peer = endpoint;
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int rescan_endpoint(struct impl *impl, struct endpoint *ep)
|
||
|
|
{
|
||
|
|
struct spa_dict *props;
|
||
|
|
const char *str, *media, *category, *role;
|
||
|
|
bool exclusive;
|
||
|
|
struct find_data find;
|
||
|
|
struct pw_endpoint_info *info;
|
||
|
|
struct endpoint *peer;
|
||
|
|
enum pw_direction direction;
|
||
|
|
|
||
|
|
if (ep->type == ENDPOINT_TYPE_DEVICE)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
if (ep->info == NULL || ep->info->props == NULL) {
|
||
|
|
pw_log_debug(NAME " %p: endpoint %d has no properties", impl, ep->obj.id);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ep->peer != NULL)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
info = ep->info;
|
||
|
|
props = info->props;
|
||
|
|
|
||
|
|
str = spa_dict_lookup(props, PW_KEY_ENDPOINT_AUTOCONNECT);
|
||
|
|
if (str == NULL || !pw_properties_parse_bool(str)) {
|
||
|
|
pw_log_debug(NAME" %p: endpoint %d does not need autoconnect", impl, ep->obj.id);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((media = spa_dict_lookup(props, PW_KEY_MEDIA_TYPE)) == NULL)
|
||
|
|
media = ep->media;
|
||
|
|
if (media == NULL) {
|
||
|
|
pw_log_debug(NAME" %p: endpoint %d has unknown media", impl, ep->obj.id);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
spa_zero(find);
|
||
|
|
|
||
|
|
if ((category = spa_dict_lookup(props, PW_KEY_MEDIA_CATEGORY)) == NULL) {
|
||
|
|
pw_log_debug(NAME" %p: endpoint %d find category",
|
||
|
|
impl, ep->obj.id);
|
||
|
|
if (ep->direction == PW_DIRECTION_INPUT) {
|
||
|
|
category = "Capture";
|
||
|
|
} else if (ep->direction == PW_DIRECTION_OUTPUT) {
|
||
|
|
category = "Playback";
|
||
|
|
} else {
|
||
|
|
pw_log_warn(NAME" %p: endpoint %d can't determine category",
|
||
|
|
impl, ep->obj.id);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((role = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE)) == NULL) {
|
||
|
|
if (strcmp(media, "Audio") == 0) {
|
||
|
|
if (strcmp(category, "Duplex") == 0)
|
||
|
|
role = "Communication";
|
||
|
|
else if (strcmp(category, "Capture") == 0)
|
||
|
|
role = "Production";
|
||
|
|
else
|
||
|
|
role = "Music";
|
||
|
|
}
|
||
|
|
else if (strcmp(media, "Video") == 0) {
|
||
|
|
if (strcmp(category, "Duplex") == 0)
|
||
|
|
role = "Communication";
|
||
|
|
else if (strcmp(category, "Capture") == 0)
|
||
|
|
role = "Camera";
|
||
|
|
else
|
||
|
|
role = "Video";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((str = spa_dict_lookup(props, PW_KEY_NODE_EXCLUSIVE)) != NULL)
|
||
|
|
exclusive = pw_properties_parse_bool(str);
|
||
|
|
else
|
||
|
|
exclusive = false;
|
||
|
|
|
||
|
|
if (strcmp(media, "Audio") == 0) {
|
||
|
|
if (strcmp(category, "Playback") == 0)
|
||
|
|
find.media_class = "Audio/Sink";
|
||
|
|
else if (strcmp(category, "Capture") == 0)
|
||
|
|
find.media_class = "Audio/Source";
|
||
|
|
else {
|
||
|
|
pw_log_debug(NAME" %p: endpoint %d unhandled category %s",
|
||
|
|
impl, ep->obj.id, category);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (strcmp(media, "Video") == 0) {
|
||
|
|
if (strcmp(category, "Capture") == 0)
|
||
|
|
find.media_class = "Video/Source";
|
||
|
|
else {
|
||
|
|
pw_log_debug(NAME" %p: endpoint %d unhandled category %s",
|
||
|
|
impl, ep->obj.id, category);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
pw_log_debug(NAME" %p: endpoint %d unhandled media %s",
|
||
|
|
impl, ep->obj.id, media);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (strcmp(category, "Capture") == 0)
|
||
|
|
direction = PW_DIRECTION_OUTPUT;
|
||
|
|
else if (strcmp(category, "Playback") == 0)
|
||
|
|
direction = PW_DIRECTION_INPUT;
|
||
|
|
else {
|
||
|
|
pw_log_debug(NAME" %p: endpoint %d unhandled category %s",
|
||
|
|
impl, ep->obj.id, category);
|
||
|
|
return -EINVAL;
|
||
|
|
}
|
||
|
|
|
||
|
|
str = spa_dict_lookup(props, PW_KEY_NODE_TARGET);
|
||
|
|
if (str != NULL)
|
||
|
|
find.path_id = atoi(str);
|
||
|
|
else
|
||
|
|
find.path_id = SPA_ID_INVALID;
|
||
|
|
|
||
|
|
pw_log_info(NAME " %p: '%s' '%s' '%s' exclusive:%d target %d", impl,
|
||
|
|
media, category, role, exclusive, find.path_id);
|
||
|
|
|
||
|
|
find.impl = impl;
|
||
|
|
find.exclusive = exclusive;
|
||
|
|
|
||
|
|
spa_list_for_each(peer, &impl->endpoint_list, l)
|
||
|
|
find_endpoint(&find, peer);
|
||
|
|
|
||
|
|
if (find.endpoint == NULL && find.path_id != SPA_ID_INVALID) {
|
||
|
|
pw_log_debug(NAME " %p: no endpoint found for %d, try endpoint", impl, ep->obj.id);
|
||
|
|
|
||
|
|
if ((peer = find_object(impl, find.path_id)) != NULL) {
|
||
|
|
if (peer->obj.type == PW_TYPE_INTERFACE_Endpoint)
|
||
|
|
goto do_link;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
str = spa_dict_lookup(props, PW_KEY_NODE_DONT_RECONNECT);
|
||
|
|
if (str != NULL && pw_properties_parse_bool(str)) {
|
||
|
|
pw_registry_proxy_destroy(impl->registry_proxy, ep->obj.id);
|
||
|
|
return -ENOENT;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (find.endpoint == NULL) {
|
||
|
|
struct client *client;
|
||
|
|
|
||
|
|
pw_log_warn(NAME " %p: no endpoint found for %d", impl, ep->obj.id);
|
||
|
|
|
||
|
|
client = find_object(impl, ep->client_id);
|
||
|
|
if (client && client->obj.type == PW_TYPE_INTERFACE_Client) {
|
||
|
|
pw_client_proxy_error((struct pw_client_proxy*)client->obj.proxy,
|
||
|
|
ep->obj.id, -ENOENT, "no endpoint available");
|
||
|
|
}
|
||
|
|
return -ENOENT;
|
||
|
|
}
|
||
|
|
peer = find.endpoint;
|
||
|
|
|
||
|
|
if (exclusive && peer->busy) {
|
||
|
|
pw_log_warn(NAME" %p: endpoint %d busy, can't get exclusive access", impl, peer->obj.id);
|
||
|
|
return -EBUSY;
|
||
|
|
}
|
||
|
|
peer->exclusive = exclusive;
|
||
|
|
|
||
|
|
pw_log_debug(NAME" %p: linking to endpoint '%d'", impl, peer->obj.id);
|
||
|
|
|
||
|
|
peer->busy = true;
|
||
|
|
|
||
|
|
do_link:
|
||
|
|
link_endpoints(ep, direction, peer, 1);
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void do_rescan(struct impl *impl)
|
||
|
|
{
|
||
|
|
struct endpoint *ep;
|
||
|
|
|
||
|
|
clock_gettime(CLOCK_MONOTONIC, &impl->now);
|
||
|
|
pw_log_debug("media-session %p: do rescan", impl);
|
||
|
|
|
||
|
|
spa_list_for_each(ep, &impl->endpoint_list, l)
|
||
|
|
rescan_endpoint(impl, ep);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void core_done(void *data, uint32_t id, int seq)
|
||
|
|
{
|
||
|
|
struct impl *impl = data;
|
||
|
|
pw_log_debug("media-session %p: sync %u %d/%d", impl, id, seq, impl->seq);
|
||
|
|
if (impl->seq == seq)
|
||
|
|
do_rescan(impl);
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_core_proxy_events core_events = {
|
||
|
|
PW_VERSION_CORE_EVENTS,
|
||
|
|
.done = core_done
|
||
|
|
};
|
||
|
|
|
||
|
|
static void on_state_changed(void *_data, enum pw_remote_state old, enum pw_remote_state state, const char *error)
|
||
|
|
{
|
||
|
|
struct impl *impl = _data;
|
||
|
|
|
||
|
|
switch (state) {
|
||
|
|
case PW_REMOTE_STATE_ERROR:
|
||
|
|
pw_log_error(NAME" %p: remote error: %s", impl, error);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PW_REMOTE_STATE_CONNECTED:
|
||
|
|
pw_log_info(NAME" %p: connected", impl);
|
||
|
|
impl->core_proxy = pw_remote_get_core_proxy(impl->remote);
|
||
|
|
pw_core_proxy_add_listener(impl->core_proxy,
|
||
|
|
&impl->core_listener,
|
||
|
|
&core_events, impl);
|
||
|
|
impl->registry_proxy = pw_core_proxy_get_registry(impl->core_proxy,
|
||
|
|
PW_VERSION_REGISTRY_PROXY, 0);
|
||
|
|
pw_registry_proxy_add_listener(impl->registry_proxy,
|
||
|
|
&impl->registry_listener,
|
||
|
|
®istry_events, impl);
|
||
|
|
schedule_rescan(impl);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case PW_REMOTE_STATE_UNCONNECTED:
|
||
|
|
pw_log_info(NAME" %p: disconnected", impl);
|
||
|
|
impl->core_proxy = NULL;
|
||
|
|
impl->registry_proxy = NULL;
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
printf("remote state: \"%s\"\n", pw_remote_state_as_string(state));
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static const struct pw_remote_events remote_events = {
|
||
|
|
PW_VERSION_REMOTE_EVENTS,
|
||
|
|
.state_changed = on_state_changed,
|
||
|
|
};
|
||
|
|
|
||
|
|
int sm_policy_ep_start(struct pw_remote *remote)
|
||
|
|
{
|
||
|
|
struct impl *impl;
|
||
|
|
|
||
|
|
impl = calloc(1, sizeof(struct impl));
|
||
|
|
if (impl == NULL)
|
||
|
|
return -errno;
|
||
|
|
|
||
|
|
impl->core = pw_remote_get_core(remote);
|
||
|
|
impl->remote = remote;
|
||
|
|
|
||
|
|
pw_map_init(&impl->globals, 64, 64);
|
||
|
|
|
||
|
|
spa_list_init(&impl->client_list);
|
||
|
|
spa_list_init(&impl->session_list);
|
||
|
|
spa_list_init(&impl->endpoint_list);
|
||
|
|
|
||
|
|
pw_remote_add_listener(impl->remote, &impl->remote_listener, &remote_events, impl);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int sm_policy_ep_stop(struct pw_core *core)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|