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 {