From 5f21ee8669a05aacd3d719aee5e23dbbcbc932e9 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Tue, 14 Jan 2025 21:53:55 +0200 Subject: [PATCH] audioconvert: add delay_frac to spa_io_rate_match Report the "fractional" part of the resampler delay in spa_io_rate_match::delay_frac, in nanosamples (1/1e9 sample) at node rate. The delay values are best reported in units where it is clear what the clock domain is, so report the value in fractional samples instead of nanoseconds. Conversion to ns is also just dividision by the appropriate rate. --- spa/include/spa/node/io.h | 46 ++++++++++++++++++++++--- spa/plugins/audioconvert/audioconvert.c | 12 +++++-- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/spa/include/spa/node/io.h b/spa/include/spa/node/io.h index a0433a09e..c1c725ebf 100644 --- a/spa/include/spa/node/io.h +++ b/spa/include/spa/node/io.h @@ -305,14 +305,50 @@ struct spa_io_position { struct spa_io_segment segments[SPA_IO_POSITION_MAX_SEGMENTS]; /**< segments */ }; -/** rate matching */ +/** + * Rate matching. + * + * It is usually set on the nodes that process resampled data, by + * the component (audioadapter) that handles resampling between graph + * and node rates. The \a flags and \a rate fields may be modified by the node. + * + * The node can request a correction to the resampling rate in its process(), by setting + * \ref SPA_IO_RATE_MATCH_ACTIVE on \a flags, and setting \a rate to the desired rate + * correction. Usually the rate is obtained from DLL or other adaptive mechanism that + * e.g. drives the node buffer fill level toward a specific value. + * + * When resampling to (graph->node) direction, the number of samples produced + * by the resampler varies on each cycle, as the rates are not commensurate. + * + * When resampling to (node->graph) direction, the number of samples consumed by the + * resampler varies. Node output ports in process() should produce \a size number of + * samples to match what the resampler needs to produce one graph quantum of output + * samples. + * + * Resampling filters introduce processing delay, given by \a delay and \a delay_frac, in + * samples at node rate. The delay varies on each cycle e.g. when resampling between + * noncommensurate rates. + * + * The first sample output (graph->node) or consumed (node->graph) by the resampler is + * offset by \a delay + \a delay_frac / 1e9 node samples relative to the nominal graph + * cycle start position: + * + * \code{.unparsed} + * first_resampled_sample_nsec = + * first_original_sample_nsec + * - (rate_match->delay * SPA_NSEC_PER_SEC + rate_match->delay_frac) / node_rate + * \endcode + */ struct spa_io_rate_match { - uint32_t delay; /**< extra delay in samples for resampler */ + uint32_t delay; /**< resampling delay, in samples at + * node rate */ uint32_t size; /**< requested input size for resampler */ - double rate; /**< rate for resampler */ + double rate; /**< rate for resampler (set by node) */ #define SPA_IO_RATE_MATCH_FLAG_ACTIVE (1 << 0) - uint32_t flags; /**< extra flags */ - uint32_t padding[7]; + uint32_t flags; /**< extra flags (set by node) */ + int32_t delay_frac; /**< resampling delay fractional part, + * in units of nanosamples (1/10^9 sample) at node rate */ + uint32_t padding[6]; }; /** async buffers */ diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index e0deefe31..056d5eafd 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -2162,23 +2162,30 @@ static int ensure_tmp(struct impl *this, uint32_t maxsize, uint32_t maxports) static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, uint32_t size, uint32_t queued) { uint32_t delay, match_size; + int32_t delay_frac; if (passthrough) { delay = 0; + delay_frac = 0; match_size = size; } else { double rate = this->rate_scale / this->props.rate; + double fdelay; + if (this->io_rate_match && SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)) rate *= this->io_rate_match->rate; resample_update_rate(&this->resample, rate); - delay = resample_delay(&this->resample); + fdelay = resample_delay(&this->resample) + resample_phase(&this->resample); if (this->direction == SPA_DIRECTION_INPUT) { match_size = resample_in_len(&this->resample, size); } else { + fdelay *= rate * this->resample.o_rate / this->resample.i_rate; match_size = resample_out_len(&this->resample, size); - delay = resample_out_len(&this->resample, delay); } + + delay = (uint32_t)round(fdelay); + delay_frac = (int32_t)((fdelay - delay) * 1e9); } match_size -= SPA_MIN(match_size, queued); @@ -2186,6 +2193,7 @@ static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, if (this->io_rate_match) { this->io_rate_match->delay = delay + queued; + this->io_rate_match->delay_frac = delay_frac; this->io_rate_match->size = match_size; } return match_size;