diff options
Diffstat (limited to '')
-rw-r--r-- | src/modules/spa/meson.build | 31 | ||||
-rw-r--r-- | src/modules/spa/module-device-factory.c | 281 | ||||
-rw-r--r-- | src/modules/spa/module-device.c | 127 | ||||
-rw-r--r-- | src/modules/spa/module-node-factory.c | 280 | ||||
-rw-r--r-- | src/modules/spa/module-node.c | 129 | ||||
-rw-r--r-- | src/modules/spa/spa-device.c | 161 | ||||
-rw-r--r-- | src/modules/spa/spa-device.h | 62 | ||||
-rw-r--r-- | src/modules/spa/spa-node.c | 292 | ||||
-rw-r--r-- | src/modules/spa/spa-node.h | 63 |
9 files changed, 1426 insertions, 0 deletions
diff --git a/src/modules/spa/meson.build b/src/modules/spa/meson.build new file mode 100644 index 0000000..8332910 --- /dev/null +++ b/src/modules/spa/meson.build @@ -0,0 +1,31 @@ +pipewire_module_spa_node = shared_library('pipewire-module-spa-node', + [ 'module-node.c', 'spa-node.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], +) + +pipewire_module_spa_device = shared_library('pipewire-module-spa-device', + [ 'module-device.c', 'spa-device.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], +) + +pipewire_module_spa_node_factory = shared_library('pipewire-module-spa-node-factory', + [ 'module-node-factory.c', 'spa-node.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], +) + +pipewire_module_spa_device_factory = shared_library('pipewire-module-spa-device-factory', + [ 'module-device-factory.c', 'spa-device.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], +) diff --git a/src/modules/spa/module-device-factory.c b/src/modules/spa/module-device-factory.c new file mode 100644 index 0000000..fd712a2 --- /dev/null +++ b/src/modules/spa/module-device-factory.c @@ -0,0 +1,281 @@ +/* PipeWire + * + * Copyright © 2018 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 <dlfcn.h> + +#include "config.h" + +#include <spa/utils/result.h> + +#include "pipewire/impl.h" + +#include "spa-device.h" + +#define NAME "spa-device-factory" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define FACTORY_USAGE SPA_KEY_FACTORY_NAME"=<factory-name> " \ + "["SPA_KEY_LIBRARY_NAME"=<library-name>]" + +static const struct spa_dict_item module_props[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Provide a factory to make SPA devices" }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct factory_data { + struct pw_context *context; + + struct pw_impl_module *module; + struct spa_hook module_listener; + + struct pw_impl_factory *factory; + struct spa_hook factory_listener; + + struct spa_list device_list; +}; + +struct device_data { + struct spa_list link; + struct pw_impl_device *device; + struct spa_hook device_listener; + struct spa_hook resource_listener; +}; + +static void resource_destroy(void *data) +{ + struct device_data *nd = data; + pw_log_debug("device %p", nd); + spa_hook_remove(&nd->resource_listener); + if (nd->device) + pw_impl_device_destroy(nd->device); +} + +static const struct pw_resource_events resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = resource_destroy +}; + +static void device_destroy(void *data) +{ + struct device_data *nd = data; + spa_list_remove(&nd->link); + spa_hook_remove(&nd->device_listener); + nd->device = NULL; +} + +static const struct pw_impl_device_events device_events = { + PW_VERSION_IMPL_DEVICE_EVENTS, + .destroy = device_destroy, +}; + +static void *create_object(void *_data, + struct pw_resource *resource, + const char *type, + uint32_t version, + struct pw_properties *properties, + uint32_t new_id) +{ + struct factory_data *data = _data; + struct pw_context *context = data->context; + struct pw_impl_device *device; + const char *str; + char *factory_name = NULL; + struct device_data *nd; + struct pw_impl_client *client; + int res; + + if (properties == NULL) + goto error_properties; + + if ((str = pw_properties_get(properties, SPA_KEY_FACTORY_NAME)) == NULL) + goto error_properties; + + if ((factory_name = strdup(str)) == NULL) + goto error_properties; + + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", + pw_global_get_id(pw_impl_factory_get_global(data->factory))); + + client = resource ? pw_resource_get_client(resource) : NULL; + + if (client) { + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", + pw_global_get_id(pw_impl_client_get_global(client))); + } + + device = pw_spa_device_load(context, + factory_name, + 0, + properties, + sizeof(struct device_data)); + if (device == NULL) { + res = -errno; + goto error_device; + } + + nd = pw_spa_device_get_user_data(device); + nd->device = device; + spa_list_append(&data->device_list, &nd->link); + + pw_impl_device_add_listener(device, &nd->device_listener, &device_events, nd); + + if (client) { + struct pw_resource *bound_resource; + + res = pw_global_bind(pw_impl_device_get_global(device), + client, + PW_PERM_ALL, version, + new_id); + if (res < 0) + goto error_bind; + + if ((bound_resource = pw_impl_client_find_resource(client, new_id)) == NULL) + goto error_bind; + + pw_resource_add_listener(bound_resource, &nd->resource_listener, &resource_events, nd); + } + free(factory_name); + return device; + +error_properties: + res = -EINVAL; + pw_resource_errorf_id(resource, new_id, res, "usage: "FACTORY_USAGE); + goto error_exit_cleanup; +error_device: + pw_resource_errorf_id(resource, new_id, res, + "can't create device %s: %s", factory_name, + spa_strerror(res)); + goto error_exit; +error_bind: + pw_resource_errorf_id(resource, new_id, res, "can't bind device"); + pw_impl_device_destroy(device); + goto error_exit; + +error_exit_cleanup: + pw_properties_free(properties); +error_exit: + free(factory_name); + errno = -res; + return NULL; +} + +static const struct pw_impl_factory_implementation factory_impl = { + PW_VERSION_IMPL_FACTORY_IMPLEMENTATION, + .create_object = create_object, +}; + +static void factory_destroy(void *data) +{ + struct factory_data *d = data; + struct device_data *nd; + + spa_hook_remove(&d->factory_listener); + + spa_list_consume(nd, &d->device_list, link) + pw_impl_device_destroy(nd->device); + + d->factory = NULL; + if (d->module) + pw_impl_module_destroy(d->module); +} + +static const struct pw_impl_factory_events factory_events = { + PW_VERSION_IMPL_FACTORY_EVENTS, + .destroy = factory_destroy, +}; + +static void module_destroy(void *data) +{ + struct factory_data *d = data; + + spa_hook_remove(&d->module_listener); + d->module = NULL; + if (d->factory) + pw_impl_factory_destroy(d->factory); +} + +static void module_registered(void *data) +{ + struct factory_data *d = data; + struct pw_impl_module *module = d->module; + struct pw_impl_factory *factory = d->factory; + struct spa_dict_item items[1]; + char id[16]; + int res; + + snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module))); + items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); + pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); + + if ((res = pw_impl_factory_register(factory, NULL)) < 0) { + pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res)); + } +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, + .registered = module_registered, +}; + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_impl_factory *factory; + struct factory_data *data; + + PW_LOG_TOPIC_INIT(mod_topic); + + factory = pw_context_create_factory(context, + "spa-device-factory", + PW_TYPE_INTERFACE_Device, + PW_VERSION_DEVICE, + NULL, + sizeof(*data)); + if (factory == NULL) + return -errno; + + data = pw_impl_factory_get_user_data(factory); + data->factory = factory; + data->module = module; + data->context = context; + spa_list_init(&data->device_list); + + pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data); + pw_impl_factory_set_implementation(factory, &factory_impl, data); + + pw_log_debug("module %p: new", module); + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + pw_impl_module_add_listener(module, &data->module_listener, &module_events, data); + + return 0; +} diff --git a/src/modules/spa/module-device.c b/src/modules/spa/module-device.c new file mode 100644 index 0000000..7bd6821 --- /dev/null +++ b/src/modules/spa/module-device.c @@ -0,0 +1,127 @@ +/* PipeWire + * Copyright © 2018 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 "config.h" + +#include <errno.h> +#include <getopt.h> +#include <limits.h> + +#include <pipewire/impl.h> + +#include "spa-device.h" + +#define NAME "spa-device" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define MODULE_USAGE "<factory> [key=value ...]" + +static const struct spa_dict_item module_props[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Load and manage an SPA device" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct device_data { + struct pw_impl_device *this; + struct pw_context *context; + + struct spa_hook module_listener; +}; + +static void module_destroy(void *_data) +{ + struct device_data *data = _data; + + spa_hook_remove(&data->module_listener); + + pw_impl_device_destroy(data->this); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_properties *props = NULL; + char **argv = NULL; + int n_tokens; + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_impl_device *device; + struct device_data *data; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + if (args == NULL) + goto error_arguments; + + argv = pw_split_strv(args, " \t", 2, &n_tokens); + if (n_tokens < 1) + goto error_arguments; + + if (n_tokens == 2) { + props = pw_properties_new_string(argv[1]); + if (props == NULL) { + res = -errno; + goto error_exit_cleanup; + } + } + + device = pw_spa_device_load(context, + argv[0], + 0, + props, + sizeof(struct device_data)); + if (device == NULL) { + res = -errno; + goto error_exit_cleanup; + } + + pw_free_strv(argv); + + data = pw_spa_device_get_user_data(device); + data->this = device; + data->context = context; + + pw_log_debug("module %p: new", module); + pw_impl_module_add_listener(module, &data->module_listener, &module_events, data); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; + +error_arguments: + res = -EINVAL; + pw_log_error("usage: module-spa-device " MODULE_USAGE); + goto error_exit_cleanup; +error_exit_cleanup: + pw_free_strv(argv); + return res; +} diff --git a/src/modules/spa/module-node-factory.c b/src/modules/spa/module-node-factory.c new file mode 100644 index 0000000..7fb2a42 --- /dev/null +++ b/src/modules/spa/module-node-factory.c @@ -0,0 +1,280 @@ +/* PipeWire + * + * Copyright © 2018 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 <dlfcn.h> + +#include <spa/utils/result.h> + +#include "config.h" + +#include "pipewire/impl.h" + +#include "spa-node.h" + +#define NAME "spa-node-factory" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define FACTORY_USAGE SPA_KEY_FACTORY_NAME"=<factory-name> " \ + "["SPA_KEY_LIBRARY_NAME"=<library-name>]" + +static const struct spa_dict_item module_props[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Provide a factory to make SPA nodes" }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct factory_data { + struct pw_context *context; + + struct pw_impl_module *module; + struct spa_hook module_listener; + + struct pw_impl_factory *factory; + struct spa_hook factory_listener; + + struct spa_list node_list; +}; + +struct node_data { + struct factory_data *data; + struct spa_list link; + struct pw_impl_node *node; + struct spa_hook node_listener; + struct pw_resource *resource; + struct spa_hook resource_listener; + unsigned int linger:1; +}; + +static void resource_destroy(void *data) +{ + struct node_data *nd = data; + pw_log_debug("node %p", nd); + spa_hook_remove(&nd->resource_listener); + nd->resource = NULL; + if (nd->node && !nd->linger) + pw_impl_node_destroy(nd->node); +} + +static const struct pw_resource_events resource_events = { + PW_VERSION_RESOURCE_EVENTS, + .destroy = resource_destroy +}; + +static void node_destroy(void *data) +{ + struct node_data *nd = data; + pw_log_debug("node %p", nd); + spa_list_remove(&nd->link); + spa_hook_remove(&nd->node_listener); + nd->node = NULL; + + if (nd->resource) { + spa_hook_remove(&nd->resource_listener); + nd->resource = NULL; + } +} + +static const struct pw_impl_node_events node_events = { + PW_VERSION_IMPL_NODE_EVENTS, + .destroy = node_destroy, +}; + +static void *create_object(void *_data, + struct pw_resource *resource, + const char *type, + uint32_t version, + struct pw_properties *properties, + uint32_t new_id) +{ + struct factory_data *data = _data; + struct pw_context *context = data->context; + struct pw_impl_node *node; + const char *factory_name; + struct node_data *nd; + int res; + struct pw_impl_client *client; + bool linger; + + if (properties == NULL) + goto error_properties; + + factory_name = pw_properties_get(properties, SPA_KEY_FACTORY_NAME); + if (factory_name == NULL) + goto error_properties; + + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", + pw_global_get_id(pw_impl_factory_get_global(data->factory))); + + linger = pw_properties_get_bool(properties, PW_KEY_OBJECT_LINGER, false); + + client = resource ? pw_resource_get_client(resource) : NULL; + if (client && !linger) { + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", + pw_global_get_id(pw_impl_client_get_global(client))); + } + node = pw_spa_node_load(context, + factory_name, + PW_SPA_NODE_FLAG_ACTIVATE, + properties, + sizeof(struct node_data)); + if (node == NULL) + goto error_create_node; + + nd = pw_spa_node_get_user_data(node); + nd->data = data; + nd->node = node; + nd->linger = linger; + spa_list_append(&data->node_list, &nd->link); + + pw_impl_node_add_listener(node, &nd->node_listener, &node_events, nd); + + if (client) { + res = pw_global_bind(pw_impl_node_get_global(node), + client, PW_PERM_ALL, version, new_id); + if (res < 0) + goto error_bind; + + if ((nd->resource = pw_impl_client_find_resource(client, new_id)) == NULL) + goto error_bind; + + pw_resource_add_listener(nd->resource, &nd->resource_listener, &resource_events, nd); + } + return node; + +error_properties: + res = -EINVAL; + pw_resource_errorf_id(resource, new_id, res, "usage: "FACTORY_USAGE); + goto error_exit_cleanup; +error_create_node: + res = -errno; + pw_resource_errorf_id(resource, new_id, res, + "can't create node: %s", spa_strerror(res)); + goto error_exit; +error_bind: + pw_resource_errorf_id(resource, new_id, res, "can't bind node"); + pw_impl_node_destroy(node); + goto error_exit; + +error_exit_cleanup: + pw_properties_free(properties); +error_exit: + errno = -res; + return NULL; +} + +static const struct pw_impl_factory_implementation factory_impl = { + PW_VERSION_IMPL_FACTORY_IMPLEMENTATION, + .create_object = create_object, +}; + +static void factory_destroy(void *data) +{ + struct factory_data *d = data; + struct node_data *nd; + + spa_hook_remove(&d->factory_listener); + spa_list_consume(nd, &d->node_list, link) + pw_impl_node_destroy(nd->node); + d->factory = NULL; + if (d->module) + pw_impl_module_destroy(d->module); +} + +static const struct pw_impl_factory_events factory_events = { + PW_VERSION_IMPL_FACTORY_EVENTS, + .destroy = factory_destroy, +}; + +static void module_destroy(void *data) +{ + struct factory_data *d = data; + spa_hook_remove(&d->module_listener); + d->module = NULL; + if (d->factory) + pw_impl_factory_destroy(d->factory); +} + +static void module_registered(void *data) +{ + struct factory_data *d = data; + struct pw_impl_module *module = d->module; + struct pw_impl_factory *factory = d->factory; + struct spa_dict_item items[1]; + char id[16]; + int res; + + snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module))); + items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); + pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); + + if ((res = pw_impl_factory_register(factory, NULL)) < 0) { + pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res)); + } +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, + .registered = module_registered, +}; + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_impl_factory *factory; + struct factory_data *data; + + PW_LOG_TOPIC_INIT(mod_topic); + + factory = pw_context_create_factory(context, + "spa-node-factory", + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE, + NULL, + sizeof(*data)); + if (factory == NULL) + return -errno; + + data = pw_impl_factory_get_user_data(factory); + data->factory = factory; + data->context = context; + data->module = module; + spa_list_init(&data->node_list); + + pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data); + pw_impl_factory_set_implementation(factory, &factory_impl, data); + + pw_log_debug("module %p: new", module); + pw_impl_module_add_listener(module, &data->module_listener, &module_events, data); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; +} diff --git a/src/modules/spa/module-node.c b/src/modules/spa/module-node.c new file mode 100644 index 0000000..9f66d2b --- /dev/null +++ b/src/modules/spa/module-node.c @@ -0,0 +1,129 @@ +/* PipeWire + * Copyright © 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * Copyright © 2018 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 "config.h" + +#include <errno.h> +#include <getopt.h> +#include <limits.h> + +#include <pipewire/impl.h> + +#include "spa-node.h" + +#define NAME "spa-node" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define MODULE_USAGE "<factory> [key=value ...]" + +static const struct spa_dict_item module_props[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Load and manage an SPA node" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct node_data { + struct pw_impl_node *this; + struct pw_context *context; + struct pw_properties *properties; + + struct spa_hook module_listener; +}; + +static void module_destroy(void *_data) +{ + struct node_data *data = _data; + spa_hook_remove(&data->module_listener); + pw_impl_node_destroy(data->this); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_properties *props = NULL; + char **argv = NULL; + int n_tokens, res; + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_impl_node *node; + struct node_data *data; + + PW_LOG_TOPIC_INIT(mod_topic); + + if (args == NULL) + goto error_arguments; + + argv = pw_split_strv(args, " \t", 2, &n_tokens); + if (n_tokens < 1) + goto error_arguments; + + if (n_tokens == 2) { + props = pw_properties_new_string(argv[1]); + if (props == NULL) { + res = -errno; + goto error_exit_cleanup; + } + } + + node = pw_spa_node_load(context, + argv[0], + PW_SPA_NODE_FLAG_ACTIVATE, + props, + sizeof(struct node_data)); + + if (node == NULL) { + res = -errno; + goto error_exit_cleanup; + } + + pw_free_strv(argv); + + data = pw_spa_node_get_user_data(node); + data->this = node; + data->context = context; + data->properties = props; + + pw_log_debug("module %p: new", module); + pw_impl_module_add_listener(module, &data->module_listener, &module_events, data); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; + +error_arguments: + res = -EINVAL; + pw_log_error("usage: module-spa-node " MODULE_USAGE); + goto error_exit_cleanup; +error_exit_cleanup: + pw_free_strv(argv); + return res; +} diff --git a/src/modules/spa/spa-device.c b/src/modules/spa/spa-device.c new file mode 100644 index 0000000..936793b --- /dev/null +++ b/src/modules/spa/spa-device.c @@ -0,0 +1,161 @@ +/* PipeWire + * + * Copyright © 2018 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 "config.h" + +#include <string.h> +#include <stdio.h> +#include <dlfcn.h> + +#include <spa/utils/result.h> +#include <spa/param/props.h> +#include <spa/pod/iter.h> +#include <spa/debug/types.h> + +#include "spa-device.h" + +struct impl { + struct pw_impl_device *this; + + enum pw_spa_device_flags flags; + + void *unload; + struct spa_handle *handle; + struct spa_device *device; + + struct spa_hook device_listener; + + void *user_data; +}; + +static void device_free(void *data) +{ + struct impl *impl = data; + struct pw_impl_device *device = impl->this; + + pw_log_debug("spa-device %p: free", device); + + spa_hook_remove(&impl->device_listener); + if (impl->handle) + pw_unload_spa_handle(impl->handle); +} + +static const struct pw_impl_device_events device_events = { + PW_VERSION_IMPL_DEVICE_EVENTS, + .free = device_free, +}; + +struct pw_impl_device * +pw_spa_device_new(struct pw_context *context, + enum pw_spa_device_flags flags, + struct spa_device *device, + struct spa_handle *handle, + struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_impl_device *this; + struct impl *impl; + int res; + + this = pw_context_create_device(context, properties, sizeof(struct impl) + user_data_size); + if (this == NULL) + return NULL; + + impl = pw_impl_device_get_user_data(this); + impl->this = this; + impl->device = device; + impl->handle = handle; + impl->flags = flags; + + if (user_data_size > 0) + impl->user_data = SPA_PTROFF(impl, sizeof(struct impl), void); + + pw_impl_device_add_listener(this, &impl->device_listener, &device_events, impl); + pw_impl_device_set_implementation(this, impl->device); + + if (!SPA_FLAG_IS_SET(impl->flags, PW_SPA_DEVICE_FLAG_NO_REGISTER)) { + if ((res = pw_impl_device_register(this, NULL)) < 0) + goto error_register; + } + return this; + +error_register: + pw_impl_device_destroy(this); + errno = -res; + return NULL; +} + +void *pw_spa_device_get_user_data(struct pw_impl_device *device) +{ + struct impl *impl = pw_impl_device_get_user_data(device); + return impl->user_data; +} + +struct pw_impl_device *pw_spa_device_load(struct pw_context *context, + const char *factory_name, + enum pw_spa_device_flags flags, + struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_impl_device *this; + struct spa_handle *handle; + void *iface; + int res; + + handle = pw_context_load_spa_handle(context, factory_name, + properties ? &properties->dict : NULL); + if (handle == NULL) + goto error_load; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0) + goto error_interface; + + this = pw_spa_device_new(context, flags, + iface, handle, properties, user_data_size); + if (this == NULL) + goto error_device; + + return this; + +error_load: + res = -errno; + pw_log_debug("can't load device handle %s: %m", factory_name); + goto error_exit; +error_interface: + pw_log_debug("can't get device interface %s: %s", factory_name, + spa_strerror(res)); + goto error_exit_unload; +error_device: + properties = NULL; + res = -errno; + pw_log_debug("can't create device %s: %m", factory_name); + goto error_exit_unload; + +error_exit_unload: + pw_unload_spa_handle(handle); +error_exit: + errno = -res; + pw_properties_free(properties); + return NULL; +} diff --git a/src/modules/spa/spa-device.h b/src/modules/spa/spa-device.h new file mode 100644 index 0000000..2d23b6f --- /dev/null +++ b/src/modules/spa/spa-device.h @@ -0,0 +1,62 @@ +/* PipeWire + * + * Copyright © 2018 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. + */ + +#ifndef PIPEWIRE_SPA_DEVICE_H +#define PIPEWIRE_SPA_DEVICE_H + +#include <spa/monitor/device.h> + +#include <pipewire/impl.h> + +#ifdef __cplusplus +extern "C" { +#endif + +enum pw_spa_device_flags { + PW_SPA_DEVICE_FLAG_DISABLE = (1 << 0), + PW_SPA_DEVICE_FLAG_NO_REGISTER = (1 << 1), +}; + +struct pw_impl_device * +pw_spa_device_new(struct pw_context *context, + enum pw_spa_device_flags flags, + struct spa_device *device, + struct spa_handle *handle, + struct pw_properties *properties, + size_t user_data_size); + +struct pw_impl_device * +pw_spa_device_load(struct pw_context *context, + const char *factory_name, + enum pw_spa_device_flags flags, + struct pw_properties *properties, + size_t user_data_size); + +void *pw_spa_device_get_user_data(struct pw_impl_device *device); + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_SPA_DEVICE_H */ diff --git a/src/modules/spa/spa-node.c b/src/modules/spa/spa-node.c new file mode 100644 index 0000000..1f8615b --- /dev/null +++ b/src/modules/spa/spa-node.c @@ -0,0 +1,292 @@ +/* PipeWire + * + * Copyright © 2018 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 "config.h" + +#include <string.h> +#include <stdio.h> +#include <dlfcn.h> + +#include <spa/node/node.h> +#include <spa/node/utils.h> +#include <spa/utils/result.h> +#include <spa/param/props.h> +#include <spa/pod/iter.h> +#include <spa/debug/types.h> + +#include "spa-node.h" + +struct impl { + struct pw_impl_node *this; + + enum pw_spa_node_flags flags; + + struct spa_handle *handle; + struct spa_node *node; /**< handle to SPA node */ + + struct spa_hook node_listener; + int init_pending; + + void *user_data; + + unsigned int async_init:1; +}; + +static void spa_node_free(void *data) +{ + struct impl *impl = data; + struct pw_impl_node *node = impl->this; + + pw_log_debug("spa-node %p: free", node); + + spa_hook_remove(&impl->node_listener); + if (impl->handle) + pw_unload_spa_handle(impl->handle); +} + +static void complete_init(struct impl *impl) +{ + struct pw_impl_node *this = impl->this; + + impl->init_pending = SPA_ID_INVALID; + + if (SPA_FLAG_IS_SET(impl->flags, PW_SPA_NODE_FLAG_ACTIVATE)) + pw_impl_node_set_active(this, true); + + if (!SPA_FLAG_IS_SET(impl->flags, PW_SPA_NODE_FLAG_NO_REGISTER)) + pw_impl_node_register(this, NULL); + else + pw_impl_node_initialized(this); +} + +static void spa_node_result(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct impl *impl = data; + struct pw_impl_node *node = impl->this; + + if (seq == impl->init_pending) { + pw_log_debug("spa-node %p: init complete event %d %d", node, seq, res); + complete_init(impl); + } +} + +static const struct pw_impl_node_events node_events = { + PW_VERSION_IMPL_NODE_EVENTS, + .free = spa_node_free, + .result = spa_node_result, +}; + +struct pw_impl_node * +pw_spa_node_new(struct pw_context *context, + enum pw_spa_node_flags flags, + struct spa_node *node, + struct spa_handle *handle, + struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_impl_node *this; + struct impl *impl; + int res; + + this = pw_context_create_node(context, properties, sizeof(struct impl) + user_data_size); + if (this == NULL) { + res = -errno; + goto error_exit; + } + + impl = pw_impl_node_get_user_data(this); + impl->this = this; + impl->node = node; + impl->handle = handle; + impl->flags = flags; + + if (user_data_size > 0) + impl->user_data = SPA_PTROFF(impl, sizeof(struct impl), void); + + pw_impl_node_add_listener(this, &impl->node_listener, &node_events, impl); + if ((res = pw_impl_node_set_implementation(this, impl->node)) < 0) + goto error_exit_clean_node; + + if (flags & PW_SPA_NODE_FLAG_ASYNC) { + impl->init_pending = spa_node_sync(impl->node, res); + } else { + complete_init(impl); + } + return this; + +error_exit_clean_node: + pw_impl_node_destroy(this); + handle = NULL; +error_exit: + if (handle) + pw_unload_spa_handle(handle); + errno = -res; + return NULL; + +} + +void *pw_spa_node_get_user_data(struct pw_impl_node *node) +{ + struct impl *impl = pw_impl_node_get_user_data(node); + return impl->user_data; +} + +static int +setup_props(struct pw_context *context, struct spa_node *spa_node, struct pw_properties *pw_props) +{ + int res; + struct spa_pod *props; + void *state = NULL; + const char *key; + uint32_t index = 0; + uint8_t buf[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); + const struct spa_pod_prop *prop = NULL; + + res = spa_node_enum_params_sync(spa_node, + SPA_PARAM_Props, &index, NULL, &props, + &b); + if (res != 1) { + if (res < 0) + pw_log_debug("spa_node_get_props result: %s", spa_strerror(res)); + if (res == -ENOTSUP || res == -ENOENT) + res = 0; + return res; + } + + while ((key = pw_properties_iterate(pw_props, &state))) { + uint32_t type = 0; + + type = spa_debug_type_find_type(spa_type_props, key); + if (type == SPA_TYPE_None) + continue; + + if ((prop = spa_pod_find_prop(props, prop, type))) { + const char *value = pw_properties_get(pw_props, key); + + if (value == NULL) + continue; + + pw_log_debug("configure prop %s to %s", key, value); + + switch(prop->value.type) { + case SPA_TYPE_Bool: + SPA_POD_VALUE(struct spa_pod_bool, &prop->value) = + pw_properties_parse_bool(value); + break; + case SPA_TYPE_Id: + SPA_POD_VALUE(struct spa_pod_id, &prop->value) = + pw_properties_parse_int(value); + break; + case SPA_TYPE_Int: + SPA_POD_VALUE(struct spa_pod_int, &prop->value) = + pw_properties_parse_int(value); + break; + case SPA_TYPE_Long: + SPA_POD_VALUE(struct spa_pod_long, &prop->value) = + pw_properties_parse_int64(value); + break; + case SPA_TYPE_Float: + SPA_POD_VALUE(struct spa_pod_float, &prop->value) = + pw_properties_parse_float(value); + break; + case SPA_TYPE_Double: + SPA_POD_VALUE(struct spa_pod_double, &prop->value) = + pw_properties_parse_double(value); + break; + case SPA_TYPE_String: + break; + default: + break; + } + } + } + + if ((res = spa_node_set_param(spa_node, SPA_PARAM_Props, 0, props)) < 0) { + pw_log_debug("spa_node_set_props failed: %s", spa_strerror(res)); + return res; + } + return 0; +} + + +struct pw_impl_node *pw_spa_node_load(struct pw_context *context, + const char *factory_name, + enum pw_spa_node_flags flags, + struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_impl_node *this; + struct spa_node *spa_node; + int res; + struct spa_handle *handle; + void *iface; + const struct pw_properties *p; + + if (properties) { + p = pw_context_get_properties(context); + pw_properties_set(properties, "clock.quantum-limit", + pw_properties_get(p, "default.clock.quantum-limit")); + } + + handle = pw_context_load_spa_handle(context, + factory_name, + properties ? &properties->dict : NULL); + if (handle == NULL) { + res = -errno; + goto error_exit; + } + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) { + pw_log_error("can't get node interface %d", res); + goto error_exit_unload; + } + if (SPA_RESULT_IS_ASYNC(res)) + flags |= PW_SPA_NODE_FLAG_ASYNC; + + spa_node = iface; + + if (properties != NULL) { + if ((res = setup_props(context, spa_node, properties)) < 0) { + pw_log_warn("can't setup properties: %s", spa_strerror(res)); + } + } + + this = pw_spa_node_new(context, flags, + spa_node, handle, properties, user_data_size); + if (this == NULL) { + res = -errno; + properties = NULL; + goto error_exit_unload; + } + + return this; + +error_exit_unload: + pw_unload_spa_handle(handle); +error_exit: + pw_properties_free(properties); + errno = -res; + return NULL; +} diff --git a/src/modules/spa/spa-node.h b/src/modules/spa/spa-node.h new file mode 100644 index 0000000..5289a17 --- /dev/null +++ b/src/modules/spa/spa-node.h @@ -0,0 +1,63 @@ +/* PipeWire + * + * Copyright © 2018 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. + */ + +#ifndef PIPEWIRE_SPA_NODE_H +#define PIPEWIRE_SPA_NODE_H + +#include <spa/node/node.h> + +#include <pipewire/impl.h> + +#ifdef __cplusplus +extern "C" { +#endif + +enum pw_spa_node_flags { + PW_SPA_NODE_FLAG_ACTIVATE = (1 << 0), + PW_SPA_NODE_FLAG_NO_REGISTER = (1 << 1), + PW_SPA_NODE_FLAG_ASYNC = (1 << 2), +}; + +struct pw_impl_node * +pw_spa_node_new(struct pw_context *context, + enum pw_spa_node_flags flags, + struct spa_node *node, + struct spa_handle *handle, + struct pw_properties *properties, + size_t user_data_size); + +struct pw_impl_node * +pw_spa_node_load(struct pw_context *context, + const char *factory_name, + enum pw_spa_node_flags flags, + struct pw_properties *properties, + size_t user_data_size); + +void *pw_spa_node_get_user_data(struct pw_impl_node *node); + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_SPA_NODE_H */ |