1 /*****************************************************************************
2 * thread.c : Win32 back-end for LibVLC
3 *****************************************************************************
4 * Copyright (C) 1999-2009 VLC authors and VideoLAN
6 * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
7 * Samuel Hocevar <sam@zoy.org>
8 * Gildas Bazin <gbazin@netcourrier.com>
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU Lesser General Public License as published by
15 * the Free Software Foundation; either version 2.1 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public License
24 * along with this program; if not, write to the Free Software Foundation,
25 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
32 #include <vlc_common.h>
40 static vlc_threadvar_t thread_key;
52 vlc_cleanup_t *cleaners;
54 void *(*entry) (void *);
58 static CRITICAL_SECTION clock_lock;
59 static vlc_mutex_t super_mutex;
60 static vlc_cond_t super_variable;
61 extern vlc_rwlock_t config_lock, msg_lock;
63 BOOL WINAPI DllMain (HINSTANCE, DWORD, LPVOID);
65 BOOL WINAPI DllMain (HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved)
72 case DLL_PROCESS_ATTACH:
73 InitializeCriticalSection (&clock_lock);
74 vlc_mutex_init (&super_mutex);
75 vlc_cond_init (&super_variable);
76 vlc_threadvar_create (&thread_key, NULL);
77 vlc_rwlock_init (&config_lock);
78 vlc_rwlock_init (&msg_lock);
82 case DLL_PROCESS_DETACH:
83 vlc_rwlock_destroy (&msg_lock);
84 vlc_rwlock_destroy (&config_lock);
85 vlc_threadvar_delete (&thread_key);
86 vlc_cond_destroy (&super_variable);
87 vlc_mutex_destroy (&super_mutex);
88 DeleteCriticalSection (&clock_lock);
94 static void CALLBACK vlc_cancel_self (ULONG_PTR);
96 static DWORD vlc_WaitForMultipleObjects (DWORD count, const HANDLE *handles,
102 ret = SleepEx (delay, TRUE);
107 ret = WaitForMultipleObjectsEx (count, handles, FALSE, delay, TRUE);
109 /* We do not abandon objects... this would be a bug */
110 assert (ret < WAIT_ABANDONED_0 || WAIT_ABANDONED_0 + count - 1 < ret);
112 if (unlikely(ret == WAIT_FAILED))
113 abort (); /* We are screwed! */
117 static DWORD vlc_WaitForSingleObject (HANDLE handle, DWORD delay)
119 return vlc_WaitForMultipleObjects (1, &handle, delay);
122 static DWORD vlc_Sleep (DWORD delay)
124 DWORD ret = vlc_WaitForMultipleObjects (0, NULL, delay);
125 return (ret != WAIT_TIMEOUT) ? ret : 0;
130 void vlc_mutex_init( vlc_mutex_t *p_mutex )
132 /* This creates a recursive mutex. This is OK as fast mutexes have
133 * no defined behavior in case of recursive locking. */
134 InitializeCriticalSection (&p_mutex->mutex);
135 p_mutex->dynamic = true;
138 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
140 InitializeCriticalSection( &p_mutex->mutex );
141 p_mutex->dynamic = true;
145 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
147 assert (p_mutex->dynamic);
148 DeleteCriticalSection (&p_mutex->mutex);
151 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
153 if (!p_mutex->dynamic)
154 { /* static mutexes */
155 int canc = vlc_savecancel ();
156 assert (p_mutex != &super_mutex); /* this one cannot be static */
158 vlc_mutex_lock (&super_mutex);
159 while (p_mutex->locked)
161 p_mutex->contention++;
162 vlc_cond_wait (&super_variable, &super_mutex);
163 p_mutex->contention--;
165 p_mutex->locked = true;
166 vlc_mutex_unlock (&super_mutex);
167 vlc_restorecancel (canc);
171 EnterCriticalSection (&p_mutex->mutex);
174 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
176 if (!p_mutex->dynamic)
177 { /* static mutexes */
180 assert (p_mutex != &super_mutex); /* this one cannot be static */
181 vlc_mutex_lock (&super_mutex);
182 if (!p_mutex->locked)
184 p_mutex->locked = true;
187 vlc_mutex_unlock (&super_mutex);
191 return TryEnterCriticalSection (&p_mutex->mutex) ? 0 : EBUSY;
194 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
196 if (!p_mutex->dynamic)
197 { /* static mutexes */
198 assert (p_mutex != &super_mutex); /* this one cannot be static */
200 vlc_mutex_lock (&super_mutex);
201 assert (p_mutex->locked);
202 p_mutex->locked = false;
203 if (p_mutex->contention)
204 vlc_cond_broadcast (&super_variable);
205 vlc_mutex_unlock (&super_mutex);
209 LeaveCriticalSection (&p_mutex->mutex);
212 /*** Condition variables ***/
215 CLOCK_STATIC=0, /* must be zero for VLC_STATIC_COND */
220 static void vlc_cond_init_common (vlc_cond_t *p_condvar, unsigned clock)
222 /* Create a manual-reset event (manual reset is needed for broadcast). */
223 p_condvar->handle = CreateEvent (NULL, TRUE, FALSE, NULL);
224 if (!p_condvar->handle)
226 p_condvar->clock = clock;
229 void vlc_cond_init (vlc_cond_t *p_condvar)
231 vlc_cond_init_common (p_condvar, CLOCK_MONOTONIC);
234 void vlc_cond_init_daytime (vlc_cond_t *p_condvar)
236 vlc_cond_init_common (p_condvar, CLOCK_REALTIME);
239 void vlc_cond_destroy (vlc_cond_t *p_condvar)
241 CloseHandle (p_condvar->handle);
244 void vlc_cond_signal (vlc_cond_t *p_condvar)
246 if (!p_condvar->clock)
249 /* This is suboptimal but works. */
250 vlc_cond_broadcast (p_condvar);
253 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
255 if (!p_condvar->clock)
258 /* Wake all threads up (as the event HANDLE has manual reset) */
259 SetEvent (p_condvar->handle);
262 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
266 if (!p_condvar->clock)
267 { /* FIXME FIXME FIXME */
275 vlc_mutex_unlock (p_mutex);
276 result = vlc_WaitForSingleObject (p_condvar->handle, INFINITE);
277 vlc_mutex_lock (p_mutex);
279 while (result == WAIT_IO_COMPLETION);
281 ResetEvent (p_condvar->handle);
284 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
294 switch (p_condvar->clock)
296 case CLOCK_MONOTONIC:
299 case CLOCK_REALTIME: /* FIXME? sub-second precision */
300 total = CLOCK_FREQ * time (NULL);
303 assert (!p_condvar->clock);
304 /* FIXME FIXME FIXME */
308 total = (deadline - total) / 1000;
312 DWORD delay = (total > 0x7fffffff) ? 0x7fffffff : total;
313 vlc_mutex_unlock (p_mutex);
314 result = vlc_WaitForSingleObject (p_condvar->handle, delay);
315 vlc_mutex_lock (p_mutex);
317 while (result == WAIT_IO_COMPLETION);
319 ResetEvent (p_condvar->handle);
321 return (result == WAIT_OBJECT_0) ? 0 : ETIMEDOUT;
325 void vlc_sem_init (vlc_sem_t *sem, unsigned value)
327 *sem = CreateSemaphore (NULL, value, 0x7fffffff, NULL);
332 void vlc_sem_destroy (vlc_sem_t *sem)
337 int vlc_sem_post (vlc_sem_t *sem)
339 ReleaseSemaphore (*sem, 1, NULL);
340 return 0; /* FIXME */
343 void vlc_sem_wait (vlc_sem_t *sem)
350 result = vlc_WaitForSingleObject (*sem, INFINITE);
352 while (result == WAIT_IO_COMPLETION);
355 /*** Read/write locks */
356 /* SRW (Slim Read Write) locks are available in Vista+ only */
357 void vlc_rwlock_init (vlc_rwlock_t *lock)
359 vlc_mutex_init (&lock->mutex);
360 vlc_cond_init (&lock->wait);
361 lock->readers = 0; /* active readers */
362 lock->writer = 0; /* ID of active writer */
365 void vlc_rwlock_destroy (vlc_rwlock_t *lock)
367 vlc_cond_destroy (&lock->wait);
368 vlc_mutex_destroy (&lock->mutex);
371 void vlc_rwlock_rdlock (vlc_rwlock_t *lock)
373 vlc_mutex_lock (&lock->mutex);
374 /* Recursive read-locking is allowed. We only need to ensure that there is
375 * no active writer. */
376 while (lock->writer != 0)
378 assert (lock->readers == 0);
379 vlc_cond_wait (&lock->wait, &lock->mutex);
381 if (unlikely(lock->readers == ULONG_MAX))
384 vlc_mutex_unlock (&lock->mutex);
387 static void vlc_rwlock_rdunlock (vlc_rwlock_t *lock)
389 vlc_mutex_lock (&lock->mutex);
390 assert (lock->readers > 0);
392 /* If there are no readers left, wake up a writer. */
393 if (--lock->readers == 0)
394 vlc_cond_signal (&lock->wait);
395 vlc_mutex_unlock (&lock->mutex);
398 void vlc_rwlock_wrlock (vlc_rwlock_t *lock)
400 vlc_mutex_lock (&lock->mutex);
401 /* Wait until nobody owns the lock in either way. */
402 while ((lock->readers > 0) || (lock->writer != 0))
403 vlc_cond_wait (&lock->wait, &lock->mutex);
404 assert (lock->writer == 0);
405 lock->writer = GetCurrentThreadId ();
406 vlc_mutex_unlock (&lock->mutex);
409 static void vlc_rwlock_wrunlock (vlc_rwlock_t *lock)
411 vlc_mutex_lock (&lock->mutex);
412 assert (lock->writer == GetCurrentThreadId ());
413 assert (lock->readers == 0);
414 lock->writer = 0; /* Write unlock */
416 /* Let reader and writer compete. Scheduler decides who wins. */
417 vlc_cond_broadcast (&lock->wait);
418 vlc_mutex_unlock (&lock->mutex);
421 void vlc_rwlock_unlock (vlc_rwlock_t *lock)
423 /* Note: If the lock is held for reading, lock->writer is nul.
424 * If the lock is held for writing, only this thread can store a value to
425 * lock->writer. Either way, lock->writer is safe to fetch here. */
426 if (lock->writer != 0)
427 vlc_rwlock_wrunlock (lock);
429 vlc_rwlock_rdunlock (lock);
432 /*** Thread-specific variables (TLS) ***/
436 void (*destroy) (void *);
437 struct vlc_threadvar *prev;
438 struct vlc_threadvar *next;
439 } *vlc_threadvar_last = NULL;
441 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
443 struct vlc_threadvar *var = malloc (sizeof (*var));
444 if (unlikely(var == NULL))
447 var->id = TlsAlloc();
448 if (var->id == TLS_OUT_OF_INDEXES)
453 var->destroy = destr;
457 vlc_mutex_lock (&super_mutex);
458 var->prev = vlc_threadvar_last;
460 var->prev->next = var;
462 vlc_threadvar_last = var;
463 vlc_mutex_unlock (&super_mutex);
467 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
469 struct vlc_threadvar *var = *p_tls;
471 vlc_mutex_lock (&super_mutex);
472 if (var->prev != NULL)
473 var->prev->next = var->next;
475 if (var->next != NULL)
476 var->next->prev = var->prev;
478 vlc_threadvar_last = var->prev;
480 vlc_mutex_unlock (&super_mutex);
486 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
488 return TlsSetValue (key->id, value) ? ENOMEM : 0;
491 void *vlc_threadvar_get (vlc_threadvar_t key)
493 return TlsGetValue (key->id);
497 void vlc_threads_setup (libvlc_int_t *p_libvlc)
502 static void vlc_thread_cleanup (struct vlc_thread *th)
507 /* TODO: use RW lock or something similar */
508 vlc_mutex_lock (&super_mutex);
509 for (key = vlc_threadvar_last; key != NULL; key = key->prev)
511 void *value = vlc_threadvar_get (key);
512 if (value != NULL && key->destroy != NULL)
514 vlc_mutex_unlock (&super_mutex);
515 vlc_threadvar_set (key, NULL);
516 key->destroy (value);
520 vlc_mutex_unlock (&super_mutex);
524 CloseHandle (th->id);
529 static unsigned __stdcall vlc_entry (void *p)
531 struct vlc_thread *th = p;
533 vlc_threadvar_set (thread_key, th);
535 th->data = th->entry (th->data);
536 vlc_thread_cleanup (th);
540 static int vlc_clone_attr (vlc_thread_t *p_handle, bool detached,
541 void *(*entry) (void *), void *data, int priority)
543 struct vlc_thread *th = malloc (sizeof (*th));
544 if (unlikely(th == NULL))
548 th->detached = detached;
549 th->killable = false; /* not until vlc_entry() ! */
554 /* When using the MSVCRT C library you have to use the _beginthreadex
555 * function instead of CreateThread, otherwise you'll end up with
556 * memory leaks and the signal functions not working (see Microsoft
557 * Knowledge Base, article 104641) */
560 h = _beginthreadex (NULL, 0, vlc_entry, th, CREATE_SUSPENDED, NULL);
569 /* Thread is suspended, so we can safely set th->id */
571 if (p_handle != NULL)
575 SetThreadPriority (hThread, priority);
577 ResumeThread (hThread);
582 int vlc_clone (vlc_thread_t *p_handle, void *(*entry) (void *),
583 void *data, int priority)
585 return vlc_clone_attr (p_handle, false, entry, data, priority);
588 void vlc_join (vlc_thread_t th, void **result)
592 while (vlc_WaitForSingleObject (th->id, INFINITE) == WAIT_IO_COMPLETION);
596 CloseHandle (th->id);
600 int vlc_clone_detach (vlc_thread_t *p_handle, void *(*entry) (void *),
601 void *data, int priority)
604 if (p_handle == NULL)
607 return vlc_clone_attr (p_handle, true, entry, data, priority);
610 int vlc_set_priority (vlc_thread_t th, int priority)
612 if (!SetThreadPriority (th->id, priority))
617 /*** Thread cancellation ***/
619 /* APC procedure for thread cancellation */
620 static void CALLBACK vlc_cancel_self (ULONG_PTR self)
622 struct vlc_thread *th = (void *)self;
624 if (likely(th != NULL))
628 void vlc_cancel (vlc_thread_t th)
630 QueueUserAPC (vlc_cancel_self, th->id, (uintptr_t)th);
633 int vlc_savecancel (void)
635 struct vlc_thread *th = vlc_threadvar_get (thread_key);
637 return false; /* Main thread - cannot be cancelled anyway */
639 int state = th->killable;
640 th->killable = false;
644 void vlc_restorecancel (int state)
646 struct vlc_thread *th = vlc_threadvar_get (thread_key);
647 assert (state == false || state == true);
650 return; /* Main thread - cannot be cancelled anyway */
652 assert (!th->killable);
653 th->killable = state != 0;
656 void vlc_testcancel (void)
658 struct vlc_thread *th = vlc_threadvar_get (thread_key);
660 return; /* Main thread - cannot be cancelled anyway */
662 if (th->killable && th->killed)
664 for (vlc_cleanup_t *p = th->cleaners; p != NULL; p = p->next)
667 th->data = NULL; /* TODO: special value? */
668 vlc_thread_cleanup (th);
673 void vlc_control_cancel (int cmd, ...)
675 /* NOTE: This function only modifies thread-specific data, so there is no
676 * need to lock anything. */
679 struct vlc_thread *th = vlc_threadvar_get (thread_key);
681 return; /* Main thread - cannot be cancelled anyway */
686 case VLC_CLEANUP_PUSH:
688 /* cleaner is a pointer to the caller stack, no need to allocate
689 * and copy anything. As a nice side effect, this cannot fail. */
690 vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
691 cleaner->next = th->cleaners;
692 th->cleaners = cleaner;
696 case VLC_CLEANUP_POP:
698 th->cleaners = th->cleaners->next;
706 static mtime_t mdate_giveup (void)
711 static mtime_t (*mdate_selected) (void) = mdate_giveup;
715 return mdate_selected ();
722 #if (_WIN32_WINNT < 0x0601)
723 BOOL (*query) (PULONGLONG);
728 #if (_WIN32_WINNT < 0x0600)
729 ULONGLONG (*get) (void);
738 static mtime_t mdate_interrupt (void)
743 #if (_WIN32_WINNT >= 0x0601)
744 ret = QueryUnbiasedInterruptTime (&ts);
746 ret = clk.interrupt.query (&ts);
751 /* hundreds of nanoseconds */
752 static_assert ((10000000 % CLOCK_FREQ) == 0, "Broken frequencies ratio");
753 return ts / (10000000 / CLOCK_FREQ);
756 static mtime_t mdate_tick (void)
758 #if (_WIN32_WINNT >= 0x0600)
759 ULONGLONG ts = GetTickCount64 ();
761 ULONGLONG ts = clk.tick.get ();
765 static_assert ((CLOCK_FREQ % 1000) == 0, "Broken frequencies ratio");
766 return ts * (CLOCK_FREQ / 1000);
768 #include <mmsystem.h>
769 static mtime_t mdate_multimedia (void)
771 DWORD ts = timeGetTime ();
774 static_assert ((CLOCK_FREQ % 1000) == 0, "Broken frequencies ratio");
775 return ts * (CLOCK_FREQ / 1000);
778 static mtime_t mdate_perf (void)
780 /* We don't need the real date, just the value of a high precision timer */
781 LARGE_INTEGER counter;
782 if (!QueryPerformanceCounter (&counter))
785 /* Convert to from (1/freq) to microsecond resolution */
786 /* We need to split the division to avoid 63-bits overflow */
787 lldiv_t d = lldiv (counter.QuadPart, clk.perf.freq.QuadPart);
789 return (d.quot * 1000000) + ((d.rem * 1000000) / clk.perf.freq.QuadPart);
792 static mtime_t mdate_wall (void)
797 #if (_WIN32_WINNT >= 0x0602)
798 GetSystemTimePreciseAsFileTime (&ts);
800 GetSystemTimeAsFileTime (&ts);
802 s.LowPart = ts.dwLowDateTime;
803 s.HighPart = ts.dwHighDateTime;
804 /* hundreds of nanoseconds */
805 static_assert ((10000000 % CLOCK_FREQ) == 0, "Broken frequencies ratio");
806 return s.QuadPart / (10000000 / CLOCK_FREQ);
810 void mwait (mtime_t deadline)
815 while ((delay = (deadline - mdate())) > 0)
818 if (unlikely(delay > 0x7fffffff))
826 void msleep (mtime_t delay)
828 mwait (mdate () + delay);
831 void SelectClockSource (vlc_object_t *obj)
833 EnterCriticalSection (&clock_lock);
834 if (mdate_selected != mdate_giveup)
836 LeaveCriticalSection (&clock_lock);
840 const char *name = "perf";
841 char *str = var_InheritString (obj, "clock-source");
844 if (!strcmp (name, "interrupt"))
846 msg_Dbg (obj, "using interrupt time as clock source");
847 #if (_WIN32_WINNT < 0x0601)
848 HANDLE h = GetModuleHandle (_T("kernel32.dll"));
849 if (unlikely(h == NULL))
851 clk.interrupt.query = (void *)GetProcAddress (h,
852 _T("QueryUnbiasedInterruptTime"));
853 if (unlikely(clk.interrupt.query == NULL))
856 mdate_selected = mdate_interrupt;
859 if (!strcmp (name, "tick"))
861 msg_Dbg (obj, "using Windows time as clock source");
862 #if (_WIN32_WINNT < 0x0601)
863 HANDLE h = GetModuleHandle (_T("kernel32.dll"));
864 if (unlikely(h == NULL))
866 clk.tick.get = (void *)GetProcAddress (h, _T("GetTickCount64"));
867 if (unlikely(clk.tick.get == NULL))
870 mdate_selected = mdate_tick;
873 if (!strcmp (name, "multimedia"))
877 msg_Dbg (obj, "using multimedia timers as clock source");
878 if (timeGetDevCaps (&caps, sizeof (caps)) != MMSYSERR_NOERROR)
880 msg_Dbg (obj, " min period: %u ms, max period: %u ms",
881 caps.wPeriodMin, caps.wPeriodMax);
882 mdate_selected = mdate_multimedia;
885 if (!strcmp (name, "perf"))
887 msg_Dbg (obj, "using performance counters as clock source");
888 if (!QueryPerformanceFrequency (&clk.perf.freq))
890 msg_Dbg (obj, " frequency: %llu Hz", clk.perf.freq.QuadPart);
891 mdate_selected = mdate_perf;
894 if (!strcmp (name, "wall"))
896 msg_Dbg (obj, "using system time as clock source");
897 mdate_selected = mdate_wall;
901 msg_Err (obj, "invalid clock source \"%s\"", name);
904 LeaveCriticalSection (&clock_lock);
908 #define xstrdup(str) (strdup(str) ?: (abort(), NULL))
910 size_t EnumClockSource (vlc_object_t *obj, char ***vp, char ***np)
912 const size_t max = 6;
913 char **values = xmalloc (sizeof (*values) * max);
914 char **names = xmalloc (sizeof (*names) * max);
917 #if (_WIN32_WINNT < 0x0601)
918 DWORD version = LOWORD(GetVersion());
919 version = (LOBYTE(version) << 8) | (HIBYTE(version) << 0);
922 values[n] = xstrdup ("");
923 names[n] = xstrdup (_("Auto"));
925 #if (_WIN32_WINNT < 0x0601)
926 if (version >= 0x0601)
929 values[n] = xstrdup ("interrupt");
930 names[n] = xstrdup ("Interrupt time");
933 #if (_WIN32_WINNT < 0x0600)
934 if (version >= 0x0600)
937 values[n] = xstrdup ("tick");
938 names[n] = xstrdup ("Windows time");
941 values[n] = xstrdup ("multimedia");
942 names[n] = xstrdup ("Multimedia timers");
944 values[n] = xstrdup ("perf");
945 names[n] = xstrdup ("Performance counters");
947 values[n] = xstrdup ("wall");
948 names[n] = xstrdup ("System time (DANGEROUS!)");
962 void (*func) (void *);
966 static void CALLBACK vlc_timer_do (void *val, BOOLEAN timeout)
968 struct vlc_timer *timer = val;
971 timer->func (timer->data);
974 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
976 struct vlc_timer *timer = malloc (sizeof (*timer));
982 timer->handle = INVALID_HANDLE_VALUE;
987 void vlc_timer_destroy (vlc_timer_t timer)
989 if (timer->handle != INVALID_HANDLE_VALUE)
990 DeleteTimerQueueTimer (NULL, timer->handle, INVALID_HANDLE_VALUE);
994 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
995 mtime_t value, mtime_t interval)
997 if (timer->handle != INVALID_HANDLE_VALUE)
999 DeleteTimerQueueTimer (NULL, timer->handle, NULL);
1000 timer->handle = INVALID_HANDLE_VALUE;
1003 return; /* Disarm */
1007 value = (value + 999) / 1000;
1008 interval = (interval + 999) / 1000;
1010 if (!CreateTimerQueueTimer (&timer->handle, NULL, vlc_timer_do, timer,
1011 value, interval, WT_EXECUTEDEFAULT))
1015 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
1023 unsigned vlc_GetCPUCount (void)
1028 if (GetProcessAffinityMask (GetCurrentProcess(), &process, &system))
1029 return popcount (system);