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.
This commit is contained in:
Pauli Virtanen 2025-01-14 21:53:55 +02:00
parent f3a9ebd569
commit 5f21ee8669
2 changed files with 51 additions and 7 deletions

View file

@ -305,14 +305,50 @@ struct spa_io_position {
struct spa_io_segment segments[SPA_IO_POSITION_MAX_SEGMENTS]; /**< segments */ 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 { 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 */ 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) #define SPA_IO_RATE_MATCH_FLAG_ACTIVE (1 << 0)
uint32_t flags; /**< extra flags */ uint32_t flags; /**< extra flags (set by node) */
uint32_t padding[7]; 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 */ /** async buffers */

View file

@ -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) static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, uint32_t size, uint32_t queued)
{ {
uint32_t delay, match_size; uint32_t delay, match_size;
int32_t delay_frac;
if (passthrough) { if (passthrough) {
delay = 0; delay = 0;
delay_frac = 0;
match_size = size; match_size = size;
} else { } else {
double rate = this->rate_scale / this->props.rate; double rate = this->rate_scale / this->props.rate;
double fdelay;
if (this->io_rate_match && if (this->io_rate_match &&
SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)) SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE))
rate *= this->io_rate_match->rate; rate *= this->io_rate_match->rate;
resample_update_rate(&this->resample, 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) { if (this->direction == SPA_DIRECTION_INPUT) {
match_size = resample_in_len(&this->resample, size); match_size = resample_in_len(&this->resample, size);
} else { } else {
fdelay *= rate * this->resample.o_rate / this->resample.i_rate;
match_size = resample_out_len(&this->resample, size); 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); 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) { if (this->io_rate_match) {
this->io_rate_match->delay = delay + queued; this->io_rate_match->delay = delay + queued;
this->io_rate_match->delay_frac = delay_frac;
this->io_rate_match->size = match_size; this->io_rate_match->size = match_size;
} }
return match_size; return match_size;