1 /* $Id$ */
2 /*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20 #include <pjmedia/clock.h>
21 #include <pjmedia/errno.h>
22 #include <pj/assert.h>
23 #include <pj/lock.h>
24 #include <pj/os.h>
25 #include <pj/pool.h>
26 #include <pj/string.h>
27 #include <pj/compat/high_precision.h>
28
29 /* API: Init clock source */
pjmedia_clock_src_init(pjmedia_clock_src * clocksrc,pjmedia_type media_type,unsigned clock_rate,unsigned ptime_usec)30 PJ_DEF(pj_status_t) pjmedia_clock_src_init( pjmedia_clock_src *clocksrc,
31 pjmedia_type media_type,
32 unsigned clock_rate,
33 unsigned ptime_usec )
34 {
35 PJ_ASSERT_RETURN(clocksrc, PJ_EINVAL);
36
37 clocksrc->media_type = media_type;
38 clocksrc->clock_rate = clock_rate;
39 clocksrc->ptime_usec = ptime_usec;
40 pj_set_timestamp32(&clocksrc->timestamp, 0, 0);
41 pj_get_timestamp(&clocksrc->last_update);
42
43 return PJ_SUCCESS;
44 }
45
46 /* API: Update clock source */
pjmedia_clock_src_update(pjmedia_clock_src * clocksrc,const pj_timestamp * timestamp)47 PJ_DECL(pj_status_t) pjmedia_clock_src_update( pjmedia_clock_src *clocksrc,
48 const pj_timestamp *timestamp )
49 {
50 PJ_ASSERT_RETURN(clocksrc, PJ_EINVAL);
51
52 if (timestamp)
53 pj_memcpy(&clocksrc->timestamp, timestamp, sizeof(pj_timestamp));
54 pj_get_timestamp(&clocksrc->last_update);
55
56 return PJ_SUCCESS;
57 }
58
59 /* API: Get clock source's current timestamp */
60 PJ_DEF(pj_status_t)
pjmedia_clock_src_get_current_timestamp(const pjmedia_clock_src * clocksrc,pj_timestamp * timestamp)61 pjmedia_clock_src_get_current_timestamp( const pjmedia_clock_src *clocksrc,
62 pj_timestamp *timestamp)
63 {
64 pj_timestamp now;
65 unsigned elapsed_ms;
66
67 PJ_ASSERT_RETURN(clocksrc && timestamp, PJ_EINVAL);
68
69 pj_get_timestamp(&now);
70 elapsed_ms = pj_elapsed_msec(&clocksrc->last_update, &now);
71 pj_memcpy(timestamp, &clocksrc->timestamp, sizeof(pj_timestamp));
72 pj_add_timestamp32(timestamp, elapsed_ms * clocksrc->clock_rate / 1000);
73
74 return PJ_SUCCESS;
75 }
76
77 /* API: Get clock source's time (in ms) */
78 PJ_DEF(pj_uint32_t)
pjmedia_clock_src_get_time_msec(const pjmedia_clock_src * clocksrc)79 pjmedia_clock_src_get_time_msec( const pjmedia_clock_src *clocksrc )
80 {
81 pj_timestamp ts;
82
83 pjmedia_clock_src_get_current_timestamp(clocksrc, &ts);
84
85 #if PJ_HAS_INT64
86 if (ts.u64 > PJ_UINT64(0x3FFFFFFFFFFFFF))
87 return (pj_uint32_t)(ts.u64 / clocksrc->clock_rate * 1000);
88 else
89 return (pj_uint32_t)(ts.u64 * 1000 / clocksrc->clock_rate);
90 #elif PJ_HAS_FLOATING_POINT
91 return (pj_uint32_t)((1.0 * ts.u32.hi * 0xFFFFFFFFUL + ts.u32.lo)
92 * 1000.0 / clocksrc->clock_rate);
93 #else
94 if (ts.u32.lo > 0x3FFFFFUL)
95 return (pj_uint32_t)(0xFFFFFFFFUL / clocksrc->clock_rate * ts.u32.hi
96 * 1000UL + ts.u32.lo / clocksrc->clock_rate *
97 1000UL);
98 else
99 return (pj_uint32_t)(0xFFFFFFFFUL / clocksrc->clock_rate * ts.u32.hi
100 * 1000UL + ts.u32.lo * 1000UL /
101 clocksrc->clock_rate);
102 #endif
103 }
104
105
106 /*
107 * Implementation of media clock with OS thread.
108 */
109
110 struct pjmedia_clock
111 {
112 pj_pool_t *pool;
113 pj_timestamp freq;
114 pj_timestamp interval;
115 pj_timestamp next_tick;
116 pj_timestamp timestamp;
117 unsigned timestamp_inc;
118 unsigned options;
119 pj_uint64_t max_jump;
120 pjmedia_clock_callback *cb;
121 void *user_data;
122 pj_thread_t *thread;
123 pj_bool_t running;
124 pj_bool_t quitting;
125 pj_lock_t *lock;
126 };
127
128
129 static int clock_thread(void *arg);
130
131 #define MAX_JUMP_MSEC 500
132 #define USEC_IN_SEC (pj_uint64_t)1000000
133
134 /*
135 * Create media clock.
136 */
pjmedia_clock_create(pj_pool_t * pool,unsigned clock_rate,unsigned channel_count,unsigned samples_per_frame,unsigned options,pjmedia_clock_callback * cb,void * user_data,pjmedia_clock ** p_clock)137 PJ_DEF(pj_status_t) pjmedia_clock_create( pj_pool_t *pool,
138 unsigned clock_rate,
139 unsigned channel_count,
140 unsigned samples_per_frame,
141 unsigned options,
142 pjmedia_clock_callback *cb,
143 void *user_data,
144 pjmedia_clock **p_clock)
145 {
146 pjmedia_clock_param param;
147
148 param.usec_interval = (unsigned)(samples_per_frame * USEC_IN_SEC /
149 channel_count / clock_rate);
150 param.clock_rate = clock_rate;
151 return pjmedia_clock_create2(pool, ¶m, options, cb,
152 user_data, p_clock);
153 }
154
pjmedia_clock_create2(pj_pool_t * pool,const pjmedia_clock_param * param,unsigned options,pjmedia_clock_callback * cb,void * user_data,pjmedia_clock ** p_clock)155 PJ_DEF(pj_status_t) pjmedia_clock_create2(pj_pool_t *pool,
156 const pjmedia_clock_param *param,
157 unsigned options,
158 pjmedia_clock_callback *cb,
159 void *user_data,
160 pjmedia_clock **p_clock)
161 {
162 pjmedia_clock *clock;
163 pj_status_t status;
164
165 PJ_ASSERT_RETURN(pool && param->usec_interval && param->clock_rate &&
166 p_clock, PJ_EINVAL);
167
168 clock = PJ_POOL_ALLOC_T(pool, pjmedia_clock);
169 clock->pool = pj_pool_create(pool->factory, "clock%p", 512, 512, NULL);
170
171 status = pj_get_timestamp_freq(&clock->freq);
172 if (status != PJ_SUCCESS)
173 return status;
174
175 clock->interval.u64 = param->usec_interval * clock->freq.u64 /
176 USEC_IN_SEC;
177 clock->next_tick.u64 = 0;
178 clock->timestamp.u64 = 0;
179 clock->max_jump = MAX_JUMP_MSEC * clock->freq.u64 / 1000;
180 clock->timestamp_inc = (unsigned)(param->usec_interval *
181 param->clock_rate /
182 (unsigned)USEC_IN_SEC);
183 clock->options = options;
184 clock->cb = cb;
185 clock->user_data = user_data;
186 clock->thread = NULL;
187 clock->running = PJ_FALSE;
188 clock->quitting = PJ_FALSE;
189
190 /* I don't think we need a mutex, so we'll use null. */
191 status = pj_lock_create_null_mutex(pool, "clock", &clock->lock);
192 if (status != PJ_SUCCESS)
193 return status;
194
195 *p_clock = clock;
196
197 return PJ_SUCCESS;
198 }
199
200
201 /*
202 * Start the clock.
203 */
pjmedia_clock_start(pjmedia_clock * clock)204 PJ_DEF(pj_status_t) pjmedia_clock_start(pjmedia_clock *clock)
205 {
206 pj_timestamp now;
207 pj_status_t status;
208
209 PJ_ASSERT_RETURN(clock != NULL, PJ_EINVAL);
210
211 if (clock->running)
212 return PJ_SUCCESS;
213
214 status = pj_get_timestamp(&now);
215 if (status != PJ_SUCCESS)
216 return status;
217
218 clock->next_tick.u64 = now.u64 + clock->interval.u64;
219 clock->running = PJ_TRUE;
220 clock->quitting = PJ_FALSE;
221
222 if ((clock->options & PJMEDIA_CLOCK_NO_ASYNC) == 0 && !clock->thread) {
223 status = pj_thread_create(clock->pool, "clock", &clock_thread, clock,
224 0, 0, &clock->thread);
225 if (status != PJ_SUCCESS) {
226 clock->running = PJ_FALSE;
227 return status;
228 }
229 }
230
231 return PJ_SUCCESS;
232 }
233
234
235 /*
236 * Stop the clock.
237 */
pjmedia_clock_stop(pjmedia_clock * clock)238 PJ_DEF(pj_status_t) pjmedia_clock_stop(pjmedia_clock *clock)
239 {
240 PJ_ASSERT_RETURN(clock != NULL, PJ_EINVAL);
241
242 clock->running = PJ_FALSE;
243 clock->quitting = PJ_TRUE;
244
245 if (clock->thread) {
246 if (pj_thread_join(clock->thread) == PJ_SUCCESS) {
247 pj_thread_destroy(clock->thread);
248 clock->thread = NULL;
249 pj_pool_reset(clock->pool);
250 } else {
251 clock->quitting = PJ_FALSE;
252 }
253 }
254
255 return PJ_SUCCESS;
256 }
257
258
259 /*
260 * Update the clock.
261 */
pjmedia_clock_modify(pjmedia_clock * clock,const pjmedia_clock_param * param)262 PJ_DEF(pj_status_t) pjmedia_clock_modify(pjmedia_clock *clock,
263 const pjmedia_clock_param *param)
264 {
265 clock->interval.u64 = param->usec_interval * clock->freq.u64 /
266 USEC_IN_SEC;
267 clock->timestamp_inc = (unsigned)(param->usec_interval *
268 param->clock_rate /
269 (unsigned)USEC_IN_SEC);
270
271 return PJ_SUCCESS;
272 }
273
274
275 /* Calculate next tick */
clock_calc_next_tick(pjmedia_clock * clock,pj_timestamp * now)276 PJ_INLINE(void) clock_calc_next_tick(pjmedia_clock *clock,
277 pj_timestamp *now)
278 {
279 if (clock->next_tick.u64+clock->max_jump < now->u64) {
280 /* Timestamp has made large jump, adjust next_tick */
281 clock->next_tick.u64 = now->u64;
282 }
283 clock->next_tick.u64 += clock->interval.u64;
284
285 }
286
287 /*
288 * Poll the clock.
289 */
pjmedia_clock_wait(pjmedia_clock * clock,pj_bool_t wait,pj_timestamp * ts)290 PJ_DEF(pj_bool_t) pjmedia_clock_wait( pjmedia_clock *clock,
291 pj_bool_t wait,
292 pj_timestamp *ts)
293 {
294 pj_timestamp now;
295 pj_status_t status;
296
297 PJ_ASSERT_RETURN(clock != NULL, PJ_FALSE);
298 PJ_ASSERT_RETURN((clock->options & PJMEDIA_CLOCK_NO_ASYNC) != 0,
299 PJ_FALSE);
300 PJ_ASSERT_RETURN(clock->running, PJ_FALSE);
301
302 status = pj_get_timestamp(&now);
303 if (status != PJ_SUCCESS)
304 return PJ_FALSE;
305
306 /* Wait for the next tick to happen */
307 if (now.u64 < clock->next_tick.u64) {
308 unsigned msec;
309
310 if (!wait)
311 return PJ_FALSE;
312
313 msec = pj_elapsed_msec(&now, &clock->next_tick);
314 pj_thread_sleep(msec);
315 }
316
317 /* Call callback, if any */
318 if (clock->cb)
319 (*clock->cb)(&clock->timestamp, clock->user_data);
320
321 /* Report timestamp to caller */
322 if (ts)
323 ts->u64 = clock->timestamp.u64;
324
325 /* Increment timestamp */
326 clock->timestamp.u64 += clock->timestamp_inc;
327
328 /* Calculate next tick */
329 clock_calc_next_tick(clock, &now);
330
331 /* Done */
332 return PJ_TRUE;
333 }
334
335
336 /*
337 * Clock thread
338 */
clock_thread(void * arg)339 static int clock_thread(void *arg)
340 {
341 pj_timestamp now;
342 pjmedia_clock *clock = (pjmedia_clock*) arg;
343
344 /* Set thread priority to maximum unless not wanted. */
345 if ((clock->options & PJMEDIA_CLOCK_NO_HIGHEST_PRIO) == 0) {
346 int max = pj_thread_get_prio_max(pj_thread_this());
347 if (max > 0)
348 pj_thread_set_prio(pj_thread_this(), max);
349 }
350
351 /* Get the first tick */
352 pj_get_timestamp(&clock->next_tick);
353 clock->next_tick.u64 += clock->interval.u64;
354
355
356 while (!clock->quitting) {
357
358 pj_get_timestamp(&now);
359
360 /* Wait for the next tick to happen */
361 if (now.u64 < clock->next_tick.u64) {
362 unsigned msec;
363 msec = pj_elapsed_msec(&now, &clock->next_tick);
364 pj_thread_sleep(msec);
365 }
366
367 /* Skip if not running */
368 if (!clock->running) {
369 /* Calculate next tick */
370 clock_calc_next_tick(clock, &now);
371 continue;
372 }
373
374 pj_lock_acquire(clock->lock);
375
376 /* Call callback, if any */
377 if (clock->cb)
378 (*clock->cb)(&clock->timestamp, clock->user_data);
379
380 /* Best effort way to detect if we've been destroyed in the callback */
381 if (clock->quitting)
382 break;
383
384 /* Increment timestamp */
385 clock->timestamp.u64 += clock->timestamp_inc;
386
387 /* Calculate next tick */
388 clock_calc_next_tick(clock, &now);
389
390 pj_lock_release(clock->lock);
391 }
392
393 return 0;
394 }
395
396
397 /*
398 * Destroy the clock.
399 */
pjmedia_clock_destroy(pjmedia_clock * clock)400 PJ_DEF(pj_status_t) pjmedia_clock_destroy(pjmedia_clock *clock)
401 {
402 PJ_ASSERT_RETURN(clock != NULL, PJ_EINVAL);
403
404 clock->running = PJ_FALSE;
405 clock->quitting = PJ_TRUE;
406
407 if (clock->thread) {
408 pj_thread_join(clock->thread);
409 pj_thread_destroy(clock->thread);
410 clock->thread = NULL;
411 }
412
413 if (clock->lock) {
414 pj_lock_destroy(clock->lock);
415 clock->lock = NULL;
416 }
417
418 pj_pool_safe_release(&clock->pool);
419
420 return PJ_SUCCESS;
421 }
422
423
424