CaptureServer.cpp
上传用户:liguizhu
上传日期:2015-11-01
资源大小:2422k
文件大小:17k
源码类别:

P2P编程

开发平台:

Visual C++

  1. /*
  2. *  Openmysee
  3. *
  4. *  This program is free software; you can redistribute it and/or modify
  5. *  it under the terms of the GNU General Public License as published by
  6. *  the Free Software Foundation; either version 2 of the License, or
  7. *  (at your option) any later version.
  8. *
  9. *  This program is distributed in the hope that it will be useful,
  10. *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. *  GNU General Public License for more details.
  13. *
  14. *  You should have received a copy of the GNU General Public License
  15. *  along with this program; if not, write to the Free Software
  16. *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  17. *
  18. */
  19. #include "stdafx.h"
  20. #include "CaptureServer.h"
  21. // { Implementation of CaptureServer
  22. CaptureServer::CaptureServer()
  23. : parentWindow(0), videoData(0), audioData(0), passwordStatus(2),
  24.   m_llVideoTime(0), m_llAudioTime(0), m_bTransDataEnd(FALSE),m_bIsOnlyOnePin(FALSE)
  25. {
  26. memset(&videoStruct, 0, sizeof(videoStruct));
  27. memset(&audioStruct, 0, sizeof(audioStruct));
  28. cfgData.reconnectSecond = 120;
  29. }
  30. CaptureServer::~CaptureServer()
  31. {
  32. for(UINT i = 0; i < bufferList.size();++i)
  33. {
  34. delete clientList[i];
  35. delete bufferList[i];
  36. delete logList[i];
  37. }
  38. bufferList.clear();
  39. clientList.clear();
  40. logList.clear();
  41. TE_CleanupLibrary();
  42. SAFE_ARRAYDELETE(audioData);
  43. SAFE_ARRAYDELETE(videoData);
  44. ::DeleteCriticalSection(&m_SamQueueCSec);
  45. }
  46. BOOL CaptureServer::Init()
  47. {
  48. TE_InitLibrary(); 
  49. if(!LoadConfigFile())
  50. return FALSE;
  51. logList.resize(cfgData.spAddress.size());
  52. bufferList.resize(cfgData.spAddress.size());
  53. clientList.resize(cfgData.spAddress.size());
  54. NormalAddress addr;
  55. addr.sin_family = AF_INET;
  56. addr.sin_port = htons(SP4CS_PORT);
  57. int i = 0;
  58. for(list<string>::iterator it = cfgData.spAddress.begin(); it != cfgData.spAddress.end(); ++it)
  59. {
  60. addr.sin_addr.s_addr = inet_addr(it->data());
  61. logList[i] = new LogMgr();
  62. logList[i]->Init();
  63. logList[i]->PrintTime(TRUE);
  64. bufferList[i] = new BufferMgr(this);
  65. if(!bufferList[i])
  66. return FALSE;
  67. clientList[i] = new SPClient(this, addr, bufferList[i], logList[i]);
  68. if(!clientList[i])
  69. return FALSE;
  70. i++;
  71. Sleep(100); // 防止按照时间取得文件名相同
  72. }
  73. totalBytes = 0;
  74. GetSystemTime(&startTime);
  75. ::InitializeCriticalSection(&m_SamQueueCSec);
  76. //cfgData.savePath = "f:\";
  77. return TRUE;
  78. }
  79. void CaptureServer::Stop()
  80. {
  81. /*此处之所以直接PutSample而不做任何限制是因为
  82.   CaptureServer的PutSample可以保证当音视频流
  83.   全部结束时一定能够把队列取空。如果是中途
  84.   应用层强制停止,有可能会造成某路队列数据
  85.   非空,但这是没法避免的,此时此处处理或者
  86.   是把数据丢掉只保留前面的同步后数据,或者
  87.   把剩余队列中不同步数据也写入,此处采用后者。
  88.   在依靠底层返回EC_COMPLETE来触发Stop是不会
  89.   出现此现象的。
  90. */
  91. ::EnterCriticalSection(&m_SamQueueCSec);
  92. int ii = m_AudioSamQueue.size();
  93. int iii = m_VideoSamQueue.size();
  94. SAMPLEDATA *pSAMPLEDATA = NULL;
  95. while(m_AudioSamQueue.size() > 0 && m_VideoSamQueue.size() > 0)
  96. {
  97. if (m_AudioSamQueue.front()->samplehr.start >= m_VideoSamQueue.front()->samplehr.start)
  98. {
  99. pSAMPLEDATA = m_VideoSamQueue.front();
  100. m_zzlWriter.PutSample(pSAMPLEDATA->samplehr, pSAMPLEDATA->pData);
  101. m_VideoSamQueue.pop();
  102. delete pSAMPLEDATA->pData;
  103. pSAMPLEDATA->pData = NULL;
  104. delete pSAMPLEDATA;
  105. pSAMPLEDATA = NULL;
  106. }
  107. else
  108. {
  109. pSAMPLEDATA = m_AudioSamQueue.front();
  110. m_zzlWriter.PutSample(pSAMPLEDATA->samplehr, pSAMPLEDATA->pData);
  111. m_AudioSamQueue.pop();
  112. delete pSAMPLEDATA->pData;
  113. pSAMPLEDATA->pData = NULL;
  114. delete pSAMPLEDATA;
  115. pSAMPLEDATA = NULL;
  116. }
  117. }
  118. int iiii = m_VideoSamQueue.size();
  119. int iiiii = m_AudioSamQueue.size();
  120. while(m_AudioSamQueue.size() > 0)
  121. {
  122. SAMPLEDATA *pSAMPLEDATA = m_AudioSamQueue.front();
  123. m_zzlWriter.PutSample(pSAMPLEDATA->samplehr, pSAMPLEDATA->pData);
  124. m_AudioSamQueue.pop();
  125. delete pSAMPLEDATA->pData;
  126. delete pSAMPLEDATA;
  127. }
  128. while(m_VideoSamQueue.size() > 0)
  129. {
  130. SAMPLEDATA *pSAMPLEDATA = m_VideoSamQueue.front();
  131. m_zzlWriter.PutSample(pSAMPLEDATA->samplehr, pSAMPLEDATA->pData);
  132. m_VideoSamQueue.pop();
  133. delete pSAMPLEDATA->pData;
  134. delete pSAMPLEDATA;
  135. }
  136. m_bTransDataEnd = TRUE;
  137. TRACE5("CaptureServer::Stop处理剩余包n");
  138. ::LeaveCriticalSection(&m_SamQueueCSec);
  139. /*
  140. ::EnterCriticalSection(&m_SamQueueCSec);
  141. int ii = m_AudioSamQueue.size();
  142. while(m_AudioSamQueue.size() > 0)
  143. {
  144. SAMPLEDATA *pSAMPLEDATA = m_AudioSamQueue.front();
  145. m_zzlWriter.PutSample(pSAMPLEDATA->samplehr, pSAMPLEDATA->pData);
  146. m_AudioSamQueue.pop();
  147. delete pSAMPLEDATA->pData;
  148. delete pSAMPLEDATA;
  149. }
  150. int iii = m_VideoSamQueue.size();
  151. while(m_VideoSamQueue.size() > 0)
  152. {
  153. SAMPLEDATA *pSAMPLEDATA = m_VideoSamQueue.front();
  154. m_zzlWriter.PutSample(pSAMPLEDATA->samplehr, pSAMPLEDATA->pData);
  155. m_VideoSamQueue.pop();
  156. delete pSAMPLEDATA->pData;
  157. delete pSAMPLEDATA;
  158. }
  159. m_bTransDataEnd = TRUE;
  160. ::LeaveCriticalSection(&m_SamQueueCSec);
  161. */
  162. }
  163. BOOL CaptureServer::LoadConfigFile()
  164. {
  165. char buf[MAX_PATH];
  166. GetModuleFileName(NULL, buf, MAX_PATH);
  167. string path = buf;
  168. int index = path.find_last_of('\', path.length());
  169. path.resize(index+1);
  170. ConfigFile cfgFile(path + "CaptureServer.cfg");
  171. if(cfgFile.fileNotFound) {
  172. MessageBox(parentWindow, "无法找到配置文件CaptureServer.cfg。", "错误", MB_OK|MB_ICONERROR);
  173. return FALSE;
  174. }
  175. string strSPList = cfgFile.Value("CaptureServer", "spAddress");
  176. if(cfgFile.stringNotFound) {
  177. MessageBox(parentWindow, "配置文件中必须存在spAddress一项。", "错误", MB_OK|MB_ICONERROR);
  178. return FALSE;
  179. }
  180. cfgData.chnlStr = cfgFile.Value("CaptureServer", "channelName");
  181. if(cfgFile.stringNotFound) {
  182. MessageBox(parentWindow, "配置文件中必须存在channelName一项。", "错误", MB_OK|MB_ICONERROR);
  183. return FALSE;
  184. }
  185. cfgData.savePath = cfgFile.Value("CaptureServer", "savepath");
  186. if (false == cfgData.savePath.empty())
  187. {
  188. //补路径后的斜杠
  189. string::const_iterator litSavePath = cfgData.savePath.end();
  190. --litSavePath;
  191. if ('\' != static_cast<BYTE>(*litSavePath))
  192. {
  193. cfgData.savePath += "\"; 
  194. }
  195. //去除路径前的空格
  196. while (' ' == *cfgData.savePath.c_str())
  197. {
  198. cfgData.savePath = cfgData.savePath.c_str() + 1;
  199. }
  200. }
  201. string lstrOnlyAudio = cfgFile.Value("CaptureServer", "isonlyaudio");
  202. if (false == lstrOnlyAudio.empty())
  203. {
  204. //去除路径前的空格
  205. while (' ' == *lstrOnlyAudio.c_str())
  206. {
  207. lstrOnlyAudio = lstrOnlyAudio.c_str() + 1;
  208. }
  209. //lstrOnlyAudio = lstrOnlyAudio.
  210. if ("true" == lstrOnlyAudio)
  211. {
  212. SetAudioOrVideoOnly(TRUE);
  213. }
  214. else if ("false" != lstrOnlyAudio)
  215. {
  216. MessageBox(NULL,"isonlyaudio 项是非法字符","注意", MB_OK|MB_ICONSTOP);
  217. return FALSE;
  218. }
  219. }
  220. string reconnect = cfgFile.Value("CaptureServer", "ReconnectTime");
  221. if(!cfgFile.fileNotFound) {
  222. cfgData.reconnectSecond = atoi(reconnect.data());
  223. }
  224. string id = cfgFile.Value("CaptureServer","userID");
  225. if(cfgFile.fileNotFound){
  226. MessageBox(parentWindow, "配置文件中必须存在userID一项。", "错误", MB_OK|MB_ICONERROR);
  227. return FALSE;
  228. }
  229. cfgData.userID = atoi(id.data());
  230. //读入的是32位密码
  231. cfgData.password = cfgFile.Value("CaptureServer", "password");
  232. if(cfgFile.fileNotFound){
  233. MessageBox(parentWindow, "配置文件中必须存在password一项目。", "错误", MB_OK|MB_ICONERROR);
  234. return FALSE;
  235. }
  236. string::const_iterator it = cfgData.chnlStr.begin();
  237. for(; it != cfgData.chnlStr.end(); it++)
  238. {
  239. BYTE tmpChar = static_cast<BYTE>(*it);
  240. if(isdigit(tmpChar) || isalpha(tmpChar))
  241. continue;
  242. if(tmpChar == '[' || tmpChar == ']')
  243. continue;
  244. //GB18030-2000编码标准的第一个字节的编码范围在0x81 ~ 0xFE之间;第二个字节的编码范围在0x40 ~ 0x7E和0x80 ~ 0xFE之间。
  245. //Big5编码标准的第一个字节的编码范围在0xA1 ~ 0xF9 之间;第二个字节的编码范围在0x40 ~ 0x7E 和0xA1 ~ 0xFE之间。
  246. //两者的并集是第一个字节[0x81, 0xfe],第二个字节[0x40, 0x7e]、[0x80, 0xfe];
  247. if(tmpChar>=0x81 && tmpChar <= 0xfe && it+1 != cfgData.chnlStr.end()) {
  248. tmpChar = static_cast<BYTE>(*(it+1));
  249. if( (tmpChar>=0x40 && tmpChar <= 0x7e) || (tmpChar>=0x80 && tmpChar <= 0xfe))
  250. {
  251. it++; // 跳过一个汉字字符
  252. continue;
  253. }
  254. }
  255. MessageBox(parentWindow, "配置文件中channelName一项只能由字母、数字、汉字和方括弧组成。", "错误", MB_OK|MB_ICONERROR);
  256. return FALSE;
  257. }
  258. cfgData.canLogin = TRUE;
  259. istrstream is(strSPList.data());
  260. string line;
  261. string ip;
  262. string port;
  263. NormalAddress tmpAddr;
  264. while(getline(is,line, ':')) {
  265. if(!line.length())
  266. continue;
  267. tmpAddr.sin_addr.s_addr = TE_GetIP(line.data(), TRUE);
  268. if(tmpAddr.sin_addr.s_addr == INADDR_NONE)
  269. continue;
  270. cfgData.spAddress.push_back(inet_ntoa(tmpAddr.sin_addr));
  271. }
  272. if(cfgData.chnlStr.length() >= 64) {
  273. MessageBox(parentWindow, "配置文件中channelName一项其长度必须小于64字符。", "错误", MB_OK|MB_ICONERROR);
  274. return FALSE;
  275. }
  276. if(cfgData.password.length() != 32) {
  277. MessageBox(parentWindow, "配置文件中Password一项其长度必须等于32字符。", "错误", MB_OK|MB_ICONERROR);
  278. return FALSE;
  279. }
  280. return TRUE;
  281. }
  282. BOOL CaptureServer::SetFormatData(TVMEDIATYPESECTION& tv, BYTE* data, BOOL isAudio)
  283. {
  284. if(!data)
  285. return FALSE;
  286. // 因为SetAudioOrVideoOnly比SetFormatData先被调用,所以要在这里记录
  287. bool bIsThisPinOnly = isAudio?audioStruct.bThisPinOnly:videoStruct.bThisPinOnly;
  288. // 分别复制视频和音频数据
  289. if(!isAudio) {
  290. SAFE_ARRAYDELETE(videoData);
  291. memcpy(&videoStruct, &tv, sizeof(TVMEDIATYPESECTION));
  292. if(videoStruct.cbFormat > 512)
  293. return FALSE;
  294. videoData = new BYTE[videoStruct.cbFormat];
  295. memcpy(videoData, data, videoStruct.cbFormat);
  296. // 恢复
  297. videoStruct.bThisPinOnly = bIsThisPinOnly;
  298. }
  299. else {
  300. SAFE_ARRAYDELETE(audioData);
  301. memcpy(&audioStruct, &tv, sizeof(TVMEDIATYPESECTION));
  302. if(audioStruct.cbFormat > 512)
  303. return FALSE;
  304. audioData = new BYTE[audioStruct.cbFormat];
  305. memcpy(audioData, data, audioStruct.cbFormat);
  306. // 恢复
  307. audioStruct.bThisPinOnly = bIsThisPinOnly;
  308. }
  309. for(UINT i = 0; i < bufferList.size();++i)
  310. {
  311. if(!bufferList[i]->AttachMediaDataToCurrentBlock(tv, data, isAudio, logList[i]))
  312. return FALSE;
  313. }
  314. if((audioData&&videoData) || // 如果视频和音频的数据都有了
  315. (audioData&&audioStruct.bThisPinOnly) || // 如果只有音频
  316. (videoData&&videoStruct.bThisPinOnly)) { // 如果只有视频
  317. // 如果需要保存zzl文件,则建立zzl文件
  318. if(!cfgData.savePath.empty()) {
  319. if(!m_zzlWriter.Init(cfgData.savePath, cfgData.chnlStr)) {
  320. //MessageBox(parentWindow, "无法建立节目文件!", "错误", MB_OK);
  321. return 0;
  322. }
  323. if(!m_zzlWriter.SetMediaType(audioStruct, audioData, videoStruct, videoData)) {
  324. MessageBox(parentWindow, "无法建立节目文件!SetMediaType Error!", "错误", MB_OK);
  325. }
  326. }
  327. }
  328. return TRUE;
  329. }
  330. BOOL CaptureServer::GetFormatData(TVMEDIATYPESECTION& tv, PBYTE& data, BOOL isAudio)
  331. {
  332. if(!isAudio)
  333. {
  334. if(audioStruct.bThisPinOnly)
  335. {
  336. memset(&tv, 0, sizeof(tv));
  337. data = NULL;
  338. return TRUE;
  339. }
  340. if(!videoData)
  341. return FALSE;
  342. memcpy(&tv, &videoStruct, sizeof(TVMEDIATYPESECTION));
  343. data = new BYTE[videoStruct.cbFormat];
  344. memcpy(data, videoData, videoStruct.cbFormat);
  345. }
  346. else
  347. {
  348. if(videoStruct.bThisPinOnly)
  349. {
  350. memset(&tv, 0, sizeof(tv));
  351. data = NULL;
  352. return TRUE;
  353. }
  354. if(!audioData)
  355. return FALSE;
  356. memcpy(&tv, &audioStruct, sizeof(TVMEDIATYPESECTION));
  357. data = new BYTE[audioStruct.cbFormat];
  358. memcpy(data, audioData, audioStruct.cbFormat);
  359. }
  360. return TRUE;
  361. }
  362. void CaptureServer::SetAudioOrVideoOnly(BOOL isAudio)
  363. {
  364. if(isAudio)
  365. {
  366. audioStruct.bThisPinOnly = true;
  367. }
  368. else
  369. {
  370. videoStruct.bThisPinOnly = true;
  371. }
  372. m_bIsOnlyOnePin = TRUE;
  373. m_zzlWriter.SetIsSingleAudio(isAudio);
  374. }
  375. BOOL CaptureServer::PutSample(const SampleHeader& header, BYTE* pData)
  376. {
  377. totalBytes += header.size;
  378. for(UINT i = 0; i < bufferList.size();++i)
  379. {
  380. if(!bufferList[i]->PutSample(header, pData, logList[i]))
  381. return FALSE;
  382. }
  383. if(!cfgData.savePath.empty())
  384. {
  385. ::EnterCriticalSection(&m_SamQueueCSec);
  386. //if(!m_bTransDataEnd)
  387. {
  388. if(audioStruct.bThisPinOnly || videoStruct.bThisPinOnly)
  389. {
  390. if(!m_zzlWriter.PutSample(header, pData))
  391. {
  392. ASSERT(FALSE);
  393. }
  394. }
  395. else
  396. {
  397. if(header.bAudioSample)
  398. {
  399. if(m_llAudioTime >= m_llVideoTime + MAX_AVDELAY)
  400. {
  401. BYTE *pSamData = new BYTE[header.size];
  402. SAMPLEDATA *pSAMPLEDATA = new SAMPLEDATA;
  403. memset(pSAMPLEDATA, 0, sizeof(SAMPLEDATA));
  404. memcpy(&(pSAMPLEDATA->samplehr), &header, sizeof(SampleHeader));
  405. memcpy(pSamData, pData, header.size);
  406. pSAMPLEDATA->pData = pSamData;
  407. m_AudioSamQueue.push(pSAMPLEDATA);
  408. }
  409. else
  410. {
  411. if(m_AudioSamQueue.size() > 0)
  412. {
  413. BYTE *pSamData = new BYTE[header.size];
  414. SAMPLEDATA *pSAMPLEDATA = new SAMPLEDATA;
  415. memset(pSAMPLEDATA, 0, sizeof(SAMPLEDATA));
  416. memcpy(&(pSAMPLEDATA->samplehr), &header, sizeof(SampleHeader));
  417. memcpy(pSamData, pData, header.size);
  418. pSAMPLEDATA->pData = pSamData;
  419. m_AudioSamQueue.push(pSAMPLEDATA);
  420. SAMPLEDATA *pCurrentSampleData = m_AudioSamQueue.front();
  421. m_AudioSamQueue.pop();
  422. if(!m_zzlWriter.PutSample(pCurrentSampleData->samplehr, pCurrentSampleData->pData))
  423. {
  424. ASSERT(FALSE);
  425. }
  426. m_llAudioTime = pCurrentSampleData->samplehr.start;
  427. delete pCurrentSampleData->pData;
  428. delete pCurrentSampleData;
  429. }
  430. else
  431. {
  432. if(!m_zzlWriter.PutSample(header, pData))
  433. {
  434. ASSERT(FALSE);
  435. }
  436. m_llAudioTime = header.start;
  437. }
  438. }
  439. //以音频的动力拉动视频符合误差范围的Sample
  440. while(m_VideoSamQueue.size() > 0 && m_VideoSamQueue.front()->samplehr.start <= m_llAudioTime + MAX_AVDELAY)
  441. {
  442. SAMPLEDATA *pCurrentSampleData = m_VideoSamQueue.front();
  443. if(!m_zzlWriter.PutSample(pCurrentSampleData->samplehr, pCurrentSampleData->pData))
  444. {
  445. ASSERT(FALSE);
  446. }
  447. m_VideoSamQueue.pop();
  448. m_llVideoTime = pCurrentSampleData->samplehr.start;
  449. delete pCurrentSampleData->pData;
  450. delete pCurrentSampleData;
  451. }
  452. }
  453. else
  454. {
  455. if(m_llVideoTime >= m_llAudioTime + MAX_AVDELAY)
  456. {
  457. BYTE *pSamData = new BYTE[header.size];
  458. SAMPLEDATA *pSAMPLEDATA = new SAMPLEDATA;
  459. memset(pSAMPLEDATA, 0, sizeof(SAMPLEDATA));
  460. memcpy(&(pSAMPLEDATA->samplehr), &header, sizeof(SampleHeader));
  461. memcpy(pSamData, pData, header.size);
  462. pSAMPLEDATA->pData = pSamData;
  463. m_VideoSamQueue.push(pSAMPLEDATA);
  464. }
  465. else
  466. {
  467. if(m_VideoSamQueue.size() > 0)
  468. {
  469. BYTE *pSamData = new BYTE[header.size];
  470. SAMPLEDATA *pSAMPLEDATA = new SAMPLEDATA;
  471. memset(pSAMPLEDATA, 0, sizeof(SAMPLEDATA));
  472. memcpy(&(pSAMPLEDATA->samplehr), &header, sizeof(SampleHeader));
  473. memcpy(pSamData, pData, header.size);
  474. pSAMPLEDATA->pData = pSamData;
  475. m_VideoSamQueue.push(pSAMPLEDATA);
  476. SAMPLEDATA *pCurrentSampleData = m_VideoSamQueue.front();
  477. m_VideoSamQueue.pop();
  478. if(!m_zzlWriter.PutSample(pCurrentSampleData->samplehr, pCurrentSampleData->pData))
  479. {
  480. ASSERT(FALSE);
  481. }
  482. m_llVideoTime = pCurrentSampleData->samplehr.start;
  483. delete pCurrentSampleData->pData;
  484. delete pCurrentSampleData;
  485. }
  486. else
  487. {
  488. if(!m_zzlWriter.PutSample(header, pData))
  489. {
  490. ASSERT(FALSE);
  491. }
  492. m_llVideoTime = header.start;
  493. }
  494. }
  495. //以视频的动力拉动音频符合误差范围的Sample
  496. while(m_AudioSamQueue.size() > 0 && m_AudioSamQueue.front()->samplehr.start <= m_llVideoTime + MAX_AVDELAY)
  497. {
  498. SAMPLEDATA *pCurrentSampleData = m_AudioSamQueue.front();
  499. if(!m_zzlWriter.PutSample(pCurrentSampleData->samplehr, pCurrentSampleData->pData))
  500. {
  501. ASSERT(FALSE);
  502. }
  503. m_AudioSamQueue.pop();
  504. m_llAudioTime = pCurrentSampleData->samplehr.start;
  505. delete pCurrentSampleData->pData;
  506. delete pCurrentSampleData;
  507. }
  508. }
  509. }
  510. }
  511.   ::LeaveCriticalSection(&m_SamQueueCSec);
  512. }
  513. return TRUE;
  514. }
  515. float CaptureServer::GetSpeedInKBPS()
  516. {
  517. if(!cfgData.savePath.empty())
  518. return (float)m_zzlWriter.GetBitRate(); // 由于压缩用的时间可能超过媒体文件的实际长度,所以此处统计的码率更加准确
  519. SYSTEMTIME stNow;
  520. GetSystemTime(&stNow);
  521. LARGE_INTEGER llNow;
  522. SystemTimeToFileTime(&stNow, (FILETIME*)&llNow);
  523. LARGE_INTEGER llStart;
  524. SystemTimeToFileTime(&startTime, (FILETIME*)&llStart);
  525. if(llNow.QuadPart - llStart.QuadPart > 0) {
  526. DbgLog((LOG_TRACE, 5, TEXT("Speed: %f"), totalBytes/(double)((llNow.QuadPart-llStart.QuadPart)/10000) ));
  527. return static_cast<float>(totalBytes/(double)((llNow.QuadPart-llStart.QuadPart)/10000));
  528. }
  529. else
  530. return 0.0;
  531. }
  532. // Implementation of CaptureServer }