diff options
Diffstat (limited to 'src/modules/module-protocol-pulse/module.c')
-rw-r--r-- | src/modules/module-protocol-pulse/module.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c new file mode 100644 index 0000000..ec10404 --- /dev/null +++ b/src/modules/module-protocol-pulse/module.c @@ -0,0 +1,333 @@ +/* PipeWire + * + * Copyright © 2020 Georges Basile Stavracas Neto + * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com> + * + * 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 <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include <spa/utils/defs.h> +#include <spa/utils/list.h> +#include <spa/utils/hook.h> +#include <spa/utils/string.h> +#include <pipewire/log.h> +#include <pipewire/map.h> +#include <pipewire/properties.h> +#include <pipewire/work-queue.h> + +#include "defs.h" +#include "format.h" +#include "internal.h" +#include "log.h" +#include "module.h" +#include "remap.h" + +static void on_module_unload(void *obj, void *data, int res, uint32_t index) +{ + struct module *module = obj; + module_unload(module); +} + +void module_schedule_unload(struct module *module) +{ + if (module->unloading) + return; + + pw_work_queue_add(module->impl->work_queue, module, 0, on_module_unload, NULL); + module->unloading = true; +} + +static struct module *module_new(struct impl *impl, const struct module_info *info) +{ + struct module *module; + + module = calloc(1, sizeof(*module) + info->data_size); + if (module == NULL) + return NULL; + + module->index = SPA_ID_INVALID; + module->impl = impl; + module->info = info; + spa_hook_list_init(&module->listener_list); + module->user_data = SPA_PTROFF(module, sizeof(*module), void); + module->loaded = false; + + return module; +} + +void module_add_listener(struct module *module, + struct spa_hook *listener, + const struct module_events *events, void *data) +{ + spa_hook_list_append(&module->listener_list, listener, events, data); +} + +int module_load(struct module *module) +{ + pw_log_info("load module index:%u name:%s", module->index, module->info->name); + if (module->info->load == NULL) + return -ENOTSUP; + /* subscription event is sent when the module does a + * module_emit_loaded() */ + return module->info->load(module); +} + +void module_free(struct module *module) +{ + struct impl *impl = module->impl; + + module_emit_destroy(module); + + if (module->index != SPA_ID_INVALID) + pw_map_remove(&impl->modules, module->index & MODULE_INDEX_MASK); + + if (module->unloading) + pw_work_queue_cancel(impl->work_queue, module, SPA_ID_INVALID); + + spa_hook_list_clean(&module->listener_list); + pw_properties_free(module->props); + + free((char*)module->args); + + free(module); +} + +int module_unload(struct module *module) +{ + struct impl *impl = module->impl; + int res = 0; + + pw_log_info("unload module index:%u name:%s", module->index, module->info->name); + + if (module->info->unload) + res = module->info->unload(module); + + if (module->loaded) + broadcast_subscribe_event(impl, + SUBSCRIPTION_MASK_MODULE, + SUBSCRIPTION_EVENT_REMOVE | SUBSCRIPTION_EVENT_MODULE, + module->index); + + module_free(module); + + return res; +} + +/** utils */ +void module_args_add_props(struct pw_properties *props, const char *str) +{ + char *s = strdup(str), *p = s, *e, f; + const char *k, *v; + const struct str_map *map; + + while (*p) { + while (*p && isspace(*p)) + p++; + e = strchr(p, '='); + if (e == NULL) + break; + *e = '\0'; + k = p; + p = e+1; + + if (*p == '\"') { + p++; + f = '\"'; + } else if (*p == '\'') { + p++; + f = '\''; + } else { + f = ' '; + } + v = p; + for (e = p; *e ; e++) { + if (*e == f) + break; + if (*e == '\\') + e++; + } + p = e; + if (*e != '\0') + p++; + *e = '\0'; + + if ((map = str_map_find(props_key_map, NULL, k)) != NULL) { + k = map->pw_str; + if (map->child != NULL && + (map = str_map_find(map->child, NULL, v)) != NULL) + v = map->pw_str; + } + pw_properties_set(props, k, v); + } + free(s); +} + +int module_args_to_audioinfo(struct impl *impl, struct pw_properties *props, struct spa_audio_info_raw *info) +{ + const char *str; + uint32_t i; + + /* We don't use any incoming format setting and use our native format */ + spa_zero(*info); + info->flags = SPA_AUDIO_FLAG_UNPOSITIONED; + info->format = SPA_AUDIO_FORMAT_F32P; + + if ((str = pw_properties_get(props, "channels")) != NULL) { + info->channels = pw_properties_parse_int(str); + if (info->channels == 0 || info->channels > SPA_AUDIO_MAX_CHANNELS) { + pw_log_error("invalid channels '%s'", str); + return -EINVAL; + } + pw_properties_set(props, "channels", NULL); + } + if ((str = pw_properties_get(props, "channel_map")) != NULL) { + struct channel_map map; + + channel_map_parse(str, &map); + if (map.channels == 0 || map.channels > SPA_AUDIO_MAX_CHANNELS) { + pw_log_error("invalid channel_map '%s'", str); + return -EINVAL; + } + if (info->channels == 0) + info->channels = map.channels; + if (info->channels != map.channels) { + pw_log_error("Mismatched channel map"); + return -EINVAL; + } + channel_map_to_positions(&map, info->position); + info->flags &= ~SPA_AUDIO_FLAG_UNPOSITIONED; + pw_properties_set(props, "channel_map", NULL); + } else { + if (info->channels == 0) + info->channels = impl->defs.sample_spec.channels; + + if (info->channels == impl->defs.channel_map.channels) { + channel_map_to_positions(&impl->defs.channel_map, info->position); + } else if (info->channels == 1) { + info->position[0] = SPA_AUDIO_CHANNEL_MONO; + } else if (info->channels == 2) { + info->position[0] = SPA_AUDIO_CHANNEL_FL; + info->position[1] = SPA_AUDIO_CHANNEL_FR; + } else { + /* FIXME add more mappings */ + for (i = 0; i < info->channels; i++) + info->position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; + } + if (info->position[0] != SPA_AUDIO_CHANNEL_UNKNOWN) + info->flags &= ~SPA_AUDIO_FLAG_UNPOSITIONED; + } + + if ((str = pw_properties_get(props, "rate")) != NULL) { + info->rate = pw_properties_parse_int(str); + pw_properties_set(props, "rate", NULL); + } else { + info->rate = 0; + } + return 0; +} + +bool module_args_parse_bool(const char *v) +{ + if (spa_streq(v, "1") || !strcasecmp(v, "y") || !strcasecmp(v, "t") || + !strcasecmp(v, "yes") || !strcasecmp(v, "true") || !strcasecmp(v, "on")) + return true; + return false; +} + +static const struct module_info *find_module_info(const char *name) +{ + extern const struct module_info __start_pw_mod_pulse_modules[]; + extern const struct module_info __stop_pw_mod_pulse_modules[]; + + const struct module_info *info = __start_pw_mod_pulse_modules; + + for (; info < __stop_pw_mod_pulse_modules; info++) { + if (spa_streq(info->name, name)) + return info; + } + + spa_assert(info == __stop_pw_mod_pulse_modules); + + return NULL; +} + +static int find_module_by_name(void *item_data, void *data) +{ + const char *name = data; + const struct module *module = item_data; + return spa_streq(module->info->name, name) ? 1 : 0; +} + +struct module *module_create(struct impl *impl, const char *name, const char *args) +{ + const struct module_info *info; + struct module *module; + + info = find_module_info(name); + if (info == NULL) { + errno = ENOENT; + return NULL; + } + + if (info->load_once) { + int exists; + exists = pw_map_for_each(&impl->modules, find_module_by_name, + (void *)name); + if (exists) { + errno = EEXIST; + return NULL; + } + } + + module = module_new(impl, info); + if (module == NULL) + return NULL; + + module->props = pw_properties_new(NULL, NULL); + if (module->props == NULL) { + module_free(module); + return NULL; + } + + if (args) + module_args_add_props(module->props, args); + + int res = module->info->prepare(module); + if (res < 0) { + module_free(module); + errno = -res; + return NULL; + } + + module->index = pw_map_insert_new(&impl->modules, module); + if (module->index == SPA_ID_INVALID) { + module_unload(module); + return NULL; + } + + module->args = args ? strdup(args) : NULL; + module->index |= MODULE_FLAG; + + return module; +} |