요새 별의 별 이상한 짓을 다 하고 있다보니 실시간으로 동영상을 저장해야할 필요가 생겼다. 그런데 MFC의 타이머는 message 기반이다보니 기본 타이머에 의존해서는 내가 원하는 것을 처리할 수 없을 것 같았다.
물론 MS 개발자들은 아주 똑똑하다보니 이런 경우를 위해 Multimedia timer라는 것을 제공하고 있다.
MFC에서 Multimedia timer 사용하기
우선 Multimedia timer에서 보장하는 resolution은 1ms까지이다. 그거보다 더 정밀하게 무언가를 처리해야한다면 multimedia timer를 사용하면 안된다.
우선 Multimedia timer를 시작/종료 하는 함수는 다음과 같다.
1 2 3 4 5 6 7 8 9 |
MMRESULT timeSetEvent( msInterval, // delay (in milli second) wTimerRes, // resolution (global variable) OneShotCallback, // callback function (DWORD)this, // user data flag ); // flag (TIME_PERIODIC or TIME_ONESHOT) MMRESULT timeKillEvent( UINT uTimerID ); |
timeSetEvent의 결과로 리턴되는 값은 uTimerID이며, 이 값은 나중에 타이머를 멈추는데 사용할 수 있다. 그리고 flag로 TIME_PERIODIC을 줄 경우 msInterval마다 callback 함수가 반복해서 실행된다.
이벤트가 발생할때마다 실행될 callback 함수는 다음과 같은 식으로 작성하면 된다.
1 2 3 4 5 6 |
void CALLBACK OneShotTimer(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2) { SOME_CLASS* npSeq = (SOME_CLASS*)dwUser; // pointer to sequencer data doSomething(npSeq); // handle tasks } |
참고로 multimedia timer를 사용하려면 mmsystem.h를 include 해주어야 하고, winmm.lib과 링크도 되어야 한다. 그러므로 multimedia timer를 사용하려는 파일 맨 위에 아래와 같은 줄을 추가해주자.
1 2 |
#include <mmsystem.h> #pragma comment(lib, "winmm.lib") |
하지만 실제 사용해보니 이것도 내가 원하는 용도로 충분하지가 못했다.
그래서 찾아낸 대안은 QueryPerformanceCounter!
QueryPerformanceCounter 사용하기
멀티미디어 카운터의 경우 알아서 쓰레드를 만들어주고, 특정 시점마다 콜백 함수를 실행시켜주지만 QueryPerformanceCounter는 훨씬 저수준 API이기 때문에 쓰레드도 직접 만들어야 하고, 얼만큼 시간이 흘렀는지도 다 직접 알아서 처리해야한다.
우선 QueryPerformanceCounter를 사용하기 위해 알아야 하는 함수는 다음 두 가지이다.
1 2 |
QueryPerformanceFrequency(LARGE_INTEGER *); QueryPerformanceCounter(LARGE_INTEGER *); |
QueryPerformanceFrequency는 1초에 몇 번이나 카운트가 증가하는지를 얻어오기 위한 함수이고, QueryPerformanceCounter는 현재 카운트 값이 무엇인지를 얻어오는 함수이다.
1초에 30번씩 무언가를 해주고 싶다면, 아래와 같은 코드를 사용하면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
LARGE_INTEGER tick; LARGE_INTEGER tps; LONGLONG wait_until; QueryPerformanceFrequency(&tps); QueryPerformanceCounter(&tick); wait_until = tick.QuadPart + tps.QuadPart/30; do { QueryPerformanceCounter(&tick); if( tick.QuadPart > wait_until ){ do_something(); wait_until += tps.QuadPart/30; } } while( 1 ); |
그리고 얘네들을 사용할 때도 아래와 같은 코드를 파일 맨 위에 삽입해주는걸 잊지 말자.
1 2 |
#include <mmsystem.h> #pragma comment(lib, "winmm.lib") |
자 그런데, 이게 CPU의 카운터를 이용하는 함수이다 보니 다중 CPU 환경에서는 문제가 생길 수 있다. 이런 문제를 피해가려면 이 쓰레드가 특정 CPU에서만 실행되도록 제한을 시킬 필요가 있다.
1 2 3 |
SYSTEM_INFO info; GetSystemInfo(&info); SetThreadAffinityMask(GetCurrentThread(), 1<<(1%info.dwNumberOfProcessors)); |
우선 GetSystemInfo()를 이용하면 현재 CPU(Core)가 몇 개가 있는지를 알아낼 수 있다. 만약 CPU Core가 2개라고 한다면 Mask값은 1또는 2가 될 수 있다. 3개라고 한다면 Mask 값은 1(1<<0), 2(1<<1), 4(1<<2) 중에 하나가 되면 된다. 자 여기까지는 윈도우에서 real time으로 뭔가를 처리하기 위한 방법... 그런데 내 메인 시스템이 맥이다보니 맥에서도 비슷한걸 하고 싶어졌다. 그런데 내 검색어가 후진지 도저히 검색이 잘 되질 않는다. 겨우 뭔가를 찾아낸거 같은데, 나랑 비슷한 문제를 겪는 사람들을 위해 이것도 좀 공유를 해놔야될 거 같다.
Mac OS X에서 real time으로 뭔가를 처리하기
맥에서의 real time처리는 High performance counter랑 매우 비슷하지만 훨씬 단순하다. 여기에 사용되는 함수는 다음과 같다.
1 2 3 |
// from /usr/include/mach/mach_time.h kern_return_t mach_wait_until(uint64_t deadline) uint64_t mach_absolute_time(void); |
현재의 시간을 nano sec단위로 얻어오고 싶다면 mach_absolute_time()을 실행시키면 된다. 그리고 특정 ns까지 기다리고 싶다면 mach_wait_until(deadline)을 실행시키면 된다.
아까 했던 것처럼 1초에 30번씩 뭔가를 실행시키기 위한 코드를 작성해보면 다음과 같다.
1 2 3 4 5 6 7 8 9 |
uint64_t cur; uint64_t next; cur = mach_absolute_time(); do { next = cur + 1000000000/30; mach_wait_until( next ); printf("Shout!!\n"); cur = next; } while(1); |
윈도우용 코드에 비해 완전 간단한 걸 알 수 있다. 그래서 설명은 생략한다.
Epilogue
확실히 윈도우 쪽은 한글 자료도 많고 검색해서 걸리는 자료도 많은데 맥 쪽은 내 검색 요령이 없는건지 원하는 정보를 찾는게 쉽지만은 않은 것 같다.
어쨌든 요번에 요상한 걸 부탁받아서 슈퍼 삽질을 했지만 덕분에 이런것도 찾아볼 수 있었던것 같다.
뿅!
헉 언젠가 쓸일이 올진 모르겠지만
함수 이름만이라도 기억해둘 필요가 있어 보이네요 +_+!