diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index bbafa134b..354ee302f 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -73,6 +73,17 @@ context.modules = [ flags = [ ifexists nofail ] } + # Set thread priorities without using RTKit. + #{ name = libpipewire-module-rt + # args = { + # nice.level = -11 + # rt.prio = 88 + # rt.time.soft = 200000 + # rt.time.hard = 200000 + # } + # flags = [ ifexists nofail ] + #} + # The native communication protocol. { name = libpipewire-module-protocol-native } diff --git a/src/daemon/systemd/system/pipewire.service.in b/src/daemon/systemd/system/pipewire.service.in index 23971e4a9..1b1d42520 100644 --- a/src/daemon/systemd/system/pipewire.service.in +++ b/src/daemon/systemd/system/pipewire.service.in @@ -22,6 +22,7 @@ RestrictNamespaces=yes SystemCallArchitectures=native SystemCallFilter=@system-service Type=simple +AmbientCapabilities=CAP_SYS_NICE ExecStart=@PW_BINARY@ Restart=on-failure RuntimeDirectory=pipewire diff --git a/src/modules/meson.build b/src/modules/meson.build index 8c6731582..4d0608d8e 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -58,6 +58,15 @@ pipewire_module_profiler = shared_library('pipewire-module-profiler', dependencies : [mathlib, dl_lib, pipewire_dep], ) +pipewire_module_rt = shared_library('pipewire-module-rt', [ 'module-rt.c' ], + c_args : pipewire_module_c_args, + include_directories : [configinc, spa_inc], + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : [dl_lib, pipewire_dep], +) + if dbus_dep.found() pipewire_module_rtkit = shared_library('pipewire-module-rtkit', [ 'module-rtkit.c' ], c_args : pipewire_module_c_args, diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c new file mode 100644 index 000000000..4cd9a83cf --- /dev/null +++ b/src/modules/module-rt.c @@ -0,0 +1,249 @@ +/* PipeWire + * + * Copyright © 2021 Axis Communications AB + * + * 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 +#include +#include +#include +#include + +#include +#include + +#include "config.h" + +#define DEFAULT_NICE_LEVEL -11 +#define DEFAULT_RT_PRIO 88 +#define DEFAULT_RT_TIME_SOFT 200000 +#define DEFAULT_RT_TIME_HARD 200000 + +#define MODULE_USAGE \ + "[nice.level=] " \ + "[rt.prio=] " \ + "[rt.time.soft=" }, + { PW_KEY_MODULE_DESCRIPTION, "Set thread priorities" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +struct impl { + struct pw_context *context; + + struct spa_loop *loop; + struct spa_system *system; + struct spa_source source; + + int rt_prio; + rlim_t rt_time_soft; + rlim_t rt_time_hard; + + struct spa_hook module_listener; +}; + +static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct spa_source *source = user_data; + + spa_loop_remove_source(loop, source); + + return 0; +} + +static void module_destroy(void *data) +{ + struct impl *impl = data; + + spa_hook_remove(&impl->module_listener); + + if (impl->source.fd != -1) { + spa_loop_invoke(impl->loop, do_remove_source, SPA_ID_INVALID, NULL, 0, true, &impl->source); + spa_system_close(impl->system, impl->source.fd); + impl->source.fd = -1; + } + free(impl); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +static void idle_func(struct spa_source *source) +{ + struct impl *impl = source->data; + uint64_t count; + int policy = SCHED_RR; + int rtprio = impl->rt_prio; + struct rlimit rl; + struct sched_param sp; + + if (SPA_UNLIKELY(spa_system_eventfd_read(impl->system, impl->source.fd, &count) < 0)) + pw_log_warn("read failed: %m"); + + if (rtprio < sched_get_priority_min(policy) || + rtprio > sched_get_priority_max(policy)) { + pw_log_warn("invalid priority %d for policy %d", rtprio, policy); + return; + } + + rl.rlim_cur = impl->rt_time_soft; + rl.rlim_max = impl->rt_time_hard; + if (setrlimit(RLIMIT_RTTIME, &rl) < 0) + pw_log_warn("could not set rlimit: %m"); + else + pw_log_debug("rt.prio %d, rt.time.soft %"PRIi64", rt.time.hard %"PRIi64, + rtprio, (int64_t)rl.rlim_cur, (int64_t)rl.rlim_max); + + spa_zero(sp); + sp.sched_priority = rtprio; + if (sched_setscheduler(0, policy | SCHED_RESET_ON_FORK, &sp) < 0) { + pw_log_warn("could not make thread realtime: %m"); + return; + } + + pw_log_info("processing thread has realtime priority %d", rtprio); +} + +static void set_nice(struct impl *impl, int nice_level) +{ + long tid; + int res; + + tid = syscall(SYS_gettid); + if (tid < 0) { + pw_log_warn("could not get main thread id: %m"); + tid = 0; /* means current thread in setpriority() on linux */ + } + res = setpriority(PRIO_PROCESS, (id_t)tid, nice_level); + if (res < 0) + pw_log_warn("could not set nice-level to %d: %m", nice_level); + else + pw_log_info("main thread nice level set to %d", nice_level); +} + +static int get_default_int(struct pw_properties *properties, const char *name, int def) +{ + const char *str; + int val; + bool set_default = true; + + if ((str = pw_properties_get(properties, name)) != NULL) { + char *endptr; + + val = (int)strtol(str, &endptr, 10); + if (*endptr == '\0') + set_default = false; + else + pw_log_warn("invalid integer value '%s' of property %s, using default (%d) instead", str, name, def); + } + + if (set_default) { + val = def; + pw_properties_setf(properties, name, "%d", val); + } + + return val; +} + +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 impl *impl; + struct spa_loop *loop; + struct spa_system *system; + const struct spa_support *support; + uint32_t n_support; + struct pw_properties *props; + int nice_level; + int res; + + support = pw_context_get_support(context, &n_support); + + loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + if (loop == NULL) + return -ENOTSUP; + + system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); + if (system == NULL) + return -ENOTSUP; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -ENOMEM; + + pw_log_debug("module %p: new %s", impl, args); + + impl->context = context; + impl->loop = loop; + impl->system = system; + props = args ? pw_properties_new_string(args) : pw_properties_new(NULL, NULL); + if (props == NULL) { + res = -errno; + goto error; + } + + nice_level = get_default_int(props, "nice.level", DEFAULT_NICE_LEVEL); + set_nice(impl, nice_level); + + impl->rt_prio = get_default_int(props, "rt.prio", DEFAULT_RT_PRIO); + impl->rt_time_soft = get_default_int(props, "rt.time.soft", DEFAULT_RT_TIME_SOFT); + impl->rt_time_hard = get_default_int(props, "rt.time.hard", DEFAULT_RT_TIME_HARD); + + impl->source.loop = loop; + impl->source.func = idle_func; + impl->source.data = impl; + impl->source.fd = spa_system_eventfd_create(system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + impl->source.mask = SPA_IO_IN; + if (impl->source.fd == -1) { + res = -errno; + goto error; + } + + spa_loop_add_source(impl->loop, &impl->source); + if (SPA_UNLIKELY(spa_system_eventfd_write(system, impl->source.fd, 1) < 0)) + pw_log_warn("write failed: %m"); + + pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + pw_impl_module_update_properties(module, &props->dict); + pw_properties_free(props); + + return 0; + +error: + free(impl); + + return res; +}