VC++或QT下 高精度 多媒體定時器


VC編程中,用SetTimer可以定義一個定時器,到時間了,就響應OnTimer消息,但這種定時器精度太低了。如果需要精度更高一些的定時器(精 確到1ms),可以使用下面的高精度多媒體定時器進行代碼優化,可以達到毫秒級的精度,而且使用方便。先要包含頭文件"mmsystem.h"和庫文 件"winmm.lib"。

 雖然Win95下可視化開發工具如VC、Delphi、C++   Builder等都有專用的定時器控件Timer,而且使用很方便,可以實現一定的定時功能,但最小計時精度僅為55ms,且定時器消息在多任務操作系統 中的優先級很低,不能得到及時響應,往往不能滿足實時控制環境下的應用。不過Microsoft公司在Win32   API函數庫中已經為用戶提供了一組用於高精度計時的底層函數,如果用戶使用得當,計時精度可到1ms。這個計時精度、對於一般的實時系統控制完全可以滿足要求。現將由C++   Builder   4.0提供的重新封裝后的一組與時間相關的主要接口函數(函數名、參數、功能與Win32   API基本相同)說明如下:    

    1.DWORD   timeGetTime(void)    

    返回從Windows啟動開始經過的毫秒數。最大值為232,約49.71天。    

  2.MMRESULT   timeSetEvent(    

    UINT   uDelay,    

    UINT   uResolution,    

    LPTIMECALLBACK   lpTimeProc,    

    DWORD   dwUser,    

    UINT   fuEvent)    

       該函數設置一個定時回調事件,此事件可以是一個一次性事件或周期性事件。事件一旦被激活,便調用指定的回調函數,成功后返回事件的標識符代碼,否則返回NULL。參數說明如下:  

      uDelay:以毫秒指定事件的周期。    

    UResolution:以毫秒指定延時的精度,數值越小定時器事件分辨率越高。缺省值為1ms。    

    LpTimeProc:指向一個回調函數。    

    DwUser:存放用戶提供的回調數據。    

    FuEvent:指定定時器事件類型:    

    TIME_ONESHOT:uDelay毫秒后只產生一次事件    

    TIME_PERIODIC   :每隔uDelay毫秒周期性地產生事件。    

    3.MMRESULT   timeKillEvent(UINT   uTimerID)    

    該函數取消一個指定的定時器回調事件。uTimerID標識要取消的事件(由timeSetEvent函數返回的標識符)。如果成功則返回TIMERR_NOERROR,如果定時器時間不存在則返回MMSYSERR_INVALPARAM。    

 4.回調函數

    void   CALLBACK   TimeProc(    

    UINT   uID,    

    UINT   uMsg,    

    DWORD   dwUser,    

    DWORD   dw1,    

    DWORD   dw2);    

    該函數是一個應用程序定義的回調函數,出現定時器事件時該函數被調用。TimeProc是應用程序定義的函數名的占位符。使用該函數  

  時要注意的是,它只能調用以下有限的幾組API函數:PostMessage,timeGetSystemTime,  timeGetTime,   timeSetEvent,timeKillEvent  

  ,midiOutShortMsg,   midiOutLongMsg,OutputDebugString。同時也不要使用完成時間很長的API函數,程序盡可能簡短。    

   

    使用以上一組函數就可以完成毫秒級精度的計時和控制(在C++Builder中使用時要將頭文件mmsystem.h加到程序中)。由於將定時控  

  制精確到幾毫秒,定時器事件將占用大量的CPU時間和系統資源,所以在滿足控制要求的前提下,應盡量將參數uResolution的數值增大。而  

  且定時器實時控制功能完成后要盡快釋放。  

 注意以下幾點問題:

一、回調函數的參數不能有誤,否則可能引起程序崩掉;

二、事件調用周期uDelay不能小於事件處理時間,否則會引起程序崩潰;

三、通過dwUser給回調函數傳遞參數

 例程如下:

  MMRESULT g_wTimerID = 0;

//回調函數,參數不能有錯

 void CALLBACK CDsisiiDlg::SendFun(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dwl, DWORD dw2)

