2 This file is part of PulseAudio.
4 Copyright 2007 Lennart Poettering
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2.1 of the
9 License, or (at your option) any later version.
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
29 #include <pulse/sample.h>
30 #include <pulse/xmalloc.h>
32 #include <pulsecore/macro.h>
34 #include "time-smoother.h"
36 #define HISTORY_MAX 64
39 * Implementation of a time smoothing algorithm to synchronize remote
40 * clocks to a local one. Evens out noise, adjusts to clock skew and
41 * allows cheap estimations of the remote time while clock updates may
42 * be seldom and recieved in non-equidistant intervals.
44 * Basically, we estimate the gradient of received clock samples in a
45 * certain history window (of size 'history_time') with linear
46 * regression. With that info we estimate the remote time in
47 * 'adjust_time' ahead and smoothen our current estimation function
48 * towards that point with a 3rd order polynomial interpolation with
49 * fitting derivatives. (more or less a b-spline)
51 * The larger 'history_time' is chosen the better we will surpress
52 * noise -- but we'll adjust to clock skew slower..
54 * The larger 'adjust_time' is chosen the smoother our estimation
55 * function will be -- but we'll adjust to clock skew slower, too.
57 * If 'monotonic' is TRUE the resulting estimation function is
58 * guaranteed to be monotonic.
62 pa_usec_t adjust_time
, history_time
;
64 pa_usec_t time_offset
;
66 pa_usec_t px
, py
; /* Point p, where we want to reach stability */
67 double dp
; /* Gradient we want at point p */
69 pa_usec_t ex
, ey
; /* Point e, which we estimated before and need to smooth to */
70 double de
; /* Gradient we estimated for point e */
71 pa_usec_t ry
; /* The original y value for ex */
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 */
78 pa_usec_t last_y
, last_x
;
80 /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */
82 pa_bool_t abc_valid
:1;
84 pa_bool_t monotonic
:1;
86 pa_bool_t smoothing
:1; /* If FALSE we skip the polonyomial interpolation step */
93 pa_smoother
* pa_smoother_new(
94 pa_usec_t adjust_time
,
95 pa_usec_t history_time
,
99 pa_usec_t time_offset
,
104 pa_assert(adjust_time
> 0);
105 pa_assert(history_time
> 0);
106 pa_assert(min_history
>= 2);
107 pa_assert(min_history
<= HISTORY_MAX
);
109 s
= pa_xnew(pa_smoother
, 1);
110 s
->adjust_time
= adjust_time
;
111 s
->history_time
= history_time
;
112 s
->min_history
= min_history
;
113 s
->monotonic
= monotonic
;
114 s
->smoothing
= smoothing
;
116 pa_smoother_reset(s
, time_offset
, paused
);
121 void pa_smoother_free(pa_smoother
* s
) {
129 x = (x) % HISTORY_MAX; \
132 #define REDUCE_INC(x) \
134 x = ((x)+1) % HISTORY_MAX; \
138 static void drop_old(pa_smoother
*s
, pa_usec_t x
) {
140 /* Drop items from history which are too old, but make sure to
141 * always keep min_history in the history */
143 while (s
->n_history
> s
->min_history
) {
145 if (s
->history_x
[s
->history_idx
] + s
->history_time
>= x
)
146 /* This item is still valid, and thus all following ones
147 * are too, so let's quit this loop */
150 /* Item is too old, let's drop it */
151 REDUCE_INC(s
->history_idx
);
157 static void add_to_history(pa_smoother
*s
, pa_usec_t x
, pa_usec_t y
) {
161 /* First try to update an existing history entry */
163 for (j
= s
->n_history
; j
> 0; j
--) {
165 if (s
->history_x
[i
] == x
) {
173 /* Drop old entries */
176 /* Calculate position for new entry */
177 j
= s
->history_idx
+ s
->n_history
;
187 /* And make sure we don't store more entries than fit in */
188 if (s
->n_history
> HISTORY_MAX
) {
189 s
->history_idx
+= s
->n_history
- HISTORY_MAX
;
190 REDUCE(s
->history_idx
);
191 s
->n_history
= HISTORY_MAX
;
195 static double avg_gradient(pa_smoother
*s
, pa_usec_t x
) {
196 unsigned i
, j
, c
= 0;
197 int64_t ax
= 0, ay
= 0, k
, t
;
200 /* FIXME: Optimization: Jason Newton suggested that instead of
201 * going through the history on each iteration we could calculated
202 * avg_gradient() as we go.
204 * Second idea: it might make sense to weight history entries:
205 * more recent entries should matter more than old ones. */
207 /* Too few measurements, assume gradient of 1 */
208 if (s
->n_history
< s
->min_history
)
211 /* First, calculate average of all measurements */
213 for (j
= s
->n_history
; j
> 0; j
--) {
215 ax
+= (int64_t) s
->history_x
[i
];
216 ay
+= (int64_t) s
->history_y
[i
];
222 pa_assert(c
>= s
->min_history
);
226 /* Now, do linear regression */
230 for (j
= s
->n_history
; j
> 0; j
--) {
233 dx
= (int64_t) s
->history_x
[i
] - ax
;
234 dy
= (int64_t) s
->history_y
[i
] - ay
;
242 r
= (double) k
/ (double) t
;
244 return (s
->monotonic
&& r
< 0) ? 0 : r
;
247 static void calc_abc(pa_smoother
*s
) {
248 pa_usec_t ex
, ey
, px
, py
;
257 /* We have two points: (ex|ey) and (px|py) with two gradients at
258 * these points de and dp. We do a polynomial
259 * interpolation of degree 3 with these 6 values */
261 ex
= s
->ex
; ey
= s
->ey
;
262 px
= s
->px
; py
= s
->py
;
263 de
= s
->de
; dp
= s
->dp
;
267 /* To increase the dynamic range and symplify calculation, we
268 * move these values to the origin */
269 kx
= (int64_t) px
- (int64_t) ex
;
270 ky
= (int64_t) py
- (int64_t) ey
;
272 /* Calculate a, b, c for y=ax^3+bx^2+cx */
274 s
->b
= (((double) (3*ky
)/ (double) kx
- dp
- (double) (2*de
))) / (double) kx
;
275 s
->a
= (dp
/(double) kx
- 2*s
->b
- de
/(double) kx
) / (double) (3*kx
);
280 static void estimate(pa_smoother
*s
, pa_usec_t x
, pa_usec_t
*y
, double *deriv
) {
285 /* Linear interpolation right from px */
288 /* The requested point is right of the point where we wanted
289 * to be on track again, thus just linearly estimate */
291 t
= (int64_t) s
->py
+ (int64_t) llrint(s
->dp
* (double) (x
- s
->px
));
301 } else if (x
<= s
->ex
) {
302 /* Linear interpolation left from ex */
305 t
= (int64_t) s
->ey
- (int64_t) llrint(s
->de
* (double) (s
->ex
- x
));
316 /* Spline interpolation between ex and px */
319 /* Ok, we're not yet on track, thus let's interpolate, and
320 * make sure that the first derivative is smooth */
325 tx
= (double) (x
- s
->ex
);
328 ty
= (tx
* (s
->c
+ tx
* (s
->b
+ tx
* s
->a
)));
330 /* Move back from origin */
331 ty
+= (double) s
->ey
;
333 *y
= ty
>= 0 ? (pa_usec_t
) llrint(ty
) : 0;
337 *deriv
= s
->c
+ (tx
* (s
->b
*2 + tx
* s
->a
*3));
340 /* Guarantee monotonicity */
343 if (deriv
&& *deriv
< 0)
348 void pa_smoother_put(pa_smoother
*s
, pa_usec_t x
, pa_usec_t y
) {
359 x
= PA_LIKELY(x
>= s
->time_offset
) ? x
- s
->time_offset
: 0;
364 /* First, we calculate the position we'd estimate for x, so that
365 * we can adjust our position smoothly from this one */
366 estimate(s
, x
, &ney
, &nde
);
367 s
->ex
= x
; s
->ey
= ney
; s
->de
= nde
;
371 /* Then, we add the new measurement to our history */
372 add_to_history(s
, x
, y
);
374 /* And determine the average gradient of the history */
375 s
->dp
= avg_gradient(s
, x
);
377 /* And calculate when we want to be on track again */
379 s
->px
= s
->ex
+ s
->adjust_time
;
380 s
->py
= s
->ry
+ (pa_usec_t
) llrint(s
->dp
* (double) s
->adjust_time
);
386 s
->abc_valid
= FALSE
;
389 pa_log_debug("%p, put(%llu | %llu) = %llu", s
, (unsigned long long) (x
+ s
->time_offset
), (unsigned long long) x
, (unsigned long long) y
);
393 pa_usec_t
pa_smoother_get(pa_smoother
*s
, pa_usec_t x
) {
402 x
= PA_LIKELY(x
>= s
->time_offset
) ? x
- s
->time_offset
: 0;
408 estimate(s
, x
, &y
, NULL
);
412 /* Make sure the querier doesn't jump forth and back. */
422 pa_log_debug("%p, get(%llu | %llu) = %llu", s
, (unsigned long long) (x
+ s
->time_offset
), (unsigned long long) x
, (unsigned long long) y
);
428 void pa_smoother_set_time_offset(pa_smoother
*s
, pa_usec_t offset
) {
431 s
->time_offset
= offset
;
434 pa_log_debug("offset(%llu)", (unsigned long long) offset
);
438 void pa_smoother_pause(pa_smoother
*s
, pa_usec_t x
) {
445 pa_log_debug("pause(%llu)", (unsigned long long) x
);
452 void pa_smoother_resume(pa_smoother
*s
, pa_usec_t x
, pa_bool_t fix_now
) {
458 if (x
< s
->pause_time
)
462 pa_log_debug("resume(%llu)", (unsigned long long) x
);
466 s
->time_offset
+= x
- s
->pause_time
;
469 pa_smoother_fix_now(s
);
472 void pa_smoother_fix_now(pa_smoother
*s
) {
479 pa_usec_t
pa_smoother_translate(pa_smoother
*s
, pa_usec_t x
, pa_usec_t y_delay
) {
489 x
= PA_LIKELY(x
>= s
->time_offset
) ? x
- s
->time_offset
: 0;
491 estimate(s
, x
, &ney
, &nde
);
493 /* Play safe and take the larger gradient, so that we wakeup
494 * earlier when this is used for sleeping */
499 pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay
, (unsigned long long) ((double) y_delay
/ nde
), nde
);
502 return (pa_usec_t
) llrint((double) y_delay
/ nde
);
505 void pa_smoother_reset(pa_smoother
*s
, pa_usec_t time_offset
, pa_bool_t paused
) {
511 s
->ex
= s
->ey
= s
->ry
= 0;
517 s
->last_y
= s
->last_x
= 0;
519 s
->abc_valid
= FALSE
;
522 s
->time_offset
= s
->pause_time
= time_offset
;
525 pa_log_debug("reset()");