怎麼才能讓你的程式延時 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;