/* PipeWire * Copyright (C) 2017 Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include "config.h" #include "pipewire/core.h" #include "pipewire/link.h" #include "pipewire/log.h" #include "pipewire/module.h" #include "pipewire/type.h" #include "modules/spa/spa-node.h" #define AUDIOMIXER_LIB "audiomixer/libspa-audiomixer" struct impl { struct pw_core *core; struct pw_type *t; struct pw_module *module; struct spa_hook core_listener; struct spa_hook module_listener; struct pw_properties *properties; void *hnd; const struct spa_handle_factory *factory; struct spa_list node_list; }; struct node_data { struct spa_list link; struct pw_node *node; }; static const struct spa_handle_factory *find_factory(struct impl *impl) { spa_handle_factory_enum_func_t enum_func; uint32_t index; const struct spa_handle_factory *factory = NULL; int res; char *filename; const char *dir; if ((dir = getenv("SPA_PLUGIN_DIR")) == NULL) dir = PLUGINDIR; asprintf(&filename, "%s/%s.so", dir, AUDIOMIXER_LIB); if ((impl->hnd = dlopen(filename, RTLD_NOW)) == NULL) { pw_log_error("can't load %s: %s", AUDIOMIXER_LIB, dlerror()); goto open_failed; } if ((enum_func = dlsym(impl->hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { pw_log_error("can't find enum function"); goto no_symbol; } for (index = 0;;) { if ((res = enum_func(&factory, &index)) <= 0) { if (res != 0) pw_log_error("can't enumerate factories: %s", spa_strerror(res)); goto enum_failed; } if (strcmp(factory->name, "audiomixer") == 0) break; } free(filename); return factory; enum_failed: no_symbol: dlclose(impl->hnd); impl->hnd = NULL; open_failed: free(filename); return NULL; } static struct pw_node *make_node(struct impl *impl) { struct spa_handle *handle; int res; void *iface; struct spa_node *spa_node; struct pw_node *node; const struct spa_support *support; uint32_t n_support; struct node_data *nd; support = pw_core_get_support(impl->core, &n_support); handle = calloc(1, impl->factory->size); if ((res = spa_handle_factory_init(impl->factory, handle, NULL, support, n_support)) < 0) { pw_log_error("can't make factory instance: %d", res); goto init_failed; } if ((res = spa_handle_get_interface(handle, impl->t->spa_node, &iface)) < 0) { pw_log_error("can't get interface %d", res); goto interface_failed; } spa_node = iface; node = pw_spa_node_new(impl->core, NULL, pw_module_get_global(impl->module), "audiomixer", PW_SPA_NODE_FLAG_ACTIVATE, spa_node, handle, NULL, sizeof(struct node_data)); nd = pw_spa_node_get_user_data(node); nd->node = node; spa_list_append(&impl->node_list, &nd->link); return node; interface_failed: spa_handle_clear(handle); init_failed: free(handle); return NULL; } static int on_global(void *data, struct pw_global *global) { struct impl *impl = data; struct pw_node *n, *node; const struct pw_properties *properties; const char *str; char *error; struct pw_port *ip, *op; struct pw_link *link; if (pw_global_get_type(global) != impl->t->node) return 0; n = pw_global_get_object(global); properties = pw_node_get_properties(n); if ((str = pw_properties_get(properties, "media.class")) == NULL) return 0; if (strcmp(str, "Audio/Sink") != 0) return 0; if ((ip = pw_node_get_free_port(n, PW_DIRECTION_INPUT)) == NULL) return 0; node = make_node(impl); op = pw_node_get_free_port(node, PW_DIRECTION_OUTPUT); if (op == NULL) return 0; link = pw_link_new(impl->core, op, ip, NULL, pw_properties_new(PW_LINK_PROP_PASSIVE, "true", NULL), &error, 0); pw_link_register(link, NULL, pw_module_get_global(impl->module)); return 0; } static void module_destroy(void *data) { struct impl *impl = data; struct node_data *nd, *t; spa_hook_remove(&impl->module_listener); spa_hook_remove(&impl->core_listener); spa_list_for_each_safe(nd, t, &impl->node_list, link) pw_node_destroy(nd->node); if (impl->properties) pw_properties_free(impl->properties); free(impl); } static const struct pw_module_events module_events = { PW_VERSION_MODULE_EVENTS, .destroy = module_destroy, }; static void core_global_added(void *data, struct pw_global *global) { on_global(data, global); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .global_added = core_global_added, }; static int module_init(struct pw_module *module, struct pw_properties *properties) { struct pw_core *core = pw_module_get_core(module); struct impl *impl; impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -ENOMEM; pw_log_debug("module %p: new", impl); impl->core = core; impl->t = pw_core_get_type(core); impl->module = module; impl->properties = properties; impl->factory = find_factory(impl); spa_list_init(&impl->node_list); pw_core_for_each_global(core, on_global, impl); pw_core_add_listener(core, &impl->core_listener, &core_events, impl); pw_module_add_listener(module, &impl->module_listener, &module_events, impl); return 0; } int pipewire__module_init(struct pw_module *module, const char *args) { return module_init(module, NULL); }