Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1672 lines
50 KiB

  1. /*==========================================================================
  2. *
  3. * Copyright (C) 1999 Microsoft Corporation. All Rights Reserved.
  4. *
  5. * File: ClientRecordSubSystem.cpp
  6. * Content: Recording sub-system.
  7. *
  8. * History:
  9. * Date By Reason
  10. * ==== == ======
  11. * 07/19/99 rodtoll Created
  12. * 07/22/99 rodtoll Added support for multicast/ client/server sessions
  13. * 08/02/99 rodtoll Added new silence detection support.
  14. * 08/04/99 rodtoll Added guard to new silence detection so only runs when
  15. * appropriate
  16. * 08/25/99 rodtoll General Cleanup/Modifications to support new
  17. * compression sub-system.
  18. * 08/26/99 rodtoll Set msgnum to 0 in constructor.
  19. * 08/26/99 rodtoll Added check for record mute flag to fix record muting\
  20. * 08/27/99 rodtoll General cleanup/Simplification of recording subsystem
  21. * Added reset of message when target changes
  22. * Fixed recording start/stop notifications
  23. * Fixed level readings when using voice activation
  24. * 08/30/99 rodtoll Fixed double record stop messages when recording muted
  25. * 09/01/99 rodtoll Re-activated auto volume control
  26. * 09/02/99 rodtoll Re-activated and fixed old auto record volume code
  27. * 09/09/99 rodtoll Fixed bug in FSM that was causing transion to VA
  28. * state in almost every frame.
  29. * rodtoll Fixed recording level checks
  30. * 09/13/99 rodtoll Can now select old VA code with compiler define
  31. * rodtoll Minor fixes to automatic volume control
  32. * 09/28/99 rodtoll Modified to use new notification mechanism
  33. * rodtoll Added playback voice volume suppression
  34. * 09/29/99 pnewson Major AGC overhaul
  35. * 10/05/99 rodtoll Dropped AGC volume reduce threshold from 3s to 500ms.
  36. * 10/25/99 rodtoll Fix: Bug#114187 Always transmits first frame
  37. * Initial state looked like trailing frame, so always sent
  38. * 10/29/99 rodtoll Bug #113726 - Integrate Voxware Codecs, updating to use new
  39. * pluggable codec architecture.
  40. * 11/12/99 rodtoll Updated to use new recording classes, improved error
  41. * handling and new initialize function.
  42. * rodtoll Added new high CPU handling code for record.
  43. * Checks for lockup AND ignores frame if pointer hasn't moved forward
  44. * 11/16/99 rodtoll Recording thread now loops everytime it wakes up until it
  45. * has compressed and transmitted all the data it can before
  46. * going back to sleep.
  47. * rodtoll Now displays instrumentation data on recording thread
  48. * 11/18/99 rodtoll Re-activated recording pointer lockup detection code
  49. * 12/01/99 rodtoll Fix: Bug #121053 Microphone auto-select not working
  50. * rodtoll Updated to use new parameters for SelectMicrophone function
  51. * 12/08/99 rodtoll Bug #121054 Integrate code for handling capture focus.
  52. * 12/16/99 rodtoll Removed voice suppression
  53. * 01/10/00 pnewson AGC and VA tuning
  54. * 01/14/2000 rodtoll Updated to handle new multiple targets and use new speech
  55. * packet formats.
  56. * 01/21/2000 pnewson Update to support new DVSOUNDCONFIG_TESTMODE internal flag
  57. * used to detect lockups quickly during wizard testing.
  58. * 01/31/2000 pnewson re-add support for absence of DVCLIENTCONFIG_AUTOSENSITIVITY flag
  59. * 02/08/2000 rodtoll Bug #131496 - Selecting DVTHRESHOLD_DEFAULT results in voice
  60. * never being detected
  61. * 02/17/2000 rodtoll Bug #133691 - Choppy audio - queue was not adapting
  62. * Added instrumentation
  63. * 03/03/2000 rodtoll Updated to handle alternative gamevoice build.
  64. * 03/28/2000 rodtoll Updated to remove uneeded locks which were causing deadlock
  65. * 04/05/2000 rodtoll Updated to use new async, no buffer copy sends, removed old transmit buffer
  66. * 04/17/2000 rodtoll Bug #31316 - Some soundcards pass test with no microphone --
  67. * made recording system ignore first 3 frames of audio
  68. * 04/19/2000 rodtoll Added support for new DVSOUNDCONFIG_NORECVOLAVAILABLE flag
  69. * 04/19/2000 pnewson Fix to make AGC code work properly with VA off
  70. * 04/25/2000 pnewson Fix to improve responsiveness of AGC when volume level too low
  71. * 07/09/2000 rodtoll Added signature bytes
  72. * 07/18/2000 rodtoll Fixed bug w/capture focus -- GetCurrentPosition will return an error
  73. * when focus is lost -- this was causing a session lost
  74. * 08/18/2000 rodtoll Bug #42542 - DPVoice retrofit: Voice retrofit locks up after host migration
  75. * 08/29/2000 rodtoll Bug #43553 - Start() returns 0x80004005 after lockup
  76. * rodtoll Bug #43620 - DPVOICE: Recording buffer locks up on Aureal Vortex (VxD).
  77. * Updated reset procedure so it ignores Stop failures and if Start() fails
  78. * it tries resetting the recording system.
  79. * 08/31/2000 rodtoll Bug #43804 - DVOICE: dwSensitivity structure member is confusing - should be dwThreshold
  80. * 09/13/2000 rodtoll Bug #44845 - DXSTRESS: DPLAY: Access violation
  81. * rodtoll Regression fix which caused fails failures in loopback test
  82. * 10/17/2000 rodtoll Bug #47224 - DPVOICE: Recording lockup reset fails w/DSERR_ALLOCATED added sleep to allow driver to cleanup
  83. * 10/30/2000 rodtoll Same as above -- Update with looping attempting to re-allocate capture device
  84. * 01/26/2001 rodtoll WINBUG #293197 - DPVOICE: [STRESS} Stress applications cannot tell difference between out of memory and internal errors.
  85. * Remap DSERR_OUTOFMEMORY to DVERR_OUTOFMEMORY instead of DVERR_SOUNDINITFAILURE.
  86. * Remap DSERR_ALLOCATED to DVERR_PLAYBACKSYSTEMERROR instead of DVERR_SOUNDINITFAILURE.
  87. * 04/11/2001 rodtoll WINBUG #221494 DPVOICE: Updates to improve lockup detection ---
  88. * Reset record event when no work to do so we don't spin unessessarily as much
  89. * Add check to ensure frame where we grab audio but pos hasn't move we don't count towards lockup
  90. * Add check to ensure we enforce a timeout + a frame count for lockup purposes.
  91. * 04/21/2001 rodtoll MANBUG #50058 DPVOICE: VoicePosition: No sound for couple of seconds when position bars are moved
  92. * - Modified lockup detection to be time based, increased timeout to 600ms.
  93. *
  94. ***************************************************************************/
  95. #include "dxvoicepch.h"
  96. #define RECORD_MAX_RESETS 10
  97. #define RECORD_MAX_TIMEOUT 2000
  98. #define TESTMODE_MAX_TIMEOUT 500
  99. #define RECORD_NUM_TARGETS_INIT 0
  100. #define RECORD_NUM_TARGETS_GROW 10
  101. #define RECORD_PASSES_BEFORE_LOCKUP 25
  102. #define RECORD_RESET_PAUSE 500
  103. #define RECORD_RESET_ALLOC_ATTEMPTS 10
  104. // RECORD_LOCKUP_TIMEOUT
  105. //
  106. // # of ms of no movement before a lockup is detected.
  107. //
  108. #define RECORD_LOCKUP_TIMEOUT 600
  109. // Comment out to use the old sensitivity detection
  110. #define __USENEWVA
  111. // RECORDTEST_MIN_POWER / RECORDTEST_MAX_POWER
  112. //
  113. // Define the max and min possible power values
  114. //#define RECORDTEST_MIN_POWER 0
  115. //#define RECORDTEST_MAX_POWER 100
  116. // We have to double the # because IsMuted is called twice / pass
  117. #define RECORDTEST_NUM_FRAMESBEFOREVOICE 6
  118. #undef DPF_MODNAME
  119. #define DPF_MODNAME "CClientRecordSubSystem::CClientRecordSubSystem"
  120. // CClientRecordSubSystem
  121. //
  122. // This is the constructor for the CClientRecordSubSystem class.
  123. // It intiailizes the classes member variables with the appropriate
  124. // values.
  125. //
  126. // Parameters:
  127. // CDirectVoiceClientEngine *clientEngine -
  128. // Pointer to the client object which is using this object.
  129. //
  130. // Returns:
  131. // N/A
  132. //
  133. CClientRecordSubSystem::CClientRecordSubSystem(
  134. CDirectVoiceClientEngine *clientEngine
  135. ): m_dwSignature(VSIG_CLIENTRECORDSYSTEM),
  136. m_recordState(RECORDSTATE_IDLE),
  137. m_converter(NULL),
  138. m_remain(0),
  139. m_clientEngine(clientEngine),
  140. m_dwCurrentPower(0),
  141. m_dwSilentTime(0),
  142. m_lastFrameTransmitted(FALSE),
  143. m_msgNum(0),
  144. m_seqNum(0),
  145. m_dwLastTargetVersion(0),
  146. m_lSavedVolume(0),
  147. m_fRecordVolumeSaved(FALSE),
  148. m_uncompressedSize(0),
  149. m_compressedSize(0),
  150. m_framesPerPeriod(0),
  151. m_pbConstructBuffer(NULL),
  152. m_dwResetCount(0),
  153. m_dwNextReadPos(0),
  154. m_fIgnoreFrame(FALSE),
  155. m_dwPassesSincePosChange(0),
  156. m_fLostFocus(FALSE),
  157. m_pagcva(NULL),
  158. m_dwFrameCount(0),
  159. m_prgdvidTargetCache(NULL),
  160. m_dwTargetCacheSize(0),
  161. m_dwTargetCacheEntries(0),
  162. m_dwFullBufferSize(0)
  163. {
  164. }
  165. #undef DPF_MODNAME
  166. #define DPF_MODNAME "CClientRecordSubSystem::Initialize"
  167. HRESULT CClientRecordSubSystem::Initialize()
  168. {
  169. HRESULT hr;
  170. hr = DVCDB_CreateConverter( m_clientEngine->m_audioRecordBuffer->GetRecordFormat(), m_clientEngine->m_lpdvfCompressionInfo->guidType, &m_converter );
  171. if( FAILED( hr ) )
  172. {
  173. DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to create converter. hr = 0x%x", hr );
  174. return hr ;
  175. }
  176. hr = m_converter->GetUnCompressedFrameSize( &m_uncompressedSize );
  177. if( FAILED( hr ) )
  178. {
  179. DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to get size hr=0x%x", hr );
  180. return hr;
  181. }
  182. hr = m_converter->GetCompressedFrameSize( &m_compressedSize );
  183. if( FAILED( hr ) )
  184. {
  185. DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to get size hr=0x%x", hr );
  186. return hr;
  187. }
  188. hr = m_converter->GetNumFramesPerBuffer( &m_framesPerPeriod );
  189. if( FAILED( hr ) )
  190. {
  191. DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to get size. hr = 0x%x", hr );
  192. return hr;
  193. }
  194. m_transmitFrame = FALSE;
  195. m_currentBuffer = 0;
  196. m_dwResetCount = 0;
  197. m_dwPassesSincePosChange = 0;
  198. m_dwLastFrameTime = GetTickCount();
  199. m_dwFrameTime = m_clientEngine->m_lpdvfCompressionInfo->dwTimeout;
  200. m_dwSilenceTimeout = m_clientEngine->m_lpdvfCompressionInfo->dwTrailFrames*m_dwFrameTime;
  201. // Prevents first frame from transmitting all the time
  202. m_dwSilentTime = m_dwSilenceTimeout+1;
  203. if( m_clientEngine->m_audioRecordBuffer->GetRecordFormat()->wBitsPerSample == 8 )
  204. {
  205. m_eightBit = TRUE;
  206. }
  207. else
  208. {
  209. m_eightBit = FALSE;
  210. }
  211. m_dwFullBufferSize = m_uncompressedSize*m_framesPerPeriod;
  212. m_pbConstructBuffer = new BYTE[m_dwFullBufferSize];
  213. if( m_pbConstructBuffer == NULL )
  214. {
  215. DPFX(DPFPREP, DVF_ERRORLEVEL, "Memory alloc failure" );
  216. return DVERR_OUTOFMEMORY;
  217. }
  218. BeginStats();
  219. InitStats();
  220. // create and init the AGC and VA algorithm class
  221. m_pagcva = new CAGCVA1();
  222. if (m_pagcva == NULL)
  223. {
  224. DPFX(DPFPREP, DVF_ERRORLEVEL, "Memory alloc failure" );
  225. return DVERR_OUTOFMEMORY;
  226. }
  227. LONG lSavedAGCVolume;
  228. hr = m_pagcva->Init(
  229. m_clientEngine->s_szRegistryPath,
  230. m_clientEngine->m_dvClientConfig.dwFlags,
  231. m_clientEngine->m_dvSoundDeviceConfig.guidCaptureDevice,
  232. m_clientEngine->m_audioRecordBuffer->GetRecordFormat()->nSamplesPerSec,
  233. m_eightBit ? 8 : 16,
  234. &lSavedAGCVolume,
  235. m_clientEngine->m_dvClientConfig.dwThreshold);
  236. if (FAILED(hr))
  237. {
  238. DPFX(DPFPREP, DVF_ERRORLEVEL, "Error initializing AGC and/or VA algorithm, code: %i", hr);
  239. delete m_pagcva;
  240. return hr;
  241. }
  242. if( m_clientEngine->m_dvSoundDeviceConfig.dwFlags & DVSOUNDCONFIG_AUTOSELECT )
  243. {
  244. m_clientEngine->m_audioRecordBuffer->SelectMicrophone(TRUE);
  245. }
  246. m_dwLastTargetVersion = m_clientEngine->m_dwTargetVersion;
  247. if( !(m_clientEngine->m_dvSoundDeviceConfig.dwFlags & DVSOUNDCONFIG_NORECVOLAVAILABLE) )
  248. {
  249. // save the original volume settings
  250. m_clientEngine->m_audioRecordBuffer->GetVolume( &m_lSavedVolume );
  251. m_fRecordVolumeSaved = TRUE;
  252. // set our initial volume
  253. if (m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_AUTORECORDVOLUME)
  254. {
  255. m_clientEngine->m_dvClientConfig.lRecordVolume = lSavedAGCVolume;
  256. }
  257. m_clientEngine->m_audioRecordBuffer->SetVolume( m_clientEngine->m_dvClientConfig.lRecordVolume );
  258. }
  259. else
  260. {
  261. m_fRecordVolumeSaved = FALSE;
  262. }
  263. // So our next
  264. m_dwNextReadPos = 0;
  265. m_dwLastReadPos = 0; //m_uncompressedSize*(m_framesPerPeriod-1);
  266. m_dwLastBufferPos = m_dwLastReadPos;
  267. return DV_OK;
  268. }
  269. #undef DPF_MODNAME
  270. #define DPF_MODNAME "CClientRecordSubSystem::CleanupForReset"
  271. HRESULT CClientRecordSubSystem::CleanupForReset()
  272. {
  273. HRESULT hr;
  274. if( m_converter != NULL )
  275. {
  276. m_converter->Release();
  277. m_converter = NULL;
  278. }
  279. if( m_clientEngine->m_audioRecordBuffer )
  280. {
  281. // Stop recording
  282. m_clientEngine->m_audioRecordBuffer->Stop();
  283. if( m_fRecordVolumeSaved )
  284. {
  285. m_clientEngine->m_audioRecordBuffer->SetVolume( m_lSavedVolume );
  286. }
  287. }
  288. // Deinit and cleanup the AGC and VA algorthms
  289. if (m_pagcva != NULL)
  290. {
  291. hr = m_pagcva->Deinit();
  292. if (FAILED(hr))
  293. {
  294. DPFX(DPFPREP, DVF_ERRORLEVEL, "Deinit error on AGC and/or VA algorithm, code: %i", hr);
  295. }
  296. delete m_pagcva;
  297. m_pagcva = NULL;
  298. }
  299. else
  300. {
  301. DPFX(DPFPREP, DVF_ERRORLEVEL, "Unexpected NULL pointer for AGC/VA algorithm");
  302. }
  303. if( m_pbConstructBuffer != NULL )
  304. {
  305. delete [] m_pbConstructBuffer;
  306. m_pbConstructBuffer = NULL;
  307. }
  308. if( m_prgdvidTargetCache )
  309. {
  310. delete [] m_prgdvidTargetCache;
  311. m_prgdvidTargetCache = NULL;
  312. m_dwTargetCacheSize = 0;
  313. m_dwTargetCacheEntries = 0;
  314. }
  315. return DV_OK;
  316. }
  317. #undef DPF_MODNAME
  318. #define DPF_MODNAME "CClientRecordSubSystem::~CClientRecordSubSystem"
  319. // CClientRecordSubSystem
  320. //
  321. // This is the destructor for the CClientRecordSubSystem
  322. // class. It cleans up the allocated memory for the class
  323. // and stops the recording device.
  324. //
  325. // Parameters:
  326. // N/A
  327. //
  328. // Returns:
  329. // N/A
  330. //
  331. CClientRecordSubSystem::~CClientRecordSubSystem()
  332. {
  333. HRESULT hr;
  334. CleanupForReset();
  335. CompleteStats();
  336. m_dwSignature = VSIG_CLIENTRECORDSYSTEM_FREE;
  337. }
  338. #undef DPF_MODNAME
  339. #define DPF_MODNAME "CClientRecordSubSystem::IsMuted"
  340. BOOL CClientRecordSubSystem::IsMuted()
  341. {
  342. // If the notification of the local player hasn't been processed and we're in peer to peer mode, don't
  343. // allow recording to start until after the player has been indicated
  344. if( !m_clientEngine->m_fLocalPlayerAvailable )
  345. {
  346. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "Local player has not yet been indicated, ignoring frame" );
  347. return TRUE;
  348. }
  349. BOOL fMuted = FALSE;
  350. // if(m_clientEngine->m_dvSoundDeviceConfig.dwFlags & DVSOUNDCONFIG_TESTMODE )
  351. // {
  352. if( m_dwFrameCount < RECORDTEST_NUM_FRAMESBEFOREVOICE )
  353. {
  354. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "Skipping first %d frames for startup burst", RECORDTEST_NUM_FRAMESBEFOREVOICE );
  355. m_dwFrameCount++;
  356. return TRUE;
  357. }
  358. // }
  359. if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_ECHOSUPPRESSION )
  360. {
  361. DNEnterCriticalSection( &m_clientEngine->m_lockPlaybackMode );
  362. if( (m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_RECORDMUTE) ||
  363. (m_clientEngine->m_dwEchoState == DVCECHOSTATE_PLAYBACK) )
  364. {
  365. fMuted = TRUE;
  366. }
  367. DNLeaveCriticalSection( &m_clientEngine->m_lockPlaybackMode );
  368. }
  369. else
  370. {
  371. fMuted = (m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_RECORDMUTE);
  372. }
  373. return fMuted;
  374. }
  375. #undef DPF_MODNAME
  376. #define DPF_MODNAME "CClientRecordSubSystem::StartMessage()"
  377. void CClientRecordSubSystem::StartMessage()
  378. {
  379. BYTE bPeakLevel;
  380. if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_AUTOVOICEACTIVATED ||
  381. m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_MANUALVOICEACTIVATED )
  382. {
  383. m_pagcva->PeakResults(&bPeakLevel);
  384. }
  385. else
  386. {
  387. bPeakLevel = 0;
  388. }
  389. DVMSG_RECORDSTART dvRecordStart;
  390. dvRecordStart.dwPeakLevel = bPeakLevel;
  391. dvRecordStart.dwSize = sizeof( DVMSG_RECORDSTART );
  392. dvRecordStart.pvLocalPlayerContext = m_clientEngine->m_pvLocalPlayerContext;
  393. m_clientEngine->NotifyQueue_Add( DVMSGID_RECORDSTART, &dvRecordStart, sizeof( DVMSG_RECORDSTART ) );
  394. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Starting Message" );
  395. // STATSBLOCK: Begin
  396. m_clientEngine->m_pStatsBlob->m_recStats.m_dwNumMessages++;
  397. // STATSBLOCK: End
  398. }
  399. #undef DPF_MODNAME
  400. #define DPF_MODNAME "CClientRecordSubSystem::EndMessage()"
  401. void CClientRecordSubSystem::EndMessage()
  402. {
  403. BYTE bPeakLevel;
  404. if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_AUTOVOICEACTIVATED ||
  405. m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_MANUALVOICEACTIVATED )
  406. {
  407. m_pagcva->PeakResults(&bPeakLevel);
  408. }
  409. else
  410. {
  411. bPeakLevel = 0;
  412. }
  413. // STATBLOCK: Begin
  414. if( m_seqNum > m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMax )
  415. {
  416. m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMax = m_seqNum;
  417. }
  418. if( m_seqNum < m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMin )
  419. {
  420. m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMin = m_seqNum;
  421. }
  422. m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLTotal += m_seqNum;
  423. // STATBLOCK: End
  424. DVMSG_RECORDSTOP dvRecordStop;
  425. dvRecordStop.dwPeakLevel = bPeakLevel;
  426. dvRecordStop.dwSize = sizeof( DVMSG_RECORDSTOP );
  427. dvRecordStop.pvLocalPlayerContext = m_clientEngine->m_pvLocalPlayerContext;
  428. m_clientEngine->NotifyQueue_Add( DVMSGID_RECORDSTOP, &dvRecordStop, sizeof( DVMSG_RECORDSTOP ) );
  429. m_msgNum++;
  430. m_seqNum = 0;
  431. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Ending Message" );
  432. }
  433. #undef DPF_MODNAME
  434. #define DPF_MODNAME "CClientRecordSubSystem::CheckVA()"
  435. BOOL CClientRecordSubSystem::CheckVA()
  436. {
  437. #ifdef __USENEWVA
  438. // We've been muted, turn off the VA
  439. if( IsMuted() )
  440. {
  441. m_dwSilentTime = m_dwSilenceTimeout+1;
  442. return FALSE;
  443. }
  444. BOOL m_fVoiceDetected;
  445. m_pagcva->VAResults(&m_fVoiceDetected);
  446. if (!m_fVoiceDetected)
  447. {
  448. // This prevents wrap-around on silence timeout
  449. if( m_dwSilentTime <= m_dwSilenceTimeout )
  450. {
  451. m_dwSilentTime += m_dwFrameTime;
  452. }
  453. DPFX(DPFPREP, DVF_INFOLEVEL, "### Silence Time to %d", m_dwSilentTime );
  454. if( m_dwSilentTime > m_dwSilenceTimeout )
  455. {
  456. DPFX(DPFPREP, DVF_INFOLEVEL, "### Silence Time exceeded %d", m_dwSilenceTimeout );
  457. if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_ECHOSUPPRESSION )
  458. {
  459. // If we're in the idle state, go to the recording state
  460. DNEnterCriticalSection( &m_clientEngine->m_lockPlaybackMode );
  461. if( m_clientEngine->m_dwEchoState == DVCECHOSTATE_RECORDING )
  462. {
  463. DPFX(DPFPREP, RECORD_SWITCH_DEBUG_LEVEL, "%%%% Switching to idle mode" );
  464. m_clientEngine->m_dwEchoState = DVCECHOSTATE_IDLE;
  465. }
  466. DNLeaveCriticalSection( &m_clientEngine->m_lockPlaybackMode );
  467. }
  468. return FALSE;
  469. }
  470. }
  471. else
  472. {
  473. m_dwSilentTime = 0;
  474. DPFX(DPFPREP, DVF_INFOLEVEL, "### Silence Time to 0" );
  475. DPFX(DPFPREP, DVF_INFOLEVEL, "### Transmit!!!!" );
  476. }
  477. if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_ECHOSUPPRESSION )
  478. {
  479. // If we're in the idle state, go to the recording state
  480. DNEnterCriticalSection( &m_clientEngine->m_lockPlaybackMode );
  481. if( m_clientEngine->m_dwEchoState == DVCECHOSTATE_IDLE )
  482. {
  483. DPFX(DPFPREP, RECORD_SWITCH_DEBUG_LEVEL, "%%%% Switching to recording mode" );
  484. m_clientEngine->m_dwEchoState = DVCECHOSTATE_RECORDING;
  485. }
  486. DNLeaveCriticalSection( &m_clientEngine->m_lockPlaybackMode );
  487. }
  488. return TRUE;
  489. #else
  490. m_peakCheck = TRUE;
  491. return !DetectSilence( m_bufferPtr, m_uncompressedSize, m_eightBit, 32, m_clientEngine->m_dwActivatePowerLevel, m_clientEngine->m_bLastPeak );
  492. #endif
  493. }
  494. #undef DPF_MODNAME
  495. #define DPF_MODNAME "CClientRecordSubSystem::DoAGC"
  496. HRESULT CClientRecordSubSystem::DoAGC()
  497. {
  498. // let AGC have a bash at changing the volume, but only if AGC is enabled in the current client config.
  499. if (m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_AUTORECORDVOLUME
  500. && !(m_clientEngine->m_dvSoundDeviceConfig.dwFlags & DVSOUNDCONFIG_NORECVOLAVAILABLE) )
  501. {
  502. // get the current hardware volume level - this will allow us to track "outside" changes
  503. // to the volume control. e.g. The user find the volume is too low, and manually drags the
  504. // volume control's volume slider up a bit. We won't become totally confused, since we're
  505. // checking the hardware level here.
  506. m_clientEngine->m_audioRecordBuffer->GetVolume( &(m_clientEngine->m_dvClientConfig.lRecordVolume) );
  507. LONG lNewVolume;
  508. m_pagcva->AGCResults(m_clientEngine->m_dvClientConfig.lRecordVolume, &lNewVolume, m_transmitFrame);
  509. // set the current hardware volume level, but only if it has changed
  510. if (m_clientEngine->m_dvClientConfig.lRecordVolume != lNewVolume)
  511. {
  512. // Problem: due to the convertion of logarithmic to linear
  513. // volume settings, there may be a rounding error at low
  514. // volumes. So, AGC will try to make a small volume adjustment
  515. // that results in NO adjustment due to rounding, and it can't
  516. // work it's way out of the hole.
  517. //
  518. // So - if AGC tried to uptick or downtick the volume, make
  519. // sure it worked!
  520. LONG lOrgVolume;
  521. LONG lVolumeDelta;
  522. LONG lNewHWVolume;
  523. lOrgVolume = m_clientEngine->m_dvClientConfig.lRecordVolume;
  524. lVolumeDelta = lNewVolume - lOrgVolume;
  525. lNewHWVolume = lOrgVolume;
  526. while(1)
  527. {
  528. m_clientEngine->m_dvClientConfig.lRecordVolume = lNewVolume;
  529. DPFX(DPFPREP, DVF_INFOLEVEL, "AGC: Setting volume to %i", lNewVolume);
  530. m_clientEngine->m_audioRecordBuffer->SetVolume(lNewVolume);
  531. m_clientEngine->m_audioRecordBuffer->GetVolume(&lNewHWVolume);
  532. if (lNewHWVolume == lOrgVolume)
  533. {
  534. // The value did not change, so we're hitting a rounding
  535. // error.
  536. // Make sure we're not already at the min or max
  537. if (lNewVolume == DSBVOLUME_MIN || lNewVolume == DSBVOLUME_MAX)
  538. {
  539. // There's nothing more we can do. Give up.
  540. break;
  541. }
  542. // Add another delta to the new volume, and try again.
  543. lNewVolume += lVolumeDelta;
  544. if (lNewVolume > DSBVOLUME_MAX)
  545. {
  546. lNewVolume = DSBVOLUME_MAX;
  547. }
  548. if (lNewVolume < DSBVOLUME_MIN)
  549. {
  550. lNewVolume = DSBVOLUME_MIN;
  551. }
  552. }
  553. else
  554. {
  555. // The value changed, so we're done.
  556. break;
  557. }
  558. }
  559. }
  560. }
  561. return DV_OK;
  562. }
  563. #undef DPF_MODNAME
  564. #define DPF_MODNAME "CClientRecordSubSystem::RecordFSM"
  565. HRESULT CClientRecordSubSystem::RecordFSM()
  566. {
  567. if( m_fIgnoreFrame )
  568. {
  569. return DV_OK;
  570. }
  571. // In this case we NEVER transmit
  572. // Shortcut to simplify the other cases
  573. if( IsMuted() || !IsValidTarget() )
  574. {
  575. // Go immediately to IDLE
  576. DPFX(DPFPREP, DVF_INFOLEVEL, "### IsMuted || IsValidTarget --> IDLE" );
  577. m_recordState = RECORDSTATE_IDLE;
  578. }
  579. if( !IsMuted() )
  580. {
  581. // before we analyze the data, push down the current
  582. // relevant portions of the client config structure.
  583. m_pagcva->SetSensitivity(m_clientEngine->m_dvClientConfig.dwFlags, m_clientEngine->m_dvClientConfig.dwThreshold);
  584. m_pagcva->AnalyzeData(m_bufferPtr, m_uncompressedSize);
  585. }
  586. m_transmitFrame = FALSE;
  587. switch( m_recordState )
  588. {
  589. case RECORDSTATE_IDLE:
  590. DPFX(DPFPREP, DVF_INFOLEVEL, "### STATE: IDLE" );
  591. if( !IsMuted() && IsValidTarget() )
  592. {
  593. if( IsPTT() )
  594. {
  595. m_recordState = RECORDSTATE_PTT;
  596. m_transmitFrame = TRUE;
  597. }
  598. else
  599. {
  600. m_transmitFrame = CheckVA();
  601. if( m_transmitFrame )
  602. {
  603. m_recordState = RECORDSTATE_VA;
  604. }
  605. }
  606. }
  607. break;
  608. case RECORDSTATE_VA:
  609. DPFX(DPFPREP, DVF_INFOLEVEL, "### STATE: VA" );
  610. if( IsPTT() )
  611. {
  612. DPFX(DPFPREP, DVF_INFOLEVEL, "### VA --> PTT" );
  613. m_recordState = RECORDSTATE_PTT;
  614. m_transmitFrame = TRUE;
  615. }
  616. else
  617. {
  618. m_transmitFrame = CheckVA();
  619. if (!m_transmitFrame)
  620. {
  621. DPFX(DPFPREP, DVF_INFOLEVEL, "### !VA --> IDLE" );
  622. m_recordState = RECORDSTATE_IDLE;
  623. }
  624. }
  625. break;
  626. case RECORDSTATE_PTT:
  627. DPFX(DPFPREP, DVF_INFOLEVEL, "### STATE: PTT" );
  628. if( IsVA() )
  629. {
  630. DPFX(DPFPREP, DVF_INFOLEVEL, "### PTT --> VA" );
  631. m_recordState = RECORDSTATE_VA;
  632. m_transmitFrame = CheckVA();
  633. }
  634. else
  635. {
  636. m_transmitFrame = TRUE;
  637. }
  638. break;
  639. }
  640. // Now that we've figured out if we're transmitting or not, do the AGC
  641. DoAGC();
  642. // Message Ended
  643. if( m_lastFrameTransmitted && !m_transmitFrame )
  644. {
  645. EndMessage();
  646. }
  647. // Message Started
  648. else if( !m_lastFrameTransmitted && m_transmitFrame )
  649. {
  650. StartMessage();
  651. }
  652. // Message Continuing
  653. else
  654. {
  655. // If the target has changed since the last frame
  656. if( m_clientEngine->m_dwTargetVersion != m_dwLastTargetVersion )
  657. {
  658. // If we're going to be transmitting
  659. if( m_transmitFrame )
  660. {
  661. EndMessage();
  662. StartMessage();
  663. }
  664. }
  665. }
  666. m_lastFrameTransmitted = m_transmitFrame;
  667. m_dwLastTargetVersion = m_clientEngine->m_dwTargetVersion;
  668. // Save the peak level to propogate up to the app
  669. if( m_fIgnoreFrame )
  670. {
  671. m_clientEngine->m_bLastPeak = 0;
  672. }
  673. else
  674. {
  675. m_pagcva->PeakResults(&(m_clientEngine->m_bLastPeak));
  676. }
  677. return DV_OK;
  678. }
  679. #undef DPF_MODNAME
  680. #define DPF_MODNAME "CClientRecordSubSystem::ResetForLockup"
  681. // Reset For Lockup
  682. //
  683. // This function is called when an amount of time specified
  684. // by the timeout is exceeded since the last movement of the
  685. // recording buffer
  686. //
  687. // Parameters:
  688. // N/A
  689. //
  690. // Returns:
  691. // hr
  692. //
  693. HRESULT CClientRecordSubSystem::ResetForLockup()
  694. {
  695. HRESULT hr;
  696. DWORD dwBufferPos;
  697. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Detected! Attempting RESET" );
  698. hr = m_clientEngine->m_audioRecordBuffer->Stop();
  699. if( FAILED( hr ) )
  700. {
  701. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Stop() Failed hr=0x%x", hr );
  702. }
  703. else
  704. {
  705. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Stop() worked", hr );
  706. }
  707. hr = m_clientEngine->m_audioRecordBuffer->Record( TRUE );
  708. if( FAILED( hr ) )
  709. {
  710. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Record() failed hr=0x%x", hr );
  711. // Cleanup for a FULL recording subsystem reset
  712. hr = CleanupForReset();
  713. if( FAILED( hr ) )
  714. {
  715. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Unable to cleanup for reset hr=0x%x", hr );
  716. return hr;
  717. }
  718. // Destroy / stop etc old buffer
  719. delete m_clientEngine->m_audioRecordBuffer;
  720. m_clientEngine->m_audioRecordBuffer = NULL;
  721. // Will now re-attempt to re-acquire the device a total of RECORD_RESET_ALLOC_ATTEMPTS times.
  722. for( DWORD dwAttempt = 0; dwAttempt < RECORD_RESET_ALLOC_ATTEMPTS; dwAttempt++ )
  723. {
  724. // Ok. So why's this sleep here?
  725. //
  726. // Turns out on some systems / drivers destroying the buffer as above and then immediately trying to create a new
  727. // one returns a DSERR_ALLOCATED (which ends up causing a session lost w/DVERR_RECORDSYSTEMERROR). There
  728. // is likely some kind of timing problem in the drivers effected. Adding this sleep alleviates the problem probably
  729. // because it gives the driver time to reset.
  730. //
  731. // The problem itself is hard to repro, so don't assume removing the assert and not seeing the problem means it
  732. // doesn't happen anymore.
  733. //
  734. Sleep( RECORD_RESET_PAUSE );
  735. // Create a new one
  736. hr = InitializeRecordBuffer( m_clientEngine->m_dvSoundDeviceConfig.hwndAppWindow, m_clientEngine->m_lpdvfCompressionInfo,
  737. m_clientEngine->m_audioRecordDevice, &m_clientEngine->m_audioRecordBuffer,
  738. m_clientEngine->m_dvSoundDeviceConfig.dwFlags );
  739. if( hr == DSERR_ALLOCATED )
  740. {
  741. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Unable to rebuild capture object, re-attempting.." );
  742. continue;
  743. }
  744. else if( FAILED( hr ) )
  745. {
  746. DSASSERT( FALSE );
  747. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Unable to restart locked up recording buffer hr=0x%x", hr );
  748. return hr;
  749. }
  750. else if( SUCCEEDED(hr) )
  751. {
  752. break;
  753. }
  754. }
  755. DSASSERT( SUCCEEDED( hr ) );
  756. // Restart the entire recording sub-system
  757. hr = Initialize();
  758. if( FAILED( hr ) )
  759. {
  760. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Unable to re-initialize recording sub-system hr=0x%x", hr );
  761. return hr;
  762. }
  763. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Full reset worked!" );
  764. }
  765. else
  766. {
  767. Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Start() succeeded" );
  768. }
  769. BOOL fLostFocus;
  770. // We're going to ignore focus results because it will be picked up by the next
  771. // pass through the loop.
  772. //
  773. hr = m_clientEngine->m_audioRecordBuffer->GetCurrentPosition( &dwBufferPos, &fLostFocus );
  774. if( FAILED( hr ) )
  775. {
  776. DSASSERT( FALSE );
  777. DPFX(DPFPREP, DVF_ERRORLEVEL, "Get Current position failed" );
  778. return hr;
  779. }
  780. m_dwLastReadPos = dwBufferPos; //(m_uncompressedSize*(m_framesPerPeriod-1));
  781. m_dwNextReadPos = dwBufferPos; // 0;
  782. m_dwLastBufferPos = dwBufferPos;
  783. m_dwLastFrameTime = GetTickCount();
  784. // Reset the record count -- this routine can take some time so the record
  785. // count will queue up on a reset. Don't want too many queued events or
  786. // we could end up with a false lockup detection.
  787. //
  788. ResetEvent( m_clientEngine->m_thTimerInfo.hRecordTimerEvent );
  789. return DV_OK;
  790. }
  791. #undef DPF_MODNAME
  792. #define DPF_MODNAME "CClientRecordSubSystem::GetNextFrame"
  793. // GetNextFrame
  794. //
  795. // This function retrieves the next frame from the recording input
  796. // and detects recording lockup. If a recording lockup is detected
  797. // then this function attempts to restart the recording system
  798. // 3 times. If it fails the recording system is considered to be
  799. // locked up.
  800. //
  801. // This was implemented as a fix for SB Live! cards which periodically
  802. // lockup while recording. (Also noticed on Aureal Vortex cards).
  803. //
  804. // Parameters:
  805. // N/A
  806. //
  807. // Returns:
  808. // bool - true if next frame was retrieved, false if lockup detected
  809. HRESULT CClientRecordSubSystem::GetNextFrame( LPBOOL pfContinue )
  810. {
  811. HRESULT hr;
  812. DWORD dwBufferPos;
  813. PVOID pBufferPtr1, pBufferPtr2;
  814. DWORD dwSizeBuffer1, dwSizeBuffer2;
  815. DWORD dwCurrentTime;
  816. BOOL fLostFocus;
  817. DWORD dwTSLM;
  818. hr = m_clientEngine->m_audioRecordBuffer->GetCurrentPosition( &dwBufferPos, &fLostFocus );
  819. if( FAILED( hr ) && !fLostFocus )
  820. {
  821. DSASSERT( FALSE );
  822. DPFX(DPFPREP, DVF_ERRORLEVEL, "Get Current position failed" );
  823. return hr;
  824. }
  825. // Get the current time
  826. dwCurrentTime = GetTickCount();
  827. // The focus has changed!
  828. if( fLostFocus != m_fLostFocus )
  829. {
  830. m_fLostFocus = fLostFocus;
  831. // We just lost focus.
  832. if( fLostFocus )
  833. {
  834. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Buffer just lost focus. No more input until focus returns" );
  835. // Keep frame time up to date since we are about to exit
  836. m_dwLastFrameTime = dwCurrentTime;
  837. // Notify the application
  838. m_clientEngine->NotifyQueue_Add( DVMSGID_LOSTFOCUS, NULL, 0 );
  839. // If we're in a message, end it. Won't be any more input until focus returns.
  840. if( m_lastFrameTransmitted )
  841. {
  842. EndMessage();
  843. m_lastFrameTransmitted = FALSE;
  844. }
  845. // Set the peak levels to 0
  846. m_clientEngine->m_bLastPeak = 0;
  847. // To ignore the frame
  848. m_fIgnoreFrame = TRUE;
  849. // To shortcircuit recording loop
  850. *pfContinue = FALSE;
  851. // Drop the data remaining in the buffer
  852. if( dwBufferPos < m_uncompressedSize )
  853. {
  854. m_dwLastReadPos = (m_dwFullBufferSize) - (m_uncompressedSize - dwBufferPos);
  855. }
  856. else
  857. {
  858. m_dwLastReadPos = (dwBufferPos - m_uncompressedSize );
  859. }
  860. m_dwNextReadPos = dwBufferPos;
  861. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Moving last read to %d and next to %d", m_dwLastReadPos, m_dwNextReadPos );
  862. return DV_OK;
  863. }
  864. // We just gained focus... continue as if normal.
  865. else
  866. {
  867. m_clientEngine->NotifyQueue_Add( DVMSGID_GAINFOCUS, NULL, 0 );
  868. }
  869. }
  870. if( fLostFocus )
  871. {
  872. // Set the peak levels to 0
  873. m_clientEngine->m_bLastPeak = 0;
  874. // Ignore this frame
  875. m_fIgnoreFrame = TRUE;
  876. // Do not continue in recording loop until next wakeup
  877. *pfContinue = FALSE;
  878. // Keep frame time up to date since we are about to exit
  879. m_dwLastFrameTime = dwCurrentTime;
  880. // Track the moving buffer (if it's moving) for the case of WDM
  881. // drivers on Millenium that continue to record
  882. if( dwBufferPos < m_uncompressedSize )
  883. {
  884. m_dwLastReadPos = (m_dwFullBufferSize) - (m_uncompressedSize - dwBufferPos);
  885. }
  886. else
  887. {
  888. m_dwLastReadPos = (dwBufferPos - m_uncompressedSize );
  889. }
  890. m_dwNextReadPos = dwBufferPos;
  891. return DV_OK;
  892. }
  893. // STATSBLOCK: Begin
  894. dwTSLM = dwCurrentTime - m_dwLastFrameTime;
  895. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMTotal += dwTSLM;
  896. if( dwTSLM > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMax )
  897. {
  898. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMax = dwTSLM;
  899. }
  900. if( dwTSLM < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMin )
  901. {
  902. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMin = dwTSLM;
  903. }
  904. // STATSBLOCK: End
  905. DPFX(DPFPREP, DVF_INFOLEVEL, "Current Pos: %d, Last Pos: %d", dwBufferPos, m_dwLastBufferPos );
  906. // Calculate how much buffer we have to catch up for.
  907. if( m_dwNextReadPos > dwBufferPos )
  908. {
  909. dwSizeBuffer2 = ((m_dwFullBufferSize)-m_dwNextReadPos)+dwBufferPos;
  910. }
  911. else
  912. {
  913. dwSizeBuffer2 = dwBufferPos - m_dwNextReadPos;
  914. }
  915. // See if a lockup occurred which means the buffer hasn't moved
  916. //
  917. // We only check for a lockup however when we aren't in the process of "Catching up". If we've
  918. // fallen behind because we lost CPU, and we're several frames behind we're going to run through
  919. // this section several times to catch up. The issue is that when we run several times there is
  920. // very little time between the runs and therefore it is perfectly reasonable for DirectSound
  921. // to have not moved it's buffer pointer.
  922. //
  923. if( m_dwLastBufferPos == dwBufferPos &&
  924. dwSizeBuffer2 < m_uncompressedSize ) // Make sure we're not just "catching up".
  925. {
  926. m_dwPassesSincePosChange++;
  927. // The lower this number is, the better the experience for people with bad drivers that lockup.
  928. // The higher it is, the more we think we've locked up in situations where the buffer legitimately
  929. // doesn't move, like high CPU usage, or when the app is in the debugger.
  930. if( (dwCurrentTime - m_dwLastFrameTime) >= RECORD_LOCKUP_TIMEOUT )
  931. {
  932. m_dwPassesSincePosChange = 0;
  933. // We've been RECORD_MAX_RESETS passes through here without the buffer moving, reset
  934. if( m_dwResetCount > RECORD_MAX_RESETS )
  935. {
  936. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Maximum Resets exceeded" );
  937. return DVERR_RECORDSYSTEMERROR;
  938. }
  939. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Lockup Detected %d ms since last movement", dwCurrentTime - m_dwLastFrameTime );
  940. // STATSBLOCK: Begin
  941. DPFX(DPFPREP, DVF_GLITCH_DEBUG_LEVEL, "GLITCH: Record: Recording buffer has stopped moving." );
  942. // STATSBLOCK: End
  943. hr = ResetForLockup();
  944. if( FAILED( hr ) )
  945. {
  946. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Reset for lockup failed hr=0x%x", hr );
  947. return hr;
  948. }
  949. m_fIgnoreFrame = TRUE;
  950. *pfContinue = FALSE;
  951. m_dwResetCount++;
  952. // STATSBLOCK: Begin
  953. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRTotal++;
  954. if( m_dwResetCount < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMin )
  955. {
  956. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMin = m_dwResetCount;
  957. }
  958. if( m_dwResetCount > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMax )
  959. {
  960. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMax = m_dwResetCount;
  961. }
  962. // STATSBLOCK: End
  963. // Keep frame time up to date since we are about to exit
  964. //
  965. // NOTE: This needs to be GetTickCount() because Reset() can take 100's of ms.
  966. m_dwLastFrameTime = GetTickCount();
  967. return DV_OK;
  968. }
  969. }
  970. else
  971. {
  972. m_dwPassesSincePosChange = 0;
  973. }
  974. // There was no lockup, and we are in focus, continue
  975. m_dwLastBufferPos = dwBufferPos;
  976. // Calc Delta in bytes of buffer read pointer
  977. if( m_dwLastBufferPos > dwBufferPos )
  978. {
  979. dwSizeBuffer1 = ((m_dwFullBufferSize)-m_dwLastBufferPos)+dwBufferPos;
  980. }
  981. else
  982. {
  983. dwSizeBuffer1 = dwBufferPos - m_dwLastBufferPos;
  984. }
  985. // The position did not move enough for a full frame.
  986. if( dwSizeBuffer2 < m_uncompressedSize )
  987. {
  988. m_fIgnoreFrame = TRUE;
  989. *pfContinue = FALSE;
  990. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, %d, %d, %d, %d, %d, %d (SKIPPING)",
  991. dwBufferPos,
  992. dwSizeBuffer1,
  993. dwCurrentTime - m_dwLastFrameTime,
  994. m_dwNextReadPos,
  995. dwSizeBuffer2,
  996. m_uncompressedSize );
  997. }
  998. else
  999. {
  1000. DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, %d, %d, %d, %d, %d, %d",
  1001. dwBufferPos,
  1002. dwSizeBuffer1,
  1003. dwCurrentTime - m_dwLastFrameTime,
  1004. m_dwNextReadPos,
  1005. dwSizeBuffer2,
  1006. m_uncompressedSize );
  1007. m_dwResetCount = 0;
  1008. // STATSBLOCK: BEGIN
  1009. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSTotal += (dwCurrentTime - m_dwLastFrameTime);
  1010. if( (dwCurrentTime - m_dwLastFrameTime) > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMax )
  1011. {
  1012. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMax = dwCurrentTime - m_dwLastFrameTime;
  1013. }
  1014. if( m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMax > 70000 )
  1015. {
  1016. }
  1017. if( (dwCurrentTime - m_dwLastFrameTime) < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMin )
  1018. {
  1019. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMin = dwCurrentTime - m_dwLastFrameTime;
  1020. }
  1021. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBTotal += dwSizeBuffer1;
  1022. if( dwSizeBuffer1 > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBMax )
  1023. {
  1024. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBMax = dwSizeBuffer1;
  1025. }
  1026. if( dwSizeBuffer1 < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBMin )
  1027. {
  1028. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBMin = dwSizeBuffer1;
  1029. }
  1030. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLTotal += dwSizeBuffer2;
  1031. if( dwSizeBuffer2 > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLMax )
  1032. {
  1033. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLMax = dwSizeBuffer2;
  1034. }
  1035. if( dwSizeBuffer2 < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLMin )
  1036. {
  1037. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLMin = dwSizeBuffer2;
  1038. }
  1039. // STATSBLOCK: END
  1040. // Get the next block of audio data and construct it into the buffer
  1041. hr = m_clientEngine->m_audioRecordBuffer->Lock( m_dwNextReadPos, m_uncompressedSize, &pBufferPtr1, &dwSizeBuffer1, &pBufferPtr2, &dwSizeBuffer2, 0 );
  1042. if( FAILED( hr ) )
  1043. {
  1044. DSASSERT( FALSE );
  1045. DPFX(DPFPREP, DVF_ERRORLEVEL, "Error locking buffer: loc=%d size=%d hr=0x%x", m_dwNextReadPos, m_uncompressedSize, hr );
  1046. return hr;
  1047. }
  1048. memcpy( m_pbConstructBuffer, pBufferPtr1, dwSizeBuffer1 );
  1049. if( dwSizeBuffer2 > 0 )
  1050. {
  1051. memcpy( &m_pbConstructBuffer[dwSizeBuffer1], pBufferPtr2, dwSizeBuffer2 );
  1052. }
  1053. hr = m_clientEngine->m_audioRecordBuffer->UnLock( pBufferPtr1, dwSizeBuffer1, pBufferPtr2, dwSizeBuffer2 );
  1054. if( FAILED( hr ) )
  1055. {
  1056. DSASSERT( FALSE );
  1057. DPFX(DPFPREP, DVF_ERRORLEVEL, "Error unlocking buffer hr=0x%x", hr );
  1058. return hr;
  1059. }
  1060. m_bufferPtr = m_pbConstructBuffer;
  1061. m_fIgnoreFrame = FALSE;
  1062. m_dwLastReadPos = m_dwNextReadPos;
  1063. m_dwNextReadPos += m_uncompressedSize;
  1064. m_dwNextReadPos %= (m_dwFullBufferSize);
  1065. // Update the last frame time
  1066. m_dwLastFrameTime = dwCurrentTime;
  1067. *pfContinue = TRUE;
  1068. }
  1069. return DV_OK;
  1070. }
  1071. #undef DPF_MODNAME
  1072. #define DPF_MODNAME "CClientRecordSubSystem::TransmitFrame"
  1073. // TransmitFrame
  1074. //
  1075. // This function looks at the state of the FSM, and if the latest
  1076. // frame is to be transmitted it compresses it and transmits
  1077. // it to the server (cleint/server mode) or to all the players
  1078. // (or player if whispering) in peer to peer mode. If the frame
  1079. // is not to be transmitted this function does nothing.
  1080. //
  1081. // It is also responsible for ensuring any microphone clicks are
  1082. // mixed into the audio stream.
  1083. //
  1084. // This function also updates the transmission statisics and
  1085. // the current transmission status.
  1086. //
  1087. // Parameters:
  1088. // N/A
  1089. //
  1090. // Returns:
  1091. // bool - always returns true at the moment
  1092. HRESULT CClientRecordSubSystem::TransmitFrame()
  1093. {
  1094. HRESULT hr = DV_OK;
  1095. if( m_transmitFrame && !m_fIgnoreFrame )
  1096. {
  1097. // STATSBLOCK: Begin
  1098. m_clientEngine->m_pStatsBlob->m_recStats.m_dwSentFrames++;
  1099. // STATSBLOCK: End
  1100. if( m_clientEngine->m_dvSessionDesc.dwSessionType == DVSESSIONTYPE_PEER )
  1101. {
  1102. hr = BuildAndTransmitSpeechHeader(FALSE);
  1103. }
  1104. else if( m_clientEngine->m_dvSessionDesc.dwSessionType == DVSESSIONTYPE_ECHO )
  1105. {
  1106. hr = BuildAndTransmitSpeechHeader(TRUE);
  1107. }
  1108. else
  1109. {
  1110. hr = BuildAndTransmitSpeechWithTarget(TRUE);
  1111. }
  1112. }
  1113. else
  1114. {
  1115. // STATSBLOCK: Begin
  1116. m_clientEngine->m_pStatsBlob->m_recStats.m_dwIgnoredFrames++;
  1117. // STATSBLOCK: End
  1118. }
  1119. return hr;
  1120. }
  1121. #undef DPF_MODNAME
  1122. #define DPF_MODNAME "CClientRecordSubSystem::BuildAndTransmitSpeechHeader"
  1123. HRESULT CClientRecordSubSystem::BuildAndTransmitSpeechHeader( BOOL bSendToServer )
  1124. {
  1125. PDVPROTOCOLMSG_SPEECHHEADER pdvSpeechHeader;
  1126. HRESULT hr;
  1127. DWORD dwTargetSize;
  1128. DWORD dwStartTime;
  1129. DWORD dwCompressTime;
  1130. PDVTRANSPORT_BUFFERDESC pBufferDesc;
  1131. LPVOID pvSendContext;
  1132. pBufferDesc = m_clientEngine->GetTransmitBuffer( sizeof(DVPROTOCOLMSG_SPEECHHEADER)+COMPRESSION_SLUSH+m_compressedSize, &pvSendContext );
  1133. if( pBufferDesc == NULL )
  1134. {
  1135. DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to get buffer for transmission" );
  1136. return DVERR_OUTOFMEMORY;
  1137. }
  1138. pdvSpeechHeader = (PDVPROTOCOLMSG_SPEECHHEADER) pBufferDesc->pBufferData;
  1139. pdvSpeechHeader->dwType = DVMSGID_SPEECH;
  1140. pdvSpeechHeader->bMsgNum = m_msgNum;
  1141. pdvSpeechHeader->bSeqNum = m_seqNum;
  1142. DPFX(DPFPREP, DVF_CLIENT_SEQNUM_DEBUG_LEVEL, "SEQ: Record: Msg [%d] Seq [%d]", m_msgNum, m_seqNum );
  1143. dwTargetSize = m_compressedSize;
  1144. dwStartTime = GetTickCount();
  1145. hr = m_converter->Convert( (BYTE *) m_bufferPtr, m_uncompressedSize, (BYTE *) &pdvSpeechHeader[1], &dwTargetSize, FALSE );
  1146. if( FAILED( hr ) )
  1147. {
  1148. m_clientEngine->ReturnTransmitBuffer( pvSendContext );
  1149. DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to perform conversion hr=0x%x", hr );
  1150. return hr;
  1151. }
  1152. dwCompressTime = GetTickCount() - dwStartTime;
  1153. DPFX(DPFPREP, DVF_INFOLEVEL, "Compressed %d bytes to %d taking %d ms", m_uncompressedSize, dwTargetSize, dwCompressTime );
  1154. // STATSBLOCK: Begin
  1155. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTTotal += dwCompressTime;
  1156. if( dwCompressTime < m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin )
  1157. {
  1158. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin = dwCompressTime;
  1159. }
  1160. if( dwCompressTime > m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMax )
  1161. {
  1162. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMax = dwCompressTime;
  1163. }
  1164. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSTotal += dwTargetSize;
  1165. if( dwTargetSize < m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin )
  1166. {
  1167. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin = dwTargetSize;
  1168. }
  1169. if( dwTargetSize > m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMax )
  1170. {
  1171. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMax = dwTargetSize;
  1172. }
  1173. // Header is fixed size
  1174. m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSTotal += sizeof( DVPROTOCOLMSG_SPEECHHEADER );
  1175. m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMax = sizeof( DVPROTOCOLMSG_SPEECHHEADER );
  1176. m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMin = sizeof( DVPROTOCOLMSG_SPEECHHEADER );
  1177. // STATSBLOCK: End
  1178. if( m_clientEngine->m_dwNumTargets == 0 )
  1179. {
  1180. DPFX(DPFPREP, DVF_INFOLEVEL, "Targets set to NONE since FSM. Not transmitting" );
  1181. m_clientEngine->ReturnTransmitBuffer( pvSendContext );
  1182. return DV_OK;
  1183. }
  1184. else
  1185. {
  1186. pBufferDesc->dwBufferSize = sizeof(DVPROTOCOLMSG_SPEECHHEADER)+dwTargetSize;
  1187. if( bSendToServer )
  1188. {
  1189. DPFX(DPFPREP, DVF_INFOLEVEL, "Transmitting %d bytes to server", pBufferDesc->dwBufferSize );
  1190. hr = m_clientEngine->m_lpSessionTransport->SendToServer( pBufferDesc, pvSendContext, 0 );
  1191. }
  1192. else
  1193. {
  1194. // Copy the target list to a cache so we can drop the lock during the send.
  1195. DNEnterCriticalSection( &m_clientEngine->m_csTargetLock );
  1196. if( m_clientEngine->m_dwNumTargets > m_dwTargetCacheSize )
  1197. {
  1198. if( m_prgdvidTargetCache )
  1199. delete [] m_prgdvidTargetCache;
  1200. m_dwTargetCacheSize = m_clientEngine->m_dwNumTargets;
  1201. m_prgdvidTargetCache = new DVID[m_dwTargetCacheSize];
  1202. if( !m_prgdvidTargetCache )
  1203. {
  1204. DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
  1205. DPFERR( "Out of memory" );
  1206. return DVERR_OUTOFMEMORY;
  1207. }
  1208. }
  1209. memcpy( m_prgdvidTargetCache, m_clientEngine->m_pdvidTargets, sizeof(DVID)*m_clientEngine->m_dwNumTargets );
  1210. m_dwTargetCacheEntries = m_clientEngine->m_dwNumTargets;
  1211. DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
  1212. // the target cache (prgdvidTargetCache) doesn't need protection since it is only accessed by one thread at a time.
  1213. DPFX(DPFPREP, DVF_INFOLEVEL, "Transmitting %d bytes to target list", pBufferDesc->dwBufferSize );
  1214. hr = m_clientEngine->m_lpSessionTransport->SendToIDS( m_prgdvidTargetCache, m_dwTargetCacheEntries,
  1215. pBufferDesc, pvSendContext, 0 );
  1216. }
  1217. if( hr == DVERR_PENDING )
  1218. {
  1219. hr = DV_OK;
  1220. }
  1221. else if ( FAILED( hr ))
  1222. {
  1223. DPFX(DPFPREP, DVF_INFOLEVEL, "Send failed hr=0x%x", hr );
  1224. }
  1225. m_seqNum++;
  1226. }
  1227. return hr;
  1228. }
  1229. #undef DPF_MODNAME
  1230. #define DPF_MODNAME "CClientRecordSubSystem::BuildAndTransmitSpeechWithTarget"
  1231. HRESULT CClientRecordSubSystem::BuildAndTransmitSpeechWithTarget( BOOL bSendToServer )
  1232. {
  1233. PDVPROTOCOLMSG_SPEECHWITHTARGET pdvSpeechWithTarget;
  1234. HRESULT hr;
  1235. DWORD dwTargetSize;
  1236. DWORD dwTransmitSize;
  1237. DWORD dwTargetInfoSize;
  1238. PBYTE pbBuilderLoc;
  1239. DWORD dwStartTime;
  1240. DWORD dwCompressTime;
  1241. PDVTRANSPORT_BUFFERDESC pBufferDesc;
  1242. LPVOID pvSendContext;
  1243. dwTransmitSize = sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET );
  1244. // Calculate size we'll need for storing the targets
  1245. DNEnterCriticalSection( &m_clientEngine->m_csTargetLock );
  1246. dwTargetInfoSize = sizeof( DVID ) * m_clientEngine->m_dwNumTargets;
  1247. if( dwTargetInfoSize == 0 )
  1248. {
  1249. DPFX(DPFPREP, DVF_INFOLEVEL, "Targets set to NONE since FSM. Not transmitting" );
  1250. DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
  1251. return DV_OK;
  1252. }
  1253. else
  1254. {
  1255. dwTransmitSize += dwTargetInfoSize;
  1256. }
  1257. DPFX(DPFPREP, DVF_CLIENT_SEQNUM_DEBUG_LEVEL, "SEQ: Record: Msg [%d] Seq [%d]", m_msgNum, m_seqNum );
  1258. dwTransmitSize += m_compressedSize;
  1259. pBufferDesc = m_clientEngine->GetTransmitBuffer( sizeof(DVPROTOCOLMSG_SPEECHWITHTARGET)+m_compressedSize+COMPRESSION_SLUSH+dwTransmitSize, &pvSendContext );
  1260. if( pBufferDesc == NULL )
  1261. {
  1262. DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to get buffer for transmission" );
  1263. return DVERR_OUTOFMEMORY;
  1264. }
  1265. pdvSpeechWithTarget = (PDVPROTOCOLMSG_SPEECHWITHTARGET) pBufferDesc->pBufferData;
  1266. pbBuilderLoc = (PBYTE) &pdvSpeechWithTarget[1];
  1267. memcpy( pbBuilderLoc, m_clientEngine->m_pdvidTargets, dwTargetInfoSize );
  1268. pdvSpeechWithTarget->dwNumTargets = m_clientEngine->m_dwNumTargets;
  1269. DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
  1270. pbBuilderLoc += dwTargetInfoSize;
  1271. pdvSpeechWithTarget->dvHeader.dwType = DVMSGID_SPEECHWITHTARGET;
  1272. pdvSpeechWithTarget->dvHeader.bMsgNum = m_msgNum;
  1273. pdvSpeechWithTarget->dvHeader.bSeqNum = m_seqNum;
  1274. dwTargetSize = m_compressedSize;
  1275. dwStartTime = GetTickCount();
  1276. DPFX(DPFPREP, DVF_COMPRESSION_DEBUG_LEVEL, "COMPRESS: < %d --> %d ", m_uncompressedSize, dwTargetSize );
  1277. hr = m_converter->Convert( (BYTE *) m_bufferPtr, m_uncompressedSize, (BYTE *) pbBuilderLoc, &dwTargetSize, FALSE );
  1278. if( FAILED( hr ) )
  1279. {
  1280. m_clientEngine->ReturnTransmitBuffer( pvSendContext );
  1281. DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to perform conversion hr=0x%x", hr );
  1282. return hr;
  1283. }
  1284. dwCompressTime = GetTickCount() - dwStartTime;
  1285. DPFX(DPFPREP, DVF_COMPRESSION_DEBUG_LEVEL, "COMPRESS: > %d --> %d %d ms", m_uncompressedSize, dwTargetSize, dwCompressTime );
  1286. // STATSBLOCK: Begin
  1287. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTTotal += dwCompressTime;
  1288. if( dwCompressTime < m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin )
  1289. {
  1290. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin = dwCompressTime;
  1291. }
  1292. if( dwCompressTime > m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMax )
  1293. {
  1294. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMax = dwCompressTime;
  1295. }
  1296. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSTotal += dwTargetSize;
  1297. if( dwTargetSize < m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin )
  1298. {
  1299. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin = dwTargetSize;
  1300. }
  1301. if( dwTargetSize > m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMax )
  1302. {
  1303. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMax = dwTargetSize;
  1304. }
  1305. // Header stats
  1306. m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSTotal += sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize;
  1307. if( sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize > m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMax )
  1308. {
  1309. m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMax = sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize;
  1310. }
  1311. if( sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize < m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMin )
  1312. {
  1313. m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMin = sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize;
  1314. }
  1315. // STATSBLOCK: End
  1316. // We need to transmit header, target info and then speech data
  1317. dwTransmitSize = sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET ) + dwTargetInfoSize + dwTargetSize;
  1318. pBufferDesc->dwBufferSize = dwTransmitSize;
  1319. hr = m_clientEngine->m_lpSessionTransport->SendToServer( pBufferDesc, pvSendContext, 0 );
  1320. m_seqNum++;
  1321. if( hr == DVERR_PENDING )
  1322. {
  1323. hr = DV_OK;
  1324. }
  1325. else if( FAILED( hr ) )
  1326. {
  1327. DPFX(DPFPREP, DVF_INFOLEVEL, "Send failed hr=0x%x", hr );
  1328. }
  1329. return hr;
  1330. }
  1331. #undef DPF_MODNAME
  1332. #define DPF_MODNAME "CClientRecordSubSystem::IsValidTarget"
  1333. BOOL CClientRecordSubSystem::IsValidTarget()
  1334. {
  1335. BOOL fValidTarget;
  1336. DNEnterCriticalSection( &m_clientEngine->m_csTargetLock );
  1337. if( m_clientEngine->m_dwNumTargets > 0 )
  1338. {
  1339. fValidTarget = TRUE;
  1340. }
  1341. else
  1342. {
  1343. fValidTarget = FALSE;
  1344. }
  1345. DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
  1346. return fValidTarget;
  1347. }
  1348. #undef DPF_MODNAME
  1349. #define DPF_MODNAME "CClientRecordSubSystem::InitStats"
  1350. void CClientRecordSubSystem::InitStats()
  1351. {
  1352. // STATSBLOCK: begin
  1353. // Setup record stats
  1354. m_clientEngine->m_pStatsBlob->m_recStats.m_dwFramesPerBuffer = m_framesPerPeriod;
  1355. m_clientEngine->m_pStatsBlob->m_recStats.m_dwFrameTime = m_dwFrameTime;
  1356. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin = 0xFFFFFFFF;
  1357. m_clientEngine->m_pStatsBlob->m_recStats.m_dwUnCompressedSize = m_uncompressedSize;
  1358. m_clientEngine->m_pStatsBlob->m_recStats.m_dwSilenceTimeout = m_dwSilenceTimeout;
  1359. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRPWMin = 0xFFFFFFFF;
  1360. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMin = 0xFFFFFFFF;
  1361. m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMin = 0xFFFFFFFF;
  1362. m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMin = 0xFFFFFFFF;
  1363. m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMin = 0xFFFFFFFF;
  1364. m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin = 0xFFFFFFFF;
  1365. // STATSBLOCK: End
  1366. }
  1367. #undef DPF_MODNAME
  1368. #define DPF_MODNAME "CClientRecordSubSystem::BeginStats"
  1369. void CClientRecordSubSystem::BeginStats()
  1370. {
  1371. // STATSBLOCK: Begin
  1372. m_clientEngine->m_pStatsBlob->m_recStats.m_dwTimeStart = GetTickCount();
  1373. m_clientEngine->m_pStatsBlob->m_recStats.m_dwStartLag = m_clientEngine->m_pStatsBlob->m_recStats.m_dwTimeStart-m_clientEngine->m_pStatsBlob->m_dwTimeStart;
  1374. // STATSBLOCK: end
  1375. }
  1376. #undef DPF_MODNAME
  1377. #define DPF_MODNAME "CClientRecordSubSystem::CompleteStats"
  1378. void CClientRecordSubSystem::CompleteStats()
  1379. {
  1380. m_clientEngine->m_pStatsBlob->m_recStats.m_dwTimeStop = GetTickCount();
  1381. }