From 1ff208875cfd18f35ae6821328c236e21d348c84 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 30 Jul 2021 16:15:34 +0200 Subject: [PATCH] context: implement dynamic rate Add a default.clock.allowed-rates property that lists the possible rates for the graph. Add node.rate and node.lock-rate properties to suggest a graph rate and lock the rate. Collect the largest clock rate from the nodes and if it is allowed, try to configure it as the graph rate. Only switch rates when the driver is IDLE or suspended or when we force a rate. No alternative samplerates are specified because we first need to work around a common driver bug (cards with 1 cristal need the same rate for capture as playback) for this to work. --- src/daemon/pipewire.conf.in | 17 ++--- src/pipewire/context.c | 127 ++++++++++++++++++++++++++++++------ src/pipewire/impl-node.c | 14 ++++ src/pipewire/keys.h | 4 ++ src/pipewire/private.h | 17 +++-- 5 files changed, 144 insertions(+), 35 deletions(-) diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index ae31be488..09e62e528 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -20,14 +20,15 @@ context.properties = { core.name = pipewire-0 # core name and socket name ## Properties for the DSP configuration. - #default.clock.rate = 48000 - #default.clock.quantum = 1024 - #default.clock.min-quantum = 32 - #default.clock.max-quantum = 8192 - #default.video.width = 640 - #default.video.height = 480 - #default.video.rate.num = 25 - #default.video.rate.denom = 1 + #default.clock.rate = 48000 + #default.clock.allowed-rates = [ 48000 ] + #default.clock.quantum = 1024 + #default.clock.min-quantum = 32 + #default.clock.max-quantum = 8192 + #default.video.width = 640 + #default.video.height = 480 + #default.video.rate.num = 25 + #default.video.rate.denom = 1 # # These overrides are only applied when running in a vm. vm.overrides = { diff --git a/src/pipewire/context.c b/src/pipewire/context.c index eeb863f5a..c18c88954 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -136,12 +137,41 @@ static bool get_default_bool(struct pw_properties *properties, const char *name, return val; } +static uint32_t parse_clock_rate(struct pw_properties *properties, const char *name, + uint32_t *rates, uint32_t def) +{ + const char *str; + uint32_t count = 0, r; + struct spa_json it[2]; + char v[256]; + + if ((str = pw_properties_get(properties, name)) == NULL) + goto fallback; + + spa_json_init(&it[0], str, strlen(str)); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], str, strlen(str)); + + while (spa_json_get_string(&it[1], v, sizeof(v)-1) > 0 && + count < MAX_RATES) { + if (spa_atou32(v, &r, 0)) + rates[count++] = r; + } + if (count == 0) + goto fallback; + return count; +fallback: + rates[0] = def; + return 1; +} + static void fill_defaults(struct pw_context *this) { struct pw_properties *p = this->properties; struct settings *d = &this->defaults; d->clock_rate = get_default_int(p, "default.clock.rate", DEFAULT_CLOCK_RATE); + d->n_clock_rates = parse_clock_rate(p, "default.clock.allowed-rates", d->clock_rates, d->clock_rate); d->clock_quantum = get_default_int(p, "default.clock.quantum", DEFAULT_CLOCK_QUANTUM); d->clock_min_quantum = get_default_int(p, "default.clock.min-quantum", DEFAULT_CLOCK_MIN_QUANTUM); d->clock_max_quantum = get_default_int(p, "default.clock.max-quantum", DEFAULT_CLOCK_MAX_QUANTUM); @@ -999,10 +1029,30 @@ static inline void get_quantums(struct pw_context *context, uint32_t *def, uint3 *min = s->clock_force_quantum == 0 ? s->clock_min_quantum : s->clock_force_quantum; *max = s->clock_force_quantum == 0 ? s->clock_max_quantum : s->clock_force_quantum; } -static inline void get_rate(struct pw_context *context, uint32_t *def) + +static inline uint32_t *get_rates(struct pw_context *context, uint32_t *def, uint32_t *n_rates, + bool *force_rate) { struct settings *s = &context->settings; - *def = s->clock_force_rate == 0 ? s->clock_rate : s->clock_force_rate; + if (s->clock_force_rate != 0) { + *force_rate = true; + *n_rates = 1; + *def = s->clock_force_rate; + return &s->clock_force_rate; + } else { + *force_rate = false; + *n_rates = s->n_clock_rates; + *def = s->clock_rate; + return s->clock_rates; + } +} +static bool rates_contains(uint32_t *rates, uint32_t n_rates, uint32_t rate) +{ + uint32_t i; + for (i = 0; i < n_rates; i++) + if (rates[i] == rate) + return true; + return false; } static void suspend_driver(struct pw_context *context, struct pw_impl_node *n) @@ -1039,8 +1089,9 @@ int pw_context_recalc_graph(struct pw_context *context, const char *reason) { struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this); struct pw_impl_node *n, *s, *target, *fallback; - uint32_t max_quantum, min_quantum, def_quantum, def_rate; - bool freewheel = false; + uint32_t max_quantum, min_quantum, def_quantum; + uint32_t *rates, n_rates, def_rate; + bool freewheel = false, force_rate; pw_log_info(NAME" %p: busy:%d reason:%s", context, impl->recalc, reason); @@ -1053,7 +1104,7 @@ again: impl->recalc = true; get_quantums(context, &def_quantum, &min_quantum, &max_quantum); - get_rate(context, &def_rate); + rates = get_rates(context, &def_rate, &n_rates, &force_rate); /* start from all drivers and group all nodes that are linked * to it. Some nodes are not (yet) linked to anything and they @@ -1128,56 +1179,90 @@ again: /* assign final quantum and set state for followers and drivers */ spa_list_for_each(n, &context->driver_list, driver_link) { - bool running = false, lock_quantum = false; + bool running = false, lock_quantum = false, lock_rate = false; struct spa_fraction latency = SPA_FRACTION(0, 0); struct spa_fraction max_latency = SPA_FRACTION(0, 0); - uint32_t quantum; + struct spa_fraction rate = SPA_FRACTION(0, 0); + uint32_t quantum, target_rate, current_rate; if (!n->driving || n->exported) continue; - /* collect quantum and count active nodes */ + /* collect quantum and rate */ spa_list_for_each(s, &n->follower_list, follower_link) { lock_quantum |= s->lock_quantum; + lock_rate |= s->lock_rate; + /* smallest latencies */ if (latency.denom == 0 || (s->latency.denom > 0 && fraction_compare(&s->latency, &latency) < 0)) latency = s->latency; - if (max_latency.denom == 0 || (s->max_latency.denom > 0 && fraction_compare(&s->max_latency, &max_latency) < 0)) max_latency = s->max_latency; + /* largest rate */ + if (rate.denom == 0 || + (s->rate.denom > 0 && + fraction_compare(&s->rate, &rate) > 0)) + rate = s->rate; + if (s->active) running = !n->passive; } + + /* calculate desired rate */ + target_rate = def_rate; + if (rate.denom != 0 && rate.num == 1) { + if (rates_contains(rates, n_rates, rate.denom)) + target_rate = rate.denom; + } + if (force_rate) + lock_rate = false; + + current_rate = n->rt.position->clock.rate.denom; + if (target_rate != current_rate && lock_rate) + target_rate = current_rate; + else if (target_rate != current_rate && !force_rate && + (n->info.state > PW_NODE_STATE_IDLE)) + target_rate = current_rate; + + if (target_rate != current_rate) { + pw_log_info("(%s-%u) new rate:%u->%u", + n->name, n->info.id, + n->rt.position->clock.rate.denom, + target_rate); + + if (force_rate) { + if (context->settings.clock_rate_update_mode == CLOCK_RATE_UPDATE_MODE_HARD) + suspend_driver(context, n); + } else { + if (n->info.state >= PW_NODE_STATE_IDLE) + suspend_driver(context, n); + } + n->rt.position->clock.rate = SPA_FRACTION(1, target_rate); + current_rate = target_rate; + } + + + /* calculate desired quantum */ if (max_latency.denom != 0) { - uint32_t tmp = (max_latency.num * def_rate / max_latency.denom); + uint32_t tmp = (max_latency.num * current_rate / max_latency.denom); if (tmp < max_quantum) max_quantum = tmp; } quantum = def_quantum; if (latency.denom != 0) - quantum = (latency.num * def_rate / latency.denom); + quantum = (latency.num * current_rate / latency.denom); quantum = SPA_CLAMP(quantum, min_quantum, max_quantum); if (context->settings.clock_power_of_two_quantum) quantum = flp2(quantum); - if (def_rate != n->rt.position->clock.rate.denom) { - pw_log_info("(%s-%u) new rate:%u->%u", - n->name, n->info.id, - n->rt.position->clock.rate.denom, - def_rate); - if (context->settings.clock_rate_update_mode == CLOCK_RATE_UPDATE_MODE_HARD) - suspend_driver(context, n); - - n->rt.position->clock.rate = SPA_FRACTION(1, def_rate); - } if (quantum != n->rt.position->clock.duration && !lock_quantum) { pw_log_info("(%s-%u) new quantum:%"PRIu64"->%u", n->name, n->info.id, diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index adee2a892..b4ccf1cba 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -888,6 +888,20 @@ static void check_properties(struct pw_impl_node *node) str = pw_properties_get(node->properties, PW_KEY_NODE_LOCK_QUANTUM); node->lock_quantum = str ? pw_properties_parse_bool(str) : false; + if ((str = pw_properties_get(node->properties, PW_KEY_NODE_RATE))) { + if (sscanf(str, "%u/%u", &frac.num, &frac.denom) == 2 && frac.denom != 0) { + if (node->rate.num != frac.num || node->rate.denom != frac.denom) { + pw_log_info("(%s-%u) rate:%u/%u -> %u/%u", node->name, + node->info.id, node->rate.num, + node->rate.denom, frac.num, frac.denom); + node->rate = frac; + recalc_reason = "node rate changed"; + } + } + } + str = pw_properties_get(node->properties, PW_KEY_NODE_LOCK_RATE); + node->lock_rate = str ? pw_properties_parse_bool(str) : false; + pw_log_debug(NAME" %p: driver:%d recalc:%s active:%d", node, node->driver, recalc_reason, node->active); diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h index a47df5780..a70ef1d47 100644 --- a/src/pipewire/keys.h +++ b/src/pipewire/keys.h @@ -157,6 +157,10 @@ extern "C" { * node as a fraction. Ex: 1024/48000 */ #define PW_KEY_NODE_LOCK_QUANTUM "node.lock-quantum" /**< don't change quantum when this node * is active */ +#define PW_KEY_NODE_RATE "node.rate" /**< the requested rate of the graph as + * a fraction. Ex: 1/48000 */ +#define PW_KEY_NODE_LOCK_RATE "node.lock-rate" /**< don't change rate when this node + * is active */ #define PW_KEY_NODE_DONT_RECONNECT "node.dont-reconnect" /**< don't reconnect this node */ #define PW_KEY_NODE_ALWAYS_PROCESS "node.always-process" /**< process even when unlinked */ diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 162894482..3655e31c1 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -49,15 +49,18 @@ struct ucred { #define spa_debug(...) pw_log_trace(__VA_ARGS__) #endif +#define MAX_RATES 16u #define CLOCK_MIN_QUANTUM 4u #define CLOCK_MAX_QUANTUM 8192u struct settings { uint32_t log_level; - uint32_t clock_rate; - uint32_t clock_quantum; - uint32_t clock_min_quantum; - uint32_t clock_max_quantum; + uint32_t clock_rate; /* default clock rate */ + uint32_t clock_rates[MAX_RATES]; /* allowed clock rates */ + uint32_t n_clock_rates; /* number of alternative clock rates */ + uint32_t clock_quantum; /* default quantum */ + uint32_t clock_min_quantum; /* min quantum */ + uint32_t clock_max_quantum; /* max quantum */ struct spa_rectangle video_size; struct spa_fraction video_rate; uint32_t link_max_buffers; @@ -67,8 +70,8 @@ struct settings { #define CLOCK_RATE_UPDATE_MODE_HARD 0 #define CLOCK_RATE_UPDATE_MODE_SOFT 1 int clock_rate_update_mode; - uint32_t clock_force_rate; - uint32_t clock_force_quantum; + uint32_t clock_force_rate; /* force a clock rate */ + uint32_t clock_force_quantum; /* force a quantum */ }; struct ratelimit { @@ -678,6 +681,7 @@ struct pw_impl_node { unsigned int loopchecked:1; /**< for feedback loop checking */ unsigned int always_process:1; /**< this node wants to always be processing, even when idle */ unsigned int lock_quantum:1; /**< don't change graph quantum */ + unsigned int lock_rate:1; /**< don't change graph rate */ uint32_t port_user_data_size; /**< extra size for port user data */ @@ -702,6 +706,7 @@ struct pw_impl_node { struct spa_fraction latency; /**< requested latency */ struct spa_fraction max_latency; /**< maximum latency */ + struct spa_fraction rate; /**< requested rate */ struct spa_source source; /**< source to remotely trigger this node */ struct pw_memblock *activation; struct {