diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index 373698fdd..347dccc1b 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -10,9 +10,7 @@ add-spa-lib api.bluez5.* bluez5/libspa-bluez5 load-module libpipewire-module-rtkit load-module libpipewire-module-protocol-native -load-module libpipewire-module-spa-monitor api.alsa.monitor alsa -load-module libpipewire-module-spa-monitor api.v4l2.monitor v4l2 -load-module libpipewire-module-spa-monitor api.bluez5.monitor bluez5 +load-module libpipewire-module-spa-node-factory #load-module libpipewire-module-spa-node videotestsrc/libspa-videotestsrc videotestsrc videotestsrc Spa:POD:Object:Props:patternType=Spa:POD:Object:Props:patternType:snow load-module libpipewire-module-client-node load-module libpipewire-module-client-device diff --git a/src/examples/alsa-monitor.c b/src/examples/alsa-monitor.c new file mode 100644 index 000000000..dcc7d2a44 --- /dev/null +++ b/src/examples/alsa-monitor.c @@ -0,0 +1,349 @@ +/* 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 +#include +#include +#include +#include + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "pipewire/pipewire.h" +#include "pipewire/private.h" + +struct alsa_object; + +struct alsa_node { + struct monitor *monitor; + struct alsa_object *object; + struct spa_list link; + uint32_t id; + + struct pw_properties *props; + + struct spa_handle *handle; + struct pw_proxy *proxy; + struct spa_node *node; +}; + +struct alsa_object { + struct monitor *monitor; + struct spa_list link; + uint32_t id; + + struct pw_properties *props; + + struct spa_handle *handle; + struct pw_proxy *proxy; + struct spa_device *device; + struct spa_hook device_listener; + + struct spa_list node_list; +}; + +static struct alsa_node *alsa_find_node(struct alsa_object *obj, uint32_t id) +{ + struct alsa_node *node; + + spa_list_for_each(node, &obj->node_list, link) { + if (node->id == id) + return node; + } + return NULL; +} + +static void alsa_update_node(struct alsa_object *obj, struct alsa_node *node, + const struct spa_device_object_info *info) +{ + pw_log_debug("update node %u", node->id); + pw_properties_update(node->props, info->props); + spa_debug_dict(0, info->props); +} + +static struct alsa_node *alsa_create_node(struct alsa_object *obj, uint32_t id, + const struct spa_device_object_info *info) +{ + struct alsa_node *node; + struct monitor *monitor = obj->monitor; + struct impl *impl = monitor->impl; + int res; + const char *str; + + pw_log_debug("new node %u", id); + + if (info->type != SPA_TYPE_INTERFACE_Node) { + errno = EINVAL; + return NULL; + } + node = calloc(1, sizeof(*node)); + if (node == NULL) { + res = -errno; + goto exit; + } + + node->props = pw_properties_copy(obj->props); + pw_properties_update(node->props, info->props); + + str = pw_properties_get(obj->props, SPA_KEY_DEVICE_NICK); + if (str == NULL) + str = pw_properties_get(obj->props, SPA_KEY_DEVICE_NAME); + if (str == NULL) + str = pw_properties_get(obj->props, SPA_KEY_DEVICE_ALIAS); + if (str == NULL) + str = "alsa-device"; + pw_properties_set(node->props, PW_KEY_NODE_NAME, str); + pw_properties_set(node->props, "factory.name", info->factory_name); + + node->monitor = monitor; + node->object = obj; + node->id = id; + node->proxy = pw_core_proxy_create_object(impl->core_proxy, + "adapter", + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE_PROXY, + &node->props->dict, + 0); + if (node->proxy == NULL) { + res = -errno; + goto clean_node; + } + + spa_list_append(&obj->node_list, &node->link); + + return node; + +clean_node: + pw_properties_free(node->props); + free(node); +exit: + errno = -res; + return NULL; +} + +static void alsa_remove_node(struct alsa_object *obj, struct alsa_node *node) +{ + pw_log_debug("remove node %u", node->id); + spa_list_remove(&node->link); + pw_proxy_destroy(node->proxy); + free(node->handle); + free(node); +} + +static void alsa_device_info(void *data, const struct spa_device_info *info) +{ + struct alsa_object *obj = data; + pw_properties_update(obj->props, info->props); + spa_debug_dict(0, info->props); +} + +static void alsa_device_object_info(void *data, uint32_t id, + const struct spa_device_object_info *info) +{ + struct alsa_object *obj = data; + struct alsa_node *node; + + node = alsa_find_node(obj, id); + + if (info == NULL) { + if (node == NULL) { + pw_log_warn("object %p: unknown node %u", obj, id); + return; + } + alsa_remove_node(obj, node); + } else if (node == NULL) { + alsa_create_node(obj, id, info); + } else { + alsa_update_node(obj, node, info); + } +} + +static const struct spa_device_events alsa_device_events = { + SPA_VERSION_DEVICE_EVENTS, + .info = alsa_device_info, + .object_info = alsa_device_object_info +}; + +static struct alsa_object *alsa_find_object(struct monitor *monitor, uint32_t id) +{ + struct alsa_object *obj; + + spa_list_for_each(obj, &monitor->object_list, link) { + if (obj->id == id) + return obj; + } + return NULL; +} + +static void alsa_update_object(struct monitor *monitor, struct alsa_object *obj, + const struct spa_monitor_object_info *info) +{ + pw_log_debug("update object %u", obj->id); + spa_debug_dict(0, info->props); + pw_properties_update(obj->props, info->props); +} + +static struct alsa_object *alsa_create_object(struct monitor *monitor, uint32_t id, + const struct spa_monitor_object_info *info) +{ + struct impl *impl = monitor->impl; + struct pw_core *core = impl->core; + struct alsa_object *obj; + struct spa_handle *handle; + int res; + void *iface; + + pw_log_debug("new object %u", id); + + if (info->type != SPA_TYPE_INTERFACE_Device) { + errno = EINVAL; + return NULL; + } + + handle = pw_core_load_spa_handle(core, + info->factory_name, + info->props); + if (handle == NULL) { + res = -errno; + pw_log_error("can't make factory instance: %m"); + goto exit; + } + + if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { + pw_log_error("can't get %d interface: %d", info->type, res); + goto unload_handle; + } + + obj = calloc(1, sizeof(*obj)); + if (obj == NULL) { + res = -errno; + goto unload_handle; + } + + obj->monitor = monitor; + obj->id = id; + obj->handle = handle; + obj->device = iface; + obj->props = pw_properties_new_dict(info->props); + obj->proxy = pw_remote_export(impl->remote, + info->type, obj->props, obj->device, 0); + if (obj->proxy == NULL) { + res = -errno; + goto clean_object; + } + + spa_list_init(&obj->node_list); + + spa_device_add_listener(obj->device, + &obj->device_listener, &alsa_device_events, obj); + + spa_list_append(&monitor->object_list, &obj->link); + + return obj; + +clean_object: + free(obj); +unload_handle: + pw_unload_spa_handle(handle); +exit: + errno = -res; + return NULL; +} + +static void alsa_remove_object(struct monitor *monitor, struct alsa_object *obj) +{ + pw_log_debug("remove object %u", obj->id); + spa_list_remove(&obj->link); + spa_hook_remove(&obj->device_listener); + pw_proxy_destroy(obj->proxy); + free(obj->handle); + free(obj); +} + +static int alsa_monitor_object_info(void *data, uint32_t id, + const struct spa_monitor_object_info *info) +{ + struct monitor *monitor = data; + struct alsa_object *obj; + + obj = alsa_find_object(monitor, id); + + if (info == NULL) { + if (obj == NULL) + return -ENODEV; + alsa_remove_object(monitor, obj); + } else if (obj == NULL) { + if ((obj = alsa_create_object(monitor, id, info)) == NULL) + return -ENOMEM; + } else { + alsa_update_object(monitor, obj, info); + } + return 0; +} + +static const struct spa_monitor_callbacks alsa_monitor_callbacks = +{ + SPA_VERSION_MONITOR_CALLBACKS, + .object_info = alsa_monitor_object_info, +}; + +static int alsa_start_monitor(struct impl *impl, struct monitor *monitor) +{ + struct spa_handle *handle; + struct pw_core *core = impl->core; + int res; + void *iface; + + handle = pw_core_load_spa_handle(core, SPA_NAME_API_ALSA_MONITOR, NULL); + if (handle == NULL) { + res = -errno; + goto out; + } + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Monitor, &iface)) < 0) { + pw_log_error("can't get MONITOR interface: %d", res); + goto out_unload; + } + + monitor->impl = impl; + monitor->handle = handle; + monitor->monitor = iface; + spa_list_init(&monitor->object_list); + + spa_monitor_set_callbacks(monitor->monitor, &alsa_monitor_callbacks, monitor); + + return 0; + + out_unload: + pw_unload_spa_handle(handle); + out: + return res; +} diff --git a/src/examples/bluez-monitor.c b/src/examples/bluez-monitor.c new file mode 100644 index 000000000..64c344a99 --- /dev/null +++ b/src/examples/bluez-monitor.c @@ -0,0 +1,343 @@ +/* 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 +#include +#include +#include +#include + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "pipewire/pipewire.h" +#include "pipewire/private.h" + +struct bluez5_object; + +struct bluez5_node { + struct monitor *monitor; + struct bluez5_object *object; + struct spa_list link; + uint32_t id; + + struct pw_properties *props; + + struct spa_handle *handle; + struct pw_proxy *proxy; + struct spa_node *node; +}; + +struct bluez5_object { + struct monitor *monitor; + struct spa_list link; + uint32_t id; + + struct pw_properties *props; + + struct spa_handle *handle; + struct pw_proxy *proxy; + struct spa_device *device; + struct spa_hook device_listener; + + struct spa_list node_list; +}; + +static struct bluez5_node *bluez5_find_node(struct bluez5_object *obj, uint32_t id) +{ + struct bluez5_node *node; + + spa_list_for_each(node, &obj->node_list, link) { + if (node->id == id) + return node; + } + return NULL; +} + +static void bluez5_update_node(struct bluez5_object *obj, struct bluez5_node *node, + const struct spa_device_object_info *info) +{ + pw_log_debug("update node %u", node->id); + spa_debug_dict(0, info->props); +} + +static struct bluez5_node *bluez5_create_node(struct bluez5_object *obj, uint32_t id, + const struct spa_device_object_info *info) +{ + struct bluez5_node *node; + struct monitor *monitor = obj->monitor; + struct impl *impl = monitor->impl; + int res; + const char *str; + + pw_log_debug("new node %u", id); + + if (info->type != SPA_TYPE_INTERFACE_Node) { + errno = EINVAL; + return NULL; + } + node = calloc(1, sizeof(*node)); + if (node == NULL) { + res = -errno; + goto exit; + } + + node->props = pw_properties_new_dict(info->props); + + str = pw_properties_get(obj->props, PW_KEY_DEVICE_NICK); + if (str == NULL) + str = pw_properties_get(obj->props, SPA_KEY_DEVICE_NAME); + if (str == NULL) + str = pw_properties_get(obj->props, SPA_KEY_DEVICE_ALIAS); + if (str == NULL) + str = "bluetooth-device"; + pw_properties_set(node->props, PW_KEY_NODE_NAME, str); + pw_properties_setf(node->props, "factory.name", info->factory_name); + + node->monitor = monitor; + node->object = obj; + node->id = id; + node->proxy = pw_core_proxy_create_object(impl->core_proxy, + "adapter", + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE_PROXY, + &node->props->dict, + 0); + if (node->proxy == NULL) { + res = -errno; + goto clean_node; + } + + spa_list_append(&obj->node_list, &node->link); + + bluez5_update_node(obj, node, info); + + return node; + +clean_node: + pw_properties_free(node->props); + free(node); +exit: + errno = -res; + return NULL; +} + +static void bluez5_remove_node(struct bluez5_object *obj, struct bluez5_node *node) +{ + pw_log_debug("remove node %u", node->id); + spa_list_remove(&node->link); + pw_proxy_destroy(node->proxy); + free(node->handle); + free(node); +} + +static void bluez5_device_object_info(void *data, uint32_t id, + const struct spa_device_object_info *info) +{ + struct bluez5_object *obj = data; + struct bluez5_node *node; + + node = bluez5_find_node(obj, id); + + if (info == NULL) { + if (node == NULL) { + pw_log_warn("object %p: unknown node %u", obj, id); + return; + } + bluez5_remove_node(obj, node); + } else if (node == NULL) { + bluez5_create_node(obj, id, info); + } else { + bluez5_update_node(obj, node, info); + } + +} + +static const struct spa_device_events bluez5_device_events = { + SPA_VERSION_DEVICE_EVENTS, + .object_info = bluez5_device_object_info +}; + +static struct bluez5_object *bluez5_find_object(struct monitor *monitor, uint32_t id) +{ + struct bluez5_object *obj; + + spa_list_for_each(obj, &monitor->object_list, link) { + if (obj->id == id) + return obj; + } + return NULL; +} + +static void bluez5_update_object(struct monitor *monitor, struct bluez5_object *obj, + const struct spa_monitor_object_info *info) +{ + pw_log_debug("update object %u", obj->id); + spa_debug_dict(0, info->props); +} + +static struct bluez5_object *bluez5_create_object(struct monitor *monitor, uint32_t id, + const struct spa_monitor_object_info *info) +{ + struct impl *impl = monitor->impl; + struct pw_core *core = impl->core; + struct bluez5_object *obj; + struct spa_handle *handle; + int res; + void *iface; + + pw_log_debug("new object %u", id); + + if (info->type != SPA_TYPE_INTERFACE_Device) { + errno = EINVAL; + return NULL; + } + + handle = pw_core_load_spa_handle(core, + info->factory_name, + info->props); + if (handle == NULL) { + res = -errno; + pw_log_error("can't make factory instance: %m"); + goto exit; + } + + if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { + pw_log_error("can't get %d interface: %d", info->type, res); + goto unload_handle; + } + + obj = calloc(1, sizeof(*obj)); + if (obj == NULL) { + res = -errno; + goto unload_handle; + } + + obj->monitor = monitor; + obj->id = id; + obj->handle = handle; + obj->device = iface; + obj->props = pw_properties_new_dict(info->props); + obj->proxy = pw_remote_export(impl->remote, + info->type, obj->props, obj->device, 0); + if (obj->proxy == NULL) { + res = -errno; + goto clean_object; + } + + spa_list_init(&obj->node_list); + + spa_device_add_listener(obj->device, + &obj->device_listener, &bluez5_device_events, obj); + + spa_list_append(&monitor->object_list, &obj->link); + + bluez5_update_object(monitor, obj, info); + + return obj; + +clean_object: + free(obj); +unload_handle: + pw_unload_spa_handle(handle); +exit: + errno = -res; + return NULL; +} + +static void bluez5_remove_object(struct monitor *monitor, struct bluez5_object *obj) +{ + pw_log_debug("remove object %u", obj->id); + spa_list_remove(&obj->link); + spa_hook_remove(&obj->device_listener); + pw_proxy_destroy(obj->proxy); + free(obj->handle); + free(obj); +} + +static int bluez5_monitor_object_info(void *data, uint32_t id, + const struct spa_monitor_object_info *info) +{ + struct monitor *monitor = data; + struct bluez5_object *obj; + + obj = bluez5_find_object(monitor, id); + + if (info == NULL) { + if (obj == NULL) + return -ENODEV; + bluez5_remove_object(monitor, obj); + } else if (obj == NULL) { + if ((obj = bluez5_create_object(monitor, id, info)) == NULL) + return -ENOMEM; + } else { + bluez5_update_object(monitor, obj, info); + } + return 0; +} + +static const struct spa_monitor_callbacks bluez5_monitor_callbacks = +{ + SPA_VERSION_MONITOR_CALLBACKS, + .object_info = bluez5_monitor_object_info, +}; + +static int bluez5_start_monitor(struct impl *impl, struct monitor *monitor) +{ + struct spa_handle *handle; + struct pw_core *core = impl->core; + int res; + void *iface; + + handle = pw_core_load_spa_handle(core, SPA_NAME_API_BLUEZ5_MONITOR, NULL); + if (handle == NULL) { + res = -errno; + goto out; + } + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Monitor, &iface)) < 0) { + pw_log_error("can't get MONITOR interface: %d", res); + goto out_unload; + } + + monitor->impl = impl; + monitor->handle = handle; + monitor->monitor = iface; + spa_list_init(&monitor->object_list); + + spa_monitor_set_callbacks(monitor->monitor, &bluez5_monitor_callbacks, monitor); + + return 0; + + out_unload: + pw_unload_spa_handle(handle); + out: + return res; +} diff --git a/src/examples/media-session.c b/src/examples/media-session.c index 14a3195b6..d81b877ae 100644 --- a/src/examples/media-session.c +++ b/src/examples/media-session.c @@ -49,6 +49,17 @@ #define MIN_QUANTUM_SIZE 64 #define MAX_QUANTUM_SIZE 1024 +struct impl; + +struct monitor { + struct impl *impl; + + struct spa_handle *handle; + struct spa_monitor *monitor; + + struct spa_list object_list; +}; + struct impl { struct timespec now; @@ -69,6 +80,10 @@ struct impl { struct spa_list node_list; struct spa_list session_list; int seq; + + struct monitor bluez5_monitor; + struct monitor alsa_monitor; + struct monitor v4l2_monitor; }; struct object { @@ -149,10 +164,6 @@ struct session { uint64_t plugged; struct node *node; - struct node *dsp; - struct pw_proxy *dsp_proxy; - struct pw_proxy *link_proxy; - struct spa_hook link_listener; struct spa_list node_list; @@ -167,6 +178,10 @@ struct session { bool need_dsp; }; +#include "alsa-monitor.c" +#include "v4l2-monitor.c" +#include "bluez-monitor.c" + static void add_object(struct impl *impl, struct object *obj) { size_t size = pw_map_get_size(&impl->globals); @@ -216,8 +231,6 @@ static void idle_timeout(void *data, uint64_t expirations) remove_idle_timeout(sess); pw_node_proxy_send_command((struct pw_node_proxy*)sess->node->obj.proxy, cmd); - if (sess->dsp) - pw_node_proxy_send_command((struct pw_node_proxy*)sess->dsp->obj.proxy, cmd); } static void add_idle_timeout(struct session *sess) @@ -358,17 +371,6 @@ static void remove_session(struct impl *impl, struct session *sess) spa_list_remove(&n->session_link); } - if (sess->dsp) { - sess->dsp->manager = NULL; - } - if (sess->dsp_proxy) { - pw_log_debug(NAME " %p: destroy dsp %p", impl, sess->dsp_proxy); - pw_proxy_destroy(sess->dsp_proxy); - sess->dsp_proxy = NULL; - } - if (sess->link_proxy) { - spa_hook_remove(&sess->link_listener); - } spa_list_remove(&sess->l); free(sess); } @@ -397,7 +399,6 @@ static void node_proxy_destroy(void *data) if (n->manager) { switch (n->type) { case NODE_TYPE_DSP: - n->manager->dsp = NULL; break; case NODE_TYPE_DEVICE: remove_session(impl, n->manager); @@ -465,9 +466,6 @@ handle_node(struct impl *impl, uint32_t id, uint32_t parent_id, node->media = strdup(media_class); pw_log_debug(NAME "%p: node %d is stream %s", impl, id, node->media); - pw_node_proxy_enum_params((struct pw_node_proxy*)p, - 0, SPA_PARAM_EnumFormat, - 0, -1, NULL); } else { struct session *sess; @@ -498,7 +496,7 @@ handle_node(struct impl *impl, uint32_t id, uint32_t parent_id, sess->id = id; sess->need_dsp = need_dsp; sess->enabled = false; - sess->starting = true; + sess->starting = need_dsp; sess->node = node; if ((str = spa_dict_lookup(props, PW_KEY_NODE_PLUGGED)) != NULL) sess->plugged = pw_properties_parse_uint64(str); @@ -515,6 +513,9 @@ handle_node(struct impl *impl, uint32_t id, uint32_t parent_id, pw_log_debug(NAME" %p: new session for device node %d %d", impl, id, need_dsp); } + pw_node_proxy_enum_params((struct pw_node_proxy*)p, + 0, SPA_PARAM_EnumFormat, + 0, -1, NULL); return 1; } @@ -1078,21 +1079,12 @@ static int rescan_node(struct impl *impl, struct node *node) return 0; } - if (exclusive || session->dsp == NULL) { - if (exclusive && session->busy) { - pw_log_warn(NAME" %p: session %d busy, can't get exclusive access", impl, session->id); - return -EBUSY; - } - if (session->link_proxy != NULL) { - pw_log_warn(NAME" %p: session %d busy with DSP", impl, session->id); - return -EBUSY; - } - peer = session->node; - session->exclusive = exclusive; - } - else { - peer = session->dsp; + if (exclusive && session->busy) { + pw_log_warn(NAME" %p: session %d busy, can't get exclusive access", impl, session->id); + return -EBUSY; } + peer = session->node; + session->exclusive = exclusive; pw_log_debug(NAME" %p: linking to session '%d'", impl, session->id); @@ -1100,7 +1092,7 @@ static int rescan_node(struct impl *impl, struct node *node) node->session = session; spa_list_append(&session->node_list, &node->session_link); - if (!exclusive && session->dsp) { + if (!exclusive) { do_link_profile: audio_info = peer->profile_format; @@ -1140,94 +1132,45 @@ do_link: return 1; } -static void dsp_node_event_info(void *object, const struct pw_node_info *info) -{ - struct session *s = object; - struct node *dsp; - - if ((dsp = find_object(s->impl, info->id)) == NULL) - return; - - pw_log_debug(NAME" %p: dsp node session %d id %d", dsp->obj.impl, s->id, info->id); - - s->dsp = dsp; - spa_hook_remove(&s->listener); - - dsp->direction = s->direction; - dsp->type = NODE_TYPE_DSP; - dsp->manager = s; - dsp->media_type = s->node->media_type; - dsp->media_subtype = s->node->media_subtype; - dsp->format = s->node->format; - dsp->profile_format = dsp->format; - dsp->profile_format.format = SPA_AUDIO_FORMAT_F32P; -} - -static const struct pw_node_proxy_events dsp_node_events = { - PW_VERSION_NODE_PROXY_EVENTS, - .info = dsp_node_event_info, -}; - static void rescan_session(struct impl *impl, struct session *sess) { - if (sess->need_dsp && sess->dsp == NULL && sess->dsp_proxy == NULL) { - struct pw_properties *props; - struct node *node = sess->node; - struct spa_audio_info_raw info = { 0, }; - uint8_t buf[1024]; - struct spa_pod_builder b = { 0, }; - struct spa_pod *param; - const char *str; + struct node *node = sess->node; + struct spa_audio_info_raw info = { 0, }; + uint8_t buf[1024]; + struct spa_pod_builder b = { 0, }; + struct spa_pod *param; - if (node->info->props == NULL) { - pw_log_debug(NAME " %p: node %p has no properties", impl, node); - return; - } + if (!sess->starting) + return; - if (node->media_type != SPA_MEDIA_TYPE_audio || - node->media_subtype != SPA_MEDIA_SUBTYPE_raw) { - pw_log_debug(NAME " %p: node %p has no media type", impl, node); - return; - } - - info = node->format; - info.rate = DEFAULT_SAMPLERATE; - - props = pw_properties_new_dict(node->info->props); - if ((str = pw_properties_get(props, PW_KEY_DEVICE_NICK)) == NULL) - str = node->info->name; - pw_properties_set(props, PW_KEY_NODE_NAME, str); - if (sess->direction == PW_DIRECTION_OUTPUT) - pw_properties_setf(props, "factory.name", "api.alsa.pcm.sink"); - else - pw_properties_setf(props, "factory.name", "api.alsa.pcm.source"); - - pw_log_debug(NAME" %p: making audio dsp for session %d", impl, sess->id); - - sess->dsp_proxy = pw_core_proxy_create_object(impl->core_proxy, - "adapter", - PW_TYPE_INTERFACE_Node, - PW_VERSION_NODE_PROXY, - &props->dict, - 0); - pw_properties_free(props); - - pw_proxy_add_object_listener(sess->dsp_proxy, &sess->listener, &dsp_node_events, sess); - - spa_pod_builder_init(&b, buf, sizeof(buf)); - param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, - SPA_PARAM_PROFILE_direction, SPA_POD_Id(pw_direction_reverse(sess->direction)), - SPA_PARAM_PROFILE_format, SPA_POD_Pod(param)); - - pw_node_proxy_set_param((struct pw_node_proxy*)sess->dsp_proxy, - SPA_PARAM_Profile, 0, param); - schedule_rescan(impl); + if (node->info->props == NULL) { + pw_log_debug(NAME " %p: node %p has no properties", impl, node); + return; } - else { - sess->starting = false; + + if (node->media_type != SPA_MEDIA_TYPE_audio || + node->media_subtype != SPA_MEDIA_SUBTYPE_raw) { + pw_log_debug(NAME " %p: node %p has no media type", impl, node); + return; } + + info = node->format; + info.rate = DEFAULT_SAMPLERATE; + + pw_log_debug(NAME" %p: setting profile for session %d", impl, sess->id); + + spa_pod_builder_init(&b, buf, sizeof(buf)); + param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, + SPA_PARAM_PROFILE_direction, SPA_POD_Id(pw_direction_reverse(sess->direction)), + SPA_PARAM_PROFILE_format, SPA_POD_Pod(param)); + + pw_node_proxy_set_param((struct pw_node_proxy*)sess->node->obj.proxy, + SPA_PARAM_Profile, 0, param); + schedule_rescan(impl); + + sess->starting = false; } static void do_rescan(struct impl *impl) @@ -1278,6 +1221,10 @@ static void on_state_changed(void *_data, enum pw_remote_state old, enum pw_remo pw_registry_proxy_add_listener(impl->registry_proxy, &impl->registry_listener, ®istry_events, impl); + bluez5_start_monitor(impl, &impl->bluez5_monitor); + alsa_start_monitor(impl, &impl->alsa_monitor); + v4l2_start_monitor(impl, &impl->v4l2_monitor); + schedule_rescan(impl); break; @@ -1315,6 +1262,12 @@ int main(int argc, char *argv[]) spa_list_init(&impl.node_list); spa_list_init(&impl.session_list); + pw_core_add_spa_lib(impl.core, "api.bluez5.*", "bluez5/libspa-bluez5"); + pw_core_add_spa_lib(impl.core, "api.alsa.*", "alsa/libspa-alsa"); + pw_core_add_spa_lib(impl.core, "api.v4l2.*", "v4l2/libspa-v4l2"); + + pw_module_load(impl.core, "libpipewire-module-client-device", NULL, NULL, NULL, NULL); + clock_gettime(CLOCK_MONOTONIC, &impl.now); pw_remote_add_listener(impl.remote, &impl.remote_listener, &remote_events, &impl); diff --git a/src/examples/v4l2-monitor.c b/src/examples/v4l2-monitor.c new file mode 100644 index 000000000..9da1097e1 --- /dev/null +++ b/src/examples/v4l2-monitor.c @@ -0,0 +1,348 @@ +/* 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 +#include +#include +#include +#include + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "pipewire/pipewire.h" +#include "pipewire/private.h" + +struct v4l2_object; + +struct v4l2_node { + struct monitor *monitor; + struct v4l2_object *object; + struct spa_list link; + uint32_t id; + + struct pw_properties *props; + + struct spa_handle *handle; + struct pw_proxy *proxy; + struct spa_node *node; +}; + +struct v4l2_object { + struct monitor *monitor; + struct spa_list link; + uint32_t id; + + struct pw_properties *props; + + struct spa_handle *handle; + struct pw_proxy *proxy; + struct spa_device *device; + struct spa_hook device_listener; + + struct spa_list node_list; +}; + +static struct v4l2_node *v4l2_find_node(struct v4l2_object *obj, uint32_t id) +{ + struct v4l2_node *node; + + spa_list_for_each(node, &obj->node_list, link) { + if (node->id == id) + return node; + } + return NULL; +} + +static void v4l2_update_node(struct v4l2_object *obj, struct v4l2_node *node, + const struct spa_device_object_info *info) +{ + pw_log_debug("update node %u", node->id); + pw_properties_update(node->props, info->props); + spa_debug_dict(0, info->props); +} + +static struct v4l2_node *v4l2_create_node(struct v4l2_object *obj, uint32_t id, + const struct spa_device_object_info *info) +{ + struct v4l2_node *node; + struct monitor *monitor = obj->monitor; + struct impl *impl = monitor->impl; + int res; + const char *str; + + pw_log_debug("new node %u", id); + + if (info->type != SPA_TYPE_INTERFACE_Node) { + errno = EINVAL; + return NULL; + } + node = calloc(1, sizeof(*node)); + if (node == NULL) { + res = -errno; + goto exit; + } + + node->props = pw_properties_copy(obj->props); + pw_properties_update(node->props, info->props); + + str = pw_properties_get(obj->props, SPA_KEY_DEVICE_NICK); + if (str == NULL) + str = pw_properties_get(obj->props, SPA_KEY_DEVICE_NAME); + if (str == NULL) + str = pw_properties_get(obj->props, SPA_KEY_DEVICE_ALIAS); + if (str == NULL) + str = "v4l2-device"; + pw_properties_set(node->props, PW_KEY_NODE_NAME, str); + pw_properties_set(node->props, "factory.name", info->factory_name); + + node->monitor = monitor; + node->object = obj; + node->id = id; + node->proxy = pw_core_proxy_create_object(impl->core_proxy, + "spa-node-factory", + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE_PROXY, + &node->props->dict, + 0); + if (node->proxy == NULL) { + res = -errno; + goto clean_node; + } + + spa_list_append(&obj->node_list, &node->link); + + return node; + +clean_node: + pw_properties_free(node->props); + free(node); +exit: + errno = -res; + return NULL; +} + +static void v4l2_remove_node(struct v4l2_object *obj, struct v4l2_node *node) +{ + pw_log_debug("remove node %u", node->id); + spa_list_remove(&node->link); + pw_proxy_destroy(node->proxy); + free(node->handle); + free(node); +} + +static void v4l2_device_info(void *data, const struct spa_device_info *info) +{ + struct v4l2_object *obj = data; + pw_properties_update(obj->props, info->props); + spa_debug_dict(0, info->props); +} + +static void v4l2_device_object_info(void *data, uint32_t id, + const struct spa_device_object_info *info) +{ + struct v4l2_object *obj = data; + struct v4l2_node *node; + + node = v4l2_find_node(obj, id); + + if (info == NULL) { + if (node == NULL) { + pw_log_warn("object %p: unknown node %u", obj, id); + return; + } + v4l2_remove_node(obj, node); + } else if (node == NULL) { + v4l2_create_node(obj, id, info); + } else { + v4l2_update_node(obj, node, info); + } +} + +static const struct spa_device_events v4l2_device_events = { + SPA_VERSION_DEVICE_EVENTS, + .info = v4l2_device_info, + .object_info = v4l2_device_object_info +}; + +static struct v4l2_object *v4l2_find_object(struct monitor *monitor, uint32_t id) +{ + struct v4l2_object *obj; + + spa_list_for_each(obj, &monitor->object_list, link) { + if (obj->id == id) + return obj; + } + return NULL; +} + +static void v4l2_update_object(struct monitor *monitor, struct v4l2_object *obj, + const struct spa_monitor_object_info *info) +{ + pw_log_debug("update object %u", obj->id); + spa_debug_dict(0, info->props); + pw_properties_update(obj->props, info->props); +} + +static struct v4l2_object *v4l2_create_object(struct monitor *monitor, uint32_t id, + const struct spa_monitor_object_info *info) +{ + struct impl *impl = monitor->impl; + struct pw_core *core = impl->core; + struct v4l2_object *obj; + struct spa_handle *handle; + int res; + void *iface; + + pw_log_debug("new object %u", id); + + if (info->type != SPA_TYPE_INTERFACE_Device) { + errno = EINVAL; + return NULL; + } + + handle = pw_core_load_spa_handle(core, + info->factory_name, + info->props); + if (handle == NULL) { + res = -errno; + pw_log_error("can't make factory instance: %m"); + goto exit; + } + + if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { + pw_log_error("can't get %d interface: %d", info->type, res); + goto unload_handle; + } + + obj = calloc(1, sizeof(*obj)); + if (obj == NULL) { + res = -errno; + goto unload_handle; + } + + obj->monitor = monitor; + obj->id = id; + obj->handle = handle; + obj->device = iface; + obj->props = pw_properties_new_dict(info->props); + obj->proxy = pw_remote_export(impl->remote, + info->type, obj->props, obj->device, 0); + if (obj->proxy == NULL) { + res = -errno; + goto clean_object; + } + + spa_list_init(&obj->node_list); + + spa_device_add_listener(obj->device, + &obj->device_listener, &v4l2_device_events, obj); + + spa_list_append(&monitor->object_list, &obj->link); + + return obj; + +clean_object: + free(obj); +unload_handle: + pw_unload_spa_handle(handle); +exit: + errno = -res; + return NULL; +} + +static void v4l2_remove_object(struct monitor *monitor, struct v4l2_object *obj) +{ + pw_log_debug("remove object %u", obj->id); + spa_list_remove(&obj->link); + spa_hook_remove(&obj->device_listener); + pw_proxy_destroy(obj->proxy); + free(obj->handle); + free(obj); +} + +static int v4l2_monitor_object_info(void *data, uint32_t id, + const struct spa_monitor_object_info *info) +{ + struct monitor *monitor = data; + struct v4l2_object *obj; + + obj = v4l2_find_object(monitor, id); + + if (info == NULL) { + if (obj == NULL) + return -ENODEV; + v4l2_remove_object(monitor, obj); + } else if (obj == NULL) { + if ((obj = v4l2_create_object(monitor, id, info)) == NULL) + return -ENOMEM; + } else { + v4l2_update_object(monitor, obj, info); + } + return 0; +} + +static const struct spa_monitor_callbacks v4l2_monitor_callbacks = +{ + SPA_VERSION_MONITOR_CALLBACKS, + .object_info = v4l2_monitor_object_info, +}; + +static int v4l2_start_monitor(struct impl *impl, struct monitor *monitor) +{ + struct spa_handle *handle; + struct pw_core *core = impl->core; + int res; + void *iface; + + handle = pw_core_load_spa_handle(core, SPA_NAME_API_V4L2_MONITOR, NULL); + if (handle == NULL) { + res = -errno; + goto out; + } + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Monitor, &iface)) < 0) { + pw_log_error("can't get MONITOR interface: %d", res); + goto out_unload; + } + + monitor->impl = impl; + monitor->handle = handle; + monitor->monitor = iface; + spa_list_init(&monitor->object_list); + + spa_monitor_set_callbacks(monitor->monitor, &v4l2_monitor_callbacks, monitor); + + return 0; + + out_unload: + pw_unload_spa_handle(handle); + out: + return res; +}