Add support for ICE/STUN/TURN in res_rtp_asterisk and chan_sip.
[asterisk/asterisk.git] / res / pjproject / third_party / BaseClasses / wxutil.cpp
1 //------------------------------------------------------------------------------
2 // File: WXUtil.cpp
3 //
4 // Desc: DirectShow base classes - implements helper classes for building
5 //       multimedia filters.
6 //
7 // Copyright (c) 1992-2001 Microsoft Corporation.  All rights reserved.
8 //------------------------------------------------------------------------------
9
10 #include <pjmedia-videodev/config.h>
11
12 #if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0
13
14 #include <streams.h>
15 #define STRSAFE_NO_DEPRECATE
16 #include <strsafe.h>
17
18
19 // --- CAMEvent -----------------------
20 CAMEvent::CAMEvent(BOOL fManualReset, __inout_opt HRESULT *phr)
21 {
22     m_hEvent = CreateEvent(NULL, fManualReset, FALSE, NULL);
23     if (NULL == m_hEvent) {
24         if (NULL != phr && SUCCEEDED(*phr)) {
25             *phr = E_OUTOFMEMORY;
26         }
27     }
28 }
29
30 CAMEvent::CAMEvent(__inout_opt HRESULT *phr)
31 {
32     m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
33     if (NULL == m_hEvent) {
34         if (NULL != phr && SUCCEEDED(*phr)) {
35             *phr = E_OUTOFMEMORY;
36         }
37     }
38 }
39
40 CAMEvent::~CAMEvent()
41 {
42     if (m_hEvent) {
43         EXECUTE_ASSERT(CloseHandle(m_hEvent));
44     }
45 }
46
47
48 // --- CAMMsgEvent -----------------------
49 // One routine.  The rest is handled in CAMEvent
50
51 CAMMsgEvent::CAMMsgEvent(__inout_opt HRESULT *phr) : CAMEvent(FALSE, phr)
52 {
53 }
54
55 BOOL CAMMsgEvent::WaitMsg(DWORD dwTimeout)
56 {
57     // wait for the event to be signalled, or for the
58     // timeout (in MS) to expire.  allow SENT messages
59     // to be processed while we wait
60     DWORD dwWait;
61     DWORD dwStartTime;
62
63     // set the waiting period.
64     DWORD dwWaitTime = dwTimeout;
65
66     // the timeout will eventually run down as we iterate
67     // processing messages.  grab the start time so that
68     // we can calculate elapsed times.
69     if (dwWaitTime != INFINITE) {
70         dwStartTime = timeGetTime();
71     }
72
73     do {
74         dwWait = MsgWaitForMultipleObjects(1,&m_hEvent,FALSE, dwWaitTime, QS_SENDMESSAGE);
75         if (dwWait == WAIT_OBJECT_0 + 1) {
76             MSG Message;
77             PeekMessage(&Message,NULL,0,0,PM_NOREMOVE);
78
79             // If we have an explicit length of time to wait calculate
80             // the next wake up point - which might be now.
81             // If dwTimeout is INFINITE, it stays INFINITE
82             if (dwWaitTime != INFINITE) {
83
84                 DWORD dwElapsed = timeGetTime()-dwStartTime;
85
86                 dwWaitTime =
87                     (dwElapsed >= dwTimeout)
88                         ? 0  // wake up with WAIT_TIMEOUT
89                         : dwTimeout-dwElapsed;
90             }
91         }
92     } while (dwWait == WAIT_OBJECT_0 + 1);
93
94     // return TRUE if we woke on the event handle,
95     //        FALSE if we timed out.
96     return (dwWait == WAIT_OBJECT_0);
97 }
98
99 // --- CAMThread ----------------------
100
101
102 CAMThread::CAMThread(__inout_opt HRESULT *phr)
103     : m_EventSend(TRUE, phr),     // must be manual-reset for CheckRequest()
104       m_EventComplete(FALSE, phr)
105 {
106     m_hThread = NULL;
107 }
108
109 CAMThread::~CAMThread() {
110     Close();
111 }
112
113
114 // when the thread starts, it calls this function. We unwrap the 'this'
115 //pointer and call ThreadProc.
116 DWORD WINAPI
117 CAMThread::InitialThreadProc(__inout LPVOID pv)
118 {
119     HRESULT hrCoInit = CAMThread::CoInitializeHelper();
120     if(FAILED(hrCoInit)) {
121         DbgLog((LOG_ERROR, 1, TEXT("CoInitializeEx failed.")));
122     }
123
124     CAMThread * pThread = (CAMThread *) pv;
125
126     HRESULT hr = pThread->ThreadProc();
127
128     if(SUCCEEDED(hrCoInit)) {
129         CoUninitialize();
130     }
131
132     return hr;
133 }
134
135 BOOL
136 CAMThread::Create()
137 {
138     DWORD threadid;
139
140     CAutoLock lock(&m_AccessLock);
141
142     if (ThreadExists()) {
143         return FALSE;
144     }
145
146     m_hThread = CreateThread(
147                     NULL,
148                     0,
149                     CAMThread::InitialThreadProc,
150                     this,
151                     0,
152                     &threadid);
153
154     if (!m_hThread) {
155         return FALSE;
156     }
157
158     return TRUE;
159 }
160
161 DWORD
162 CAMThread::CallWorker(DWORD dwParam)
163 {
164     // lock access to the worker thread for scope of this object
165     CAutoLock lock(&m_AccessLock);
166
167     if (!ThreadExists()) {
168         return (DWORD) E_FAIL;
169     }
170
171     // set the parameter
172     m_dwParam = dwParam;
173
174     // signal the worker thread
175     m_EventSend.Set();
176
177     // wait for the completion to be signalled
178     m_EventComplete.Wait();
179
180     // done - this is the thread's return value
181     return m_dwReturnVal;
182 }
183
184 // Wait for a request from the client
185 DWORD
186 CAMThread::GetRequest()
187 {
188     m_EventSend.Wait();
189     return m_dwParam;
190 }
191
192 // is there a request?
193 BOOL
194 CAMThread::CheckRequest(__out_opt DWORD * pParam)
195 {
196     if (!m_EventSend.Check()) {
197         return FALSE;
198     } else {
199         if (pParam) {
200             *pParam = m_dwParam;
201         }
202         return TRUE;
203     }
204 }
205
206 // reply to the request
207 void
208 CAMThread::Reply(DWORD dw)
209 {
210     m_dwReturnVal = dw;
211
212     // The request is now complete so CheckRequest should fail from
213     // now on
214     //
215     // This event should be reset BEFORE we signal the client or
216     // the client may Set it before we reset it and we'll then
217     // reset it (!)
218
219     m_EventSend.Reset();
220
221     // Tell the client we're finished
222
223     m_EventComplete.Set();
224 }
225
226 HRESULT CAMThread::CoInitializeHelper()
227 {
228     // call CoInitializeEx and tell OLE not to create a window (this
229     // thread probably won't dispatch messages and will hang on
230     // broadcast msgs o/w).
231     //
232     // If CoInitEx is not available, threads that don't call CoCreate
233     // aren't affected. Threads that do will have to handle the
234     // failure. Perhaps we should fall back to CoInitialize and risk
235     // hanging?
236     //
237
238     // older versions of ole32.dll don't have CoInitializeEx
239
240     HRESULT hr = E_FAIL;
241     HINSTANCE hOle = GetModuleHandle(TEXT("ole32.dll"));
242     if(hOle)
243     {
244         typedef HRESULT (STDAPICALLTYPE *PCoInitializeEx)(
245             LPVOID pvReserved, DWORD dwCoInit);
246         PCoInitializeEx pCoInitializeEx =
247             (PCoInitializeEx)(GetProcAddress(hOle, "CoInitializeEx"));
248         if(pCoInitializeEx)
249         {
250             hr = (*pCoInitializeEx)(0, COINIT_DISABLE_OLE1DDE );
251         }
252     }
253     else
254     {
255         // caller must load ole32.dll
256         DbgBreak("couldn't locate ole32.dll");
257     }
258
259     return hr;
260 }
261
262
263 // destructor for CMsgThread  - cleans up any messages left in the
264 // queue when the thread exited
265 CMsgThread::~CMsgThread()
266 {
267     if (m_hThread != NULL) {
268         WaitForSingleObject(m_hThread, INFINITE);
269         EXECUTE_ASSERT(CloseHandle(m_hThread));
270     }
271
272     POSITION pos = m_ThreadQueue.GetHeadPosition();
273     while (pos) {
274         CMsg * pMsg = m_ThreadQueue.GetNext(pos);
275         delete pMsg;
276     }
277     m_ThreadQueue.RemoveAll();
278
279     if (m_hSem != NULL) {
280         EXECUTE_ASSERT(CloseHandle(m_hSem));
281     }
282 }
283
284 BOOL
285 CMsgThread::CreateThread(
286     )
287 {
288     m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
289     if (m_hSem == NULL) {
290         return FALSE;
291     }
292
293     m_hThread = ::CreateThread(NULL, 0, DefaultThreadProc,
294                                (LPVOID)this, 0, &m_ThreadId);
295     return m_hThread != NULL;
296 }
297
298
299 // This is the threads message pump.  Here we get and dispatch messages to
300 // clients thread proc until the client refuses to process a message.
301 // The client returns a non-zero value to stop the message pump, this
302 // value becomes the threads exit code.
303
304 DWORD WINAPI
305 CMsgThread::DefaultThreadProc(
306     __inout LPVOID lpParam
307     )
308 {
309     CMsgThread *lpThis = (CMsgThread *)lpParam;
310     CMsg msg;
311     LRESULT lResult;
312
313     // !!!
314     CoInitialize(NULL);
315
316     // allow a derived class to handle thread startup
317     lpThis->OnThreadInit();
318
319     do {
320         lpThis->GetThreadMsg(&msg);
321         lResult = lpThis->ThreadMessageProc(msg.uMsg,msg.dwFlags,
322                                             msg.lpParam, msg.pEvent);
323     } while (lResult == 0L);
324
325     // !!!
326     CoUninitialize();
327
328     return (DWORD)lResult;
329 }
330
331
332 // Block until the next message is placed on the list m_ThreadQueue.
333 // copies the message to the message pointed to by *pmsg
334 void
335 CMsgThread::GetThreadMsg(__out CMsg *msg)
336 {
337     CMsg * pmsg = NULL;
338
339     // keep trying until a message appears
340     while (TRUE) {
341         {
342             CAutoLock lck(&m_Lock);
343             pmsg = m_ThreadQueue.RemoveHead();
344             if (pmsg == NULL) {
345                 m_lWaiting++;
346             } else {
347                 break;
348             }
349         }
350         // the semaphore will be signalled when it is non-empty
351         WaitForSingleObject(m_hSem, INFINITE);
352     }
353     // copy fields to caller's CMsg
354     *msg = *pmsg;
355
356     // this CMsg was allocated by the 'new' in PutThreadMsg
357     delete pmsg;
358
359 }
360
361 // Helper function - convert int to WSTR
362 void WINAPI IntToWstr(int i, __out_ecount(12) LPWSTR wstr)
363 {
364 #ifdef UNICODE
365     if (FAILED(StringCchPrintf(wstr, 12, L"%d", i))) {
366         wstr[0] = 0;
367     }
368 #else
369     TCHAR temp[12];
370     if (FAILED(StringCchPrintf(temp, NUMELMS(temp), "%d", i))) {
371         wstr[0] = 0;
372     } else {
373         MultiByteToWideChar(CP_ACP, 0, temp, -1, wstr, 12);
374     }
375 #endif
376 } // IntToWstr
377
378
379 #define MEMORY_ALIGNMENT        4
380 #define MEMORY_ALIGNMENT_LOG2   2
381 #define MEMORY_ALIGNMENT_MASK   MEMORY_ALIGNMENT - 1
382
383 void * __stdcall memmoveInternal(void * dst, const void * src, size_t count)
384 {
385     void * ret = dst;
386
387 #ifdef _X86_
388     if (dst <= src || (char *)dst >= ((char *)src + count)) {
389
390         /*
391          * Non-Overlapping Buffers
392          * copy from lower addresses to higher addresses
393          */
394         _asm {
395             mov     esi,src
396             mov     edi,dst
397             mov     ecx,count
398             cld
399             mov     edx,ecx
400             and     edx,MEMORY_ALIGNMENT_MASK
401             shr     ecx,MEMORY_ALIGNMENT_LOG2
402             rep     movsd
403             or      ecx,edx
404             jz      memmove_done
405             rep     movsb
406 memmove_done:
407         }
408     }
409     else {
410
411         /*
412          * Overlapping Buffers
413          * copy from higher addresses to lower addresses
414          */
415         _asm {
416             mov     esi,src
417             mov     edi,dst
418             mov     ecx,count
419             std
420             add     esi,ecx
421             add     edi,ecx
422             dec     esi
423             dec     edi
424             rep     movsb
425             cld
426         }
427     }
428 #else
429     MoveMemory(dst, src, count);
430 #endif
431
432     return ret;
433 }
434
435 HRESULT AMSafeMemMoveOffset(
436     __in_bcount(dst_size) void * dst,
437     __in size_t dst_size,
438     __in DWORD cb_dst_offset,
439     __in_bcount(src_size) const void * src,
440     __in size_t src_size,
441     __in DWORD cb_src_offset,
442     __in size_t count)
443 {
444     // prevent read overruns
445     if( count + cb_src_offset < count ||   // prevent integer overflow
446         count + cb_src_offset > src_size)  // prevent read overrun
447     {
448         return E_INVALIDARG;
449     }
450
451     // prevent write overruns
452     if( count + cb_dst_offset < count ||   // prevent integer overflow
453         count + cb_dst_offset > dst_size)  // prevent write overrun
454     {
455         return E_INVALIDARG;
456     }
457
458     memmoveInternal( (BYTE *)dst+cb_dst_offset, (BYTE *)src+cb_src_offset, count);
459     return S_OK;
460 }
461
462
463 #ifdef DEBUG
464 /******************************Public*Routine******************************\
465 * Debug CCritSec helpers
466 *
467 * We provide debug versions of the Constructor, destructor, Lock and Unlock
468 * routines.  The debug code tracks who owns each critical section by
469 * maintaining a depth count.
470 *
471 * History:
472 *
473 \**************************************************************************/
474
475 CCritSec::CCritSec()
476 {
477     InitializeCriticalSection(&m_CritSec);
478     m_currentOwner = m_lockCount = 0;
479     m_fTrace = FALSE;
480 }
481
482 CCritSec::~CCritSec()
483 {
484     DeleteCriticalSection(&m_CritSec);
485 }
486
487 void CCritSec::Lock()
488 {
489     UINT tracelevel=3;
490     DWORD us = GetCurrentThreadId();
491     DWORD currentOwner = m_currentOwner;
492     if (currentOwner && (currentOwner != us)) {
493         // already owned, but not by us
494         if (m_fTrace) {
495             DbgLog((LOG_LOCKING, 2, TEXT("Thread %d about to wait for lock %x owned by %d"),
496                 GetCurrentThreadId(), &m_CritSec, currentOwner));
497             tracelevel=2;
498                 // if we saw the message about waiting for the critical
499                 // section we ensure we see the message when we get the
500                 // critical section
501         }
502     }
503     EnterCriticalSection(&m_CritSec);
504     if (0 == m_lockCount++) {
505         // we now own it for the first time.  Set owner information
506         m_currentOwner = us;
507
508         if (m_fTrace) {
509             DbgLog((LOG_LOCKING, tracelevel, TEXT("Thread %d now owns lock %x"), m_currentOwner, &m_CritSec));
510         }
511     }
512 }
513
514 void CCritSec::Unlock() {
515     if (0 == --m_lockCount) {
516         // about to be unowned
517         if (m_fTrace) {
518             DbgLog((LOG_LOCKING, 3, TEXT("Thread %d releasing lock %x"), m_currentOwner, &m_CritSec));
519         }
520
521         m_currentOwner = 0;
522     }
523     LeaveCriticalSection(&m_CritSec);
524 }
525
526 void WINAPI DbgLockTrace(CCritSec * pcCrit, BOOL fTrace)
527 {
528     pcCrit->m_fTrace = fTrace;
529 }
530
531 BOOL WINAPI CritCheckIn(CCritSec * pcCrit)
532 {
533     return (GetCurrentThreadId() == pcCrit->m_currentOwner);
534 }
535
536 BOOL WINAPI CritCheckIn(const CCritSec * pcCrit)
537 {
538     return (GetCurrentThreadId() == pcCrit->m_currentOwner);
539 }
540
541 BOOL WINAPI CritCheckOut(CCritSec * pcCrit)
542 {
543     return (GetCurrentThreadId() != pcCrit->m_currentOwner);
544 }
545
546 BOOL WINAPI CritCheckOut(const CCritSec * pcCrit)
547 {
548     return (GetCurrentThreadId() != pcCrit->m_currentOwner);
549 }
550 #endif
551
552
553 STDAPI WriteBSTR(__deref_out BSTR *pstrDest, LPCWSTR szSrc)
554 {
555     *pstrDest = SysAllocString( szSrc );
556     if( !(*pstrDest) ) return E_OUTOFMEMORY;
557     return NOERROR;
558 }
559
560
561 STDAPI FreeBSTR(__deref_in BSTR* pstr)
562 {
563     if( (PVOID)*pstr == NULL ) return S_FALSE;
564     SysFreeString( *pstr );
565     return NOERROR;
566 }
567
568
569 // Return a wide string - allocating memory for it
570 // Returns:
571 //    S_OK          - no error
572 //    E_POINTER     - ppszReturn == NULL
573 //    E_OUTOFMEMORY - can't allocate memory for returned string
574 STDAPI AMGetWideString(LPCWSTR psz, __deref_out LPWSTR *ppszReturn)
575 {
576     CheckPointer(ppszReturn, E_POINTER);
577     ValidateReadWritePtr(ppszReturn, sizeof(LPWSTR));
578     *ppszReturn = NULL;
579     size_t nameLen;
580     HRESULT hr = StringCbLengthW(psz, 100000, &nameLen);
581     if (FAILED(hr)) {
582         return hr;
583     }
584     *ppszReturn = (LPWSTR)CoTaskMemAlloc(nameLen + sizeof(WCHAR));
585     if (*ppszReturn == NULL) {
586        return E_OUTOFMEMORY;
587     }
588     CopyMemory(*ppszReturn, psz, nameLen + sizeof(WCHAR));
589     return NOERROR;
590 }
591
592 // Waits for the HANDLE hObject.  While waiting messages sent
593 // to windows on our thread by SendMessage will be processed.
594 // Using this function to do waits and mutual exclusion
595 // avoids some deadlocks in objects with windows.
596 // Return codes are the same as for WaitForSingleObject
597 DWORD WINAPI WaitDispatchingMessages(
598     HANDLE hObject,
599     DWORD dwWait,
600     HWND hwnd,
601     UINT uMsg,
602     HANDLE hEvent)
603 {
604     BOOL bPeeked = FALSE;
605     DWORD dwResult;
606     DWORD dwStart;
607     DWORD dwThreadPriority;
608
609     static UINT uMsgId = 0;
610
611     HANDLE hObjects[2] = { hObject, hEvent };
612     if (dwWait != INFINITE && dwWait != 0) {
613         dwStart = GetTickCount();
614     }
615     for (; ; ) {
616         DWORD nCount = NULL != hEvent ? 2 : 1;
617
618         //  Minimize the chance of actually dispatching any messages
619         //  by seeing if we can lock immediately.
620         dwResult = WaitForMultipleObjects(nCount, hObjects, FALSE, 0);
621         if (dwResult < WAIT_OBJECT_0 + nCount) {
622             break;
623         }
624
625         DWORD dwTimeOut = dwWait;
626         if (dwTimeOut > 10) {
627             dwTimeOut = 10;
628         }
629         dwResult = MsgWaitForMultipleObjects(
630                              nCount,
631                              hObjects,
632                              FALSE,
633                              dwTimeOut,
634                              hwnd == NULL ? QS_SENDMESSAGE :
635                                             QS_SENDMESSAGE + QS_POSTMESSAGE);
636         if (dwResult == WAIT_OBJECT_0 + nCount ||
637             dwResult == WAIT_TIMEOUT && dwTimeOut != dwWait) {
638             MSG msg;
639             if (hwnd != NULL) {
640                 while (PeekMessage(&msg, hwnd, uMsg, uMsg, PM_REMOVE)) {
641                     DispatchMessage(&msg);
642                 }
643             }
644             // Do this anyway - the previous peek doesn't flush out the
645             // messages
646             PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
647
648             if (dwWait != INFINITE && dwWait != 0) {
649                 DWORD dwNow = GetTickCount();
650
651                 // Working with differences handles wrap-around
652                 DWORD dwDiff = dwNow - dwStart;
653                 if (dwDiff > dwWait) {
654                     dwWait = 0;
655                 } else {
656                     dwWait -= dwDiff;
657                 }
658                 dwStart = dwNow;
659             }
660             if (!bPeeked) {
661                 //  Raise our priority to prevent our message queue
662                 //  building up
663                 dwThreadPriority = GetThreadPriority(GetCurrentThread());
664                 if (dwThreadPriority < THREAD_PRIORITY_HIGHEST) {
665                     SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
666                 }
667                 bPeeked = TRUE;
668             }
669         } else {
670             break;
671         }
672     }
673     if (bPeeked) {
674         SetThreadPriority(GetCurrentThread(), dwThreadPriority);
675         if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) {
676             if (uMsgId == 0) {
677                 uMsgId = RegisterWindowMessage(TEXT("AMUnblock"));
678             }
679             if (uMsgId != 0) {
680                 MSG msg;
681                 //  Remove old ones
682                 while (PeekMessage(&msg, (HWND)-1, uMsgId, uMsgId, PM_REMOVE)) {
683                 }
684             }
685             PostThreadMessage(GetCurrentThreadId(), uMsgId, 0, 0);
686         }
687     }
688     return dwResult;
689 }
690
691 HRESULT AmGetLastErrorToHResult()
692 {
693     DWORD dwLastError = GetLastError();
694     if(dwLastError != 0)
695     {
696         return HRESULT_FROM_WIN32(dwLastError);
697     }
698     else
699     {
700         return E_FAIL;
701     }
702 }
703
704 IUnknown* QzAtlComPtrAssign(__deref_inout_opt IUnknown** pp, __in_opt IUnknown* lp)
705 {
706     if (lp != NULL)
707         lp->AddRef();
708     if (*pp)
709         (*pp)->Release();
710     *pp = lp;
711     return lp;
712 }
713
714 /******************************************************************************
715
716 CompatibleTimeSetEvent
717
718     CompatibleTimeSetEvent() sets the TIME_KILL_SYNCHRONOUS flag before calling
719 timeSetEvent() if the current operating system supports it.  TIME_KILL_SYNCHRONOUS
720 is supported on Windows XP and later operating systems.
721
722 Parameters:
723 - The same parameters as timeSetEvent().  See timeSetEvent()'s documentation in 
724 the Platform SDK for more information.
725
726 Return Value:
727 - The same return value as timeSetEvent().  See timeSetEvent()'s documentation in 
728 the Platform SDK for more information.
729
730 ******************************************************************************/
731 MMRESULT CompatibleTimeSetEvent( UINT uDelay, UINT uResolution, __in LPTIMECALLBACK lpTimeProc, DWORD_PTR dwUser, UINT fuEvent )
732 {
733     #if WINVER >= 0x0501
734     {
735         static bool fCheckedVersion = false;
736         static bool fTimeKillSynchronousFlagAvailable = false; 
737
738         if( !fCheckedVersion ) {
739             fTimeKillSynchronousFlagAvailable = TimeKillSynchronousFlagAvailable();
740             fCheckedVersion = true;
741         }
742
743         if( fTimeKillSynchronousFlagAvailable ) {
744             fuEvent = fuEvent | TIME_KILL_SYNCHRONOUS;
745         }
746     }
747     #endif // WINVER >= 0x0501
748
749     return timeSetEvent( uDelay, uResolution, lpTimeProc, dwUser, fuEvent );
750 }
751
752 bool TimeKillSynchronousFlagAvailable( void )
753 {
754     OSVERSIONINFO osverinfo;
755
756     osverinfo.dwOSVersionInfoSize = sizeof(osverinfo);
757
758     if( GetVersionEx( &osverinfo ) ) {
759         
760         // Windows XP's major version is 5 and its' minor version is 1.
761         // timeSetEvent() started supporting the TIME_KILL_SYNCHRONOUS flag
762         // in Windows XP.
763         if( (osverinfo.dwMajorVersion > 5) || 
764             ( (osverinfo.dwMajorVersion == 5) && (osverinfo.dwMinorVersion >= 1) ) ) {
765             return true;
766         }
767     }
768
769     return false;
770 }
771
772
773 #endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */