]>
code.delx.au - pulseaudio/blob - src/pulsecore/time-smoother.c
4 This file is part of PulseAudio.
6 Copyright 2007 Lennart Poettering
8 PulseAudio is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as
10 published by the Free Software Foundation; either version 2.1 of the
11 License, or (at your option) any later version.
13 PulseAudio is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public
19 License along with PulseAudio; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
30 #include <pulse/sample.h>
31 #include <pulse/xmalloc.h>
33 #include <pulsecore/macro.h>
35 #include "time-smoother.h"
37 #define HISTORY_MAX 64
40 * Implementation of a time smoothing algorithm to synchronize remote
41 * clocks to a local one. Evens out noise, adjusts to clock skew and
42 * allows cheap estimations of the remote time while clock updates may
43 * be seldom and recieved in non-equidistant intervals.
45 * Basically, we estimate the gradient of received clock samples in a
46 * certain history window (of size 'history_time') with linear
47 * regression. With that info we estimate the remote time in
48 * 'adjust_time' ahead and smoothen our current estimation function
49 * towards that point with a 3rd order polynomial interpolation with
50 * fitting derivatives. (more or less a b-spline)
52 * The larger 'history_time' is chosen the better we will surpress
53 * noise -- but we'll adjust to clock skew slower..
55 * The larger 'adjust_time' is chosen the smoother our estimation
56 * function will be -- but we'll adjust to clock skew slower, too.
58 * If 'monotonic' is TRUE the resulting estimation function is
59 * guaranteed to be monotonic.
63 pa_usec_t adjust_time
, history_time
;
65 pa_usec_t time_offset
;
67 pa_usec_t px
, py
; /* Point p, where we want to reach stability */
68 double dp
; /* Gradient we want at point p */
70 pa_usec_t ex
, ey
; /* Point e, which we estimated before and need to smooth to */
71 double de
; /* Gradient we estimated for point e */
73 /* History of last measurements */
74 pa_usec_t history_x
[HISTORY_MAX
], history_y
[HISTORY_MAX
];
75 unsigned history_idx
, n_history
;
77 /* To even out for monotonicity */
80 /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */
84 pa_bool_t monotonic
:1;
90 pa_smoother
* pa_smoother_new(pa_usec_t adjust_time
, pa_usec_t history_time
, pa_bool_t monotonic
) {
93 pa_assert(adjust_time
> 0);
94 pa_assert(history_time
> 0);
96 s
= pa_xnew(pa_smoother
, 1);
97 s
->adjust_time
= adjust_time
;
98 s
->history_time
= history_time
;
100 s
->monotonic
= monotonic
;
113 s
->abc_valid
= FALSE
;
120 void pa_smoother_free(pa_smoother
* s
) {
128 x = (x) % HISTORY_MAX; \
131 #define REDUCE_INC(x) \
133 x = ((x)+1) % HISTORY_MAX; \
137 static void drop_old(pa_smoother
*s
, pa_usec_t x
) {
139 /* Drop items from history which are too old, but make sure to
140 * always keep two entries in the history */
142 while (s
->n_history
> 2) {
144 if (s
->history_x
[s
->history_idx
] + s
->history_time
>= x
)
145 /* This item is still valid, and thus all following ones
146 * are too, so let's quit this loop */
149 /* Item is too old, let's drop it */
150 REDUCE_INC(s
->history_idx
);
156 static void add_to_history(pa_smoother
*s
, pa_usec_t x
, pa_usec_t y
) {
160 /* First try to update an existing history entry */
162 for (j
= s
->n_history
; j
> 0; j
--) {
164 if (s
->history_x
[i
] == x
) {
172 /* Drop old entries */
175 /* Calculate position for new entry */
176 j
= s
->history_idx
+ s
->n_history
;
186 /* And make sure we don't store more entries than fit in */
187 if (s
->n_history
>= HISTORY_MAX
) {
188 s
->history_idx
+= s
->n_history
- HISTORY_MAX
;
189 REDUCE(s
->history_idx
);
190 s
->n_history
= HISTORY_MAX
;
194 static double avg_gradient(pa_smoother
*s
, pa_usec_t x
) {
195 unsigned i
, j
, c
= 0;
196 int64_t ax
= 0, ay
= 0, k
, t
;
201 /* First, calculate average of all measurements */
203 for (j
= s
->n_history
; j
> 0; j
--) {
205 ax
+= s
->history_x
[i
];
206 ay
+= s
->history_y
[i
];
212 /* Too few measurements, assume gradient of 1 */
219 /* Now, do linear regression */
223 for (j
= s
->n_history
; j
> 0; j
--) {
226 dx
= (int64_t) s
->history_x
[i
] - ax
;
227 dy
= (int64_t) s
->history_y
[i
] - ay
;
237 return (s
->monotonic
&& r
< 0) ? 0 : r
;
240 static void estimate(pa_smoother
*s
, pa_usec_t x
, pa_usec_t
*y
, double *deriv
) {
247 /* The requested point is right of the point where we wanted
248 * to be on track again, thus just linearly estimate */
250 t
= (int64_t) s
->py
+ (int64_t) (s
->dp
* (x
- s
->px
));
263 pa_usec_t ex
, ey
, px
, py
;
267 /* Ok, we're not yet on track, thus let's interpolate, and
268 * make sure that the first derivative is smooth */
270 /* We have two points: (ex|ey) and (px|py) with two gradients
271 * at these points de and dp. We do a polynomial interpolation
272 * of degree 3 with these 6 values */
274 ex
= s
->ex
; ey
= s
->ey
;
275 px
= s
->px
; py
= s
->py
;
276 de
= s
->de
; dp
= s
->dp
;
280 /* To increase the dynamic range and symplify calculation, we
281 * move these values to the origin */
282 kx
= (int64_t) px
- (int64_t) ex
;
283 ky
= (int64_t) py
- (int64_t) ey
;
285 /* Calculate a, b, c for y=ax^3+b^2+cx */
287 s
->b
= (((double) (3*ky
)/kx
- dp
- 2*de
)) / kx
;
288 s
->a
= (dp
/kx
- 2*s
->b
- de
/kx
) / (3*kx
);
297 *y
= (pa_usec_t
) ((double) x
* (s
->c
+ (double) x
* (s
->b
+ (double) x
* s
->a
)));
299 /* Move back from origin */
304 *deriv
= s
->c
+ ((double) x
* (s
->b
*2 + (double) x
* s
->a
*3));
307 /* Guarantee monotonicity */
315 if (deriv
&& *deriv
< 0)
320 void pa_smoother_put(pa_smoother
*s
, pa_usec_t x
, pa_usec_t y
) {
330 x
= PA_LIKELY(x
>= s
->time_offset
) ? x
- s
->time_offset
: 0;
332 pa_assert(x
>= s
->ex
);
334 /* First, we calculate the position we'd estimate for x, so that
335 * we can adjust our position smoothly from this one */
336 estimate(s
, x
, &ney
, &nde
);
337 s
->ex
= x
; s
->ey
= ney
; s
->de
= nde
;
339 /* Then, we add the new measurement to our history */
340 add_to_history(s
, x
, y
);
342 /* And determine the average gradient of the history */
343 s
->dp
= avg_gradient(s
, x
);
345 /* And calculate when we want to be on track again */
346 s
->px
= x
+ s
->adjust_time
;
347 s
->py
= y
+ s
->dp
*s
->adjust_time
;
349 s
->abc_valid
= FALSE
;
351 /* pa_log_debug("put(%llu | %llu) = %llu", (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */
354 pa_usec_t
pa_smoother_get(pa_smoother
*s
, pa_usec_t x
) {
363 x
= PA_LIKELY(x
>= s
->time_offset
) ? x
- s
->time_offset
: 0;
364 pa_assert(x
>= s
->ex
);
366 estimate(s
, x
, &y
, NULL
);
368 /* pa_log_debug("get(%llu | %llu) = %llu", (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */
373 void pa_smoother_set_time_offset(pa_smoother
*s
, pa_usec_t offset
) {
376 s
->time_offset
= offset
;
378 /* pa_log_debug("offset(%llu)", (unsigned long long) offset); */
381 void pa_smoother_pause(pa_smoother
*s
, pa_usec_t x
) {
387 /* pa_log_debug("pause(%llu)", (unsigned long long) x); */
393 void pa_smoother_resume(pa_smoother
*s
, pa_usec_t x
) {
399 pa_assert(x
>= s
->pause_time
);
401 /* pa_log_debug("resume(%llu)", (unsigned long long) x); */
404 s
->time_offset
+= x
- s
->pause_time
;
407 pa_usec_t
pa_smoother_translate(pa_smoother
*s
, pa_usec_t x
, pa_usec_t y_delay
) {
417 x
= PA_LIKELY(x
>= s
->time_offset
) ? x
- s
->time_offset
: 0;
419 pa_assert(x
>= s
->ex
);
421 estimate(s
, x
, &ney
, &nde
);
423 /* pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / s->dp), s->dp); */
425 return (pa_usec_t
) ((double) y_delay
/ s
->dp
);