1 | /* |
2 | Copyright (c) 2012, Broadcom Europe Ltd |
3 | All rights reserved. |
4 | |
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the following conditions are met: |
7 | * Redistributions of source code must retain the above copyright |
8 | notice, this list of conditions and the following disclaimer. |
9 | * Redistributions in binary form must reproduce the above copyright |
10 | notice, this list of conditions and the following disclaimer in the |
11 | documentation and/or other materials provided with the distribution. |
12 | * Neither the name of the copyright holder nor the |
13 | names of its contributors may be used to endorse or promote products |
14 | derived from this software without specific prior written permission. |
15 | |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | |
28 | #include "interface/vcos/vcos.h" |
29 | #include "interface/mmal/mmal_logging.h" |
30 | #include "interface/mmal/util/mmal_list.h" |
31 | #include "interface/mmal/util/mmal_util_rational.h" |
32 | #include "interface/mmal/core/mmal_clock_private.h" |
33 | |
34 | #ifdef __VIDEOCORE__ |
35 | /* Use RTOS timer for improved accuracy */ |
36 | # include "vcfw/rtos/rtos.h" |
37 | # define USE_RTOS_TIMER |
38 | #endif |
39 | |
40 | |
41 | /*****************************************************************************/ |
42 | #ifdef USE_RTOS_TIMER |
43 | # define MIN_TIMER_DELAY 1 /* microseconds */ |
44 | #else |
45 | # define MIN_TIMER_DELAY 10000 /* microseconds */ |
46 | #endif |
47 | |
48 | /* 1.0 in Q16 format */ |
49 | #define Q16_ONE (1 << 16) |
50 | |
51 | /* Maximum number of pending requests */ |
52 | #define CLOCK_REQUEST_SLOTS 32 |
53 | |
54 | /* Number of microseconds the clock tries to service requests early |
55 | * to account for processing overhead */ |
56 | #define CLOCK_TARGET_OFFSET 20 |
57 | |
58 | /* Default wait time (in microseconds) when the clock is paused. */ |
59 | #define CLOCK_WAIT_TIME 200000LL |
60 | |
61 | /* In order to prevent unnecessary clock jitter when updating the local media-time of the |
62 | * clock, an upper and lower threshold is used. If the difference between the reference |
63 | * media-time and local media-time is greater than the upper threshold, local media-time |
64 | * is set to the reference time. Below this threshold, a weighted moving average is applied |
65 | * to the difference. If this is greater than the lower threshold, the local media-time is |
66 | * adjusted by the average. Anything below the lower threshold is ignored. */ |
67 | #define CLOCK_UPDATE_THRESHOLD_LOWER 8000 /* microseconds */ |
68 | #define CLOCK_UPDATE_THRESHOLD_UPPER 50000 /* microseconds */ |
69 | |
70 | /* Default threshold after which backward jumps in media time are treated as a discontinuity. */ |
71 | #define CLOCK_DISCONT_THRESHOLD 1000000 /* microseconds */ |
72 | |
73 | /* Default duration for which a discontinuity applies. Used for wall time duration for which |
74 | * a discontinuity continues to cause affected requests to fire immediately, and as the media |
75 | * time span for detecting discontinuous requests. */ |
76 | #define CLOCK_DISCONT_DURATION 1000000 /* microseconds */ |
77 | |
78 | /* Absolute value macro */ |
79 | #define ABS_VALUE(v) (((v) < 0) ? -(v) : (v)) |
80 | |
81 | /* Macros used to make clock access thread-safe */ |
82 | #define LOCK(p) vcos_mutex_lock(&(p)->lock); |
83 | #define UNLOCK(p) vcos_mutex_unlock(&(p)->lock); |
84 | |
85 | |
86 | /*****************************************************************************/ |
87 | #ifdef USE_RTOS_TIMER |
88 | typedef RTOS_TIMER_T MMAL_TIMER_T; |
89 | #else |
90 | typedef VCOS_TIMER_T MMAL_TIMER_T; |
91 | #endif |
92 | |
93 | typedef struct MMAL_CLOCK_REQUEST_T |
94 | { |
95 | MMAL_LIST_ELEMENT_T link; /**< must be first */ |
96 | MMAL_CLOCK_VOID_FP priv; /**< client-supplied function pointer */ |
97 | MMAL_CLOCK_REQUEST_CB cb; /**< client-supplied callback to invoke */ |
98 | void *cb_data; /**< client-supplied callback data */ |
99 | int64_t media_time; /**< media-time requested by the client (microseconds) */ |
100 | int64_t media_time_adj; /**< adjusted media-time at which the request will |
101 | be serviced in microseconds (this takes |
102 | CLOCK_TARGET_OFFSET into account) */ |
103 | } MMAL_CLOCK_REQUEST_T; |
104 | |
105 | typedef struct MMAL_CLOCK_PRIVATE_T |
106 | { |
107 | MMAL_CLOCK_T clock; /**< must be first */ |
108 | |
109 | MMAL_BOOL_T is_active; /**< TRUE -> media-time is advancing */ |
110 | |
111 | MMAL_BOOL_T scheduling; /**< TRUE -> client request scheduling is enabled */ |
112 | MMAL_BOOL_T stop_thread; |
113 | VCOS_SEMAPHORE_T event; |
114 | VCOS_THREAD_T thread; /**< processing thread for client requests */ |
115 | MMAL_TIMER_T timer; /**< used for scheduling client requests */ |
116 | |
117 | VCOS_MUTEX_T lock; /**< lock access to the request lists */ |
118 | |
119 | int32_t scale; /**< media-time scale factor (Q16 format) */ |
120 | int32_t scale_inv; /**< 1/scale (Q16 format) */ |
121 | MMAL_RATIONAL_T scale_rational; |
122 | /**< clock scale as a rational number; keep a copy since |
123 | converting from Q16 will result in precision errors */ |
124 | |
125 | int64_t average_ref_diff; /**< media-time moving average adjustment */ |
126 | int64_t media_time; /**< current local media-time in microseconds */ |
127 | uint32_t media_time_frac; /**< media-time fraction in microseconds (Q24 format) */ |
128 | int64_t wall_time; /**< current local wall-time (microseconds) */ |
129 | uint32_t rtc_at_update; /**< real-time clock value at local time update (microseconds) */ |
130 | int64_t media_time_at_timer; |
131 | /**< media-time when the timer was last set */ |
132 | |
133 | int64_t discont_expiry; /**< wall-time when discontinuity expires; 0 = no discontinuity |
134 | in effect */ |
135 | int64_t discont_start; /**< media-time at start of discontinuity |
136 | (n/a if discont_expiry = 0) */ |
137 | int64_t discont_end; /**< media-time at end of discontinuity |
138 | (n/a if discont_expiry = 0) */ |
139 | int64_t discont_threshold;/**< Threshold after which backward jumps in media time are treated |
140 | as a discontinuity (microseconds) */ |
141 | int64_t discont_duration; /**< Duration (wall-time) for which a discontinuity applies */ |
142 | |
143 | int64_t request_threshold;/**< Threshold after which frames exceeding the media-time are |
144 | dropped (microseconds) */ |
145 | MMAL_BOOL_T request_threshold_enable;/**< Enable the request threshold */ |
146 | int64_t update_threshold_lower; |
147 | /**< Time differences below this threshold are ignored */ |
148 | int64_t update_threshold_upper; |
149 | /**< Time differences above this threshold reset media time */ |
150 | |
151 | /* Client requests */ |
152 | struct |
153 | { |
154 | MMAL_LIST_T* list_free; |
155 | MMAL_LIST_T* list_pending; |
156 | MMAL_CLOCK_REQUEST_T pool[CLOCK_REQUEST_SLOTS]; |
157 | } request; |
158 | |
159 | } MMAL_CLOCK_PRIVATE_T; |
160 | |
161 | |
162 | /*****************************************************************************/ |
163 | static void mmal_clock_wake_thread(MMAL_CLOCK_PRIVATE_T *private); |
164 | |
165 | /***************************************************************************** |
166 | * Timer-specific functions |
167 | *****************************************************************************/ |
168 | /* Callback invoked when timer expires */ |
169 | #ifdef USE_RTOS_TIMER |
170 | static void mmal_clock_timer_cb(MMAL_TIMER_T *timer, void *ctx) |
171 | { |
172 | MMAL_PARAM_UNUSED(timer); |
173 | /* Notify the worker thread */ |
174 | mmal_clock_wake_thread((MMAL_CLOCK_PRIVATE_T*)ctx); |
175 | } |
176 | #else |
177 | static void mmal_clock_timer_cb(void *ctx) |
178 | { |
179 | /* Notify the worker thread */ |
180 | mmal_clock_wake_thread((MMAL_CLOCK_PRIVATE_T*)ctx); |
181 | } |
182 | #endif |
183 | |
184 | /* Create a timer */ |
185 | static inline MMAL_BOOL_T mmal_clock_timer_create(MMAL_TIMER_T *timer, void *ctx) |
186 | { |
187 | #ifdef USE_RTOS_TIMER |
188 | return (rtos_timer_init(timer, mmal_clock_timer_cb, ctx) == 0); |
189 | #else |
190 | return (vcos_timer_create(timer, "mmal-clock timer" , mmal_clock_timer_cb, ctx) == VCOS_SUCCESS); |
191 | #endif |
192 | } |
193 | |
194 | /* Destroy a timer */ |
195 | static inline void mmal_clock_timer_destroy(MMAL_TIMER_T *timer) |
196 | { |
197 | #ifdef USE_RTOS_TIMER |
198 | /* Nothing to do */ |
199 | #else |
200 | vcos_timer_delete(timer); |
201 | #endif |
202 | } |
203 | |
204 | /* Set the timer. Delay is in microseconds. */ |
205 | static inline void mmal_clock_timer_set(MMAL_TIMER_T *timer, int64_t delay_us) |
206 | { |
207 | #ifdef USE_RTOS_TIMER |
208 | rtos_timer_set(timer, (RTOS_TIMER_TIME_T)delay_us); |
209 | #else |
210 | /* VCOS timer only provides millisecond accuracy */ |
211 | vcos_timer_set(timer, (VCOS_UNSIGNED)(delay_us / 1000)); |
212 | #endif |
213 | } |
214 | |
215 | /* Stop the timer. */ |
216 | static inline void mmal_clock_timer_cancel(MMAL_TIMER_T *timer) |
217 | { |
218 | #ifdef USE_RTOS_TIMER |
219 | rtos_timer_cancel(timer); |
220 | #else |
221 | vcos_timer_cancel(timer); |
222 | #endif |
223 | } |
224 | |
225 | |
226 | /***************************************************************************** |
227 | * Clock module private functions |
228 | *****************************************************************************/ |
229 | /* Update the internal wall-time and media-time */ |
230 | static void mmal_clock_update_local_time_locked(MMAL_CLOCK_PRIVATE_T *private) |
231 | { |
232 | uint32_t time_now = vcos_getmicrosecs(); |
233 | uint32_t time_diff = (time_now > private->rtc_at_update) ? (time_now - private->rtc_at_update) : 0; |
234 | |
235 | private->wall_time += time_diff; |
236 | |
237 | /* For small clock scale values (i.e. slow motion), the media-time increment |
238 | * could potentially be rounded down when doing lots of updates, so also keep |
239 | * track of the fractional increment. */ |
240 | int64_t media_diff = ((int64_t)time_diff) * (int64_t)(private->scale << 8) + private->media_time_frac; |
241 | |
242 | private->media_time += media_diff >> 24; |
243 | private->media_time_frac = media_diff & ((1<<24)-1); |
244 | |
245 | private->rtc_at_update = time_now; |
246 | } |
247 | |
248 | /* Return the current local media-time */ |
249 | static int64_t mmal_clock_media_time_get_locked(MMAL_CLOCK_PRIVATE_T *private) |
250 | { |
251 | mmal_clock_update_local_time_locked(private); |
252 | return private->media_time; |
253 | } |
254 | |
255 | /* Comparison function used for inserting a request into |
256 | * the list of pending requests when clock scale is positive. */ |
257 | static int mmal_clock_request_compare_pos(MMAL_LIST_ELEMENT_T *lhs, MMAL_LIST_ELEMENT_T *rhs) |
258 | { |
259 | return ((MMAL_CLOCK_REQUEST_T*)lhs)->media_time_adj < ((MMAL_CLOCK_REQUEST_T*)rhs)->media_time_adj; |
260 | } |
261 | |
262 | /* Comparison function used for inserting a request into |
263 | * the list of pending requests when clock scale is negative. */ |
264 | static int mmal_clock_request_compare_neg(MMAL_LIST_ELEMENT_T *lhs, MMAL_LIST_ELEMENT_T *rhs) |
265 | { |
266 | return ((MMAL_CLOCK_REQUEST_T*)lhs)->media_time_adj > ((MMAL_CLOCK_REQUEST_T*)rhs)->media_time_adj; |
267 | } |
268 | |
269 | /* Insert a new request into the list of pending requests */ |
270 | static MMAL_BOOL_T mmal_clock_request_insert(MMAL_CLOCK_PRIVATE_T *private, MMAL_CLOCK_REQUEST_T *request) |
271 | { |
272 | MMAL_LIST_T *list = private->request.list_pending; |
273 | MMAL_CLOCK_REQUEST_T *pending; |
274 | |
275 | if (private->stop_thread) |
276 | return MMAL_FALSE; /* the clock is being destroyed */ |
277 | |
278 | if (list->length == 0) |
279 | { |
280 | mmal_list_push_front(list, &request->link); |
281 | return MMAL_TRUE; |
282 | } |
283 | |
284 | /* It is more likely for requests to be received in sequence, |
285 | * so try adding to the back of the list first before doing |
286 | * a more expensive list insert. */ |
287 | pending = (MMAL_CLOCK_REQUEST_T*)list->last; |
288 | if ((private->scale >= 0 && (request->media_time_adj >= pending->media_time_adj)) || |
289 | (private->scale < 0 && (request->media_time_adj <= pending->media_time_adj))) |
290 | { |
291 | mmal_list_push_back(list, &request->link); |
292 | } |
293 | else |
294 | { |
295 | mmal_list_insert(list, &request->link, |
296 | (private->scale >= 0) ? mmal_clock_request_compare_pos : mmal_clock_request_compare_neg); |
297 | } |
298 | return MMAL_TRUE; |
299 | } |
300 | |
301 | /* Flush all pending requests */ |
302 | static MMAL_STATUS_T mmal_clock_request_flush_locked(MMAL_CLOCK_PRIVATE_T *private, |
303 | int64_t media_time) |
304 | { |
305 | MMAL_LIST_T *pending = private->request.list_pending; |
306 | MMAL_LIST_T *list_free = private->request.list_free; |
307 | MMAL_CLOCK_REQUEST_T *request; |
308 | |
309 | while ((request = (MMAL_CLOCK_REQUEST_T *)mmal_list_pop_front(pending)) != NULL) |
310 | { |
311 | /* Inform the client */ |
312 | request->cb(&private->clock, media_time, request->cb_data, request->priv); |
313 | /* Recycle request slot */ |
314 | mmal_list_push_back(list_free, &request->link); |
315 | } |
316 | |
317 | private->media_time_at_timer = 0; |
318 | |
319 | return MMAL_SUCCESS; |
320 | } |
321 | |
322 | /* Process all pending requests */ |
323 | static void mmal_clock_process_requests(MMAL_CLOCK_PRIVATE_T *private) |
324 | { |
325 | int64_t media_time_now; |
326 | MMAL_LIST_T* free = private->request.list_free; |
327 | MMAL_LIST_T* pending = private->request.list_pending; |
328 | MMAL_CLOCK_REQUEST_T *next; |
329 | |
330 | if (pending->length == 0 || !private->is_active) |
331 | return; |
332 | |
333 | LOCK(private); |
334 | |
335 | /* Detect discontinuity */ |
336 | if (private->media_time_at_timer != 0) |
337 | { |
338 | media_time_now = mmal_clock_media_time_get_locked(private); |
339 | /* Currently only applied to forward speeds */ |
340 | if (private->scale > 0 && |
341 | media_time_now + private->discont_threshold < private->media_time_at_timer) |
342 | { |
343 | LOG_INFO("discontinuity: was=%" PRIi64 " now=%" PRIi64 " pending=%d" , |
344 | private->media_time_at_timer, media_time_now, pending->length); |
345 | |
346 | /* It's likely that packets from before the discontinuity will continue to arrive for |
347 | * a short time. Ensure these are detected and the requests fired immediately. */ |
348 | private->discont_start = private->media_time_at_timer; |
349 | private->discont_end = private->discont_start + private->discont_duration; |
350 | private->discont_expiry = private->wall_time + private->discont_duration; |
351 | |
352 | /* Fire all pending requests */ |
353 | mmal_clock_request_flush_locked(private, media_time_now); |
354 | } |
355 | } |
356 | |
357 | /* Earliest request is always at the front */ |
358 | next = (MMAL_CLOCK_REQUEST_T*)mmal_list_pop_front(pending); |
359 | while (next) |
360 | { |
361 | media_time_now = mmal_clock_media_time_get_locked(private); |
362 | |
363 | if (private->discont_expiry != 0 && private->wall_time > private->discont_expiry) |
364 | private->discont_expiry = 0; |
365 | |
366 | /* Fire the request if it matches the pending discontinuity or if its requested media time |
367 | * has been reached. */ |
368 | if ((private->discont_expiry != 0 && |
369 | next->media_time_adj >= private->discont_start && |
370 | next->media_time_adj < private->discont_end) || |
371 | (private->scale > 0 && ((media_time_now + MIN_TIMER_DELAY) >= next->media_time_adj)) || |
372 | (private->scale < 0 && ((media_time_now - MIN_TIMER_DELAY) <= next->media_time_adj))) |
373 | { |
374 | LOG_TRACE("servicing request: next %" PRIi64" now %" PRIi64, next->media_time_adj, media_time_now); |
375 | /* Inform the client */ |
376 | next->cb(&private->clock, media_time_now, next->cb_data, next->priv); |
377 | /* Recycle the request slot */ |
378 | mmal_list_push_back(free, &next->link); |
379 | /* Move onto next pending request */ |
380 | next = (MMAL_CLOCK_REQUEST_T*)mmal_list_pop_front(pending); |
381 | } |
382 | else |
383 | { |
384 | /* The next request is in the future, so re-schedule the |
385 | * timer based on the current clock scale and media-time diff */ |
386 | int64_t media_time_delay = ABS_VALUE(media_time_now - next->media_time_adj); |
387 | int64_t wall_time_delay = ABS_VALUE(((int64_t)private->scale_inv * media_time_delay) >> 16); |
388 | |
389 | if (private->scale == 0) |
390 | wall_time_delay = CLOCK_WAIT_TIME; /* Clock is paused */ |
391 | |
392 | /* Put next request back into pending list */ |
393 | mmal_list_push_front(pending, &next->link); |
394 | next = NULL; |
395 | |
396 | /* Set the timer */ |
397 | private->media_time_at_timer = media_time_now; |
398 | mmal_clock_timer_set(&private->timer, wall_time_delay); |
399 | |
400 | LOG_TRACE("re-schedule timer: now %" PRIi64" delay %" PRIi64, media_time_now, wall_time_delay); |
401 | } |
402 | } |
403 | |
404 | UNLOCK(private); |
405 | } |
406 | |
407 | /* Trigger the worker thread (if present) */ |
408 | static void mmal_clock_wake_thread(MMAL_CLOCK_PRIVATE_T *private) |
409 | { |
410 | if (private->scheduling) |
411 | vcos_semaphore_post(&private->event); |
412 | } |
413 | |
414 | /* Stop the worker thread */ |
415 | static void mmal_clock_stop_thread(MMAL_CLOCK_PRIVATE_T *private) |
416 | { |
417 | private->stop_thread = MMAL_TRUE; |
418 | mmal_clock_wake_thread(private); |
419 | vcos_thread_join(&private->thread, NULL); |
420 | } |
421 | |
422 | /* Main processing thread */ |
423 | static void* mmal_clock_worker_thread(void *ctx) |
424 | { |
425 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)ctx; |
426 | |
427 | while (1) |
428 | { |
429 | vcos_semaphore_wait(&private->event); |
430 | |
431 | /* Either the timer has expired or a new request is pending */ |
432 | mmal_clock_timer_cancel(&private->timer); |
433 | |
434 | if (private->stop_thread) |
435 | break; |
436 | |
437 | mmal_clock_process_requests(private); |
438 | } |
439 | return NULL; |
440 | } |
441 | |
442 | /* Create scheduling resources */ |
443 | static MMAL_STATUS_T mmal_clock_create_scheduling(MMAL_CLOCK_PRIVATE_T *private) |
444 | { |
445 | unsigned int i; |
446 | MMAL_BOOL_T timer_status = MMAL_FALSE; |
447 | VCOS_STATUS_T event_status = VCOS_EINVAL; |
448 | VCOS_UNSIGNED priority; |
449 | |
450 | timer_status = mmal_clock_timer_create(&private->timer, private); |
451 | if (!timer_status) |
452 | { |
453 | LOG_ERROR("failed to create timer %p" , private); |
454 | goto error; |
455 | } |
456 | |
457 | event_status = vcos_semaphore_create(&private->event, "mmal-clock sema" , 0); |
458 | if (event_status != VCOS_SUCCESS) |
459 | { |
460 | LOG_ERROR("failed to create event semaphore %d" , event_status); |
461 | goto error; |
462 | } |
463 | |
464 | private->request.list_free = mmal_list_create(); |
465 | private->request.list_pending = mmal_list_create(); |
466 | if (!private->request.list_free || !private->request.list_pending) |
467 | { |
468 | LOG_ERROR("failed to create list %p %p" , private->request.list_free, private->request.list_pending); |
469 | goto error; |
470 | } |
471 | |
472 | /* Populate the list of available request slots */ |
473 | for (i = 0; i < CLOCK_REQUEST_SLOTS; ++i) |
474 | mmal_list_push_back(private->request.list_free, &private->request.pool[i].link); |
475 | |
476 | if (vcos_thread_create(&private->thread, "mmal-clock thread" , NULL, |
477 | mmal_clock_worker_thread, private) != VCOS_SUCCESS) |
478 | { |
479 | LOG_ERROR("failed to create worker thread" ); |
480 | goto error; |
481 | } |
482 | priority = vcos_thread_get_priority(&private->thread); |
483 | vcos_thread_set_priority(&private->thread, 1 | (priority & VCOS_AFFINITY_MASK)); |
484 | |
485 | private->scheduling = MMAL_TRUE; |
486 | |
487 | return MMAL_SUCCESS; |
488 | |
489 | error: |
490 | if (event_status == VCOS_SUCCESS) vcos_semaphore_delete(&private->event); |
491 | if (timer_status) mmal_clock_timer_destroy(&private->timer); |
492 | if (private->request.list_free) mmal_list_destroy(private->request.list_free); |
493 | if (private->request.list_pending) mmal_list_destroy(private->request.list_pending); |
494 | return MMAL_ENOSPC; |
495 | } |
496 | |
497 | /* Destroy all scheduling resources */ |
498 | static void mmal_clock_destroy_scheduling(MMAL_CLOCK_PRIVATE_T *private) |
499 | { |
500 | mmal_clock_stop_thread(private); |
501 | |
502 | mmal_clock_request_flush(&private->clock); |
503 | |
504 | mmal_list_destroy(private->request.list_free); |
505 | mmal_list_destroy(private->request.list_pending); |
506 | |
507 | vcos_semaphore_delete(&private->event); |
508 | |
509 | mmal_clock_timer_destroy(&private->timer); |
510 | } |
511 | |
512 | /* Start the media-time */ |
513 | static void mmal_clock_start(MMAL_CLOCK_T *clock) |
514 | { |
515 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
516 | |
517 | private->is_active = MMAL_TRUE; |
518 | |
519 | mmal_clock_wake_thread(private); |
520 | } |
521 | |
522 | /* Stop the media-time */ |
523 | static void mmal_clock_stop(MMAL_CLOCK_T *clock) |
524 | { |
525 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
526 | |
527 | private->is_active = MMAL_FALSE; |
528 | |
529 | mmal_clock_wake_thread(private); |
530 | } |
531 | |
532 | static int mmal_clock_is_paused(MMAL_CLOCK_T *clock) |
533 | { |
534 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
535 | return private->scale == 0; |
536 | } |
537 | |
538 | /***************************************************************************** |
539 | * Clock module public functions |
540 | *****************************************************************************/ |
541 | /* Create new clock instance */ |
542 | MMAL_STATUS_T mmal_clock_create(MMAL_CLOCK_T **clock) |
543 | { |
544 | unsigned int size = sizeof(MMAL_CLOCK_PRIVATE_T); |
545 | MMAL_RATIONAL_T scale = { 1, 1 }; |
546 | MMAL_CLOCK_PRIVATE_T *private; |
547 | |
548 | /* Sanity checking */ |
549 | if (clock == NULL) |
550 | return MMAL_EINVAL; |
551 | |
552 | private = vcos_calloc(1, size, "mmal-clock" ); |
553 | if (!private) |
554 | { |
555 | LOG_ERROR("failed to allocate memory" ); |
556 | return MMAL_ENOMEM; |
557 | } |
558 | |
559 | if (vcos_mutex_create(&private->lock, "mmal-clock lock" ) != VCOS_SUCCESS) |
560 | { |
561 | LOG_ERROR("failed to create lock mutex" ); |
562 | vcos_free(private); |
563 | return MMAL_ENOSPC; |
564 | } |
565 | |
566 | /* Set the default threshold values */ |
567 | private->update_threshold_lower = CLOCK_UPDATE_THRESHOLD_LOWER; |
568 | private->update_threshold_upper = CLOCK_UPDATE_THRESHOLD_UPPER; |
569 | private->discont_threshold = CLOCK_DISCONT_THRESHOLD; |
570 | private->discont_duration = CLOCK_DISCONT_DURATION; |
571 | private->request_threshold = 0; |
572 | private->request_threshold_enable = MMAL_FALSE; |
573 | |
574 | /* Default scale = 1.0, i.e. normal playback speed */ |
575 | mmal_clock_scale_set(&private->clock, scale); |
576 | |
577 | *clock = &private->clock; |
578 | return MMAL_SUCCESS; |
579 | } |
580 | |
581 | /* Destroy a clock instance */ |
582 | MMAL_STATUS_T mmal_clock_destroy(MMAL_CLOCK_T *clock) |
583 | { |
584 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
585 | |
586 | if (private->scheduling) |
587 | mmal_clock_destroy_scheduling(private); |
588 | |
589 | vcos_mutex_delete(&private->lock); |
590 | |
591 | vcos_free(private); |
592 | |
593 | return MMAL_SUCCESS; |
594 | } |
595 | |
596 | /* Add new client request to list of pending requests */ |
597 | MMAL_STATUS_T mmal_clock_request_add(MMAL_CLOCK_T *clock, int64_t media_time, |
598 | MMAL_CLOCK_REQUEST_CB cb, void *cb_data, MMAL_CLOCK_VOID_FP priv) |
599 | { |
600 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
601 | MMAL_CLOCK_REQUEST_T *request; |
602 | MMAL_BOOL_T wake_thread = MMAL_FALSE; |
603 | int64_t media_time_now; |
604 | |
605 | LOG_TRACE("media time %" PRIi64, media_time); |
606 | |
607 | LOCK(private); |
608 | |
609 | media_time_now = mmal_clock_media_time_get_locked(private); |
610 | |
611 | /* Drop the request if request_threshold_enable and the frame exceeds the request threshold */ |
612 | if (private->request_threshold_enable && (media_time > (media_time_now + private->request_threshold))) |
613 | { |
614 | LOG_TRACE("dropping request: media time %" PRIi64" now %" PRIi64, media_time, media_time_now); |
615 | UNLOCK(private); |
616 | return MMAL_ECORRUPT; |
617 | } |
618 | |
619 | /* The clock module is usually only used for time-keeping, so all the |
620 | * objects needed to process client requests are not allocated by default |
621 | * and need to be created on the first client request received */ |
622 | if (!private->scheduling) |
623 | { |
624 | if (mmal_clock_create_scheduling(private) != MMAL_SUCCESS) |
625 | { |
626 | LOG_ERROR("failed to create scheduling objects" ); |
627 | UNLOCK(private); |
628 | return MMAL_ENOSPC; |
629 | } |
630 | } |
631 | |
632 | request = (MMAL_CLOCK_REQUEST_T*)mmal_list_pop_front(private->request.list_free); |
633 | if (request == NULL) |
634 | { |
635 | LOG_ERROR("no more free clock request slots" ); |
636 | UNLOCK(private); |
637 | return MMAL_ENOSPC; |
638 | } |
639 | |
640 | request->cb = cb; |
641 | request->cb_data = cb_data; |
642 | request->priv = priv; |
643 | request->media_time = media_time; |
644 | request->media_time_adj = media_time - (int64_t)(private->scale * CLOCK_TARGET_OFFSET >> 16); |
645 | |
646 | if (mmal_clock_request_insert(private, request)) |
647 | wake_thread = private->is_active; |
648 | |
649 | UNLOCK(private); |
650 | |
651 | /* Notify the worker thread */ |
652 | if (wake_thread) |
653 | mmal_clock_wake_thread(private); |
654 | |
655 | return MMAL_SUCCESS; |
656 | } |
657 | |
658 | /* Flush all pending requests */ |
659 | MMAL_STATUS_T mmal_clock_request_flush(MMAL_CLOCK_T *clock) |
660 | { |
661 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
662 | |
663 | LOCK(private); |
664 | if (private->scheduling) |
665 | mmal_clock_request_flush_locked(private, MMAL_TIME_UNKNOWN); |
666 | UNLOCK(private); |
667 | |
668 | return MMAL_SUCCESS; |
669 | } |
670 | |
671 | /* Update the local media-time with the given reference */ |
672 | MMAL_STATUS_T mmal_clock_media_time_set(MMAL_CLOCK_T *clock, int64_t media_time) |
673 | { |
674 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
675 | MMAL_BOOL_T wake_thread = MMAL_TRUE; |
676 | int64_t time_diff; |
677 | |
678 | LOCK(private); |
679 | |
680 | if (!private->is_active) |
681 | { |
682 | uint32_t time_now = vcos_getmicrosecs(); |
683 | private->wall_time = time_now; |
684 | private->media_time = media_time; |
685 | private->media_time_frac = 0; |
686 | private->rtc_at_update = time_now; |
687 | |
688 | UNLOCK(private); |
689 | return MMAL_SUCCESS; |
690 | } |
691 | |
692 | if (mmal_clock_is_paused(clock)) |
693 | { |
694 | LOG_TRACE("clock is paused; ignoring update" ); |
695 | UNLOCK(private); |
696 | return MMAL_SUCCESS; |
697 | } |
698 | |
699 | /* Reset the local media-time with the given time reference */ |
700 | mmal_clock_update_local_time_locked(private); |
701 | |
702 | time_diff = private->media_time - media_time; |
703 | if (time_diff > private->update_threshold_upper || |
704 | time_diff < -private->update_threshold_upper) |
705 | { |
706 | LOG_TRACE("cur:%" PRIi64" new:%" PRIi64" diff:%" PRIi64, private->media_time, media_time, time_diff); |
707 | private->media_time = media_time; |
708 | private->average_ref_diff = 0; |
709 | } |
710 | else |
711 | { |
712 | private->average_ref_diff = ((private->average_ref_diff << 6) - private->average_ref_diff + time_diff) >> 6; |
713 | if(private->average_ref_diff > private->update_threshold_lower || |
714 | private->average_ref_diff < -private->update_threshold_lower) |
715 | { |
716 | LOG_TRACE("cur:%" PRIi64" new:%" PRIi64" ave:%" PRIi64, private->media_time, |
717 | private->media_time - private->average_ref_diff, private->average_ref_diff); |
718 | private->media_time -= private->average_ref_diff; |
719 | private->average_ref_diff = 0; |
720 | } |
721 | else |
722 | { |
723 | /* Don't update the media-time */ |
724 | wake_thread = MMAL_FALSE; |
725 | LOG_TRACE("cur:%" PRIi64" new:%" PRIi64" diff:%" PRIi64" ave:%" PRIi64" ignored" , private->media_time, |
726 | media_time, private->media_time - media_time, private->average_ref_diff); |
727 | } |
728 | } |
729 | |
730 | UNLOCK(private); |
731 | |
732 | if (wake_thread) |
733 | mmal_clock_wake_thread(private); |
734 | |
735 | return MMAL_SUCCESS; |
736 | } |
737 | |
738 | /* Change the clock scale */ |
739 | MMAL_STATUS_T mmal_clock_scale_set(MMAL_CLOCK_T *clock, MMAL_RATIONAL_T scale) |
740 | { |
741 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
742 | |
743 | LOG_TRACE("new scale %d/%d" , scale.num, scale.den); |
744 | |
745 | LOCK(private); |
746 | |
747 | mmal_clock_update_local_time_locked(private); |
748 | |
749 | private->scale_rational = scale; |
750 | private->scale = mmal_rational_to_fixed_16_16(scale); |
751 | |
752 | if (private->scale) |
753 | private->scale_inv = (int32_t)((1LL << 32) / (int64_t)private->scale); |
754 | else |
755 | private->scale_inv = Q16_ONE; /* clock is paused */ |
756 | |
757 | UNLOCK(private); |
758 | |
759 | mmal_clock_wake_thread(private); |
760 | |
761 | return MMAL_SUCCESS; |
762 | } |
763 | |
764 | /* Set the clock state */ |
765 | MMAL_STATUS_T mmal_clock_active_set(MMAL_CLOCK_T *clock, MMAL_BOOL_T active) |
766 | { |
767 | if (active) |
768 | mmal_clock_start(clock); |
769 | else |
770 | mmal_clock_stop(clock); |
771 | |
772 | return MMAL_SUCCESS; |
773 | } |
774 | |
775 | /* Get the clock's scale */ |
776 | MMAL_RATIONAL_T mmal_clock_scale_get(MMAL_CLOCK_T *clock) |
777 | { |
778 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
779 | MMAL_RATIONAL_T scale; |
780 | |
781 | LOCK(private); |
782 | scale = private->scale_rational; |
783 | UNLOCK(private); |
784 | |
785 | return scale; |
786 | } |
787 | |
788 | /* Return the current local media-time */ |
789 | int64_t mmal_clock_media_time_get(MMAL_CLOCK_T *clock) |
790 | { |
791 | int64_t media_time; |
792 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T*)clock; |
793 | |
794 | LOCK(private); |
795 | media_time = mmal_clock_media_time_get_locked(private); |
796 | UNLOCK(private); |
797 | |
798 | return media_time; |
799 | } |
800 | |
801 | /* Get the clock's state */ |
802 | MMAL_BOOL_T mmal_clock_is_active(MMAL_CLOCK_T *clock) |
803 | { |
804 | return ((MMAL_CLOCK_PRIVATE_T*)clock)->is_active; |
805 | } |
806 | |
807 | /* Get the clock's media-time update threshold values */ |
808 | MMAL_STATUS_T mmal_clock_update_threshold_get(MMAL_CLOCK_T *clock, MMAL_CLOCK_UPDATE_THRESHOLD_T *update_threshold) |
809 | { |
810 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T *)clock; |
811 | |
812 | LOCK(private); |
813 | update_threshold->threshold_lower = private->update_threshold_lower; |
814 | update_threshold->threshold_upper = private->update_threshold_upper; |
815 | UNLOCK(private); |
816 | |
817 | return MMAL_SUCCESS; |
818 | } |
819 | |
820 | /* Set the clock's media-time update threshold values */ |
821 | MMAL_STATUS_T mmal_clock_update_threshold_set(MMAL_CLOCK_T *clock, const MMAL_CLOCK_UPDATE_THRESHOLD_T *update_threshold) |
822 | { |
823 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T *)clock; |
824 | |
825 | LOG_TRACE("new clock update thresholds: upper %" PRIi64", lower %" PRIi64, |
826 | update_threshold->threshold_lower, update_threshold->threshold_upper); |
827 | |
828 | LOCK(private); |
829 | private->update_threshold_lower = update_threshold->threshold_lower; |
830 | private->update_threshold_upper = update_threshold->threshold_upper; |
831 | UNLOCK(private); |
832 | |
833 | return MMAL_SUCCESS; |
834 | } |
835 | |
836 | /* Get the clock's discontinuity threshold values */ |
837 | MMAL_STATUS_T mmal_clock_discont_threshold_get(MMAL_CLOCK_T *clock, MMAL_CLOCK_DISCONT_THRESHOLD_T *discont) |
838 | { |
839 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T *)clock; |
840 | |
841 | LOCK(private); |
842 | discont->threshold = private->discont_threshold; |
843 | discont->duration = private->discont_duration; |
844 | UNLOCK(private); |
845 | |
846 | return MMAL_SUCCESS; |
847 | } |
848 | |
849 | /* Set the clock's discontinuity threshold values */ |
850 | MMAL_STATUS_T mmal_clock_discont_threshold_set(MMAL_CLOCK_T *clock, const MMAL_CLOCK_DISCONT_THRESHOLD_T *discont) |
851 | { |
852 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T *)clock; |
853 | |
854 | LOG_TRACE("new clock discontinuity values: threshold %" PRIi64", duration %" PRIi64, |
855 | discont->threshold, discont->duration); |
856 | |
857 | LOCK(private); |
858 | private->discont_threshold = discont->threshold; |
859 | private->discont_duration = discont->duration; |
860 | UNLOCK(private); |
861 | |
862 | return MMAL_SUCCESS; |
863 | } |
864 | |
865 | /* Get the clock's request threshold values */ |
866 | MMAL_STATUS_T mmal_clock_request_threshold_get(MMAL_CLOCK_T *clock, MMAL_CLOCK_REQUEST_THRESHOLD_T *req) |
867 | { |
868 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T *)clock; |
869 | |
870 | LOCK(private); |
871 | req->threshold = private->request_threshold; |
872 | req->threshold_enable = private->request_threshold_enable; |
873 | UNLOCK(private); |
874 | |
875 | return MMAL_SUCCESS; |
876 | } |
877 | |
878 | /* Set the clock's request threshold values */ |
879 | MMAL_STATUS_T mmal_clock_request_threshold_set(MMAL_CLOCK_T *clock, const MMAL_CLOCK_REQUEST_THRESHOLD_T *req) |
880 | { |
881 | MMAL_CLOCK_PRIVATE_T *private = (MMAL_CLOCK_PRIVATE_T *)clock; |
882 | |
883 | LOG_TRACE("new clock request values: threshold %" PRIi64, |
884 | req->threshold); |
885 | |
886 | LOCK(private); |
887 | private->request_threshold = req->threshold; |
888 | private->request_threshold_enable = req->threshold_enable; |
889 | UNLOCK(private); |
890 | |
891 | return MMAL_SUCCESS; |
892 | } |
893 | |