windows-nt/Source/XPSP1/NT/multimedia/directx/dplay/dvoice/dxvoice/dvrecsub.cpp

1673 lines
50 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*==========================================================================
*
* Copyright (C) 1999 Microsoft Corporation. All Rights Reserved.
*
* File: ClientRecordSubSystem.cpp
* Content: Recording sub-system.
*
* History:
* Date By Reason
* ==== == ======
* 07/19/99 rodtoll Created
* 07/22/99 rodtoll Added support for multicast/ client/server sessions
* 08/02/99 rodtoll Added new silence detection support.
* 08/04/99 rodtoll Added guard to new silence detection so only runs when
* appropriate
* 08/25/99 rodtoll General Cleanup/Modifications to support new
* compression sub-system.
* 08/26/99 rodtoll Set msgnum to 0 in constructor.
* 08/26/99 rodtoll Added check for record mute flag to fix record muting\
* 08/27/99 rodtoll General cleanup/Simplification of recording subsystem
* Added reset of message when target changes
* Fixed recording start/stop notifications
* Fixed level readings when using voice activation
* 08/30/99 rodtoll Fixed double record stop messages when recording muted
* 09/01/99 rodtoll Re-activated auto volume control
* 09/02/99 rodtoll Re-activated and fixed old auto record volume code
* 09/09/99 rodtoll Fixed bug in FSM that was causing transion to VA
* state in almost every frame.
* rodtoll Fixed recording level checks
* 09/13/99 rodtoll Can now select old VA code with compiler define
* rodtoll Minor fixes to automatic volume control
* 09/28/99 rodtoll Modified to use new notification mechanism
* rodtoll Added playback voice volume suppression
* 09/29/99 pnewson Major AGC overhaul
* 10/05/99 rodtoll Dropped AGC volume reduce threshold from 3s to 500ms.
* 10/25/99 rodtoll Fix: Bug#114187 Always transmits first frame
* Initial state looked like trailing frame, so always sent
* 10/29/99 rodtoll Bug #113726 - Integrate Voxware Codecs, updating to use new
* pluggable codec architecture.
* 11/12/99 rodtoll Updated to use new recording classes, improved error
* handling and new initialize function.
* rodtoll Added new high CPU handling code for record.
* Checks for lockup AND ignores frame if pointer hasn't moved forward
* 11/16/99 rodtoll Recording thread now loops everytime it wakes up until it
* has compressed and transmitted all the data it can before
* going back to sleep.
* rodtoll Now displays instrumentation data on recording thread
* 11/18/99 rodtoll Re-activated recording pointer lockup detection code
* 12/01/99 rodtoll Fix: Bug #121053 Microphone auto-select not working
* rodtoll Updated to use new parameters for SelectMicrophone function
* 12/08/99 rodtoll Bug #121054 Integrate code for handling capture focus.
* 12/16/99 rodtoll Removed voice suppression
* 01/10/00 pnewson AGC and VA tuning
* 01/14/2000 rodtoll Updated to handle new multiple targets and use new speech
* packet formats.
* 01/21/2000 pnewson Update to support new DVSOUNDCONFIG_TESTMODE internal flag
* used to detect lockups quickly during wizard testing.
* 01/31/2000 pnewson re-add support for absence of DVCLIENTCONFIG_AUTOSENSITIVITY flag
* 02/08/2000 rodtoll Bug #131496 - Selecting DVTHRESHOLD_DEFAULT results in voice
* never being detected
* 02/17/2000 rodtoll Bug #133691 - Choppy audio - queue was not adapting
* Added instrumentation
* 03/03/2000 rodtoll Updated to handle alternative gamevoice build.
* 03/28/2000 rodtoll Updated to remove uneeded locks which were causing deadlock
* 04/05/2000 rodtoll Updated to use new async, no buffer copy sends, removed old transmit buffer
* 04/17/2000 rodtoll Bug #31316 - Some soundcards pass test with no microphone --
* made recording system ignore first 3 frames of audio
* 04/19/2000 rodtoll Added support for new DVSOUNDCONFIG_NORECVOLAVAILABLE flag
* 04/19/2000 pnewson Fix to make AGC code work properly with VA off
* 04/25/2000 pnewson Fix to improve responsiveness of AGC when volume level too low
* 07/09/2000 rodtoll Added signature bytes
* 07/18/2000 rodtoll Fixed bug w/capture focus -- GetCurrentPosition will return an error
* when focus is lost -- this was causing a session lost
* 08/18/2000 rodtoll Bug #42542 - DPVoice retrofit: Voice retrofit locks up after host migration
* 08/29/2000 rodtoll Bug #43553 - Start() returns 0x80004005 after lockup
* rodtoll Bug #43620 - DPVOICE: Recording buffer locks up on Aureal Vortex (VxD).
* Updated reset procedure so it ignores Stop failures and if Start() fails
* it tries resetting the recording system.
* 08/31/2000 rodtoll Bug #43804 - DVOICE: dwSensitivity structure member is confusing - should be dwThreshold
* 09/13/2000 rodtoll Bug #44845 - DXSTRESS: DPLAY: Access violation
* rodtoll Regression fix which caused fails failures in loopback test
* 10/17/2000 rodtoll Bug #47224 - DPVOICE: Recording lockup reset fails w/DSERR_ALLOCATED added sleep to allow driver to cleanup
* 10/30/2000 rodtoll Same as above -- Update with looping attempting to re-allocate capture device
* 01/26/2001 rodtoll WINBUG #293197 - DPVOICE: [STRESS} Stress applications cannot tell difference between out of memory and internal errors.
* Remap DSERR_OUTOFMEMORY to DVERR_OUTOFMEMORY instead of DVERR_SOUNDINITFAILURE.
* Remap DSERR_ALLOCATED to DVERR_PLAYBACKSYSTEMERROR instead of DVERR_SOUNDINITFAILURE.
* 04/11/2001 rodtoll WINBUG #221494 DPVOICE: Updates to improve lockup detection ---
* Reset record event when no work to do so we don't spin unessessarily as much
* Add check to ensure frame where we grab audio but pos hasn't move we don't count towards lockup
* Add check to ensure we enforce a timeout + a frame count for lockup purposes.
* 04/21/2001 rodtoll MANBUG #50058 DPVOICE: VoicePosition: No sound for couple of seconds when position bars are moved
* - Modified lockup detection to be time based, increased timeout to 600ms.
*
***************************************************************************/
#include "dxvoicepch.h"
#define RECORD_MAX_RESETS 10
#define RECORD_MAX_TIMEOUT 2000
#define TESTMODE_MAX_TIMEOUT 500
#define RECORD_NUM_TARGETS_INIT 0
#define RECORD_NUM_TARGETS_GROW 10
#define RECORD_PASSES_BEFORE_LOCKUP 25
#define RECORD_RESET_PAUSE 500
#define RECORD_RESET_ALLOC_ATTEMPTS 10
// RECORD_LOCKUP_TIMEOUT
//
// # of ms of no movement before a lockup is detected.
//
#define RECORD_LOCKUP_TIMEOUT 600
// Comment out to use the old sensitivity detection
#define __USENEWVA
// RECORDTEST_MIN_POWER / RECORDTEST_MAX_POWER
//
// Define the max and min possible power values
//#define RECORDTEST_MIN_POWER 0
//#define RECORDTEST_MAX_POWER 100
// We have to double the # because IsMuted is called twice / pass
#define RECORDTEST_NUM_FRAMESBEFOREVOICE 6
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::CClientRecordSubSystem"
// CClientRecordSubSystem
//
// This is the constructor for the CClientRecordSubSystem class.
// It intiailizes the classes member variables with the appropriate
// values.
//
// Parameters:
// CDirectVoiceClientEngine *clientEngine -
// Pointer to the client object which is using this object.
//
// Returns:
// N/A
//
CClientRecordSubSystem::CClientRecordSubSystem(
CDirectVoiceClientEngine *clientEngine
): m_dwSignature(VSIG_CLIENTRECORDSYSTEM),
m_recordState(RECORDSTATE_IDLE),
m_converter(NULL),
m_remain(0),
m_clientEngine(clientEngine),
m_dwCurrentPower(0),
m_dwSilentTime(0),
m_lastFrameTransmitted(FALSE),
m_msgNum(0),
m_seqNum(0),
m_dwLastTargetVersion(0),
m_lSavedVolume(0),
m_fRecordVolumeSaved(FALSE),
m_uncompressedSize(0),
m_compressedSize(0),
m_framesPerPeriod(0),
m_pbConstructBuffer(NULL),
m_dwResetCount(0),
m_dwNextReadPos(0),
m_fIgnoreFrame(FALSE),
m_dwPassesSincePosChange(0),
m_fLostFocus(FALSE),
m_pagcva(NULL),
m_dwFrameCount(0),
m_prgdvidTargetCache(NULL),
m_dwTargetCacheSize(0),
m_dwTargetCacheEntries(0),
m_dwFullBufferSize(0)
{
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::Initialize"
HRESULT CClientRecordSubSystem::Initialize()
{
HRESULT hr;
hr = DVCDB_CreateConverter( m_clientEngine->m_audioRecordBuffer->GetRecordFormat(), m_clientEngine->m_lpdvfCompressionInfo->guidType, &m_converter );
if( FAILED( hr ) )
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to create converter. hr = 0x%x", hr );
return hr ;
}
hr = m_converter->GetUnCompressedFrameSize( &m_uncompressedSize );
if( FAILED( hr ) )
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to get size hr=0x%x", hr );
return hr;
}
hr = m_converter->GetCompressedFrameSize( &m_compressedSize );
if( FAILED( hr ) )
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to get size hr=0x%x", hr );
return hr;
}
hr = m_converter->GetNumFramesPerBuffer( &m_framesPerPeriod );
if( FAILED( hr ) )
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to get size. hr = 0x%x", hr );
return hr;
}
m_transmitFrame = FALSE;
m_currentBuffer = 0;
m_dwResetCount = 0;
m_dwPassesSincePosChange = 0;
m_dwLastFrameTime = GetTickCount();
m_dwFrameTime = m_clientEngine->m_lpdvfCompressionInfo->dwTimeout;
m_dwSilenceTimeout = m_clientEngine->m_lpdvfCompressionInfo->dwTrailFrames*m_dwFrameTime;
// Prevents first frame from transmitting all the time
m_dwSilentTime = m_dwSilenceTimeout+1;
if( m_clientEngine->m_audioRecordBuffer->GetRecordFormat()->wBitsPerSample == 8 )
{
m_eightBit = TRUE;
}
else
{
m_eightBit = FALSE;
}
m_dwFullBufferSize = m_uncompressedSize*m_framesPerPeriod;
m_pbConstructBuffer = new BYTE[m_dwFullBufferSize];
if( m_pbConstructBuffer == NULL )
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Memory alloc failure" );
return DVERR_OUTOFMEMORY;
}
BeginStats();
InitStats();
// create and init the AGC and VA algorithm class
m_pagcva = new CAGCVA1();
if (m_pagcva == NULL)
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Memory alloc failure" );
return DVERR_OUTOFMEMORY;
}
LONG lSavedAGCVolume;
hr = m_pagcva->Init(
m_clientEngine->s_szRegistryPath,
m_clientEngine->m_dvClientConfig.dwFlags,
m_clientEngine->m_dvSoundDeviceConfig.guidCaptureDevice,
m_clientEngine->m_audioRecordBuffer->GetRecordFormat()->nSamplesPerSec,
m_eightBit ? 8 : 16,
&lSavedAGCVolume,
m_clientEngine->m_dvClientConfig.dwThreshold);
if (FAILED(hr))
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Error initializing AGC and/or VA algorithm, code: %i", hr);
delete m_pagcva;
return hr;
}
if( m_clientEngine->m_dvSoundDeviceConfig.dwFlags & DVSOUNDCONFIG_AUTOSELECT )
{
m_clientEngine->m_audioRecordBuffer->SelectMicrophone(TRUE);
}
m_dwLastTargetVersion = m_clientEngine->m_dwTargetVersion;
if( !(m_clientEngine->m_dvSoundDeviceConfig.dwFlags & DVSOUNDCONFIG_NORECVOLAVAILABLE) )
{
// save the original volume settings
m_clientEngine->m_audioRecordBuffer->GetVolume( &m_lSavedVolume );
m_fRecordVolumeSaved = TRUE;
// set our initial volume
if (m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_AUTORECORDVOLUME)
{
m_clientEngine->m_dvClientConfig.lRecordVolume = lSavedAGCVolume;
}
m_clientEngine->m_audioRecordBuffer->SetVolume( m_clientEngine->m_dvClientConfig.lRecordVolume );
}
else
{
m_fRecordVolumeSaved = FALSE;
}
// So our next
m_dwNextReadPos = 0;
m_dwLastReadPos = 0; //m_uncompressedSize*(m_framesPerPeriod-1);
m_dwLastBufferPos = m_dwLastReadPos;
return DV_OK;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::CleanupForReset"
HRESULT CClientRecordSubSystem::CleanupForReset()
{
HRESULT hr;
if( m_converter != NULL )
{
m_converter->Release();
m_converter = NULL;
}
if( m_clientEngine->m_audioRecordBuffer )
{
// Stop recording
m_clientEngine->m_audioRecordBuffer->Stop();
if( m_fRecordVolumeSaved )
{
m_clientEngine->m_audioRecordBuffer->SetVolume( m_lSavedVolume );
}
}
// Deinit and cleanup the AGC and VA algorthms
if (m_pagcva != NULL)
{
hr = m_pagcva->Deinit();
if (FAILED(hr))
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Deinit error on AGC and/or VA algorithm, code: %i", hr);
}
delete m_pagcva;
m_pagcva = NULL;
}
else
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Unexpected NULL pointer for AGC/VA algorithm");
}
if( m_pbConstructBuffer != NULL )
{
delete [] m_pbConstructBuffer;
m_pbConstructBuffer = NULL;
}
if( m_prgdvidTargetCache )
{
delete [] m_prgdvidTargetCache;
m_prgdvidTargetCache = NULL;
m_dwTargetCacheSize = 0;
m_dwTargetCacheEntries = 0;
}
return DV_OK;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::~CClientRecordSubSystem"
// CClientRecordSubSystem
//
// This is the destructor for the CClientRecordSubSystem
// class. It cleans up the allocated memory for the class
// and stops the recording device.
//
// Parameters:
// N/A
//
// Returns:
// N/A
//
CClientRecordSubSystem::~CClientRecordSubSystem()
{
HRESULT hr;
CleanupForReset();
CompleteStats();
m_dwSignature = VSIG_CLIENTRECORDSYSTEM_FREE;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::IsMuted"
BOOL CClientRecordSubSystem::IsMuted()
{
// If the notification of the local player hasn't been processed and we're in peer to peer mode, don't
// allow recording to start until after the player has been indicated
if( !m_clientEngine->m_fLocalPlayerAvailable )
{
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "Local player has not yet been indicated, ignoring frame" );
return TRUE;
}
BOOL fMuted = FALSE;
// if(m_clientEngine->m_dvSoundDeviceConfig.dwFlags & DVSOUNDCONFIG_TESTMODE )
// {
if( m_dwFrameCount < RECORDTEST_NUM_FRAMESBEFOREVOICE )
{
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "Skipping first %d frames for startup burst", RECORDTEST_NUM_FRAMESBEFOREVOICE );
m_dwFrameCount++;
return TRUE;
}
// }
if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_ECHOSUPPRESSION )
{
DNEnterCriticalSection( &m_clientEngine->m_lockPlaybackMode );
if( (m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_RECORDMUTE) ||
(m_clientEngine->m_dwEchoState == DVCECHOSTATE_PLAYBACK) )
{
fMuted = TRUE;
}
DNLeaveCriticalSection( &m_clientEngine->m_lockPlaybackMode );
}
else
{
fMuted = (m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_RECORDMUTE);
}
return fMuted;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::StartMessage()"
void CClientRecordSubSystem::StartMessage()
{
BYTE bPeakLevel;
if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_AUTOVOICEACTIVATED ||
m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_MANUALVOICEACTIVATED )
{
m_pagcva->PeakResults(&bPeakLevel);
}
else
{
bPeakLevel = 0;
}
DVMSG_RECORDSTART dvRecordStart;
dvRecordStart.dwPeakLevel = bPeakLevel;
dvRecordStart.dwSize = sizeof( DVMSG_RECORDSTART );
dvRecordStart.pvLocalPlayerContext = m_clientEngine->m_pvLocalPlayerContext;
m_clientEngine->NotifyQueue_Add( DVMSGID_RECORDSTART, &dvRecordStart, sizeof( DVMSG_RECORDSTART ) );
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Starting Message" );
// STATSBLOCK: Begin
m_clientEngine->m_pStatsBlob->m_recStats.m_dwNumMessages++;
// STATSBLOCK: End
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::EndMessage()"
void CClientRecordSubSystem::EndMessage()
{
BYTE bPeakLevel;
if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_AUTOVOICEACTIVATED ||
m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_MANUALVOICEACTIVATED )
{
m_pagcva->PeakResults(&bPeakLevel);
}
else
{
bPeakLevel = 0;
}
// STATBLOCK: Begin
if( m_seqNum > m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMax = m_seqNum;
}
if( m_seqNum < m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMin = m_seqNum;
}
m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLTotal += m_seqNum;
// STATBLOCK: End
DVMSG_RECORDSTOP dvRecordStop;
dvRecordStop.dwPeakLevel = bPeakLevel;
dvRecordStop.dwSize = sizeof( DVMSG_RECORDSTOP );
dvRecordStop.pvLocalPlayerContext = m_clientEngine->m_pvLocalPlayerContext;
m_clientEngine->NotifyQueue_Add( DVMSGID_RECORDSTOP, &dvRecordStop, sizeof( DVMSG_RECORDSTOP ) );
m_msgNum++;
m_seqNum = 0;
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Ending Message" );
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::CheckVA()"
BOOL CClientRecordSubSystem::CheckVA()
{
#ifdef __USENEWVA
// We've been muted, turn off the VA
if( IsMuted() )
{
m_dwSilentTime = m_dwSilenceTimeout+1;
return FALSE;
}
BOOL m_fVoiceDetected;
m_pagcva->VAResults(&m_fVoiceDetected);
if (!m_fVoiceDetected)
{
// This prevents wrap-around on silence timeout
if( m_dwSilentTime <= m_dwSilenceTimeout )
{
m_dwSilentTime += m_dwFrameTime;
}
DPFX(DPFPREP, DVF_INFOLEVEL, "### Silence Time to %d", m_dwSilentTime );
if( m_dwSilentTime > m_dwSilenceTimeout )
{
DPFX(DPFPREP, DVF_INFOLEVEL, "### Silence Time exceeded %d", m_dwSilenceTimeout );
if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_ECHOSUPPRESSION )
{
// If we're in the idle state, go to the recording state
DNEnterCriticalSection( &m_clientEngine->m_lockPlaybackMode );
if( m_clientEngine->m_dwEchoState == DVCECHOSTATE_RECORDING )
{
DPFX(DPFPREP, RECORD_SWITCH_DEBUG_LEVEL, "%%%% Switching to idle mode" );
m_clientEngine->m_dwEchoState = DVCECHOSTATE_IDLE;
}
DNLeaveCriticalSection( &m_clientEngine->m_lockPlaybackMode );
}
return FALSE;
}
}
else
{
m_dwSilentTime = 0;
DPFX(DPFPREP, DVF_INFOLEVEL, "### Silence Time to 0" );
DPFX(DPFPREP, DVF_INFOLEVEL, "### Transmit!!!!" );
}
if( m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_ECHOSUPPRESSION )
{
// If we're in the idle state, go to the recording state
DNEnterCriticalSection( &m_clientEngine->m_lockPlaybackMode );
if( m_clientEngine->m_dwEchoState == DVCECHOSTATE_IDLE )
{
DPFX(DPFPREP, RECORD_SWITCH_DEBUG_LEVEL, "%%%% Switching to recording mode" );
m_clientEngine->m_dwEchoState = DVCECHOSTATE_RECORDING;
}
DNLeaveCriticalSection( &m_clientEngine->m_lockPlaybackMode );
}
return TRUE;
#else
m_peakCheck = TRUE;
return !DetectSilence( m_bufferPtr, m_uncompressedSize, m_eightBit, 32, m_clientEngine->m_dwActivatePowerLevel, m_clientEngine->m_bLastPeak );
#endif
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::DoAGC"
HRESULT CClientRecordSubSystem::DoAGC()
{
// let AGC have a bash at changing the volume, but only if AGC is enabled in the current client config.
if (m_clientEngine->m_dvClientConfig.dwFlags & DVCLIENTCONFIG_AUTORECORDVOLUME
&& !(m_clientEngine->m_dvSoundDeviceConfig.dwFlags & DVSOUNDCONFIG_NORECVOLAVAILABLE) )
{
// get the current hardware volume level - this will allow us to track "outside" changes
// to the volume control. e.g. The user find the volume is too low, and manually drags the
// volume control's volume slider up a bit. We won't become totally confused, since we're
// checking the hardware level here.
m_clientEngine->m_audioRecordBuffer->GetVolume( &(m_clientEngine->m_dvClientConfig.lRecordVolume) );
LONG lNewVolume;
m_pagcva->AGCResults(m_clientEngine->m_dvClientConfig.lRecordVolume, &lNewVolume, m_transmitFrame);
// set the current hardware volume level, but only if it has changed
if (m_clientEngine->m_dvClientConfig.lRecordVolume != lNewVolume)
{
// Problem: due to the convertion of logarithmic to linear
// volume settings, there may be a rounding error at low
// volumes. So, AGC will try to make a small volume adjustment
// that results in NO adjustment due to rounding, and it can't
// work it's way out of the hole.
//
// So - if AGC tried to uptick or downtick the volume, make
// sure it worked!
LONG lOrgVolume;
LONG lVolumeDelta;
LONG lNewHWVolume;
lOrgVolume = m_clientEngine->m_dvClientConfig.lRecordVolume;
lVolumeDelta = lNewVolume - lOrgVolume;
lNewHWVolume = lOrgVolume;
while(1)
{
m_clientEngine->m_dvClientConfig.lRecordVolume = lNewVolume;
DPFX(DPFPREP, DVF_INFOLEVEL, "AGC: Setting volume to %i", lNewVolume);
m_clientEngine->m_audioRecordBuffer->SetVolume(lNewVolume);
m_clientEngine->m_audioRecordBuffer->GetVolume(&lNewHWVolume);
if (lNewHWVolume == lOrgVolume)
{
// The value did not change, so we're hitting a rounding
// error.
// Make sure we're not already at the min or max
if (lNewVolume == DSBVOLUME_MIN || lNewVolume == DSBVOLUME_MAX)
{
// There's nothing more we can do. Give up.
break;
}
// Add another delta to the new volume, and try again.
lNewVolume += lVolumeDelta;
if (lNewVolume > DSBVOLUME_MAX)
{
lNewVolume = DSBVOLUME_MAX;
}
if (lNewVolume < DSBVOLUME_MIN)
{
lNewVolume = DSBVOLUME_MIN;
}
}
else
{
// The value changed, so we're done.
break;
}
}
}
}
return DV_OK;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::RecordFSM"
HRESULT CClientRecordSubSystem::RecordFSM()
{
if( m_fIgnoreFrame )
{
return DV_OK;
}
// In this case we NEVER transmit
// Shortcut to simplify the other cases
if( IsMuted() || !IsValidTarget() )
{
// Go immediately to IDLE
DPFX(DPFPREP, DVF_INFOLEVEL, "### IsMuted || IsValidTarget --> IDLE" );
m_recordState = RECORDSTATE_IDLE;
}
if( !IsMuted() )
{
// before we analyze the data, push down the current
// relevant portions of the client config structure.
m_pagcva->SetSensitivity(m_clientEngine->m_dvClientConfig.dwFlags, m_clientEngine->m_dvClientConfig.dwThreshold);
m_pagcva->AnalyzeData(m_bufferPtr, m_uncompressedSize);
}
m_transmitFrame = FALSE;
switch( m_recordState )
{
case RECORDSTATE_IDLE:
DPFX(DPFPREP, DVF_INFOLEVEL, "### STATE: IDLE" );
if( !IsMuted() && IsValidTarget() )
{
if( IsPTT() )
{
m_recordState = RECORDSTATE_PTT;
m_transmitFrame = TRUE;
}
else
{
m_transmitFrame = CheckVA();
if( m_transmitFrame )
{
m_recordState = RECORDSTATE_VA;
}
}
}
break;
case RECORDSTATE_VA:
DPFX(DPFPREP, DVF_INFOLEVEL, "### STATE: VA" );
if( IsPTT() )
{
DPFX(DPFPREP, DVF_INFOLEVEL, "### VA --> PTT" );
m_recordState = RECORDSTATE_PTT;
m_transmitFrame = TRUE;
}
else
{
m_transmitFrame = CheckVA();
if (!m_transmitFrame)
{
DPFX(DPFPREP, DVF_INFOLEVEL, "### !VA --> IDLE" );
m_recordState = RECORDSTATE_IDLE;
}
}
break;
case RECORDSTATE_PTT:
DPFX(DPFPREP, DVF_INFOLEVEL, "### STATE: PTT" );
if( IsVA() )
{
DPFX(DPFPREP, DVF_INFOLEVEL, "### PTT --> VA" );
m_recordState = RECORDSTATE_VA;
m_transmitFrame = CheckVA();
}
else
{
m_transmitFrame = TRUE;
}
break;
}
// Now that we've figured out if we're transmitting or not, do the AGC
DoAGC();
// Message Ended
if( m_lastFrameTransmitted && !m_transmitFrame )
{
EndMessage();
}
// Message Started
else if( !m_lastFrameTransmitted && m_transmitFrame )
{
StartMessage();
}
// Message Continuing
else
{
// If the target has changed since the last frame
if( m_clientEngine->m_dwTargetVersion != m_dwLastTargetVersion )
{
// If we're going to be transmitting
if( m_transmitFrame )
{
EndMessage();
StartMessage();
}
}
}
m_lastFrameTransmitted = m_transmitFrame;
m_dwLastTargetVersion = m_clientEngine->m_dwTargetVersion;
// Save the peak level to propogate up to the app
if( m_fIgnoreFrame )
{
m_clientEngine->m_bLastPeak = 0;
}
else
{
m_pagcva->PeakResults(&(m_clientEngine->m_bLastPeak));
}
return DV_OK;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::ResetForLockup"
// Reset For Lockup
//
// This function is called when an amount of time specified
// by the timeout is exceeded since the last movement of the
// recording buffer
//
// Parameters:
// N/A
//
// Returns:
// hr
//
HRESULT CClientRecordSubSystem::ResetForLockup()
{
HRESULT hr;
DWORD dwBufferPos;
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Detected! Attempting RESET" );
hr = m_clientEngine->m_audioRecordBuffer->Stop();
if( FAILED( hr ) )
{
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Stop() Failed hr=0x%x", hr );
}
else
{
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Stop() worked", hr );
}
hr = m_clientEngine->m_audioRecordBuffer->Record( TRUE );
if( FAILED( hr ) )
{
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Record() failed hr=0x%x", hr );
// Cleanup for a FULL recording subsystem reset
hr = CleanupForReset();
if( FAILED( hr ) )
{
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Unable to cleanup for reset hr=0x%x", hr );
return hr;
}
// Destroy / stop etc old buffer
delete m_clientEngine->m_audioRecordBuffer;
m_clientEngine->m_audioRecordBuffer = NULL;
// Will now re-attempt to re-acquire the device a total of RECORD_RESET_ALLOC_ATTEMPTS times.
for( DWORD dwAttempt = 0; dwAttempt < RECORD_RESET_ALLOC_ATTEMPTS; dwAttempt++ )
{
// Ok. So why's this sleep here?
//
// Turns out on some systems / drivers destroying the buffer as above and then immediately trying to create a new
// one returns a DSERR_ALLOCATED (which ends up causing a session lost w/DVERR_RECORDSYSTEMERROR). There
// is likely some kind of timing problem in the drivers effected. Adding this sleep alleviates the problem probably
// because it gives the driver time to reset.
//
// The problem itself is hard to repro, so don't assume removing the assert and not seeing the problem means it
// doesn't happen anymore.
//
Sleep( RECORD_RESET_PAUSE );
// Create a new one
hr = InitializeRecordBuffer( m_clientEngine->m_dvSoundDeviceConfig.hwndAppWindow, m_clientEngine->m_lpdvfCompressionInfo,
m_clientEngine->m_audioRecordDevice, &m_clientEngine->m_audioRecordBuffer,
m_clientEngine->m_dvSoundDeviceConfig.dwFlags );
if( hr == DSERR_ALLOCATED )
{
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Unable to rebuild capture object, re-attempting.." );
continue;
}
else if( FAILED( hr ) )
{
DSASSERT( FALSE );
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Unable to restart locked up recording buffer hr=0x%x", hr );
return hr;
}
else if( SUCCEEDED(hr) )
{
break;
}
}
DSASSERT( SUCCEEDED( hr ) );
// Restart the entire recording sub-system
hr = Initialize();
if( FAILED( hr ) )
{
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Unable to re-initialize recording sub-system hr=0x%x", hr );
return hr;
}
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Full reset worked!" );
}
else
{
Diagnostics_Write( DVF_ERRORLEVEL, "LOCKUP: Start() succeeded" );
}
BOOL fLostFocus;
// We're going to ignore focus results because it will be picked up by the next
// pass through the loop.
//
hr = m_clientEngine->m_audioRecordBuffer->GetCurrentPosition( &dwBufferPos, &fLostFocus );
if( FAILED( hr ) )
{
DSASSERT( FALSE );
DPFX(DPFPREP, DVF_ERRORLEVEL, "Get Current position failed" );
return hr;
}
m_dwLastReadPos = dwBufferPos; //(m_uncompressedSize*(m_framesPerPeriod-1));
m_dwNextReadPos = dwBufferPos; // 0;
m_dwLastBufferPos = dwBufferPos;
m_dwLastFrameTime = GetTickCount();
// Reset the record count -- this routine can take some time so the record
// count will queue up on a reset. Don't want too many queued events or
// we could end up with a false lockup detection.
//
ResetEvent( m_clientEngine->m_thTimerInfo.hRecordTimerEvent );
return DV_OK;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::GetNextFrame"
// GetNextFrame
//
// This function retrieves the next frame from the recording input
// and detects recording lockup. If a recording lockup is detected
// then this function attempts to restart the recording system
// 3 times. If it fails the recording system is considered to be
// locked up.
//
// This was implemented as a fix for SB Live! cards which periodically
// lockup while recording. (Also noticed on Aureal Vortex cards).
//
// Parameters:
// N/A
//
// Returns:
// bool - true if next frame was retrieved, false if lockup detected
HRESULT CClientRecordSubSystem::GetNextFrame( LPBOOL pfContinue )
{
HRESULT hr;
DWORD dwBufferPos;
PVOID pBufferPtr1, pBufferPtr2;
DWORD dwSizeBuffer1, dwSizeBuffer2;
DWORD dwCurrentTime;
BOOL fLostFocus;
DWORD dwTSLM;
hr = m_clientEngine->m_audioRecordBuffer->GetCurrentPosition( &dwBufferPos, &fLostFocus );
if( FAILED( hr ) && !fLostFocus )
{
DSASSERT( FALSE );
DPFX(DPFPREP, DVF_ERRORLEVEL, "Get Current position failed" );
return hr;
}
// Get the current time
dwCurrentTime = GetTickCount();
// The focus has changed!
if( fLostFocus != m_fLostFocus )
{
m_fLostFocus = fLostFocus;
// We just lost focus.
if( fLostFocus )
{
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Buffer just lost focus. No more input until focus returns" );
// Keep frame time up to date since we are about to exit
m_dwLastFrameTime = dwCurrentTime;
// Notify the application
m_clientEngine->NotifyQueue_Add( DVMSGID_LOSTFOCUS, NULL, 0 );
// If we're in a message, end it. Won't be any more input until focus returns.
if( m_lastFrameTransmitted )
{
EndMessage();
m_lastFrameTransmitted = FALSE;
}
// Set the peak levels to 0
m_clientEngine->m_bLastPeak = 0;
// To ignore the frame
m_fIgnoreFrame = TRUE;
// To shortcircuit recording loop
*pfContinue = FALSE;
// Drop the data remaining in the buffer
if( dwBufferPos < m_uncompressedSize )
{
m_dwLastReadPos = (m_dwFullBufferSize) - (m_uncompressedSize - dwBufferPos);
}
else
{
m_dwLastReadPos = (dwBufferPos - m_uncompressedSize );
}
m_dwNextReadPos = dwBufferPos;
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Moving last read to %d and next to %d", m_dwLastReadPos, m_dwNextReadPos );
return DV_OK;
}
// We just gained focus... continue as if normal.
else
{
m_clientEngine->NotifyQueue_Add( DVMSGID_GAINFOCUS, NULL, 0 );
}
}
if( fLostFocus )
{
// Set the peak levels to 0
m_clientEngine->m_bLastPeak = 0;
// Ignore this frame
m_fIgnoreFrame = TRUE;
// Do not continue in recording loop until next wakeup
*pfContinue = FALSE;
// Keep frame time up to date since we are about to exit
m_dwLastFrameTime = dwCurrentTime;
// Track the moving buffer (if it's moving) for the case of WDM
// drivers on Millenium that continue to record
if( dwBufferPos < m_uncompressedSize )
{
m_dwLastReadPos = (m_dwFullBufferSize) - (m_uncompressedSize - dwBufferPos);
}
else
{
m_dwLastReadPos = (dwBufferPos - m_uncompressedSize );
}
m_dwNextReadPos = dwBufferPos;
return DV_OK;
}
// STATSBLOCK: Begin
dwTSLM = dwCurrentTime - m_dwLastFrameTime;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMTotal += dwTSLM;
if( dwTSLM > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMax = dwTSLM;
}
if( dwTSLM < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMin = dwTSLM;
}
// STATSBLOCK: End
DPFX(DPFPREP, DVF_INFOLEVEL, "Current Pos: %d, Last Pos: %d", dwBufferPos, m_dwLastBufferPos );
// Calculate how much buffer we have to catch up for.
if( m_dwNextReadPos > dwBufferPos )
{
dwSizeBuffer2 = ((m_dwFullBufferSize)-m_dwNextReadPos)+dwBufferPos;
}
else
{
dwSizeBuffer2 = dwBufferPos - m_dwNextReadPos;
}
// See if a lockup occurred which means the buffer hasn't moved
//
// We only check for a lockup however when we aren't in the process of "Catching up". If we've
// fallen behind because we lost CPU, and we're several frames behind we're going to run through
// this section several times to catch up. The issue is that when we run several times there is
// very little time between the runs and therefore it is perfectly reasonable for DirectSound
// to have not moved it's buffer pointer.
//
if( m_dwLastBufferPos == dwBufferPos &&
dwSizeBuffer2 < m_uncompressedSize ) // Make sure we're not just "catching up".
{
m_dwPassesSincePosChange++;
// The lower this number is, the better the experience for people with bad drivers that lockup.
// The higher it is, the more we think we've locked up in situations where the buffer legitimately
// doesn't move, like high CPU usage, or when the app is in the debugger.
if( (dwCurrentTime - m_dwLastFrameTime) >= RECORD_LOCKUP_TIMEOUT )
{
m_dwPassesSincePosChange = 0;
// We've been RECORD_MAX_RESETS passes through here without the buffer moving, reset
if( m_dwResetCount > RECORD_MAX_RESETS )
{
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Maximum Resets exceeded" );
return DVERR_RECORDSYSTEMERROR;
}
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Lockup Detected %d ms since last movement", dwCurrentTime - m_dwLastFrameTime );
// STATSBLOCK: Begin
DPFX(DPFPREP, DVF_GLITCH_DEBUG_LEVEL, "GLITCH: Record: Recording buffer has stopped moving." );
// STATSBLOCK: End
hr = ResetForLockup();
if( FAILED( hr ) )
{
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, Reset for lockup failed hr=0x%x", hr );
return hr;
}
m_fIgnoreFrame = TRUE;
*pfContinue = FALSE;
m_dwResetCount++;
// STATSBLOCK: Begin
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRTotal++;
if( m_dwResetCount < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMin = m_dwResetCount;
}
if( m_dwResetCount > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMax = m_dwResetCount;
}
// STATSBLOCK: End
// Keep frame time up to date since we are about to exit
//
// NOTE: This needs to be GetTickCount() because Reset() can take 100's of ms.
m_dwLastFrameTime = GetTickCount();
return DV_OK;
}
}
else
{
m_dwPassesSincePosChange = 0;
}
// There was no lockup, and we are in focus, continue
m_dwLastBufferPos = dwBufferPos;
// Calc Delta in bytes of buffer read pointer
if( m_dwLastBufferPos > dwBufferPos )
{
dwSizeBuffer1 = ((m_dwFullBufferSize)-m_dwLastBufferPos)+dwBufferPos;
}
else
{
dwSizeBuffer1 = dwBufferPos - m_dwLastBufferPos;
}
// The position did not move enough for a full frame.
if( dwSizeBuffer2 < m_uncompressedSize )
{
m_fIgnoreFrame = TRUE;
*pfContinue = FALSE;
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, %d, %d, %d, %d, %d, %d (SKIPPING)",
dwBufferPos,
dwSizeBuffer1,
dwCurrentTime - m_dwLastFrameTime,
m_dwNextReadPos,
dwSizeBuffer2,
m_uncompressedSize );
}
else
{
DPFX(DPFPREP, RRI_DEBUGOUTPUT_LEVEL, "RRI, %d, %d, %d, %d, %d, %d",
dwBufferPos,
dwSizeBuffer1,
dwCurrentTime - m_dwLastFrameTime,
m_dwNextReadPos,
dwSizeBuffer2,
m_uncompressedSize );
m_dwResetCount = 0;
// STATSBLOCK: BEGIN
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSTotal += (dwCurrentTime - m_dwLastFrameTime);
if( (dwCurrentTime - m_dwLastFrameTime) > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMax = dwCurrentTime - m_dwLastFrameTime;
}
if( m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMax > 70000 )
{
}
if( (dwCurrentTime - m_dwLastFrameTime) < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMMSMin = dwCurrentTime - m_dwLastFrameTime;
}
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBTotal += dwSizeBuffer1;
if( dwSizeBuffer1 > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBMax = dwSizeBuffer1;
}
if( dwSizeBuffer1 < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRMBMin = dwSizeBuffer1;
}
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLTotal += dwSizeBuffer2;
if( dwSizeBuffer2 > m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLMax = dwSizeBuffer2;
}
if( dwSizeBuffer2 < m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRLMin = dwSizeBuffer2;
}
// STATSBLOCK: END
// Get the next block of audio data and construct it into the buffer
hr = m_clientEngine->m_audioRecordBuffer->Lock( m_dwNextReadPos, m_uncompressedSize, &pBufferPtr1, &dwSizeBuffer1, &pBufferPtr2, &dwSizeBuffer2, 0 );
if( FAILED( hr ) )
{
DSASSERT( FALSE );
DPFX(DPFPREP, DVF_ERRORLEVEL, "Error locking buffer: loc=%d size=%d hr=0x%x", m_dwNextReadPos, m_uncompressedSize, hr );
return hr;
}
memcpy( m_pbConstructBuffer, pBufferPtr1, dwSizeBuffer1 );
if( dwSizeBuffer2 > 0 )
{
memcpy( &m_pbConstructBuffer[dwSizeBuffer1], pBufferPtr2, dwSizeBuffer2 );
}
hr = m_clientEngine->m_audioRecordBuffer->UnLock( pBufferPtr1, dwSizeBuffer1, pBufferPtr2, dwSizeBuffer2 );
if( FAILED( hr ) )
{
DSASSERT( FALSE );
DPFX(DPFPREP, DVF_ERRORLEVEL, "Error unlocking buffer hr=0x%x", hr );
return hr;
}
m_bufferPtr = m_pbConstructBuffer;
m_fIgnoreFrame = FALSE;
m_dwLastReadPos = m_dwNextReadPos;
m_dwNextReadPos += m_uncompressedSize;
m_dwNextReadPos %= (m_dwFullBufferSize);
// Update the last frame time
m_dwLastFrameTime = dwCurrentTime;
*pfContinue = TRUE;
}
return DV_OK;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::TransmitFrame"
// TransmitFrame
//
// This function looks at the state of the FSM, and if the latest
// frame is to be transmitted it compresses it and transmits
// it to the server (cleint/server mode) or to all the players
// (or player if whispering) in peer to peer mode. If the frame
// is not to be transmitted this function does nothing.
//
// It is also responsible for ensuring any microphone clicks are
// mixed into the audio stream.
//
// This function also updates the transmission statisics and
// the current transmission status.
//
// Parameters:
// N/A
//
// Returns:
// bool - always returns true at the moment
HRESULT CClientRecordSubSystem::TransmitFrame()
{
HRESULT hr = DV_OK;
if( m_transmitFrame && !m_fIgnoreFrame )
{
// STATSBLOCK: Begin
m_clientEngine->m_pStatsBlob->m_recStats.m_dwSentFrames++;
// STATSBLOCK: End
if( m_clientEngine->m_dvSessionDesc.dwSessionType == DVSESSIONTYPE_PEER )
{
hr = BuildAndTransmitSpeechHeader(FALSE);
}
else if( m_clientEngine->m_dvSessionDesc.dwSessionType == DVSESSIONTYPE_ECHO )
{
hr = BuildAndTransmitSpeechHeader(TRUE);
}
else
{
hr = BuildAndTransmitSpeechWithTarget(TRUE);
}
}
else
{
// STATSBLOCK: Begin
m_clientEngine->m_pStatsBlob->m_recStats.m_dwIgnoredFrames++;
// STATSBLOCK: End
}
return hr;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::BuildAndTransmitSpeechHeader"
HRESULT CClientRecordSubSystem::BuildAndTransmitSpeechHeader( BOOL bSendToServer )
{
PDVPROTOCOLMSG_SPEECHHEADER pdvSpeechHeader;
HRESULT hr;
DWORD dwTargetSize;
DWORD dwStartTime;
DWORD dwCompressTime;
PDVTRANSPORT_BUFFERDESC pBufferDesc;
LPVOID pvSendContext;
pBufferDesc = m_clientEngine->GetTransmitBuffer( sizeof(DVPROTOCOLMSG_SPEECHHEADER)+COMPRESSION_SLUSH+m_compressedSize, &pvSendContext );
if( pBufferDesc == NULL )
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to get buffer for transmission" );
return DVERR_OUTOFMEMORY;
}
pdvSpeechHeader = (PDVPROTOCOLMSG_SPEECHHEADER) pBufferDesc->pBufferData;
pdvSpeechHeader->dwType = DVMSGID_SPEECH;
pdvSpeechHeader->bMsgNum = m_msgNum;
pdvSpeechHeader->bSeqNum = m_seqNum;
DPFX(DPFPREP, DVF_CLIENT_SEQNUM_DEBUG_LEVEL, "SEQ: Record: Msg [%d] Seq [%d]", m_msgNum, m_seqNum );
dwTargetSize = m_compressedSize;
dwStartTime = GetTickCount();
hr = m_converter->Convert( (BYTE *) m_bufferPtr, m_uncompressedSize, (BYTE *) &pdvSpeechHeader[1], &dwTargetSize, FALSE );
if( FAILED( hr ) )
{
m_clientEngine->ReturnTransmitBuffer( pvSendContext );
DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to perform conversion hr=0x%x", hr );
return hr;
}
dwCompressTime = GetTickCount() - dwStartTime;
DPFX(DPFPREP, DVF_INFOLEVEL, "Compressed %d bytes to %d taking %d ms", m_uncompressedSize, dwTargetSize, dwCompressTime );
// STATSBLOCK: Begin
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTTotal += dwCompressTime;
if( dwCompressTime < m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin = dwCompressTime;
}
if( dwCompressTime > m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMax = dwCompressTime;
}
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSTotal += dwTargetSize;
if( dwTargetSize < m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin = dwTargetSize;
}
if( dwTargetSize > m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMax = dwTargetSize;
}
// Header is fixed size
m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSTotal += sizeof( DVPROTOCOLMSG_SPEECHHEADER );
m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMax = sizeof( DVPROTOCOLMSG_SPEECHHEADER );
m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMin = sizeof( DVPROTOCOLMSG_SPEECHHEADER );
// STATSBLOCK: End
if( m_clientEngine->m_dwNumTargets == 0 )
{
DPFX(DPFPREP, DVF_INFOLEVEL, "Targets set to NONE since FSM. Not transmitting" );
m_clientEngine->ReturnTransmitBuffer( pvSendContext );
return DV_OK;
}
else
{
pBufferDesc->dwBufferSize = sizeof(DVPROTOCOLMSG_SPEECHHEADER)+dwTargetSize;
if( bSendToServer )
{
DPFX(DPFPREP, DVF_INFOLEVEL, "Transmitting %d bytes to server", pBufferDesc->dwBufferSize );
hr = m_clientEngine->m_lpSessionTransport->SendToServer( pBufferDesc, pvSendContext, 0 );
}
else
{
// Copy the target list to a cache so we can drop the lock during the send.
DNEnterCriticalSection( &m_clientEngine->m_csTargetLock );
if( m_clientEngine->m_dwNumTargets > m_dwTargetCacheSize )
{
if( m_prgdvidTargetCache )
delete [] m_prgdvidTargetCache;
m_dwTargetCacheSize = m_clientEngine->m_dwNumTargets;
m_prgdvidTargetCache = new DVID[m_dwTargetCacheSize];
if( !m_prgdvidTargetCache )
{
DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
DPFERR( "Out of memory" );
return DVERR_OUTOFMEMORY;
}
}
memcpy( m_prgdvidTargetCache, m_clientEngine->m_pdvidTargets, sizeof(DVID)*m_clientEngine->m_dwNumTargets );
m_dwTargetCacheEntries = m_clientEngine->m_dwNumTargets;
DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
// the target cache (prgdvidTargetCache) doesn't need protection since it is only accessed by one thread at a time.
DPFX(DPFPREP, DVF_INFOLEVEL, "Transmitting %d bytes to target list", pBufferDesc->dwBufferSize );
hr = m_clientEngine->m_lpSessionTransport->SendToIDS( m_prgdvidTargetCache, m_dwTargetCacheEntries,
pBufferDesc, pvSendContext, 0 );
}
if( hr == DVERR_PENDING )
{
hr = DV_OK;
}
else if ( FAILED( hr ))
{
DPFX(DPFPREP, DVF_INFOLEVEL, "Send failed hr=0x%x", hr );
}
m_seqNum++;
}
return hr;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::BuildAndTransmitSpeechWithTarget"
HRESULT CClientRecordSubSystem::BuildAndTransmitSpeechWithTarget( BOOL bSendToServer )
{
PDVPROTOCOLMSG_SPEECHWITHTARGET pdvSpeechWithTarget;
HRESULT hr;
DWORD dwTargetSize;
DWORD dwTransmitSize;
DWORD dwTargetInfoSize;
PBYTE pbBuilderLoc;
DWORD dwStartTime;
DWORD dwCompressTime;
PDVTRANSPORT_BUFFERDESC pBufferDesc;
LPVOID pvSendContext;
dwTransmitSize = sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET );
// Calculate size we'll need for storing the targets
DNEnterCriticalSection( &m_clientEngine->m_csTargetLock );
dwTargetInfoSize = sizeof( DVID ) * m_clientEngine->m_dwNumTargets;
if( dwTargetInfoSize == 0 )
{
DPFX(DPFPREP, DVF_INFOLEVEL, "Targets set to NONE since FSM. Not transmitting" );
DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
return DV_OK;
}
else
{
dwTransmitSize += dwTargetInfoSize;
}
DPFX(DPFPREP, DVF_CLIENT_SEQNUM_DEBUG_LEVEL, "SEQ: Record: Msg [%d] Seq [%d]", m_msgNum, m_seqNum );
dwTransmitSize += m_compressedSize;
pBufferDesc = m_clientEngine->GetTransmitBuffer( sizeof(DVPROTOCOLMSG_SPEECHWITHTARGET)+m_compressedSize+COMPRESSION_SLUSH+dwTransmitSize, &pvSendContext );
if( pBufferDesc == NULL )
{
DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to get buffer for transmission" );
return DVERR_OUTOFMEMORY;
}
pdvSpeechWithTarget = (PDVPROTOCOLMSG_SPEECHWITHTARGET) pBufferDesc->pBufferData;
pbBuilderLoc = (PBYTE) &pdvSpeechWithTarget[1];
memcpy( pbBuilderLoc, m_clientEngine->m_pdvidTargets, dwTargetInfoSize );
pdvSpeechWithTarget->dwNumTargets = m_clientEngine->m_dwNumTargets;
DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
pbBuilderLoc += dwTargetInfoSize;
pdvSpeechWithTarget->dvHeader.dwType = DVMSGID_SPEECHWITHTARGET;
pdvSpeechWithTarget->dvHeader.bMsgNum = m_msgNum;
pdvSpeechWithTarget->dvHeader.bSeqNum = m_seqNum;
dwTargetSize = m_compressedSize;
dwStartTime = GetTickCount();
DPFX(DPFPREP, DVF_COMPRESSION_DEBUG_LEVEL, "COMPRESS: < %d --> %d ", m_uncompressedSize, dwTargetSize );
hr = m_converter->Convert( (BYTE *) m_bufferPtr, m_uncompressedSize, (BYTE *) pbBuilderLoc, &dwTargetSize, FALSE );
if( FAILED( hr ) )
{
m_clientEngine->ReturnTransmitBuffer( pvSendContext );
DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to perform conversion hr=0x%x", hr );
return hr;
}
dwCompressTime = GetTickCount() - dwStartTime;
DPFX(DPFPREP, DVF_COMPRESSION_DEBUG_LEVEL, "COMPRESS: > %d --> %d %d ms", m_uncompressedSize, dwTargetSize, dwCompressTime );
// STATSBLOCK: Begin
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTTotal += dwCompressTime;
if( dwCompressTime < m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin = dwCompressTime;
}
if( dwCompressTime > m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMax = dwCompressTime;
}
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSTotal += dwTargetSize;
if( dwTargetSize < m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin = dwTargetSize;
}
if( dwTargetSize > m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMax = dwTargetSize;
}
// Header stats
m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSTotal += sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize;
if( sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize > m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMax )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMax = sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize;
}
if( sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize < m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMin )
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMin = sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET )+dwTargetInfoSize;
}
// STATSBLOCK: End
// We need to transmit header, target info and then speech data
dwTransmitSize = sizeof( DVPROTOCOLMSG_SPEECHWITHTARGET ) + dwTargetInfoSize + dwTargetSize;
pBufferDesc->dwBufferSize = dwTransmitSize;
hr = m_clientEngine->m_lpSessionTransport->SendToServer( pBufferDesc, pvSendContext, 0 );
m_seqNum++;
if( hr == DVERR_PENDING )
{
hr = DV_OK;
}
else if( FAILED( hr ) )
{
DPFX(DPFPREP, DVF_INFOLEVEL, "Send failed hr=0x%x", hr );
}
return hr;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::IsValidTarget"
BOOL CClientRecordSubSystem::IsValidTarget()
{
BOOL fValidTarget;
DNEnterCriticalSection( &m_clientEngine->m_csTargetLock );
if( m_clientEngine->m_dwNumTargets > 0 )
{
fValidTarget = TRUE;
}
else
{
fValidTarget = FALSE;
}
DNLeaveCriticalSection( &m_clientEngine->m_csTargetLock );
return fValidTarget;
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::InitStats"
void CClientRecordSubSystem::InitStats()
{
// STATSBLOCK: begin
// Setup record stats
m_clientEngine->m_pStatsBlob->m_recStats.m_dwFramesPerBuffer = m_framesPerPeriod;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwFrameTime = m_dwFrameTime;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCSMin = 0xFFFFFFFF;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwUnCompressedSize = m_uncompressedSize;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwSilenceTimeout = m_dwSilenceTimeout;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRPWMin = 0xFFFFFFFF;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRRMin = 0xFFFFFFFF;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwRTSLMMin = 0xFFFFFFFF;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwHSMin = 0xFFFFFFFF;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwMLMin = 0xFFFFFFFF;
m_clientEngine->m_pStatsBlob->m_recStats.m_dwCTMin = 0xFFFFFFFF;
// STATSBLOCK: End
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::BeginStats"
void CClientRecordSubSystem::BeginStats()
{
// STATSBLOCK: Begin
m_clientEngine->m_pStatsBlob->m_recStats.m_dwTimeStart = GetTickCount();
m_clientEngine->m_pStatsBlob->m_recStats.m_dwStartLag = m_clientEngine->m_pStatsBlob->m_recStats.m_dwTimeStart-m_clientEngine->m_pStatsBlob->m_dwTimeStart;
// STATSBLOCK: end
}
#undef DPF_MODNAME
#define DPF_MODNAME "CClientRecordSubSystem::CompleteStats"
void CClientRecordSubSystem::CompleteStats()
{
m_clientEngine->m_pStatsBlob->m_recStats.m_dwTimeStop = GetTickCount();
}