diff options
-rw-r--r-- | src/examples/media-session/alsa-monitor.c | 4 | ||||
-rw-r--r-- | src/examples/media-session/bluez-endpoint.c | 694 | ||||
-rw-r--r-- | src/examples/media-session/bluez-monitor.c | 96 | ||||
-rw-r--r-- | src/examples/media-session/media-session.c | 187 | ||||
-rw-r--r-- | src/examples/media-session/media-session.h | 6 | ||||
-rw-r--r-- | src/examples/media-session/policy-node.c | 512 | ||||
-rw-r--r-- | src/examples/media-session/session-manager.c | 170 | ||||
-rw-r--r-- | src/examples/meson.build | 3 | ||||
-rw-r--r-- | src/pipewire/impl-device.c | 30 |
9 files changed, 1569 insertions, 133 deletions
diff --git a/src/examples/media-session/alsa-monitor.c b/src/examples/media-session/alsa-monitor.c index 992c2959..ed9d8708 100644 --- a/src/examples/media-session/alsa-monitor.c +++ b/src/examples/media-session/alsa-monitor.c @@ -39,9 +39,10 @@ #include <spa/utils/hook.h> #include <spa/utils/names.h> #include <spa/utils/keys.h> -#include <spa/param/audio/format-utils.h> #include <spa/param/props.h> +#include <spa/pod/builder.h> #include <spa/debug/dict.h> +#include <spa/debug/pod.h> #include <spa/support/dbus.h> #include <pipewire/pipewire.h> @@ -240,6 +241,7 @@ static struct node *alsa_create_node(struct device *device, uint32_t id, res = -errno; goto clean_node; } + spa_list_append(&device->node_list, &node->link); return node; diff --git a/src/examples/media-session/bluez-endpoint.c b/src/examples/media-session/bluez-endpoint.c new file mode 100644 index 00000000..1ba4b29f --- /dev/null +++ b/src/examples/media-session/bluez-endpoint.c @@ -0,0 +1,694 @@ +/* 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/utils/result.h> +#include <spa/utils/names.h> +#include <spa/utils/keys.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/props.h> +#include <spa/debug/dict.h> +#include <spa/debug/pod.h> + +#include "pipewire/pipewire.h" + +#include <extensions/session-manager.h> + +#include "media-session.h" + +#define NAME "bluez-endpoint" +#define SESSION_KEY "bluez-endpoint" + +struct endpoint { + struct spa_list link; + + struct impl *impl; + + struct pw_properties *props; + + struct node *node; + struct spa_hook listener; + + struct pw_client_endpoint *client_endpoint; + struct spa_hook proxy_listener; + struct spa_hook client_endpoint_listener; + struct pw_endpoint_info info; + + struct spa_param_info params[5]; + + struct endpoint *monitor; + + struct spa_audio_info format; + + struct spa_list stream_list; +}; + +struct stream { + struct spa_list link; + struct endpoint *endpoint; + + struct pw_properties *props; + struct pw_endpoint_stream_info info; + + struct spa_audio_info format; + + unsigned int active:1; +}; + +struct node { + struct impl *impl; + struct sm_node *node; + + struct device *device; + + struct endpoint *endpoint; +}; + +struct device { + struct impl *impl; + uint32_t id; + struct sm_device *device; + struct spa_hook listener; + + struct spa_list endpoint_list; +}; + +struct impl { + struct sm_media_session *session; + struct spa_hook listener; +}; + +static int client_endpoint_set_session_id(void *object, uint32_t id) +{ + struct endpoint *endpoint = object; + endpoint->info.session_id = id; + return 0; +} + +static int client_endpoint_set_param(void *object, + uint32_t id, uint32_t flags, const struct spa_pod *param) +{ + struct endpoint *endpoint = object; + struct impl *impl = endpoint->impl; + pw_log_debug(NAME " %p: endpoint %p set param %d", impl, endpoint, id); + return pw_node_set_param((struct pw_node*)endpoint->node->node->obj.proxy, + id, flags, param); +} + + +static int client_endpoint_stream_set_param(void *object, uint32_t stream_id, + uint32_t id, uint32_t flags, const struct spa_pod *param) +{ + return -ENOTSUP; +} + +static int stream_set_active(struct endpoint *endpoint, struct stream *stream, bool active) +{ + char buf[1024]; + struct spa_pod_builder b = { 0, }; + struct spa_pod *param; + + if (stream->active == active) + return 0; + + if (active) { + stream->format.info.raw.rate = 48000; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &stream->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_set_param((struct pw_node*)endpoint->node->node->obj.proxy, + SPA_PARAM_PortConfig, 0, param); + } + stream->active = active; + return 0; +} + +static int client_endpoint_create_link(void *object, const struct spa_dict *props) +{ + struct endpoint *endpoint = object; + struct impl *impl = endpoint->impl; + struct pw_properties *p; + int res; + + pw_log_debug(NAME" %p: endpoint %p", impl, endpoint); + + if (props == NULL) + return -EINVAL; + + p = pw_properties_new_dict(props); + if (p == NULL) + return -errno; + + if (endpoint->info.direction == PW_DIRECTION_OUTPUT) { + const char *str; + struct sm_object *obj; + + str = spa_dict_lookup(props, PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT); + if (str == NULL) { + pw_log_warn(NAME" %p: no target endpoint given", impl); + res = -EINVAL; + goto exit; + } + obj = sm_media_session_find_object(impl->session, atoi(str)); + if (obj == NULL || strcmp(obj->type, PW_TYPE_INTERFACE_Endpoint) !=0) { + pw_log_warn(NAME" %p: could not find endpoint %s (%p)", impl, str, obj); + res = -EINVAL; + goto exit; + } + + pw_properties_setf(p, PW_KEY_LINK_OUTPUT_NODE, "%d", endpoint->node->node->info->id); + pw_properties_setf(p, PW_KEY_LINK_OUTPUT_PORT, "-1"); + + pw_endpoint_create_link((struct pw_endpoint*)obj->proxy, &p->dict); + } else { + pw_properties_setf(p, PW_KEY_LINK_INPUT_NODE, "%d", endpoint->node->node->info->id); + pw_properties_setf(p, PW_KEY_LINK_INPUT_PORT, "-1"); + + sm_media_session_create_links(impl->session, &p->dict); + } + + res = 0; +exit: + pw_properties_free(p); + + return res; +} + +static const struct pw_client_endpoint_events client_endpoint_events = { + PW_VERSION_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, +}; + +static struct stream *endpoint_add_stream(struct endpoint *endpoint) +{ + struct stream *s; + const char *str; + + s = calloc(1, sizeof(*s)); + if (s == NULL) + return NULL; + + s->props = pw_properties_new(NULL, NULL); + s->endpoint = endpoint; + + if ((str = pw_properties_get(endpoint->props, PW_KEY_MEDIA_CLASS)) != NULL) + pw_properties_set(s->props, PW_KEY_MEDIA_CLASS, str); + if ((str = pw_properties_get(endpoint->props, PW_KEY_PRIORITY_SESSION)) != NULL) + pw_properties_set(s->props, PW_KEY_PRIORITY_SESSION, str); + if (endpoint->info.direction == PW_DIRECTION_OUTPUT) { + if (endpoint->monitor != NULL) + pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Monitor"); + else + pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Capture"); + } else { + pw_properties_set(s->props, PW_KEY_ENDPOINT_STREAM_NAME, "Playback"); + } + + s->info.version = PW_VERSION_ENDPOINT_STREAM_INFO; + s->info.id = endpoint->info.n_streams; + s->info.endpoint_id = endpoint->info.id; + s->info.name = (char*)pw_properties_get(s->props, PW_KEY_ENDPOINT_STREAM_NAME); + s->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS; + s->info.props = &s->props->dict; + s->format = endpoint->format; + + pw_log_debug("stream %d", s->info.id); + pw_client_endpoint_stream_update(endpoint->client_endpoint, + s->info.id, + PW_CLIENT_ENDPOINT_STREAM_UPDATE_INFO, + 0, NULL, + &s->info); + + spa_list_append(&endpoint->stream_list, &s->link); + endpoint->info.n_streams++; + + return s; +} + +static void destroy_stream(struct stream *stream) +{ + struct endpoint *endpoint = stream->endpoint; + + pw_client_endpoint_stream_update(endpoint->client_endpoint, + stream->info.id, + PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED, + 0, NULL, + &stream->info); + + spa_list_remove(&stream->link); + endpoint->info.n_streams--; + + pw_properties_free(stream->props); + free(stream); +} + +static void update_params(void *data) +{ + uint32_t n_params; + const struct spa_pod **params; + struct endpoint *endpoint = data; + struct sm_node *node = endpoint->node->node; + struct sm_param *p; + + pw_log_debug(NAME" %p: endpoint", endpoint); + + params = alloca(sizeof(struct spa_pod *) * node->n_params); + n_params = 0; + spa_list_for_each(p, &node->param_list, link) { + switch (p->id) { + case SPA_PARAM_Props: + case SPA_PARAM_PropInfo: + params[n_params++] = p->param; + break; + default: + break; + } + } + + pw_client_endpoint_update(endpoint->client_endpoint, + PW_CLIENT_ENDPOINT_UPDATE_PARAMS | + PW_CLIENT_ENDPOINT_UPDATE_INFO, + n_params, params, + &endpoint->info); +} + +static struct endpoint *create_endpoint(struct node *node, struct endpoint *monitor); + +static void object_update(void *data) +{ + struct endpoint *endpoint = data; + struct impl *impl = endpoint->impl; + struct sm_node *node = endpoint->node->node; + + pw_log_debug(NAME" %p: endpoint %p", impl, endpoint); + + if (node->obj.changed & SM_NODE_CHANGE_MASK_PARAMS) + update_params(endpoint); +} + +static const struct sm_object_events object_events = { + SM_VERSION_OBJECT_EVENTS, + .update = object_update +}; + +static void complete_endpoint(void *data) +{ + struct endpoint *endpoint = data; + struct stream *stream; + struct sm_param *p; + + pw_log_debug("endpoint %p: complete", endpoint); + + spa_list_for_each(p, &endpoint->node->node->param_list, link) { + struct spa_audio_info info = { 0, }; + + if (p->id != SPA_PARAM_EnumFormat) + continue; + + if (spa_format_parse(p->param, &info.media_type, &info.media_subtype) < 0) + continue; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + continue; + + spa_pod_object_fixate((struct spa_pod_object*)p->param); + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_pod(2, NULL, p->param); + + if (spa_format_audio_raw_parse(p->param, &info.info.raw) < 0) + continue; + + if (endpoint->format.info.raw.channels < info.info.raw.channels) + endpoint->format = info; + } + + pw_client_endpoint_update(endpoint->client_endpoint, + PW_CLIENT_ENDPOINT_UPDATE_INFO, + 0, NULL, + &endpoint->info); + + stream = endpoint_add_stream(endpoint); + + if (endpoint->info.direction == PW_DIRECTION_INPUT) { + struct endpoint *monitor; + + /* make monitor for sinks */ + monitor = create_endpoint(endpoint->node, endpoint); + if (monitor == NULL) + return; + + endpoint_add_stream(monitor); + } + stream_set_active(endpoint, stream, true); + + sm_object_add_listener(&endpoint->node->node->obj, &endpoint->listener, &object_events, endpoint); +} + +static void proxy_destroy(void *data) +{ + struct endpoint *endpoint = data; + struct stream *s; + + spa_list_consume(s, &endpoint->stream_list, link) + destroy_stream(s); + + pw_properties_free(endpoint->props); + spa_list_remove(&endpoint->link); + spa_hook_remove(&endpoint->proxy_listener); + spa_hook_remove(&endpoint->client_endpoint_listener); + endpoint->client_endpoint = NULL; +} + +static void proxy_bound(void *data, uint32_t id) +{ + struct endpoint *endpoint = data; + endpoint->info.id = id; +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .destroy = proxy_destroy, + .bound = proxy_bound, +}; + +static struct endpoint *create_endpoint(struct node *node, struct endpoint *monitor) +{ + struct impl *impl = node->impl; + struct device *device = node->device; + struct pw_properties *props; + struct endpoint *endpoint; + struct pw_proxy *proxy; + const char *str, *media_class = NULL, *name = NULL; + uint32_t subscribe[4], n_subscribe = 0; + struct pw_properties *pr = node->node->obj.props; + enum pw_direction direction; + + if (pr == NULL) { + errno = EINVAL; + return NULL; + } + + if ((media_class = pw_properties_get(pr, PW_KEY_MEDIA_CLASS)) == NULL) { + errno = EINVAL; + return NULL; + } + + props = pw_properties_new(NULL, NULL); + if (props == NULL) + return NULL; + + if (strstr(media_class, "Source") != NULL) { + direction = PW_DIRECTION_OUTPUT; + } else if (strstr(media_class, "Sink") != NULL) { + direction = PW_DIRECTION_INPUT; + } else { + errno = EINVAL; + return NULL; + } + + if (monitor != NULL) { + pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Source"); + direction = PW_DIRECTION_OUTPUT; + } else { + pw_properties_set(props, PW_KEY_MEDIA_CLASS, media_class); + } + + if ((str = pw_properties_get(pr, PW_KEY_PRIORITY_SESSION)) != NULL) + pw_properties_set(props, PW_KEY_PRIORITY_SESSION, str); + if ((name = pw_properties_get(pr, PW_KEY_NODE_DESCRIPTION)) != NULL) { + if (monitor != NULL) { + pw_properties_setf(props, PW_KEY_ENDPOINT_NAME, "Monitor of %s", monitor->info.name); + pw_properties_setf(props, PW_KEY_ENDPOINT_MONITOR, "%d", monitor->info.id); + } else { + pw_properties_set(props, PW_KEY_ENDPOINT_NAME, name); + } + } + if ((str = pw_properties_get(pr, PW_KEY_DEVICE_ICON_NAME)) != NULL) + pw_properties_set(props, PW_KEY_ENDPOINT_ICON_NAME, str); + + proxy = sm_media_session_create_object(impl->session, + "client-endpoint", + PW_TYPE_INTERFACE_ClientEndpoint, + PW_VERSION_CLIENT_ENDPOINT, + &props->dict, sizeof(*endpoint)); + if (proxy == NULL) { + pw_properties_free(props); + return NULL; + } + + endpoint = pw_proxy_get_user_data(proxy); + endpoint->impl = impl; + endpoint->node = node; + endpoint->monitor = monitor; + endpoint->props = props; + endpoint->client_endpoint = (struct pw_client_endpoint *) 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(endpoint->props, PW_KEY_MEDIA_CLASS); + endpoint->info.session_id = impl->session->session->obj.id; + endpoint->info.direction = direction; + endpoint->info.flags = 0; + endpoint->info.change_mask = + PW_ENDPOINT_CHANGE_MASK_STREAMS | + PW_ENDPOINT_CHANGE_MASK_SESSION | + PW_ENDPOINT_CHANGE_MASK_PROPS | + PW_ENDPOINT_CHANGE_MASK_PARAMS; + endpoint->info.n_streams = 0; + endpoint->info.props = &endpoint->props->dict; + endpoint->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + endpoint->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + endpoint->info.params = endpoint->params; + endpoint->info.n_params = 2; + spa_list_init(&endpoint->stream_list); + + pw_log_debug(NAME" %p: new endpoint %p for bluez node %p", impl, endpoint, node); + pw_proxy_add_listener(proxy, + &endpoint->proxy_listener, + &proxy_events, endpoint); + + pw_client_endpoint_add_listener(endpoint->client_endpoint, + &endpoint->client_endpoint_listener, + &client_endpoint_events, + endpoint); + + subscribe[n_subscribe++] = SPA_PARAM_EnumFormat; + subscribe[n_subscribe++] = SPA_PARAM_Props; + subscribe[n_subscribe++] = SPA_PARAM_PropInfo; + pw_log_debug(NAME" %p: endpoint %p proxy %p subscribe %d params", impl, + endpoint, node->node->obj.proxy, n_subscribe); + pw_node_subscribe_params((struct pw_node*)node->node->obj.proxy, + subscribe, n_subscribe); + + spa_list_append(&device->endpoint_list, &endpoint->link); + + if (monitor == NULL) + sm_media_session_sync(impl->session, complete_endpoint, endpoint); + + return endpoint; +} + +static void destroy_endpoint(struct endpoint *endpoint) +{ + if (endpoint->client_endpoint) + pw_proxy_destroy((struct pw_proxy*)endpoint->client_endpoint); +} + +/** fallback, one stream for each node */ +static int setup_bluez_endpoint(struct device *device) +{ + struct impl *impl = device->impl; + struct sm_node *n; + struct sm_device *d = device->device; + + pw_log_debug(NAME" %p: device %p fallback", impl, d); + + spa_list_for_each(n, &d->node_list, link) { + struct node *node; + + pw_log_debug(NAME" %p: device %p has node %p", impl, d, n); + + node = sm_object_add_data(&n->obj, SESSION_KEY, sizeof(struct node)); + node->device = device; + node->node = n; + node->impl = impl; + node->endpoint = create_endpoint(node, NULL); + if (node->endpoint == NULL) + return -errno; + } + return 0; +} + +static int activate_device(struct device *device) +{ + return setup_bluez_endpoint(device); +} + +static int deactivate_device(struct device *device) +{ + struct endpoint *e; + spa_list_consume(e, &device->endpoint_list, link) + destroy_endpoint(e); + return 0; +} + +static void device_update(void *data) +{ + struct device *device = data; + struct impl *impl = device->impl; + + pw_log_debug(NAME" %p: device %p %08x %08x", impl, device, + device->device->obj.avail, device->device->obj.changed); + + if (!SPA_FLAG_IS_SET(device->device->obj.avail, + SM_DEVICE_CHANGE_MASK_INFO | + SM_DEVICE_CHANGE_MASK_NODES | + SM_DEVICE_CHANGE_MASK_PARAMS)) + return; + +// if (SPA_FLAG_IS_SET(device->device->obj.changed, +// SM_DEVICE_CHANGE_MASK_NODES | +// SM_DEVICE_CHANGE_MASK_PARAMS)) { + activate_device(device); +// } +} + +static const struct sm_object_events device_events = { + SM_VERSION_OBJECT_EVENTS, + .update = device_update +}; + +static int +handle_device(struct impl *impl, struct sm_object *obj) +{ + const char *media_class, *str; + struct device *device; + + if (obj->props == NULL) + return 0; + + media_class = pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS); + str = pw_properties_get(obj->props, PW_KEY_DEVICE_API); + + pw_log_debug(NAME" %p: device "PW_KEY_MEDIA_CLASS":%s api:%s", impl, media_class, str); + + if (strstr(media_class, "Audio/") != media_class) + return 0; + if (strcmp(str, "bluez5") != 0) + return 0; + + device = sm_object_add_data(obj, SESSION_KEY, sizeof(struct device)); + device->impl = impl; + device->id = obj->id; + device->device = (struct sm_device*)obj; + spa_list_init(&device->endpoint_list); + pw_log_debug(NAME" %p: found bluez device %d media_class %s", impl, obj->id, media_class); + + sm_object_add_listener(obj, &device->listener, &device_events, device); + + return 0; +} + +static void destroy_device(struct impl *impl, struct device *device) +{ + deactivate_device(device); + spa_hook_remove(&device->listener); + sm_object_remove_data((struct sm_object*)device->device, SESSION_KEY); +} + +static void session_create(void *data, struct sm_object *object) +{ + struct impl *impl = data; + int res; + + if (strcmp(object->type, PW_TYPE_INTERFACE_Device) == 0) + res = handle_device(impl, object); + else + res = 0; + + if (res < 0) { + pw_log_warn(NAME" %p: can't handle global %d: %s", impl, + object->id, spa_strerror(res)); + } +} + +static void session_remove(void *data, struct sm_object *object) +{ + struct impl *impl = data; + + if (strcmp(object->type, PW_TYPE_INTERFACE_Device) == 0) { + struct device *device; + if ((device = sm_object_get_data(object, SESSION_KEY)) != NULL) + destroy_device(impl, device); + } +} + +static void session_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->listener); + free(impl); +} + +static const struct sm_media_session_events session_events = { + SM_VERSION_MEDIA_SESSION_EVENTS, + .create = session_create, + .remove = session_remove, + .destroy = session_destroy, +}; + +int sm_bluez5_endpoint_start(struct sm_media_session *session) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + impl->session = session; + sm_media_session_add_listener(session, &impl->listener, &session_events, impl); + return 0; +} diff --git a/src/examples/media-session/bluez-monitor.c b/src/examples/media-session/bluez-monitor.c index 5c6f293e..0769a6f5 100644 --- a/src/examples/media-session/bluez-monitor.c +++ b/src/examples/media-session/bluez-monitor.c @@ -36,17 +36,21 @@ #include <spa/utils/result.h> #include <spa/utils/names.h> #include <spa/utils/result.h> -#include <spa/param/audio/format-utils.h> +#include <spa/pod/builder.h> #include <spa/param/props.h> #include <spa/debug/dict.h> +#include <spa/debug/pod.h> #include "pipewire/impl.h" #include "media-session.h" +#define NAME "bluez5-monitor" + struct device; struct node { struct impl *impl; + enum pw_direction direction; struct device *device; struct spa_list link; uint32_t id; @@ -54,22 +58,29 @@ struct node { struct pw_properties *props; struct pw_impl_node *adapter; - struct pw_proxy *proxy; + + struct sm_node *snode; }; struct device { struct impl *impl; struct spa_list link; uint32_t id; + uint32_t device_id; + + int priority; + int profile; struct pw_properties *props; struct spa_handle *handle; struct spa_device *device; + struct spa_hook device_listener; struct sm_device *sdevice; - struct spa_hook device_listener; + struct spa_hook listener; + unsigned int appeared:1; struct spa_list node_list; }; @@ -139,6 +150,7 @@ static struct node *bluez5_create_node(struct device *device, uint32_t id, if (str == NULL) str = "bluetooth-device"; + pw_properties_setf(node->props, PW_KEY_DEVICE_ID, "%d", device->device_id); pw_properties_setf(node->props, PW_KEY_NODE_NAME, "%s.%s", info->factory_name, str); pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, str); pw_properties_set(node->props, "factory.name", info->factory_name); @@ -163,10 +175,13 @@ static struct node *bluez5_create_node(struct device *device, uint32_t id, res = -errno; goto clean_node; } - node->proxy = sm_media_session_export(impl->session, - PW_TYPE_INTERFACE_Node, + node->snode = sm_media_session_export_node(impl->session, &node->props->dict, - node->adapter, 0); + node->adapter); + if (node->snode == NULL) { + res = -errno; + goto clean_node; + } spa_list_append(&device->node_list, &node->link); @@ -229,15 +244,72 @@ static struct device *bluez5_find_device(struct impl *impl, uint32_t id) return NULL; } -static void bluez5_update_device(struct impl *impl, struct device *device, +static void bluez5_update_device(struct impl *impl, struct device *dev, const struct spa_device_object_info *info) { - pw_log_debug("update device %u", device->id); + pw_log_debug("update device %u", dev->id); if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) spa_debug_dict(0, info->props); + + pw_properties_update(dev->props, info->props); +} + +static void set_profile(struct device *device, int index) +{ + char buf[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); + + pw_log_debug("%p: set profile %d id:%d", device, index, device->device_id); + + device->profile = index; + if (device->device_id != 0) { + spa_device_set_param(device->device, + SPA_PARAM_Profile, 0, + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, + SPA_PARAM_PROFILE_index, SPA_POD_Int(index))); + } +} + +static void device_destroy(void *data) +{ + struct device *device = data; + struct node *node; + + pw_log_debug("device %p destroy", device); + + spa_list_consume(node, &device->node_list, link) + bluez5_remove_node(device, node); +} + +static void device_update(void *data) +{ + struct device *device = data; + + pw_log_debug("device %p appeared %d %d", device, device->appeared, device->profile); + + if (device->appeared) + return; + + device->device_id = device->sdevice->obj.id; + device->appeared = true; + + spa_device_add_listener(device->device, + &device->device_listener, + &bluez5_device_events, device); + + set_profile(device, 1); + sm_object_sync_update(&device->sdevice->obj); } +static const struct sm_object_events device_events = { + SM_VERSION_OBJECT_EVENTS, + .destroy = device_destroy, + .update = device_update, +}; + + static struct device *bluez5_create_device(struct impl *impl, uint32_t id, const struct spa_device_object_info *info) { @@ -288,12 +360,12 @@ static struct device *bluez5_create_device(struct impl *impl, uint32_t id, spa_list_init(&device->node_list); - spa_device_add_listener(device->device, - &device->device_listener, &bluez5_device_events, device); - spa_list_append(&impl->device_list, &device->link); + sm_object_add_listener(&device->sdevice->obj, + &device->listener, + &device_events, device); - bluez5_update_device(impl, device, info); + spa_list_append(&impl->device_list, &device->link); return device; diff --git a/src/examples/media-session/media-session.c b/src/examples/media-session/media-session.c index c4edee4e..7991e3b0 100644 --- a/src/examples/media-session/media-session.c +++ b/src/examples/media-session/media-session.c @@ -60,15 +60,15 @@ #define sm_media_session_emit_rescan(s,seq) sm_media_session_emit(s, rescan, 0, seq) #define sm_media_session_emit_destroy(s) sm_media_session_emit(s, destroy, 0) -int sm_stream_endpoint_start(struct sm_media_session *sess); int sm_metadata_start(struct sm_media_session *sess); int sm_alsa_midi_start(struct sm_media_session *sess); int sm_v4l2_monitor_start(struct sm_media_session *sess); -int sm_v4l2_endpoint_start(struct sm_media_session *sess); int sm_bluez5_monitor_start(struct sm_media_session *sess); int sm_alsa_monitor_start(struct sm_media_session *sess); -int sm_alsa_endpoint_start(struct sm_media_session *sess); -int sm_policy_ep_start(struct sm_media_session *sess); + +int sm_policy_node_start(struct sm_media_session *sess); + +int sm_session_manager_start(struct sm_media_session *sess); /** user data to add to an object */ struct data { @@ -90,7 +90,6 @@ struct sync { struct impl { struct sm_media_session this; - uint32_t session_id; struct pw_main_loop *loop; struct spa_dbus *dbus; @@ -110,10 +109,6 @@ struct impl { struct spa_hook_list hooks; - struct pw_client_session *client_session; - struct spa_hook proxy_client_session_listener; - struct spa_hook client_session_listener; - struct spa_list endpoint_link_list; /** list of struct endpoint_link */ struct pw_map endpoint_links; /** map of endpoint_link */ @@ -639,7 +634,7 @@ static int session_init(void *object) struct sm_session *sess = object; struct impl *impl = SPA_CONTAINER_OF(sess->obj.session, struct impl, this); - if (sess->obj.id == impl->session_id) + if (sess->obj.id == impl->this.session_id) impl->this.session = sess; spa_list_init(&sess->endpoint_list); @@ -1005,27 +1000,33 @@ static const struct object_info *get_object_info(struct impl *impl, const char * } static struct sm_object *init_object(struct impl *impl, const struct object_info *info, - struct pw_proxy *proxy, uint32_t id, + struct pw_proxy *proxy, struct pw_proxy *handle, uint32_t id, const struct spa_dict *props) { struct sm_object *obj; - obj = pw_proxy_get_user_data(proxy); + obj = pw_proxy_get_user_data(handle); obj->session = &impl->this; obj->id = id; obj->type = info->type; obj->props = props ? pw_properties_new_dict(props) : pw_properties_new(NULL, NULL); obj->proxy = proxy; + obj->handle = handle; obj->destroy = info->destroy; obj->mask |= SM_OBJECT_CHANGE_MASK_PROPERTIES | SM_OBJECT_CHANGE_MASK_BIND; obj->avail |= obj->mask; spa_hook_list_init(&obj->hooks); spa_list_init(&obj->data); - pw_proxy_add_listener(obj->proxy, &obj->proxy_listener, &proxy_events, obj); - if (info->events != NULL) - pw_proxy_add_object_listener(obj->proxy, &obj->object_listener, info->events, obj); - SPA_FLAG_UPDATE(obj->mask, SM_OBJECT_CHANGE_MASK_LISTENER, info->events != NULL); + if (proxy) { + pw_proxy_add_listener(obj->proxy, &obj->proxy_listener, &proxy_events, obj); + if (info->events != NULL) + pw_proxy_add_object_listener(obj->proxy, &obj->object_listener, info->events, obj); + SPA_FLAG_UPDATE(obj->mask, SM_OBJECT_CHANGE_MASK_LISTENER, info->events != NULL); + } + if (handle) { + pw_proxy_add_listener(obj->handle, &obj->handle_listener, &proxy_events, obj); + } if (info->init) info->init(obj); @@ -1037,14 +1038,14 @@ static struct sm_object *init_object(struct impl *impl, const struct object_info } static struct sm_object * -create_object(struct impl *impl, struct pw_proxy *proxy, +create_object(struct impl *impl, struct pw_proxy *proxy, struct pw_proxy *handle, const struct spa_dict *props) { const char *type; const struct object_info *info; struct sm_object *obj; - type = pw_proxy_get_type(proxy, NULL); + type = pw_proxy_get_type(handle, NULL); info = get_object_info(impl, type); if (info == NULL) { @@ -1052,7 +1053,7 @@ create_object(struct impl *impl, struct pw_proxy *proxy, errno = ENOTSUP; return NULL; } - obj = init_object(impl, info, proxy, SPA_ID_INVALID, props); + obj = init_object(impl, info, proxy, handle, SPA_ID_INVALID, props); pw_log_debug(NAME" %p: created new object %p proxy %p", impl, obj, obj->proxy); @@ -1074,7 +1075,7 @@ bind_object(struct impl *impl, const struct object_info *info, uint32_t id, res = -errno; goto error; } - obj = init_object(impl, info, proxy, id, props); + obj = init_object(impl, info, proxy, proxy, id, props); pw_log_debug(NAME" %p: bound new object %p proxy %p id:%d", impl, obj, obj->proxy, obj->id); @@ -1094,19 +1095,16 @@ update_object(struct impl *impl, const struct object_info *info, { pw_properties_update(obj->props, props); - if (strcmp(obj->type, type) == 0) + if (obj->proxy != NULL) return 0; - pw_log_debug(NAME" %p: update type:%s -> type:%s", impl, obj->type, type); - obj->handle = obj->proxy; - spa_hook_remove(&obj->proxy_listener); - pw_proxy_add_listener(obj->handle, &obj->handle_listener, &proxy_events, obj); - - if (SPA_FLAG_IS_SET(obj->mask, SM_OBJECT_CHANGE_MASK_LISTENER)) - spa_hook_remove(&obj->object_listener); + pw_log_debug(NAME" %p: update type:%s", impl, obj->type); obj->proxy = pw_registry_bind(impl->registry, id, info->type, info->version, 0); + if (obj->proxy == NULL) + return -errno; + obj->type = info->type; pw_proxy_add_listener(obj->proxy, &obj->proxy_listener, &proxy_events, obj); @@ -1266,19 +1264,36 @@ struct pw_proxy *sm_media_session_export(struct sm_media_session *sess, props, object, user_data_size); } +struct sm_node *sm_media_session_export_node(struct sm_media_session *sess, + const struct spa_dict *props, struct pw_impl_node *object) +{ + struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); + struct sm_node *node; + struct pw_proxy *handle; + + pw_log_debug(NAME " %p: node %p", impl, object); + + handle = pw_core_export(impl->monitor_core, PW_TYPE_INTERFACE_Node, + props, object, sizeof(struct sm_node)); + + node = (struct sm_node *) create_object(impl, NULL, handle, props); + + return node; +} + struct sm_device *sm_media_session_export_device(struct sm_media_session *sess, const struct spa_dict *props, struct spa_device *object) { struct impl *impl = SPA_CONTAINER_OF(sess, struct impl, this); struct sm_device *device; - struct pw_proxy *proxy; + struct pw_proxy *handle; pw_log_debug(NAME " %p: device %p", impl, object); - proxy = pw_core_export(impl->monitor_core, SPA_TYPE_INTERFACE_Device, + handle = pw_core_export(impl->monitor_core, SPA_TYPE_INTERFACE_Device, props, object, sizeof(struct sm_device)); - device = (struct sm_device *) create_object(impl, proxy, props); + device = (struct sm_device *) create_object(impl, NULL, handle, props); return device; } @@ -1308,7 +1323,7 @@ struct sm_node *sm_media_session_create_node(struct sm_media_session *sess, props, sizeof(struct sm_node)); - node = (struct sm_node *)create_object(impl, proxy, props); + node = (struct sm_node *)create_object(impl, proxy, proxy, props); return node; } @@ -1322,7 +1337,7 @@ static void check_endpoint_link(struct endpoint_link *link) spa_list_remove(&link->link); pw_map_remove(&link->impl->endpoint_links, link->id); - pw_client_session_link_update(link->impl->client_session, + pw_client_session_link_update(link->impl->this.client_session, link->id, PW_CLIENT_SESSION_LINK_UPDATE_DESTROYED, 0, NULL, NULL); @@ -1495,7 +1510,7 @@ int sm_media_session_create_links(struct sm_media_session *sess, if (link != NULL) { /* now create the endpoint link */ - pw_client_session_link_update(impl->client_session, + pw_client_session_link_update(impl->this.client_session, link->id, PW_CLIENT_SESSION_UPDATE_INFO, 0, NULL, @@ -1504,73 +1519,6 @@ int sm_media_session_create_links(struct sm_media_session *sess, return res; } -/** - * Session implementation - */ -static int client_session_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *impl = object; - pw_proxy_error((struct pw_proxy*)impl->client_session, - -ENOTSUP, "Session:SetParam not supported"); - return -ENOTSUP; -} - -static int client_session_link_set_param(void *object, uint32_t link_id, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *impl = object; - pw_proxy_error((struct pw_proxy*)impl->client_session, - -ENOTSUP, "Session:LinkSetParam not supported"); - return -ENOTSUP; -} - -static int client_session_link_request_state(void *object, uint32_t link_id, uint32_t state) -{ - return -ENOTSUP; -} - -static const struct pw_client_session_events client_session_events = { - PW_VERSION_CLIENT_SESSION_METHODS, - .set_param = client_session_set_param, - .link_set_param = client_session_link_set_param, - .link_request_state = client_session_link_request_state, -}; - -static void proxy_client_session_bound(void *data, uint32_t id) -{ - struct impl *impl = data; - struct pw_session_info info; - - impl->session_id = id; - - spa_zero(info); - info.version = PW_VERSION_SESSION_INFO; - info.id = id; - - pw_log_debug("got sesssion id:%d", id); - - pw_client_session_update(impl->client_session, - PW_CLIENT_SESSION_UPDATE_INFO, - 0, NULL, - &info); - - /* start monitors */ - sm_metadata_start(&impl->this); - sm_alsa_midi_start(&impl->this); - sm_bluez5_monitor_start(&impl->this); - sm_alsa_monitor_start(&impl->this); - sm_alsa_endpoint_start(&impl->this); - sm_v4l2_monitor_start(&impl->this); - sm_v4l2_endpoint_start(&impl->this); - sm_stream_endpoint_start(&impl->this); -} - -static const struct pw_proxy_events proxy_client_session_events = { - PW_VERSION_PROXY_EVENTS, - .bound = proxy_client_session_bound, -}; - static int start_session(struct impl *impl) { impl->monitor_core = pw_context_connect(impl->this.context, NULL, 0); @@ -1578,21 +1526,6 @@ static int start_session(struct impl *impl) pw_log_error("can't start monitor: %m"); return -errno; } - - impl->client_session = pw_core_create_object(impl->monitor_core, - "client-session", - PW_TYPE_INTERFACE_ClientSession, - PW_VERSION_CLIENT_SESSION, - NULL, 0); - - pw_proxy_add_listener((struct pw_proxy*)impl->client_session, - &impl->proxy_client_session_listener, - &proxy_client_session_events, impl); - - pw_client_session_add_listener(impl->client_session, - &impl->client_session_listener, - &client_session_events, impl); - return 0; } @@ -1610,8 +1543,17 @@ static void core_done(void *data, uint32_t id, int seq) } } if (impl->rescan_seq == seq) { + struct sm_object *obj, *to; + pw_log_trace(NAME" %p: rescan %u %d", impl, id, seq); sm_media_session_emit_rescan(impl, seq); + + spa_list_for_each_safe(obj, to, &impl->global_list, link) { + pw_log_trace(NAME" %p: obj %p %08x", impl, obj, obj->changed); + if (obj->changed) + sm_object_emit_update(obj); + obj->changed = 0; + } } } @@ -1668,7 +1610,6 @@ static int start_policy(struct impl *impl) &impl->registry_listener, ®istry_events, impl); - sm_policy_ep_start(&impl->this); return 0; } @@ -1685,8 +1626,6 @@ static void session_shutdown(struct impl *impl) pw_proxy_destroy((struct pw_proxy*)impl->registry); if (impl->policy_core) pw_core_disconnect(impl->policy_core); - if (impl->client_session) - pw_proxy_destroy((struct pw_proxy*)impl->client_session); if (impl->monitor_core) pw_core_disconnect(impl->monitor_core); } @@ -1732,6 +1671,16 @@ int main(int argc, char *argv[]) if ((res = start_policy(&impl)) < 0) goto exit; + sm_metadata_start(&impl.this); + sm_alsa_midi_start(&impl.this); + sm_bluez5_monitor_start(&impl.this); + sm_alsa_monitor_start(&impl.this); + sm_v4l2_monitor_start(&impl.this); + + sm_policy_node_start(&impl.this); + +// sm_session_manager_start(&impl.this); + pw_main_loop_run(impl.loop); exit: diff --git a/src/examples/media-session/media-session.h b/src/examples/media-session/media-session.h index bd673411..50a626fa 100644 --- a/src/examples/media-session/media-session.h +++ b/src/examples/media-session/media-session.h @@ -27,6 +27,7 @@ #define SM_MEDIA_SESSION_H #include <spa/monitor/device.h> +#include <pipewire/impl.h> #ifdef __cplusplus extern "C" { @@ -207,6 +208,9 @@ struct sm_media_session_events { struct sm_media_session { struct sm_session *session; /** session object managed by this session */ + uint32_t session_id; + struct pw_client_session *client_session; + struct pw_loop *loop; /** the main loop */ struct pw_context *context; @@ -229,6 +233,8 @@ struct pw_proxy *sm_media_session_export(struct sm_media_session *sess, const char *type, const struct spa_dict *props, void *object, size_t user_data_size); +struct sm_node *sm_media_session_export_node(struct sm_media_session *sess, + const struct spa_dict *props, struct pw_impl_node *node); struct sm_device *sm_media_session_export_device(struct sm_media_session *sess, const struct spa_dict *props, struct spa_device *device); diff --git a/src/examples/media-session/policy-node.c b/src/examples/media-session/policy-node.c new file mode 100644 index 00000000..9634bae0 --- /dev/null +++ b/src/examples/media-session/policy-node.c @@ -0,0 +1,512 @@ +/* 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 "media-session.h" + +#define NAME "policy-node" +#define SESSION_KEY "policy-node" + +#define DEFAULT_CHANNELS 2 +#define DEFAULT_SAMPLERATE 48000 + +#define DEFAULT_IDLE_SECONDS 3 + +struct impl { + struct timespec now; + + struct sm_media_session *session; + struct spa_hook listener; + + struct pw_context *context; + + struct spa_list node_list; + int seq; +}; + +struct node { + struct sm_node *obj; + + uint32_t id; + struct impl *impl; + + struct spa_list link; /**< link in impl node_list */ + enum pw_direction direction; + + struct spa_hook listener; + + uint32_t linked; + + uint32_t client_id; + int32_t priority; + +#define NODE_TYPE_UNKNOWN 0 +#define NODE_TYPE_STREAM 1 +#define NODE_TYPE_DEVICE 2 + uint32_t type; + char *media; + + struct spa_audio_info format; + + uint64_t plugged; + unsigned int active:1; + unsigned int exclusive:1; + unsigned int enabled:1; + unsigned int busy:1; +}; + +static int activate_node(struct node *node) +{ + struct impl *impl = node->impl; + struct sm_param *p; + char buf[1024]; + struct spa_pod_builder b = { 0, }; + struct spa_pod *param; + + pw_log_debug(NAME" %p: node %p activate", impl, node); + + spa_list_for_each(p, &node->obj->param_list, link) { + struct spa_audio_info info = { 0, }; + + if (p->id != SPA_PARAM_EnumFormat) + continue; + + if (spa_format_parse(p->param, &info.media_type, &info.media_subtype) < 0) + continue; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + continue; + + spa_pod_object_fixate((struct spa_pod_object*)p->param); + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_pod(2, NULL, p->param); + + if (spa_format_audio_raw_parse(p->param, &info.info.raw) < 0) + continue; + + if (node->format.info.raw.channels < info.info.raw.channels) + node->format = info; + } + node->format.info.raw.rate = 48000; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &node->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(node->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_set_param((struct pw_node*)node->obj->obj.proxy, + SPA_PARAM_PortConfig, 0, param); + + node->active = true; + return 0; +} + +static void object_update(void *data) +{ + struct node *node = data; + struct impl *impl = node->impl; + + pw_log_debug(NAME" %p: node %p %08x", impl, node, node->obj->obj.changed); + + if (node->obj->obj.avail & SM_NODE_CHANGE_MASK_PARAMS && + !node->active) + activate_node(node); +} + +static const struct sm_object_events object_events = { + SM_VERSION_OBJECT_EVENTS, + .update = object_update +}; + +static int +handle_node(struct impl *impl, struct sm_object *object) +{ + const char *str, *media_class; + enum pw_direction direction; + struct node *node; + uint32_t client_id = SPA_ID_INVALID; + + if (object->props) { + if ((str = pw_properties_get(object->props, PW_KEY_CLIENT_ID)) != NULL) + client_id = atoi(str); + } + + media_class = object->props ? pw_properties_get(object->props, PW_KEY_MEDIA_CLASS) : NULL; + + pw_log_debug(NAME" %p: node "PW_KEY_MEDIA_CLASS" %s", impl, media_class); + + if (media_class == NULL) + return 0; + + node = sm_object_add_data(object, SESSION_KEY, sizeof(struct node)); + node->obj = (struct sm_node*)object; + node->id = object->id; + node->impl = impl; + node->client_id = client_id; + node->type = NODE_TYPE_UNKNOWN; + node->enabled = true; + spa_list_append(&impl->node_list, &node->link); + + 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; + + node->direction = direction; + node->type = NODE_TYPE_STREAM; + node->media = strdup(media_class); + pw_log_debug(NAME "%p: node %d is stream %s", impl, object->id, node->media); + } + else { + const char *media; + if (strstr(media_class, "Audio/") == media_class) { + media_class += strlen("Audio/"); + media = "Audio"; + } + else if (strstr(media_class, "Video/") == media_class) { + media_class += strlen("Video/"); + media = "Video"; + } + else + return 0; + + if (strcmp(media_class, "Sink") == 0) + direction = PW_DIRECTION_INPUT; + else if (strcmp(media_class, "Source") == 0) + direction = PW_DIRECTION_OUTPUT; + else + return 0; + + node->direction = direction; + node->type = NODE_TYPE_DEVICE; + node->media = strdup(media); + + pw_log_debug(NAME" %p: node %d '%s' prio:%d", impl, + object->id, node->media, node->priority); + } + + node->obj->obj.mask |= SM_NODE_CHANGE_MASK_PARAMS; + sm_object_add_listener(&node->obj->obj, &node->listener, &object_events, node); + + return 1; +} + +static void destroy_node(struct impl *impl, struct node *node) +{ + spa_list_remove(&node->link); + free(node->media); + sm_object_remove_data((struct sm_object*)node->obj, SESSION_KEY); +} + +static void session_create(void *data, struct sm_object *object) +{ + struct impl *impl = data; + int res; + + if (strcmp(object->type, PW_TYPE_INTERFACE_Node) == 0) + res = handle_node(impl, object); + else + res = 0; + + if (res < 0) { + pw_log_warn(NAME" %p: can't handle global %d", impl, object->id); + } + else + sm_media_session_schedule_rescan(impl->session); +} + +static void session_remove(void *data, struct sm_object *object) +{ + struct impl *impl = data; + pw_log_debug(NAME " %p: remove global '%d'", impl, object->id); + + if (strcmp(object->type, PW_TYPE_INTERFACE_Node) == 0) { + struct node *node; + if ((node = sm_object_get_data(object, SESSION_KEY)) != NULL) + destroy_node(impl, node); + } + + sm_media_session_schedule_rescan(impl->session); +} + +struct find_data { + struct impl *impl; + struct node *target; + struct node *node; + bool exclusive; + int priority; + uint64_t plugged; +}; + +static int find_node(void *data, struct node *node) +{ + struct find_data *find = data; + struct impl *impl = find->impl; + int priority = 0; + uint64_t plugged = 0; + + pw_log_debug(NAME " %p: looking at node '%d' enabled:%d busy:%d exclusive:%d", + impl, node->id, node->enabled, node->busy, node->exclusive); + + if (!node->enabled || node->type == NODE_TYPE_UNKNOWN) + return 0; + + if (node->direction == find->target->direction) { + pw_log_debug(".. same direction"); + return 0; + } + if (strcmp(node->media, find->target->media) != 0) { + pw_log_debug(".. incompatible media %s <-> %s", node->media, find->target->media); + return 0; + } + + plugged = node->plugged; + priority = node->priority; + + if ((find->exclusive && node->busy) || node->exclusive) { + pw_log_debug(NAME " %p: node '%d' in use", impl, node->id); + return 0; + } + + pw_log_debug(NAME " %p: found node '%d' %"PRIu64" prio:%d", impl, + node->id, plugged, priority); + + if (find->node == NULL || + priority > find->priority || + (priority == find->priority && plugged > find->plugged)) { + pw_log_debug(NAME " %p: new best %d %" PRIu64, impl, priority, plugged); + find->node = node; + find->priority = priority; + find->plugged = plugged; + } + return 0; +} + +static int link_nodes(struct node *node, struct node *peer) +{ + struct impl *impl = node->impl; + struct pw_properties *props; + + pw_log_debug(NAME " %p: link nodes %d %d", impl, node->id, peer->id); + + if (node->direction == PW_DIRECTION_INPUT) { + struct node *t = node; + node = peer; + peer = t; + } + props = pw_properties_new(NULL, NULL); + pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", node->id); + pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", peer->id); + pw_log_debug(NAME " %p: node %d -> node %d", impl, + node->id, peer->id); + + sm_media_session_create_links(impl->session, &props->dict); + + pw_properties_free(props); + + node->linked++; + peer->linked++; + + return 0; +} + +static int rescan_node(struct impl *impl, struct node *n) +{ + struct spa_dict *props; + const char *str; + bool exclusive; + struct find_data find; + struct pw_node_info *info; + struct node *peer; + struct sm_object *obj; + + if (n->type == NODE_TYPE_DEVICE) + return 0; + + if (!n->active) + return 0; + + if (n->obj->info == NULL || n->obj->info->props == NULL) { + pw_log_debug(NAME " %p: node %d has no properties", impl, n->id); + return 0; + } + + if (n->linked > 0) { + pw_log_debug(NAME " %p: node %d is already linked", impl, n->id); + return 0; + } + + info = n->obj->info; + props = info->props; + + str = spa_dict_lookup(props, PW_KEY_NODE_AUTOCONNECT); + if (str == NULL || !pw_properties_parse_bool(str)) { + pw_log_debug(NAME" %p: node %d does not need autoconnect", impl, n->id); + return 0; + } + + if (n->media == NULL) { + pw_log_debug(NAME" %p: node %d has unknown media", impl, n->id); + return 0; + } + + spa_zero(find); + + if ((str = spa_dict_lookup(props, PW_KEY_NODE_EXCLUSIVE)) != NULL) + exclusive = pw_properties_parse_bool(str); + else + exclusive = false; + + find.impl = impl; + find.target = n; + find.exclusive = exclusive; + + pw_log_info(NAME " %p: exclusive:%d", impl, exclusive); + + str = spa_dict_lookup(props, PW_KEY_NODE_TARGET); + if (str != NULL) { + uint32_t path_id = atoi(str); + pw_log_info(NAME " %p: target:%d", impl, path_id); + + if ((obj = sm_media_session_find_object(impl->session, path_id)) != NULL) { + if (strcmp(obj->type, PW_TYPE_INTERFACE_Node) == 0) { + peer = sm_object_get_data(obj, SESSION_KEY); + goto do_link; + } + } + } + + spa_list_for_each(peer, &impl->node_list, link) + find_node(&find, peer); + + if (find.node == NULL) { + struct sm_object *obj; + + pw_log_warn(NAME " %p: no node found for %d", impl, n->id); + + str = spa_dict_lookup(props, PW_KEY_NODE_DONT_RECONNECT); + if (str != NULL && pw_properties_parse_bool(str)) { +// pw_registry_destroy(impl->registry, n->id); + } + + obj = sm_media_session_find_object(impl->session, n->client_id); + if (obj && strcmp(obj->type, PW_TYPE_INTERFACE_Client) == 0) { + pw_client_error((struct pw_client*)obj->proxy, + n->id, -ENOENT, "no node available"); + } + return -ENOENT; + } + peer = find.node; + + if (exclusive && peer->busy) { + pw_log_warn(NAME" %p: node %d busy, can't get exclusive access", impl, peer->id); + return -EBUSY; + } + peer->exclusive = exclusive; + + pw_log_debug(NAME" %p: linking to node '%d'", impl, peer->id); + + peer->busy = true; + +do_link: + link_nodes(n, peer); + return 1; +} + +static void session_rescan(void *data, int seq) +{ + struct impl *impl = data; + struct node *node; + + clock_gettime(CLOCK_MONOTONIC, &impl->now); + pw_log_debug(NAME" %p: rescan", impl); + + spa_list_for_each(node, &impl->node_list, link) + rescan_node(impl, node); +} + +static void session_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->listener); + free(impl); +} + +static const struct sm_media_session_events session_events = { + SM_VERSION_MEDIA_SESSION_EVENTS, + .create = session_create, + .remove = session_remove, + .rescan = session_rescan, + .destroy = session_destroy, +}; + +int sm_policy_node_start(struct sm_media_session *session) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + impl->session = session; + impl->context = session->context; + + spa_list_init(&impl->node_list); + + sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); + + return 0; +} diff --git a/src/examples/media-session/session-manager.c b/src/examples/media-session/session-manager.c new file mode 100644 index 00000000..18604fa9 --- /dev/null +++ b/src/examples/media-session/session-manager.c @@ -0,0 +1,170 @@ +/* PipeWire + * + * Copyright © 2020 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 "extensions/session-manager.h" + +#include "media-session.h" + +#define NAME "session-manager" +#define SESSION_KEY "session-manager" + +int sm_stream_endpoint_start(struct sm_media_session *sess); +int sm_v4l2_endpoint_start(struct sm_media_session *sess); +int sm_bluez5_endpoint_start(struct sm_media_session *sess); +int sm_alsa_endpoint_start(struct sm_media_session *sess); +int sm_policy_ep_start(struct sm_media_session *sess); + +struct impl { + struct timespec now; + + struct sm_media_session *session; + struct spa_hook listener; + + struct pw_context *context; + + struct spa_hook proxy_client_session_listener; + struct spa_hook client_session_listener; +}; + +/** + * Session implementation + */ +static int client_session_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *impl = object; + pw_proxy_error((struct pw_proxy*)impl->session->client_session, + -ENOTSUP, "Session:SetParam not supported"); + return -ENOTSUP; +} + +static int client_session_link_set_param(void *object, uint32_t link_id, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *impl = object; + pw_proxy_error((struct pw_proxy*)impl->session->client_session, + -ENOTSUP, "Session:LinkSetParam not supported"); + return -ENOTSUP; +} + +static int client_session_link_request_state(void *object, uint32_t link_id, uint32_t state) +{ + return -ENOTSUP; +} + +static const struct pw_client_session_events client_session_events = { + PW_VERSION_CLIENT_SESSION_METHODS, + .set_param = client_session_set_param, + .link_set_param = client_session_link_set_param, + .link_request_state = client_session_link_request_state, +}; + +static void proxy_client_session_bound(void *data, uint32_t id) +{ + struct impl *impl = data; + struct pw_session_info info; + + impl->session->session_id = id; + + spa_zero(info); + info.version = PW_VERSION_SESSION_INFO; + info.id = id; + + pw_log_debug("got sesssion id:%d", id); + + pw_client_session_update(impl->session->client_session, + PW_CLIENT_SESSION_UPDATE_INFO, + 0, NULL, + &info); + + /* start endpoints */ + sm_bluez5_endpoint_start(impl->session); + sm_alsa_endpoint_start(impl->session); + sm_v4l2_endpoint_start(impl->session); + sm_stream_endpoint_start(impl->session); + + sm_policy_ep_start(impl->session); +} + +static const struct pw_proxy_events proxy_client_session_events = { + PW_VERSION_PROXY_EVENTS, + .bound = proxy_client_session_bound, +}; + +static void session_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->listener); + free(impl); +} + +static const struct sm_media_session_events session_events = { + SM_VERSION_MEDIA_SESSION_EVENTS, + .destroy = session_destroy, +}; + +int sm_session_manager_start(struct sm_media_session *session) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + impl->session = session; + impl->context = session->context; + sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); + + session->client_session = (struct pw_client_session *) + sm_media_session_create_object(impl->session, + "client-session", + PW_TYPE_INTERFACE_ClientSession, + PW_VERSION_CLIENT_SESSION, + NULL, 0); + + pw_proxy_add_listener((struct pw_proxy*)session->client_session, + &impl->proxy_client_session_listener, + &proxy_client_session_events, impl); + + pw_client_session_add_listener(session->client_session, + &impl->client_session_listener, + &client_session_events, impl); + + return 0; +} diff --git a/src/examples/meson.build b/src/examples/meson.build index 42cfc73d..c7973512 100644 --- a/src/examples/meson.build +++ b/src/examples/meson.build @@ -49,10 +49,13 @@ executable('media-session', 'media-session/alsa-monitor.c', 'media-session/alsa-endpoint.c', 'media-session/bluez-monitor.c', + 'media-session/bluez-endpoint.c', 'media-session/media-session.c', + 'media-session/session-manager.c', 'media-session/metadata.c', 'media-session/stream-endpoint.c', 'media-session/policy-ep.c', + 'media-session/policy-node.c', 'media-session/v4l2-monitor.c', 'media-session/v4l2-endpoint.c', c_args : [ '-D_GNU_SOURCE' ], diff --git a/src/pipewire/impl-device.c b/src/pipewire/impl-device.c index 1c285549..65cd5c20 100644 --- a/src/pipewire/impl-device.c +++ b/src/pipewire/impl-device.c @@ -318,16 +318,44 @@ static int device_enum_params(void *object, int seq, uint32_t id, uint32_t start return res; } +static void result_device_done(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct resource_data *d = data; + + pw_log_debug(NAME" %p: async result %d %d (%d/%d)", d->device, + res, seq, d->seq, d->end); + + if (seq == d->end) { + spa_hook_remove(&d->listener); + d->end = -1; + pw_impl_client_set_busy(d->resource->client, false); + } +} + static int device_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct resource_data *data = object; struct pw_resource *resource = data->resource; struct pw_impl_device *device = data->device; + struct pw_impl_client *client = resource->client; int res; + static const struct spa_device_events device_events = { + SPA_VERSION_DEVICE_EVENTS, + .result = result_device_done, + }; - if ((res = spa_device_set_param(device->device, id, flags, param)) < 0) + if ((res = spa_device_set_param(device->device, id, flags, param)) < 0) { pw_resource_error(resource, res, spa_strerror(res)); + } else if (SPA_RESULT_IS_ASYNC(res)) { + pw_impl_client_set_busy(client, true); + data->data.data = data; + if (data->end == -1) + spa_device_add_listener(device->device, &data->listener, + &device_events, data); + data->seq = res; + data->end = spa_device_sync(device->device, res); + } return res; } |