漫漫延時路,上下求索

來源:互聯網
上載者:User

 怎麼才能讓你的程式延時 1ms ,2ms... ...

 以前用VB編程式的時候,我一直用Timer()函數,而且用了很久。但是仔細分析的話,會發現,timer()函數的精度其實很低的,精度只能達到十幾毫秒,也就是說,假如你的本意是延時1毫秒,可實際上卻延時了十幾毫秒。證據呢?怎麼看出來的?下面這個簡單的VB程式就能說明問題了:

Option Explicit

Private Sub Command1_Click()

    Dim i As Long, j As Long
   
    For i = 0 To 1000000
       
        For j = 0 To 999
       
            delay (1)  '假如delay(1)真的延時一毫秒的話,那麼執行1000次迴圈只需要1秒鐘。
            
        Next j
       
       
        Label1.Caption = Label1.Caption + 1
       
        DoEvents
       
    Next i

End Sub

'msec 毫秒
Private Sub delay(ByVal msec As Long)
   
    Dim tmr As Long
   
    tmr = Timer * 1000
   
    Do
   
        DoEvents
       
    Loop Until Timer * 1000 - tmr > msec
   
End Sub

代碼不用過多解釋,假如delay(1)真的延時一毫秒的話,那麼執行1000次迴圈只需要1秒鐘,那個label控制項的值會每秒鐘增加一次。實際運行一下看(不用虛擬碼,編譯後再執行),數字大約10秒鐘才跳一下。對於精度要求不高的場合,其實這種方法已經夠用了,以前寫過一個JSGPlayer,用於播放自訂格式的隧道廣告檔案,幀間延時用的就是這種方法,因為就是一個簡單的播放器,不需要太高的精度,雖然畫面播放速率會有一些偏差,但效果也確實可以接受。

現在因為要264解碼,而且RTP發送,幀間延時,資料的拆包、組包必須都精確同步,就無法接受這種精度了,如果延時不精確,就會造成堵包、丟包(接收不及時引起的)、播放忽快忽慢的問題(這個問題可是忽悠不過去的,測試用的標準視頻檔案裡面有很多勻速運動的鏡頭,播放的時候如果有停頓、跳幀很容易看出來。如果是播放一般的電影,興許能夠忽悠過去:-)如何才能精確的做到毫秒級延時呢?我踏上了漫漫的尋求之路。

試試其他的方法行不行呢?API函數GetTickCount()、還有微軟的多媒體時鐘(mmsystem.lib中的函數),經過實驗發現,GetTickCount()和VB是一類型的,精度都在10毫秒以上。微軟的多媒體時鐘精度高了一些,但是還遠遠達不到毫秒級,而且聽說多媒體時鐘的精度和你的電腦硬體、和你裝了什麼版本的Windows、DirectX有一定關係,比方說在你的電腦上可能精度高,在我的電腦上就不一定精度高,這一特性足以讓它被遺棄了,我可不想辛辛苦苦調完程式,放到別人電腦上卻無法正常運行。

經過在網上辛辛苦苦地尋找,終於找到一種方法,可以實現1毫秒級延時,(理論上該方法可以達到納秒級延時,可實際上意義不大,當你調用延時函數的時候,函數調用的開銷就需要幾十個納秒)。代碼經過整理,已經用VC寫成一個動態連結程式庫,VB,VC都可以調用,當然,為了在VC裡面使用,把它改寫成一個類更好。

該連結庫一共有三大功能:1、使用GetFreq()函數擷取CPU主頻;2、使用asmDelay()函數精確延時;3、使用stopWatchBegin()函數開啟秒錶(stopWatch是一個詞,秒錶),stopWatchEnd()關閉秒錶,並且返回從開啟秒錶到關閉秒錶一共經過了多少毫秒,該功能適用於代碼效能統計工作。

 

代碼如下:----------------------------------------------------------------------------------------------------------------------------

// HighDelay.cpp : Defines the initialization routines for the DLL.
//

//
//高精度定時器的代碼.
//由於精度太高,只適合於小於1秒(即1000ms)的延時(超過一秒,unsigned long 型變數會溢出)。
//大於1秒的延時請用多個asmDelay組合;
//延時原理,通過CPU指令周期寄存器,CPU主頻,通過計算,延時一定的指令周期。理論來源:網路。
//作者:。
//

#include "stdafx.h"
#include "HighDelay.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//
// Note!
//
//  If this DLL is dynamically linked against the MFC
//  DLLs, any functions exported from this DLL which
//  call into MFC must have the AFX_MANAGE_STATE macro
//  added at the very beginning of the function.
//
//  For example:
//
//  extern "C" BOOL PASCAL EXPORT ExportedFunction()
//  {
//   AFX_MANAGE_STATE(AfxGetStaticModuleState());
//   // normal function body here
//  }
//
//  It is very important that this macro appear in each
//  function, prior to any calls into MFC.  This means that
//  it must appear as the first statement within the
//  function, even before any object variable declarations
//  as their constructors may generate calls into the MFC
//  DLL.
//
//  Please see MFC Technical Notes 33 and 58 for additional
//  details.
//

