]> code.delx.au - pulseaudio/blob - src/pulsecore/time-smoother.c
merge 'lennart' branch back into trunk.
[pulseaudio] / src / pulsecore / time-smoother.c
1 /* $Id$ */
2
3 /***
4 This file is part of PulseAudio.
5
6 Copyright 2007 Lennart Poettering
7
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.
12
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.
17
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
21 USA.
22 ***/
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdio.h>
29
30 #include <pulse/sample.h>
31 #include <pulse/xmalloc.h>
32
33 #include <pulsecore/macro.h>
34
35 #include "time-smoother.h"
36
37 #define HISTORY_MAX 50
38
39 /*
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.
44 *
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)
51 *
52 * The larger 'history_time' is chosen the better we will surpress
53 * noise -- but we'll adjust to clock skew slower..
54 *
55 * The larger 'adjust_time' is chosen the smoother our estimation
56 * function will be -- but we'll adjust to clock skew slower, too.
57 *
58 * If 'monotonic' is TRUE the resulting estimation function is
59 * guaranteed to be monotonic.
60 */
61
62 struct pa_smoother {
63 pa_usec_t adjust_time, history_time;
64 pa_bool_t monotonic;
65
66 pa_usec_t time_offset;
67
68 pa_usec_t px, py; /* Point p, where we want to reach stability */
69 double dp; /* Gradient we want at point p */
70
71 pa_usec_t ex, ey; /* Point e, which we estimated before and need to smooth to */
72 double de; /* Gradient we estimated for point e */
73
74 /* History of last measurements */
75 pa_usec_t history_x[HISTORY_MAX], history_y[HISTORY_MAX];
76 unsigned history_idx, n_history;
77
78 /* To even out for monotonicity */
79 pa_usec_t last_y;
80
81 /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */
82 double a, b, c;
83 pa_bool_t abc_valid;
84
85 pa_bool_t paused;
86 pa_usec_t pause_time;
87 };
88
89 pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic) {
90 pa_smoother *s;
91
92 pa_assert(adjust_time > 0);
93 pa_assert(history_time > 0);
94
95 s = pa_xnew(pa_smoother, 1);
96 s->adjust_time = adjust_time;
97 s->history_time = history_time;
98 s->time_offset = 0;
99 s->monotonic = monotonic;
100
101 s->px = s->py = 0;
102 s->dp = 1;
103
104 s->ex = s->ey = 0;
105 s->de = 1;
106
107 s->history_idx = 0;
108 s->n_history = 0;
109
110 s->last_y = 0;
111
112 s->abc_valid = FALSE;
113
114 s->paused = FALSE;
115
116 return s;
117 }
118
119 void pa_smoother_free(pa_smoother* s) {
120 pa_assert(s);
121
122 pa_xfree(s);
123 }
124
125 static void drop_old(pa_smoother *s, pa_usec_t x) {
126 unsigned j;
127
128 /* First drop items from history which are too old, but make sure
129 * to always keep two entries in the history */
130
131 for (j = s->n_history; j > 2; j--) {
132
133 if (s->history_x[s->history_idx] + s->history_time >= x) {
134 /* This item is still valid, and thus all following ones
135 * are too, so let's quit this loop */
136 break;
137 }
138
139 /* Item is too old, let's drop it */
140 s->history_idx ++;
141 while (s->history_idx >= HISTORY_MAX)
142 s->history_idx -= HISTORY_MAX;
143
144 s->n_history --;
145 }
146 }
147
148 static void add_to_history(pa_smoother *s, pa_usec_t x, pa_usec_t y) {
149 unsigned j;
150 pa_assert(s);
151
152 drop_old(s, x);
153
154 /* Calculate position for new entry */
155 j = s->history_idx + s->n_history;
156 while (j >= HISTORY_MAX)
157 j -= HISTORY_MAX;
158
159 /* Fill in entry */
160 s->history_x[j] = x;
161 s->history_y[j] = y;
162
163 /* Adjust counter */
164 s->n_history ++;
165
166 /* And make sure we don't store more entries than fit in */
167 if (s->n_history >= HISTORY_MAX) {
168 s->history_idx += s->n_history - HISTORY_MAX;
169 s->n_history = HISTORY_MAX;
170 }
171 }
172
173 static double avg_gradient(pa_smoother *s, pa_usec_t x) {
174 unsigned i, j, c = 0;
175 int64_t ax = 0, ay = 0, k, t;
176 double r;
177
178 drop_old(s, x);
179
180 /* First, calculate average of all measurements */
181 i = s->history_idx;
182 for (j = s->n_history; j > 0; j--) {
183
184 ax += s->history_x[i];
185 ay += s->history_y[i];
186 c++;
187
188 i++;
189 while (i >= HISTORY_MAX)
190 i -= HISTORY_MAX;
191 }
192
193 /* Too few measurements, assume gradient of 1 */
194 if (c < 2)
195 return 1;
196
197 ax /= c;
198 ay /= c;
199
200 /* Now, do linear regression */
201 k = t = 0;
202
203 i = s->history_idx;
204 for (j = s->n_history; j > 0; j--) {
205 int64_t dx, dy;
206
207 dx = (int64_t) s->history_x[i] - ax;
208 dy = (int64_t) s->history_y[i] - ay;
209
210 k += dx*dy;
211 t += dx*dx;
212
213 i++;
214 while (i >= HISTORY_MAX)
215 i -= HISTORY_MAX;
216 }
217
218 r = (double) k / t;
219
220 return s->monotonic && r < 0 ? 0 : r;
221 }
222
223 static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) {
224 pa_assert(s);
225 pa_assert(y);
226
227 if (x >= s->px) {
228 int64_t t;
229
230 /* The requested point is right of the point where we wanted
231 * to be on track again, thus just linearly estimate */
232
233 t = (int64_t) s->py + (int64_t) (s->dp * (x - s->px));
234
235 if (t < 0)
236 t = 0;
237
238 *y = (pa_usec_t) t;
239
240 if (deriv)
241 *deriv = s->dp;
242
243 } else {
244
245 if (!s->abc_valid) {
246 pa_usec_t ex, ey, px, py;
247 int64_t kx, ky;
248 double de, dp;
249
250 /* Ok, we're not yet on track, thus let's interpolate, and
251 * make sure that the first derivative is smooth */
252
253 /* We have two points: (ex|ey) and (px|py) with two gradients
254 * at these points de and dp. We do a polynomial interpolation
255 * of degree 3 with these 6 values */
256
257 ex = s->ex; ey = s->ey;
258 px = s->px; py = s->py;
259 de = s->de; dp = s->dp;
260
261 pa_assert(ex < px);
262
263 /* To increase the dynamic range and symplify calculation, we
264 * move these values to the origin */
265 kx = (int64_t) px - (int64_t) ex;
266 ky = (int64_t) py - (int64_t) ey;
267
268 /* Calculate a, b, c for y=ax^3+b^2+cx */
269 s->c = de;
270 s->b = (((double) (3*ky)/kx - dp - 2*de)) / kx;
271 s->a = (dp/kx - 2*s->b - de/kx) / (3*kx);
272
273 s->abc_valid = TRUE;
274 }
275
276 /* Move to origin */
277 x -= s->ex;
278
279 /* Horner scheme */
280 *y = (pa_usec_t) ((double) x * (s->c + (double) x * (s->b + (double) x * s->a)));
281
282 /* Move back from origin */
283 *y += s->ey;
284
285 /* Horner scheme */
286 if (deriv)
287 *deriv = s->c + ((double) x * (s->b*2 + (double) x * s->a*3));
288 }
289
290 /* Guarantee monotonicity */
291 if (s->monotonic) {
292
293 if (*y < s->last_y)
294 *y = s->last_y;
295 else
296 s->last_y = *y;
297
298 if (deriv && *deriv < 0)
299 *deriv = 0;
300 }
301 }
302
303 void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) {
304 pa_usec_t ney;
305 double nde;
306
307 pa_assert(s);
308 pa_assert(x >= s->time_offset);
309
310 /* Fix up x value */
311 if (s->paused)
312 x = s->pause_time;
313 else
314 x -= s->time_offset;
315
316 pa_assert(x >= s->ex);
317
318 /* First, we calculate the position we'd estimate for x, so that
319 * we can adjust our position smoothly from this one */
320 estimate(s, x, &ney, &nde);
321 s->ex = x; s->ey = ney; s->de = nde;
322
323 /* Then, we add the new measurement to our history */
324 add_to_history(s, x, y);
325
326 /* And determine the average gradient of the history */
327 s->dp = avg_gradient(s, x);
328
329 /* And calculate when we want to be on track again */
330 s->px = x + s->adjust_time;
331 s->py = y + s->dp *s->adjust_time;
332
333 s->abc_valid = FALSE;
334 }
335
336 pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) {
337 pa_usec_t y;
338
339 pa_assert(s);
340 pa_assert(x >= s->time_offset);
341
342 /* Fix up x value */
343 if (s->paused)
344 x = s->pause_time;
345 else
346 x -= s->time_offset;
347
348 pa_assert(x >= s->ex);
349
350 estimate(s, x, &y, NULL);
351 return y;
352 }
353
354 void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) {
355 pa_assert(s);
356
357 s->time_offset = offset;
358 }
359
360 void pa_smoother_pause(pa_smoother *s, pa_usec_t x) {
361 pa_assert(s);
362
363 if (s->paused)
364 return;
365
366 s->paused = TRUE;
367 s->pause_time = x;
368 }
369
370 void pa_smoother_resume(pa_smoother *s, pa_usec_t x) {
371 pa_assert(s);
372
373 if (!s->paused)
374 return;
375
376 s->paused = FALSE;
377 s->time_offset += x - s->pause_time;
378 }