diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index bf2a57f7f..70de19d6b 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -1030,11 +1030,6 @@ static struct descriptor *descriptor_load(struct impl *impl, const char *type, } } } - if (desc->n_input == 0 && desc->n_output == 0 && desc->n_control == 0 && desc->n_notify == 0) { - spa_log_error(impl->log, "plugin has no input and no output ports"); - res = -ENOTSUP; - goto exit; - } for (i = 0; i < desc->n_control; i++) { p = desc->control[i]; desc->default_control[i] = get_default(impl, desc, p); @@ -1794,7 +1789,7 @@ static int setup_graph(struct graph *graph) struct port *port; struct graph_port *gp; struct graph_hndl *gh; - uint32_t i, j, n, n_input, n_output, n_hndl = 0; + uint32_t i, j, n, n_input, n_output, n_hndl = 0, n_out_hndl; int res; struct descriptor *desc; const struct spa_fga_descriptor *d; @@ -1815,16 +1810,11 @@ static int setup_graph(struct graph *graph) SPA_FLAG_IS_SET(first->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA) && SPA_FLAG_IS_SET(last->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA); - if (n_input == 0) { - spa_log_error(impl->log, "no inputs"); - res = -EINVAL; - goto error; - } - if (n_output == 0) { - spa_log_error(impl->log, "no outputs"); - res = -EINVAL; - goto error; - } + if (n_input == 0) + n_input = n_output; + if (n_output == 0) + n_output = n_input; + if (graph->n_inputs == 0) graph->n_inputs = impl->info.n_inputs; if (graph->n_inputs == 0) @@ -1835,12 +1825,14 @@ static int setup_graph(struct graph *graph) /* compare to the requested number of inputs and duplicate the * graph n_hndl times when needed. */ - n_hndl = graph->n_inputs / n_input; + n_hndl = n_input ? graph->n_inputs / n_input : 1; if (graph->n_outputs == 0) graph->n_outputs = n_output * n_hndl; - if (n_hndl != graph->n_outputs / n_output) { + n_out_hndl = n_output ? graph->n_outputs / n_output : 1; + + if (n_hndl != n_out_hndl) { spa_log_error(impl->log, "invalid ports. The input stream has %1$d ports and " "the filter has %2$d inputs. The output stream has %3$d ports " "and the filter has %4$d outputs. input:%1$d / input:%2$d != " diff --git a/spa/plugins/filter-graph/plugin_builtin.c b/spa/plugins/filter-graph/plugin_builtin.c index 8a964ccd7..69ed34fc2 100644 --- a/spa/plugins/filter-graph/plugin_builtin.c +++ b/spa/plugins/filter-graph/plugin_builtin.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -3115,6 +3116,107 @@ static const struct spa_fga_descriptor noisegate_desc = { .cleanup = builtin_cleanup, }; +/* busy */ +struct busy_impl { + struct plugin *plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + + unsigned long rate; + + float wait_scale; + float cpu_scale; +}; + +static void *busy_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct busy_impl *impl; + struct spa_json it[1]; + const char *val; + char key[256]; + float wait_percent = 0.0f, cpu_percent = 0.0f; + int len; + + if (config != NULL) { + if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { + spa_log_error(pl->log, "busy:config must be an object"); + return NULL; + } + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "wait-percent")) { + if (spa_json_parse_float(val, len, &wait_percent) <= 0) { + spa_log_error(pl->log, "busy:wait-percent requires a number"); + return NULL; + } + } else if (spa_streq(key, "cpu-percent")) { + if (spa_json_parse_float(val, len, &cpu_percent) <= 0) { + spa_log_error(pl->log, "busy:cpu-percent requires a number"); + return NULL; + } + } else { + spa_log_warn(pl->log, "busy: ignoring config key: '%s'", key); + } + } + if (wait_percent <= 0.0f) + wait_percent = 0.0f; + if (cpu_percent <= 0.0f) + cpu_percent = 0.0f; + } + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return NULL; + + impl->plugin = pl; + impl->dsp = pl->dsp; + impl->log = pl->log; + impl->rate = SampleRate; + impl->wait_scale = wait_percent * SPA_NSEC_PER_SEC / (100.0f * SampleRate); + impl->cpu_scale = cpu_percent * SPA_NSEC_PER_SEC / (100.0f * SampleRate); + spa_log_info(impl->log, "wait-percent:%f cpu-percent:%f", wait_percent, cpu_percent); + + return impl; +} + +static void busy_run(void * Instance, unsigned long SampleCount) +{ + struct busy_impl *impl = Instance; + struct timespec ts; + uint64_t busy_nsec; + + if (impl->wait_scale > 0.0f) { + busy_nsec = (uint64_t)(impl->wait_scale * SampleCount); + ts.tv_sec = busy_nsec / SPA_NSEC_PER_SEC; + ts.tv_nsec = busy_nsec % SPA_NSEC_PER_SEC; + clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); + } + if (impl->cpu_scale > 0.0f) { + clock_gettime(CLOCK_MONOTONIC, &ts); + busy_nsec = SPA_TIMESPEC_TO_NSEC(&ts); + busy_nsec += (uint64_t)(impl->cpu_scale * SampleCount); + do { + clock_gettime(CLOCK_MONOTONIC, &ts); + } while ((uint64_t)SPA_TIMESPEC_TO_NSEC(&ts) < busy_nsec); + } +} + +static const struct spa_fga_descriptor busy_desc = { + .name = "busy", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = 0, + .ports = NULL, + + .instantiate = busy_instantiate, + .connect_port = builtin_connect_port, + .run = busy_run, + .cleanup = builtin_cleanup, +}; + static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) { switch(Index) { @@ -3180,6 +3282,8 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) return &zeroramp_desc; case 30: return &noisegate_desc; + case 31: + return &busy_desc; } return NULL; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 9a723a859..9ab3639bd 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -652,6 +652,36 @@ extern struct spa_handle_factory spa_filter_graph_factory; * of "Attack (s)" seconds. The noise gate stays open for at least "Hold (s)" * seconds before it can close again. * + * ### Busy + * + * The `busy` plugin has no input or output ports and it can be used to keep the + * CPU or graph busy for the given percent of time. + * + * The node requires a `config` section with extra configuration: + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * type = builtin + * name = ... + * label = busy + * config = { + * wait-percent = 0.0 + * cpu-percent = 50.0 + * } + * ... + * } + * } + * ... + * } + *\endcode + * + * - `wait-percent` the percentage of time to wait. This keeps the graph busy but + * not the CPU. Default 0.0 + * - `cpu-percent` the percentage of time to keep the CPU busy. This keeps both the + * graph and CPU busy. Default 0.0 + * * * ## SOFA filters *