/////////////////////////////////////////////////////////////////////////////
// CHighDelayApp

BEGIN_MESSAGE_MAP(CHighDelayApp, CWinApp)
 //{{AFX_MSG_MAP(CHighDelayApp)
  // NOTE - the ClassWizard will add and remove mapping macros here.
  //    DO NOT EDIT what you see in these blocks of generated code!
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CHighDelayApp construction

CHighDelayApp::CHighDelayApp()
{
 // TODO: add construction code here,
 // Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CHighDelayApp object

CHighDelayApp theApp;

//以下是自訂代碼://///////////////////////////////////////////////////////

//模組層級的變數CPU頻率,MHZ,實際用的時候要轉換成HZ;
unsigned int m_Freq =0;
__int64  m_stopWatchStartTimeStamp;//秒錶開啟時的CPU時間戳記

//擷取CPU頻率的內建函式  //MHz
int GetFrequency(void)
{
 LARGE_INTEGER CurrTicks, TicksCount;
 __int64 iStartCounter, iStopCounter;

 DWORD dwOldProcessP = GetPriorityClass(GetCurrentProcess());
 DWORD dwOldThreadP = GetThreadPriority(GetCurrentThread());

 SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);

 QueryPerformanceFrequency(&TicksCount);
 QueryPerformanceCounter(&CurrTicks);

 TicksCount.QuadPart /= 16;
 TicksCount.QuadPart += CurrTicks.QuadPart;

 _asm rdtsc
 _asm mov DWORD PTR iStartCounter, EAX
 _asm mov DWORD PTR (iStartCounter+4), EDX

 while(CurrTicks.QuadPart<TicksCount.QuadPart)
 QueryPerformanceCounter(&CurrTicks);

 _asm rdtsc
 _asm mov DWORD PTR iStopCounter, EAX
 _asm mov DWORD PTR (iStopCounter + 4), EDX

 SetThreadPriority(GetCurrentThread(), dwOldThreadP);
 SetPriorityClass(GetCurrentProcess(), dwOldProcessP);

 return (int)((iStopCounter-iStartCounter)/62500);
}

unsigned __int64 GetCycleCount()//讀取CPU的指令周期寄存器
{
 __asm RDTSC
}

//對外的API,下列函數請在.def檔案中匯出/////////////////////////////////////////////////////

//初始化延時串連庫;要想正確的使用延時函數,必須在之前調用該函數。建議在程式初始化的時候調用一次,
//並且只需要調一次。該函數的執行需要幾個毫秒。
void __stdcall InitAsmDelay()
{
 m_Freq= GetFrequency();
}

//對外介面,擷取CPU主頻;
int __stdcall GetFreq()
{ if ( 0==m_Freq )
  return m_Freq= GetFrequency();
 else
  return m_Freq;
}

//高精度延時函數;msec毫秒數;
void __stdcall asmDelay(unsigned long msec)
{
 //把毫秒數轉成刻度數
 unsigned long msecToCycle =( m_Freq * 1000)*msec;
 //記錄當前刻度
 unsigned long tmr=(unsigned long) GetCycleCount();
 //延時,直到刻度數滿足預定的毫秒數;
 while((unsigned long)GetCycleCount()-tmr < msecToCycle)
 {
 }
}

//秒錶函數,用於計算某段代碼執行了多長時間;
//開啟秒錶
void __stdcall stopWatchBegin()
{
 m_stopWatchStartTimeStamp = GetCycleCount();
}
//關閉秒錶,同時返回從開啟秒錶到關閉秒錶共經過了多長毫秒0;
long __stdcall stopWatchEnd()
{
 __int64 TimeStampDiff = GetCycleCount()-m_stopWatchStartTimeStamp;
 return TimeStampDiff / (m_Freq *1000);
}

--------------------------------------------------------------------------------------------------------------------------------------------

asmDelay()函數的延時原理很簡單,就是一個比例式。CPU主頻的定義是:一秒鐘(1000毫秒)的時間內CPU執行的指令周期數。假定CPU主頻是 a HZ,那麼假如我們想要延時 b毫秒,那麼需要等待多少個指令周期呢?假設是X個指令周期,這是個比例式:

a / 1000 = x / b

求得X= (a *b )/ 1000

由於我們的連結庫中的變數m_Freq的單位是MHZ,所以要乘以1000 000才是HZ。

 所以函數asmDelay()中,要根據想要延時的毫秒數求得需要等待的刻度數,要用如下這句代碼:
 unsigned long msecToCycle =( m_Freq * 1000000*msec)/1000;

經過簡化後,就變成了這句代碼:
 unsigned long msecToCycle =( m_Freq * 1000)*msec;

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.