{

           CDsisiiDlg* pdcpackerdlg = (CDsisiiDlg*)dwUser;

           ...

}

 

bool  CDsisiiDlg::CreateTimer()

{

   TIMECAPS   tc;  

   UINT wTimerRes;

 

    //設置多媒體定時器 

    if(timeGetDevCaps(&tc,sizeof(TIMECAPS))!=TIMERR_NOERROR)//向機器申請一個多媒體定時器      

       return false;

    //獲得機器允許的時間間隔(一般可達到1毫秒)  

    wTimerRes=min(max(tc.wPeriodMin,1),tc.wPeriodMax);  

    //定時器開始工作  

   timeBeginPeriod(wTimerRes);  

    //每過6毫秒調用回調函數timerback(),wTimerID為定時器ID.TIME_PERIODIC表周期性調用,TIME_ONESHOT表只產生一次事件  

   g_wTimerID = timeSetEvent(6,  wTimerRes, (LPTIMECALLBACK)SendFun,  (DWORD)this, TIME_PERIODIC);  

   if(g_wTimerID == 0)

        return false;

    return true;

}

//刪除定時器

void CDsisiiDlg::DestroyTimer()

{

   if (g_wTimerID)

    {

       timeKillEvent(g_wTimerID);

       g_wTimerID = 0;

   }

}

 一下為在QT下使用windows多媒體計時器

 在QTimer源碼分析(以Windows下實現為例) 一文中,我們看到了Qt在windows下對計時器的使用:

 對於間隔為零的情況,Qt並沒有動用系統的計時器

對於間隔非零的情況

間隔小於20ms 且系統支持多媒體計時器,則使用多媒體計時器

否則,使用普通計時器

Qt 的這種策略應該能很好地滿足我們的需求了,但qtcn上一個網友還是比較期待自己直接調用系統的多媒體計時器。既然這樣,自己還是嘗試寫寫吧,寫一個自己的Timer類

 代碼

 代碼還是比較簡單的,頭文件 mmtimer.h 如下:

#ifndef MMTIMER_H

#define MMTIMER_H

#include

#include

 class MMTimer : public QObject

{

    Q_OBJECT

public:

    explicit MMTimer(int interval, QObject *parent = 0);

    ~MMTimer();

 signals:

    void timeout();

 public slots:

    void start();

    void stop();

 

friend void WINAPI CALLBACK mmtimer_proc(uint, uint, DWORD_PTR, DWORD_PTR, DWORD_PTR);

 

private:

    int m_interval;

    int m_id;

};

 #endif // MMTIMER_H

源碼文件 mmtimer.cpp 如下:

#include "mmtimer.h"  

#include

#ifdef __MINGW32__ //w32api bug

#define TIME_KILL_SYNCHRONOUS 0x0100

#endif

void WINAPI CALLBACK mmtimer_proc(uint timerId, uint, DWORD_PTR user, DWORD_PTR, DWORD_PTR)

{

    MMTimer *t = reinterpret_cast(user);

    emit t->timeout();

}

 MMTimer::MMTimer(int interval, QObject *parent) :

    QObject(parent),m_interval(interval),m_id(0)

{

}

 MMTimer::~MMTimer()

{

    stop();

}

 void MMTimer::start()

{

    m_id = timeSetEvent(m_interval, 1, mmtimer_proc, (DWORD_PTR)this,

                 TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS);

 }

 void MMTimer::stop()

{

    if (m_id){

        timeKillEvent(m_id);

        m_id = 0;

    }

}

說明

 上面的代碼應該不需要什么解釋了:

 timeSetEvent 和 timeKillEvent 可直接查閱 MSDN

另外,MinGW的win32api包,對TIME_KILL_SYNCHRONOUS沒有定義,代碼中做了一點修正

請確保正確鏈接所需要的庫

 LIBS += -lwinmm

注意:MSDN 對timeSetEvent的介紹中這么說的(對此不做評論)

 Note  This function is obsolete. New applications should use CreateTimerQueueTimer to create a timer-queue timer.

http://blog.sina.com.cn/s/blog_668aae780101dfij.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM