在流媒体系统中,获得精确的时间用于时间戳标记,是一个非常关键的点。编码时要根据流的采集的实际时间来标记,解码播放时,则要相对于系统时钟的节奏来安排流的呈现时间,也就是说要得到当前系统的时间,和流的时间对比,确定是不是时候该让某一帧数据出场表演了,需要一个tick,精度大约为10ms。
开放源码ffmpeg里用到的获取时间的函数为av_gettime,它的实现如下:
int64_t av_gettime(void)
{
#if HAVE_GETTIMEOFDAY
struct timeval tv;
gettimeofday(&tv, NULL);
return (int64_t)tv.tv_sec * 1000000 + tv.tv_usec;
#elif HAVE_GETSYSTEMTIMEASFILETIME
FILETIME ft;
int64_t t;
GetSystemTimeAsFileTime(&ft);
t = (int64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime;
return t / 10 - 11644473600000000; /* Jan 1, 1601 */
#else
return -1;
#endif
}
它用到了gettimeofday,这是对linux一类的系统而言的。如果是windows就用到FILETIME结构体。
再来看看live555的源码里是怎么实现的:
#if defined(__WIN32__) || defined(_WIN32)
// For Windoze, we need to implement our own gettimeofday()
#if !defined(_WIN32_WCE)
#include <sys/timeb.h>
#endif
int gettimeofday(struct timeval* tp, int* /*tz*/) {
#if defined(_WIN32_WCE)
/* FILETIME of Jan 1 1970 00:00:00. */
static const unsigned __int64 epoch = 116444736000000000LL;
FILETIME file_time;
SYSTEMTIME system_time;
ULARGE_INTEGER ularge;
GetSystemTime(&system_time);
SystemTimeToFileTime(&system_time, &file_time);
ularge.LowPart = file_time.dwLowDateTime;
ularge.HighPart = file_time.dwHighDateTime;
tp->tv_sec = (long) ((ularge.QuadPart - epoch) / 10000000L);
tp->tv_usec = (long) (system_time.wMilliseconds * 1000);
#else
static LARGE_INTEGER tickFrequency, epochOffset;
// For our first call, use "ftime()", so that we get a time with a proper epoch.
// For subsequent calls, use "QueryPerformanceCount()", because it's more fine-grain.
static Boolean isFirstCall = True;
LARGE_INTEGER tickNow;
QueryPerformanceCounter(&tickNow);
if (isFirstCall) {
struct timeb tb;
ftime(&tb);
tp->tv_sec = tb.time;
tp->tv_usec = 1000*tb.millitm;
// Also get our counter frequency:
QueryPerformanceFrequency(&tickFrequency);
// And compute an offset to add to subsequent counter times, so we get a proper epoch:
epochOffset.QuadPart
= tb.time*tickFrequency.QuadPart + (tb.millitm*tickFrequency.QuadPart)/1000 - tickNow.QuadPart;
isFirstCall = False; // for next time
} else {
// Adjust our counter time so that we get a proper epoch:
tickNow.QuadPart += epochOffset.QuadPart;
tp->tv_sec = (long) (tickNow.QuadPart / tickFrequency.QuadPart);
tp->tv_usec = (long) (((tickNow.QuadPart % tickFrequency.QuadPart) * 1000000L) / tickFrequency.QuadPart);
}
#endif
return 0;
}
#endif
可以看出来是用到了QueryPerformCounter和QuerPerformanceFrequency来得到的,而对linux类的系统还是用gettimeofday。
最后看看vlc是怎么弄的:
/**
* Precision monotonic clock.
*
* In principles, the clock has a precision of 1 MHz. But the actual resolution
* may be much lower, especially when it comes to sleeping with mwait() or
* msleep(). Most general-purpose operating systems provide a resolution of
* only 100 to 1000 Hz.
*
* @warning The origin date (time value "zero") is not specified. It is
* typically the time the kernel started, but this is platform-dependent.
* If you need wall clock time, use gettimeofday() instead.
*
* @return a timestamp in microseconds.
*/
mtime_t mdate (void)
{
#if (_POSIX_TIMERS > 0)
struct timespec ts;
vlc_clock_setup ();
if (unlikely(clock_gettime (vlc_clock_id, &ts) != 0))
abort ();
return (INT64_C(1000000) * ts.tv_sec) + (ts.tv_nsec / 1000);
#else
struct timeval tv;
if (unlikely(gettimeofday (&tv, NULL) != 0))
abort ();
return (INT64_C(1000000) * tv.tv_sec) + tv.tv_usec;
#endif
}
这是在linux类系统下的实现,不作评论。
vlc对于win32平台的处理如下:
static mtime_t mdate_interrupt (void)
{
ULONGLONG ts;
BOOL ret;
#if (_WIN32_WINNT >= 0x0601)
ret = QueryUnbiasedInterruptTime (&ts);
#else
ret = clk.interrupt.query (&ts);
#endif
if (unlikely(!ret))
abort ();
/* hundreds of nanoseconds */
static_assert ((10000000 % CLOCK_FREQ) == 0, "Broken frequencies ratio");
return ts / (10000000 / CLOCK_FREQ);
}
static mtime_t mdate_tick (void)
{
#if (_WIN32_WINNT >= 0x0600)
ULONGLONG ts = GetTickCount64 ();
#else
ULONGLONG ts = clk.tick.get ();
#endif
/* milliseconds */
static_assert ((CLOCK_FREQ % 1000) == 0, "Broken frequencies ratio");
return ts * (CLOCK_FREQ / 1000);
}
#if !VLC_WINSTORE_APP
#include <mmsystem.h>
static mtime_t mdate_multimedia (void)
{
DWORD ts = timeGetTime ();
/* milliseconds */
static_assert ((CLOCK_FREQ % 1000) == 0, "Broken frequencies ratio");
return ts * (CLOCK_FREQ / 1000);
}
#endif
static mtime_t mdate_perf (void)
{
/* We don't need the real date, just the value of a high precision timer */
LARGE_INTEGER counter;
if (!QueryPerformanceCounter (&counter))
abort ();
/* Convert to from (1/freq) to microsecond resolution */
/* We need to split the division to avoid 63-bits overflow */
lldiv_t d = lldiv (counter.QuadPart, clk.perf.freq.QuadPart);
return (d.quot * 1000000) + ((d.rem * 1000000) / clk.perf.freq.QuadPart);
}
static mtime_t mdate_wall (void)
{
FILETIME ts;
ULARGE_INTEGER s;
#if (_WIN32_WINNT >= 0x0602) && !VLC_WINSTORE_APP
GetSystemTimePreciseAsFileTime (&ts);
#else
GetSystemTimeAsFileTime (&ts);
#endif
s.LowPart = ts.dwLowDateTime;
s.HighPart = ts.dwHighDateTime;
/* hundreds of nanoseconds */
static_assert ((10000000 % CLOCK_FREQ) == 0, "Broken frequencies ratio");
return s.QuadPart / (10000000 / CLOCK_FREQ);
}
有几个不同的版本可以供选择。