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.
1247 lines
42 KiB
1247 lines
42 KiB
/*==========================================================================
|
|
*
|
|
* Copyright (C) 1999, 2000 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
* File: mixserver.cpp
|
|
* Content: Implements the mixing server portion of the server class
|
|
*
|
|
* History:
|
|
* Date By Reason
|
|
* ==== == ======
|
|
* 11/01/2000 rodtoll Split out from dvsereng.cpp
|
|
* 12/14/2000 rodtoll DPVOICE: [Mixing Server] Mixer may create infinite loop
|
|
* 02/20/2001 rodtoll WINBUG #321297 - DPVOICE: Access violation in DPVoice.dll while running DVSalvo server
|
|
* 04/09/2001 rodtoll WINBUG #364126 - DPVoice : Memory leak when Initializing 2 Voice Servers with same DPlay transport
|
|
* 02/28/2002 rodtoll WINBUG #549959 - SECURITY: DPVOICE: Voice server trusts client's target list
|
|
* - Update receive path to use server's copy of client target list when server controlled targetting enabled
|
|
* 06/13/2002 simonpow BUG #59944 Switched over to using Threadpool based timers rather than multimedia
|
|
***************************************************************************/
|
|
|
|
#include "dxvoicepch.h"
|
|
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::AddPlayerToMixingAddList"
|
|
void CDirectVoiceServerEngine::AddPlayerToMixingAddList( CVoicePlayer *pPlayer )
|
|
{
|
|
CDVCSPlayer *pVoicePlayer = (CDVCSPlayer *) pPlayer;
|
|
ASSERT_VPLAYER( pVoicePlayer );
|
|
|
|
for( DWORD dwIndex = 0; dwIndex < m_dwNumMixingThreads; dwIndex++ )
|
|
{
|
|
DNEnterCriticalSection( &m_prWorkerControl[dwIndex].m_csMixingAddList );
|
|
pVoicePlayer->AddToMixingList( dwIndex, &m_prWorkerControl[dwIndex].m_blMixingAddPlayers );
|
|
pVoicePlayer->AddRef();
|
|
DNLeaveCriticalSection( &m_prWorkerControl[dwIndex].m_csMixingAddList );
|
|
}
|
|
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::StartWorkerThreads"
|
|
// StartWorkerThreads
|
|
//
|
|
// This function starts mixer worker threads. The number started is based on the
|
|
// m_dwNumMixingThreads variable which must be initialized before this is called.
|
|
//
|
|
HRESULT CDirectVoiceServerEngine::StartWorkerThreads()
|
|
{
|
|
HRESULT hr = DV_OK;
|
|
DWORD dwIndex;
|
|
|
|
m_prWorkerControl = new MIXERTHREAD_CONTROL[m_dwNumMixingThreads];
|
|
|
|
if( m_prWorkerControl == NULL )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Out of memory!" );
|
|
return DVERR_OUTOFMEMORY;
|
|
}
|
|
|
|
// Zero memory so everything is initialized.
|
|
ZeroMemory( m_prWorkerControl, sizeof( MIXERTHREAD_CONTROL )*m_dwNumMixingThreads );
|
|
|
|
for( dwIndex = 0; dwIndex < m_dwNumMixingThreads; dwIndex++ )
|
|
{
|
|
m_prWorkerControl[dwIndex].dwThreadIndex = dwIndex;
|
|
m_prWorkerControl[dwIndex].hThreadDone = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
m_prWorkerControl[dwIndex].hThreadDoWork = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
m_prWorkerControl[dwIndex].hThreadIdle = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
m_prWorkerControl[dwIndex].hThreadQuit = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
m_prWorkerControl[dwIndex].m_pServerObject = this;
|
|
|
|
m_prWorkerControl[dwIndex].m_blMixingAddPlayers.Initialize();
|
|
m_prWorkerControl[dwIndex].m_blMixingActivePlayers.Initialize();
|
|
m_prWorkerControl[dwIndex].m_blMixingSpeakingPlayers.Initialize();
|
|
m_prWorkerControl[dwIndex].m_blMixingHearingPlayers.Initialize();
|
|
|
|
if (!DNInitializeCriticalSection( &m_prWorkerControl[dwIndex].m_csMixingAddList ))
|
|
{
|
|
hr = DVERR_OUTOFMEMORY;
|
|
goto EXIT_ERROR;
|
|
}
|
|
|
|
if( m_prWorkerControl[dwIndex].hThreadDone == NULL ||
|
|
m_prWorkerControl[dwIndex].hThreadDoWork == NULL ||
|
|
m_prWorkerControl[dwIndex].hThreadIdle == NULL ||
|
|
m_prWorkerControl[dwIndex].hThreadQuit == NULL )
|
|
{
|
|
hr = GetLastError();
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Error creating events hr=0x%x", hr );
|
|
hr = DVERR_GENERIC;
|
|
goto EXIT_ERROR;
|
|
}
|
|
|
|
m_prWorkerControl[dwIndex].m_mixerBuffer = new BYTE[m_dwUnCompressedFrameSize];
|
|
m_prWorkerControl[dwIndex].m_realMixerBuffer = new LONG[m_dwMixerSize];
|
|
|
|
if( m_prWorkerControl[dwIndex].m_mixerBuffer == NULL ||
|
|
m_prWorkerControl[dwIndex].m_realMixerBuffer == NULL )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Error allocating memory" );
|
|
hr = DVERR_OUTOFMEMORY;
|
|
goto EXIT_ERROR;
|
|
}
|
|
|
|
m_prWorkerControl[dwIndex].hThread = (HANDLE) CreateThread( NULL, 0, MixerWorker, &m_prWorkerControl[dwIndex], 0, &m_prWorkerControl[dwIndex].dwThreadID );
|
|
|
|
if( m_prWorkerControl[dwIndex].hThread == NULL )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Error creating events/thread" );
|
|
hr = DVERR_GENERIC;
|
|
goto EXIT_ERROR;
|
|
}
|
|
|
|
::SetThreadPriority( m_prWorkerControl[dwIndex].hThread, THREAD_PRIORITY_TIME_CRITICAL );
|
|
|
|
|
|
}
|
|
|
|
return DV_OK;
|
|
|
|
EXIT_ERROR:
|
|
|
|
ShutdownWorkerThreads();
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CDirectVoiceServerEngine::ShutdownWorkerThreads()
|
|
{
|
|
DWORD dwIndex;
|
|
|
|
if( m_prWorkerControl )
|
|
{
|
|
for( dwIndex = 0; dwIndex < m_dwNumMixingThreads; dwIndex++ )
|
|
{
|
|
if( m_prWorkerControl[dwIndex].hThread )
|
|
{
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "[%d]: Shutting down ID=[0x%x]", dwIndex, m_prWorkerControl[dwIndex].dwThreadID );
|
|
|
|
SetEvent( m_prWorkerControl[dwIndex].hThreadQuit );
|
|
WaitForSingleObject( m_prWorkerControl[dwIndex].hThreadDone, INFINITE );
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "[%d]: Shutting down complete", dwIndex );
|
|
|
|
CloseHandle( m_prWorkerControl[dwIndex].hThread );
|
|
m_prWorkerControl[dwIndex].hThread = NULL;
|
|
|
|
DNDeleteCriticalSection( &m_prWorkerControl[dwIndex].m_csMixingAddList );
|
|
}
|
|
|
|
if( m_prWorkerControl[dwIndex].hThreadDone )
|
|
CloseHandle( m_prWorkerControl[dwIndex].hThreadDone );
|
|
|
|
if( m_prWorkerControl[dwIndex].hThreadDoWork )
|
|
CloseHandle( m_prWorkerControl[dwIndex].hThreadDoWork );
|
|
|
|
if( m_prWorkerControl[dwIndex].hThreadIdle )
|
|
CloseHandle( m_prWorkerControl[dwIndex].hThreadIdle );
|
|
|
|
if( m_prWorkerControl[dwIndex].hThreadQuit )
|
|
CloseHandle( m_prWorkerControl[dwIndex].hThreadQuit );
|
|
|
|
if( m_prWorkerControl[dwIndex].m_mixerBuffer )
|
|
delete [] m_prWorkerControl[dwIndex].m_mixerBuffer;
|
|
|
|
if( m_prWorkerControl[dwIndex].m_realMixerBuffer )
|
|
delete [] m_prWorkerControl[dwIndex].m_realMixerBuffer;
|
|
|
|
DNASSERT( m_prWorkerControl[dwIndex].m_blMixingAddPlayers.IsEmpty() );
|
|
DNASSERT( m_prWorkerControl[dwIndex].m_blMixingActivePlayers.IsEmpty() );
|
|
|
|
}
|
|
|
|
delete [] m_prWorkerControl;
|
|
m_prWorkerControl = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::StartupClientServer"
|
|
//
|
|
// StartupClientServer
|
|
//
|
|
// This function is called to initialize the Mixer portion of the server object.
|
|
// Only called for Mixing Sessions. Initialization includes the startup of
|
|
// the mixing thread and startup of the mixer multimedia timer.
|
|
//
|
|
// Called By:
|
|
// - StartSession
|
|
//
|
|
// Locks Required:
|
|
// - None
|
|
//
|
|
HRESULT CDirectVoiceServerEngine::StartupClientServer()
|
|
{
|
|
HRESULT hr;
|
|
HANDLE tmpThreadHandle;
|
|
SYSTEM_INFO sysInfo;
|
|
DWORD dwIndex;
|
|
|
|
m_pFramePool = NULL;
|
|
|
|
m_dwCompressedFrameSize = m_lpdvfCompressionInfo->dwFrameLength;
|
|
m_dwUnCompressedFrameSize = DVCDB_CalcUnCompressedFrameSize( m_lpdvfCompressionInfo, s_lpwfxMixerFormat );
|
|
m_dwNumPerBuffer = m_lpdvfCompressionInfo->dwFramesPerBuffer;
|
|
|
|
m_pFramePool = new CFramePool( m_dwCompressedFrameSize );
|
|
|
|
if( m_pFramePool == NULL )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to allocate frame pool" );
|
|
return DVERR_OUTOFMEMORY;
|
|
}
|
|
|
|
if (!m_pFramePool->Init())
|
|
{
|
|
delete m_pFramePool;
|
|
m_pFramePool = NULL;
|
|
return DVERR_OUTOFMEMORY;
|
|
}
|
|
|
|
m_mixerEightBit = (s_lpwfxMixerFormat->wBitsPerSample==8) ? TRUE : FALSE;
|
|
|
|
GetSystemInfo( &sysInfo );
|
|
m_dwNumMixingThreads = sysInfo.dwNumberOfProcessors;
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXER: There will be %d worker threads", m_dwNumMixingThreads );
|
|
|
|
if( m_mixerEightBit )
|
|
{
|
|
m_dwMixerSize = m_dwUnCompressedFrameSize;
|
|
}
|
|
else
|
|
{
|
|
// Mixer size is / 2 because 16-bit samples, only need 1 LONG for
|
|
// each 16-bit sample = 2 * 8bit.
|
|
m_dwMixerSize = m_dwUnCompressedFrameSize / 2;
|
|
}
|
|
|
|
m_pStats->m_dwNumMixingThreads = m_dwNumMixingThreads;
|
|
|
|
hr = StartWorkerThreads();
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed starting worker threads hr=0x%x", hr );
|
|
goto EXIT_CLIENTSERVERSTARTUP;
|
|
}
|
|
|
|
// General info
|
|
m_pTimer = new DvTimer;
|
|
|
|
if( m_pTimer == NULL )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Out of memory!" );
|
|
hr = DVERR_OUTOFMEMORY;
|
|
goto EXIT_CLIENTSERVERSTARTUP;
|
|
}
|
|
|
|
m_hTickSemaphore = CreateSemaphore( NULL, 0, 0xFFFFFF, NULL );
|
|
|
|
if( m_hTickSemaphore == NULL )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to create semaphore" );
|
|
hr = DVERR_GENERIC;
|
|
goto EXIT_CLIENTSERVERSTARTUP;
|
|
}
|
|
|
|
m_hShutdownMixerEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
m_hMixerDoneEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
|
|
if( m_hShutdownMixerEvent == NULL ||
|
|
m_hMixerDoneEvent == NULL )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to create events" );
|
|
hr = DVERR_GENERIC;
|
|
goto EXIT_CLIENTSERVERSTARTUP;
|
|
}
|
|
|
|
m_hMixerControlThread = CreateThread( NULL, 0, MixerControl, this, 0, &m_dwMixerControlThreadID );
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXER: Controller Started: ID=0x%x", m_dwMixerControlThreadID );
|
|
|
|
if( m_hMixerControlThread == NULL )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Error creating events/thread" );
|
|
hr = DVERR_GENERIC;
|
|
goto EXIT_CLIENTSERVERSTARTUP;
|
|
}
|
|
|
|
::SetThreadPriority( m_hMixerControlThread, THREAD_PRIORITY_TIME_CRITICAL );
|
|
|
|
if( !m_pTimer->Create( m_lpdvfCompressionInfo->dwTimeout, &m_hTickSemaphore, MixingServerWakeupProc ) )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Unable to create multimedia timer" );
|
|
hr = DVERR_GENERIC;
|
|
goto EXIT_CLIENTSERVERSTARTUP;
|
|
}
|
|
|
|
return DV_OK;
|
|
|
|
EXIT_CLIENTSERVERSTARTUP:
|
|
|
|
ShutdownClientServer();
|
|
|
|
return hr;
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::ShutdownClientServer"
|
|
//
|
|
// ShutdownClientServer
|
|
//
|
|
// This function is responsible for shutting down the mixer portion of the
|
|
// server object. This function should only be called for mixing sessions.
|
|
//
|
|
// This function will stop the mixer thread and the mixer multimedia timer.
|
|
//
|
|
// Called By:
|
|
// - StopSession
|
|
//
|
|
// Locks Required:
|
|
// - None
|
|
//
|
|
HRESULT CDirectVoiceServerEngine::ShutdownClientServer()
|
|
{
|
|
if( m_hMixerControlThread )
|
|
{
|
|
SetEvent( m_hShutdownMixerEvent );
|
|
WaitForSingleObject( m_hMixerDoneEvent, INFINITE );
|
|
CloseHandle( m_hMixerControlThread );
|
|
m_hMixerControlThread = NULL;
|
|
|
|
// Cleanup the mixing list
|
|
CleanupMixingList();
|
|
}
|
|
|
|
if( m_hShutdownMixerEvent )
|
|
{
|
|
CloseHandle( m_hShutdownMixerEvent );
|
|
m_hShutdownMixerEvent = NULL;
|
|
}
|
|
|
|
if( m_hMixerDoneEvent )
|
|
{
|
|
CloseHandle( m_hMixerDoneEvent );
|
|
m_hMixerDoneEvent = NULL;
|
|
}
|
|
|
|
ShutdownWorkerThreads();
|
|
|
|
if( m_pTimer )
|
|
{
|
|
delete m_pTimer;
|
|
m_pTimer = NULL;
|
|
}
|
|
|
|
if( m_hTickSemaphore )
|
|
{
|
|
CloseHandle( m_hTickSemaphore );
|
|
m_hTickSemaphore = NULL;
|
|
}
|
|
|
|
return DV_OK;
|
|
}
|
|
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::Mixer_Buffer_Reset"
|
|
// Mixer_Buffer_Reset
|
|
//
|
|
// This function resets the mixer buffer back to silence.
|
|
void CDirectVoiceServerEngine::Mixer_Buffer_Reset( DWORD dwThreadIndex )
|
|
{
|
|
FillBufferWithSilence( m_prWorkerControl[dwThreadIndex].m_realMixerBuffer,
|
|
m_prWorkerControl[dwThreadIndex].m_pServerObject->m_mixerEightBit,
|
|
m_prWorkerControl[dwThreadIndex].m_pServerObject->m_dwUnCompressedFrameSize );
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::Mixer_Buffer_MixBuffer"
|
|
// Mixer_Buffer_MixBuffer
|
|
//
|
|
// This function mixes the speech pointed to by the source parameter
|
|
// into the mixer buffer.
|
|
//
|
|
// Parameters:
|
|
// unsigned char *source -
|
|
// Pointer to source data in uncompressed format
|
|
void CDirectVoiceServerEngine::Mixer_Buffer_MixBuffer( DWORD dwThreadIndex, const unsigned char *source )
|
|
{
|
|
MixInBuffer( m_prWorkerControl[dwThreadIndex].m_realMixerBuffer, source,
|
|
m_prWorkerControl[dwThreadIndex].m_pServerObject->m_mixerEightBit,
|
|
m_prWorkerControl[dwThreadIndex].m_pServerObject->m_dwUnCompressedFrameSize );
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::Mixer_Buffer_Normalize"
|
|
// Mixer_Buffer_Normalize
|
|
//
|
|
// This function takes the mixed audio data from the mixer
|
|
// buffer and transfers it back to the mixer format
|
|
// and places it into the m_mixerBuffer buffer.
|
|
//
|
|
void CDirectVoiceServerEngine::Mixer_Buffer_Normalize( DWORD dwThreadIndex )
|
|
{
|
|
NormalizeBuffer( m_prWorkerControl[dwThreadIndex].m_mixerBuffer,
|
|
m_prWorkerControl[dwThreadIndex].m_realMixerBuffer,
|
|
m_prWorkerControl[dwThreadIndex].m_pServerObject->m_mixerEightBit,
|
|
m_prWorkerControl[dwThreadIndex].m_pServerObject->m_dwUnCompressedFrameSize );
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::HandleMixerThreadError"
|
|
//
|
|
// HandleMixerThreadError
|
|
//
|
|
// This function is called by the mixer when an unrecoverable error
|
|
// occurs.
|
|
//
|
|
void CDirectVoiceServerEngine::HandleMixerThreadError( HRESULT hr )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Mixer Thread Encountered an error. hr=0x%x", hr );
|
|
SetEvent( m_hMixerDoneEvent );
|
|
StopSession( 0, FALSE, hr );
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::MixerControl"
|
|
DWORD WINAPI CDirectVoiceServerEngine::MixerControl( void *pvContext )
|
|
{
|
|
CDirectVoiceServerEngine *This = (CDirectVoiceServerEngine *) pvContext;
|
|
|
|
HANDLE hEvents[3];
|
|
HANDLE *hIdleEvents = new HANDLE[This->m_dwNumMixingThreads+1];
|
|
DWORD dwIndex = 0;
|
|
LONG lFreeThreadIndex = 0;
|
|
DWORD dwNumToMix = 0;
|
|
DWORD dwTickCountStart;
|
|
LONG lWaitResult;
|
|
|
|
if( !hIdleEvents )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "MIXCTRL: Error allocating array" );
|
|
DNASSERT( FALSE );
|
|
SetEvent( This->m_hMixerDoneEvent );
|
|
return 0;
|
|
}
|
|
|
|
hEvents[0] = This->m_hShutdownMixerEvent;
|
|
hEvents[1] = This->m_hTickSemaphore;
|
|
hEvents[2] = (HANDLE) ((DWORD_PTR) 0xFFFFFFFF);
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXCTRL: Starting up" );
|
|
|
|
for( dwIndex = 0; dwIndex < This->m_dwNumMixingThreads; dwIndex++ )
|
|
{
|
|
hIdleEvents[dwIndex] = This->m_prWorkerControl[dwIndex].hThreadIdle;
|
|
}
|
|
|
|
hIdleEvents[This->m_dwNumMixingThreads] = (HANDLE) ((DWORD_PTR) 0xFFFFFFFF);
|
|
|
|
// Wait for tick or for quit command
|
|
while( (lWaitResult = WaitForMultipleObjects( 2, hEvents, FALSE, INFINITE )) != WAIT_OBJECT_0 )
|
|
{
|
|
// On Win9X we may occationally over run the end of the wait list
|
|
// and the result is we hit the FFFFFFFFF which will cause
|
|
// a failure.
|
|
if( lWaitResult == WAIT_FAILED )
|
|
continue;
|
|
|
|
// Update statistics block
|
|
InterlockedIncrement( &This->m_pStats->m_dwNumMixingPasses );
|
|
|
|
dwTickCountStart = GetTickCount();
|
|
|
|
// On Win95 you may occasionally encounter a situation where the waitformultiple runs
|
|
// off the end of the list and ends up with the invalid handle above. Just continue
|
|
// in this case.
|
|
lFreeThreadIndex = WAIT_FAILED;
|
|
|
|
while( lFreeThreadIndex == WAIT_FAILED )
|
|
{
|
|
// Wait for a single mixing thread to be free
|
|
lFreeThreadIndex = WaitForMultipleObjects( This->m_dwNumMixingThreads, hIdleEvents, FALSE, INFINITE );
|
|
//// TODO: Error checking!
|
|
}
|
|
|
|
lFreeThreadIndex -= WAIT_OBJECT_0;
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXCTRL: Worker [%d] is elected to do work", lFreeThreadIndex );
|
|
|
|
This->SpinWorkToThread( lFreeThreadIndex );
|
|
}
|
|
|
|
delete [] hIdleEvents;
|
|
|
|
SetEvent( This->m_hMixerDoneEvent );
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXCTRL: Shutting down" );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::MixerWorker"
|
|
DWORD WINAPI CDirectVoiceServerEngine::MixerWorker( void *pvContext )
|
|
{
|
|
PMIXERTHREAD_CONTROL This = (PMIXERTHREAD_CONTROL) pvContext;
|
|
HANDLE hEvents[3];
|
|
CBilink *pblSearch, *pblSubSearch;
|
|
CDVCSPlayer *pCurrentPlayer = NULL, *pTmpPlayer = NULL;
|
|
DWORD dwNumTargets = 0;
|
|
DWORD dwTargetIndex = 0;
|
|
DWORD dwResultSize = 0;
|
|
DWORD dwIndex = 0;
|
|
DWORD dwThreadIndex = This->dwThreadIndex;
|
|
HRESULT hr;
|
|
CDVCSPlayer **ppThreadHearList = NULL;
|
|
PDVPROTOCOLMSG_SPEECHHEADER pdvmSpeechHeader = NULL;
|
|
PDVTRANSPORT_BUFFERDESC pdvbTransmitBufferDesc = NULL;
|
|
PVOID pvSendContext = NULL;
|
|
DVID dvidSendTarget;
|
|
DWORD dwTickCountStart;
|
|
DWORD dwTickCountDecStart;
|
|
DWORD dwTickCountMixStart;
|
|
DWORD dwTickCountDupStart;
|
|
DWORD dwTickCountRetStart;
|
|
DWORD dwStatIndex;
|
|
DWORD dwTickCountEnd;
|
|
DWORD dwTotalMix, dwForwardMix, dwReuseMix, dwOriginalMix;
|
|
LONG lWaitResult;
|
|
|
|
MixingServerStats *pStats = This->m_pServerObject->m_pStats;
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXWORKER: [%d] Started [0x%x] Thread", This->dwThreadIndex, GetCurrentThreadId() );
|
|
|
|
hEvents[0] = This->hThreadQuit;
|
|
hEvents[1] = This->hThreadDoWork;
|
|
hEvents[2] = (HANDLE) ((DWORD_PTR) 0xFFFFFFFF);
|
|
|
|
SetEvent( This->hThreadIdle );
|
|
|
|
while( (lWaitResult = WaitForMultipleObjects( 2, hEvents, FALSE, INFINITE )) != WAIT_OBJECT_0 )
|
|
{
|
|
// On Win95 it may occationally move off the end of the list and hit the guard value
|
|
if( lWaitResult == WAIT_FAILED )
|
|
continue;
|
|
|
|
// Statistics update
|
|
dwTickCountStart = GetTickCount();
|
|
InterlockedIncrement( &pStats->m_dwNumMixingThreadsActive );
|
|
pStats->m_dwNumMixingPassesPerThread[dwThreadIndex]++;
|
|
|
|
if( pStats->m_dwNumMixingThreadsActive >
|
|
pStats->m_dwMaxMixingThreadsActive )
|
|
{
|
|
pStats->m_dwMaxMixingThreadsActive = pStats->m_dwNumMixingThreadsActive;
|
|
}
|
|
|
|
dwStatIndex = pStats->m_dwCurrentMixingHistoryLoc[dwThreadIndex];
|
|
|
|
pStats->m_lCurrentPlayerCount[dwThreadIndex][dwStatIndex] = This->dwNumToMix;
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXWORKER: [%d] Starting work", This->dwThreadIndex );
|
|
|
|
if( This->dwNumToMix == 0 )
|
|
{
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXWORKER: No players to process!" );
|
|
goto WORK_COMPLETE;
|
|
}
|
|
|
|
dwTickCountDecStart = GetTickCount();
|
|
|
|
pStats->m_lCurrentDecCountHistory[dwThreadIndex][dwStatIndex] = 0;
|
|
|
|
// Pass through player list and decompress those who need decompression
|
|
//
|
|
pblSearch = This->m_blMixingSpeakingPlayers.GetNext();
|
|
|
|
while( pblSearch != &This->m_blMixingSpeakingPlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pCurrentPlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER( pCurrentPlayer );
|
|
|
|
// Dereference the array of can hear players for this player
|
|
ppThreadHearList = pCurrentPlayer->m_pppCanHear[dwThreadIndex];
|
|
|
|
// Player needs to have their voice decompressed
|
|
if( pCurrentPlayer->m_pfNeedsDecompression[dwThreadIndex] )
|
|
{
|
|
DNASSERT( pCurrentPlayer );
|
|
DNASSERT( pCurrentPlayer->m_pSourceFrame[dwThreadIndex] );
|
|
DNASSERT( !pCurrentPlayer->m_pSourceFrame[dwThreadIndex]->GetIsSilence() );
|
|
|
|
dwResultSize = This->m_pServerObject->m_dwUnCompressedFrameSize;
|
|
|
|
hr = pCurrentPlayer->DeCompressInBound(
|
|
pCurrentPlayer->m_pSourceFrame[dwThreadIndex],
|
|
&pCurrentPlayer->m_sourceUnCompressed[pCurrentPlayer->m_pdwUnCompressedBufferOffset[dwThreadIndex]],
|
|
&dwResultSize );
|
|
|
|
pStats->m_lCurrentDecCountHistory[dwThreadIndex][dwStatIndex]++;
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
DNASSERT( FALSE );
|
|
// TODO: ERROR Handling for failed decompression
|
|
}
|
|
else
|
|
{
|
|
pCurrentPlayer->m_pfDecompressed[dwThreadIndex] = TRUE;
|
|
}
|
|
|
|
DNASSERT( dwResultSize == This->m_pServerObject->m_dwUnCompressedFrameSize );
|
|
}
|
|
|
|
// Integrity checks
|
|
//
|
|
// Check to ensure that each player who this person can hear is supposed to be decompressed
|
|
#ifdef _DEBUG
|
|
DNASSERT( pCurrentPlayer->m_pdwHearCount[dwThreadIndex] < This->dwNumToMix );
|
|
if( pCurrentPlayer->m_pdwHearCount[dwThreadIndex] > 1 )
|
|
{
|
|
for( dwIndex; dwIndex < pCurrentPlayer->m_pdwHearCount[dwThreadIndex]; dwIndex++ )
|
|
{
|
|
DNASSERT( ppThreadHearList[dwIndex] );
|
|
DNASSERT( ppThreadHearList[dwIndex]->m_pfNeedsDecompression[dwThreadIndex] );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
pblSearch = pblSearch->GetNext();
|
|
}
|
|
|
|
dwTickCountDupStart = GetTickCount();
|
|
pStats->m_lCurrentDecTimeHistory[dwThreadIndex][dwStatIndex] = dwTickCountDupStart - dwTickCountDecStart;
|
|
|
|
// Check for duplicates in the sending. If there is duplicates then we need
|
|
// to setup the reuse
|
|
pblSearch = This->m_blMixingHearingPlayers.GetNext();
|
|
|
|
while( pblSearch != &This->m_blMixingHearingPlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pCurrentPlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER( pCurrentPlayer );
|
|
|
|
// If we don't hear anybody, this step is irrelevant
|
|
if( pCurrentPlayer->m_pdwHearCount[dwThreadIndex] < 2 )
|
|
goto DUPLICATE_CHECK_LOOP_DONE;
|
|
|
|
// Dereference the array of can hear players for this player
|
|
ppThreadHearList = pCurrentPlayer->m_pppCanHear[dwThreadIndex];
|
|
|
|
pblSubSearch = This->m_blMixingHearingPlayers.GetNext();
|
|
|
|
// Only do the people who come before them.
|
|
while( pblSubSearch != pblSearch )
|
|
{
|
|
CBilinkPlusObject* pRealSubBilink = CONTAINING_OBJECT( pblSubSearch, CBilinkPlusObject, m_bl );
|
|
pTmpPlayer = pRealSubBilink->m_pPlayer;
|
|
ASSERT_VPLAYER( pTmpPlayer );
|
|
|
|
// This person's mix is the same, re-use it!
|
|
if( pTmpPlayer->ComparePlayerMix( dwThreadIndex, pCurrentPlayer ) )
|
|
{
|
|
pCurrentPlayer->m_pReuseMixFromThisPlayer[dwThreadIndex] = pTmpPlayer;
|
|
pTmpPlayer->m_pfMixToBeReused[dwThreadIndex] = TRUE;
|
|
break;
|
|
}
|
|
|
|
pblSubSearch = pblSubSearch->GetNext();
|
|
}
|
|
DUPLICATE_CHECK_LOOP_DONE:
|
|
|
|
pblSearch = pblSearch->GetNext();
|
|
|
|
}
|
|
|
|
dwTickCountMixStart = GetTickCount();
|
|
pStats->m_lCurrentDupTimeHistory[dwThreadIndex][dwStatIndex] = dwTickCountMixStart - dwTickCountDupStart;
|
|
|
|
dwTotalMix = 0;
|
|
dwForwardMix = 0;
|
|
dwReuseMix = 0;
|
|
dwOriginalMix = 0;
|
|
|
|
// Pass through player list and compress and send mixes as appropriate
|
|
pblSearch = This->m_blMixingHearingPlayers.GetNext();
|
|
|
|
while( pblSearch != &This->m_blMixingHearingPlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pCurrentPlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER( pCurrentPlayer );
|
|
|
|
// Dereference the array of can hear players for this player
|
|
ppThreadHearList = pCurrentPlayer->m_pppCanHear[dwThreadIndex];
|
|
|
|
// Pre-set next so we can continue() below and still go to next item
|
|
pblSearch = pblSearch->GetNext();
|
|
|
|
if( !pCurrentPlayer->m_pdwHearCount[dwThreadIndex] )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
dwTotalMix++;
|
|
|
|
// Get a transmission buffer and description
|
|
pdvbTransmitBufferDesc = This->m_pServerObject->GetTransmitBuffer( This->m_pServerObject->m_dwCompressedFrameSize+sizeof(DVPROTOCOLMSG_SPEECHHEADER)+COMPRESSION_SLUSH,
|
|
&pvSendContext );
|
|
|
|
if( pdvbTransmitBufferDesc == NULL )
|
|
{
|
|
// TODO: Error handling for out of memory condition
|
|
DNASSERT( FALSE );
|
|
}
|
|
|
|
// Setup the packet header
|
|
pdvmSpeechHeader = (PDVPROTOCOLMSG_SPEECHHEADER) pdvbTransmitBufferDesc->pBufferData;
|
|
|
|
pdvmSpeechHeader->dwType = DVMSGID_SPEECHBOUNCE;
|
|
pdvmSpeechHeader->bMsgNum = pCurrentPlayer->m_pbMsgNumToSend[dwThreadIndex];
|
|
pdvmSpeechHeader->bSeqNum = pCurrentPlayer->m_pbSeqNumToSend[dwThreadIndex];
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXWORKER: [%d] Sending Packet to 0x%x Msg=0x%x Seq=0x%x",
|
|
dwThreadIndex,
|
|
pCurrentPlayer->GetPlayerID(),
|
|
pdvmSpeechHeader->bMsgNum,
|
|
pdvmSpeechHeader->bSeqNum );
|
|
|
|
// If this player hears something they will be getting a packet
|
|
//
|
|
// Only hear one person -- forward the packet
|
|
//
|
|
if( pCurrentPlayer->m_pdwHearCount[dwThreadIndex] == 1)
|
|
{
|
|
dwResultSize = ppThreadHearList[0]->m_pSourceFrame[dwThreadIndex]->GetFrameLength();
|
|
|
|
memcpy( &pdvmSpeechHeader[1],
|
|
ppThreadHearList[0]->m_pSourceFrame[dwThreadIndex]->GetDataPointer(),
|
|
dwResultSize );
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXWORKER: [%d] Forwarding already compressed packet", dwThreadIndex );
|
|
|
|
pCurrentPlayer->m_pfMixed[dwThreadIndex] = TRUE;
|
|
|
|
dwForwardMix++;
|
|
}
|
|
else if( pCurrentPlayer->m_pdwHearCount[dwThreadIndex] > 1)
|
|
{
|
|
pTmpPlayer = pCurrentPlayer->m_pReuseMixFromThisPlayer[dwThreadIndex];
|
|
|
|
// We are re-using a previous player's mix
|
|
if( pTmpPlayer )
|
|
{
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXERWORKER: [%d] Forwarding pre-built mix", dwThreadIndex );
|
|
ASSERT_VPLAYER( pTmpPlayer );
|
|
DNASSERT( pTmpPlayer->m_pfMixed[dwThreadIndex] );
|
|
DNASSERT( pTmpPlayer->m_pfMixToBeReused[dwThreadIndex] );
|
|
DNASSERT( pTmpPlayer->m_pdwResultLength[dwThreadIndex] );
|
|
|
|
dwResultSize = pTmpPlayer->m_pdwResultLength[dwThreadIndex];
|
|
|
|
memcpy( &pdvmSpeechHeader[1],
|
|
&pTmpPlayer->m_targetCompressed[pTmpPlayer->m_pdwCompressedBufferOffset[dwThreadIndex]],
|
|
dwResultSize );
|
|
|
|
dwReuseMix++;
|
|
}
|
|
else
|
|
{
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXERWORKER: [%d] Creating original mix", dwThreadIndex );
|
|
|
|
dwOriginalMix++;
|
|
|
|
dwResultSize = This->m_pServerObject->m_dwCompressedFrameSize;
|
|
|
|
// Reset the high resolution mixer buffer
|
|
This->m_pServerObject->Mixer_Buffer_Reset(dwThreadIndex);
|
|
|
|
// Mix in specified player's audio.
|
|
for( dwIndex = 0; dwIndex < pCurrentPlayer->m_pdwHearCount[dwThreadIndex]; dwIndex++ )
|
|
{
|
|
DNASSERT( !ppThreadHearList[dwIndex]->m_pfSilence[dwThreadIndex] );
|
|
This->m_pServerObject->Mixer_Buffer_MixBuffer(dwThreadIndex,ppThreadHearList[dwIndex]->m_sourceUnCompressed );
|
|
}
|
|
|
|
// Normalize the buffer back to the thread's mix buffer
|
|
This->m_pServerObject->Mixer_Buffer_Normalize(dwThreadIndex);
|
|
|
|
hr = pCurrentPlayer->CompressOutBound( This->m_mixerBuffer,
|
|
This->m_pServerObject->m_dwUnCompressedFrameSize,
|
|
(BYTE *) &pdvmSpeechHeader[1],
|
|
&dwResultSize );
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
DNASSERT( FALSE );
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed compressing outbound audio" );
|
|
|
|
}
|
|
|
|
pCurrentPlayer->m_pfMixed[dwThreadIndex] = TRUE;
|
|
pCurrentPlayer->m_pdwResultLength[dwThreadIndex] = dwResultSize;
|
|
|
|
// This player's mix will be re-used, ensure that we cache it
|
|
if( pCurrentPlayer->m_pfMixToBeReused[dwThreadIndex] )
|
|
{
|
|
memcpy( &pCurrentPlayer->m_targetCompressed[pCurrentPlayer->m_pdwCompressedBufferOffset[dwThreadIndex]],
|
|
&pdvmSpeechHeader[1],
|
|
pCurrentPlayer->m_pdwResultLength[dwThreadIndex] );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DNASSERT(FALSE);
|
|
}
|
|
|
|
dvidSendTarget = pCurrentPlayer->GetPlayerID();
|
|
|
|
pdvbTransmitBufferDesc->dwBufferSize= dwResultSize + sizeof( DVPROTOCOLMSG_SPEECHHEADER );
|
|
|
|
hr = This->m_pServerObject->m_lpSessionTransport->SendToIDS( &dvidSendTarget, 1, pdvbTransmitBufferDesc, pvSendContext, 0 );
|
|
|
|
if( hr == DVERR_PENDING )
|
|
hr = DV_OK;
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "MIXWORKER: [%d] Unable to transmit to target [0x%x]", pCurrentPlayer->GetPlayerID() );
|
|
}
|
|
|
|
}
|
|
|
|
dwTickCountRetStart = GetTickCount();
|
|
pStats->m_lCurrentMixTimeHistory[dwThreadIndex][dwStatIndex] = dwTickCountRetStart - dwTickCountMixStart;
|
|
|
|
pStats->m_lCurrentMixCountTotalHistory[dwThreadIndex][dwStatIndex] = dwTotalMix;
|
|
pStats->m_lCurrentMixCountFwdHistory[dwThreadIndex][dwStatIndex] = dwForwardMix;
|
|
pStats->m_lCurrentMixCountReuseHistory[dwThreadIndex][dwStatIndex] = dwReuseMix;
|
|
pStats->m_lCurrentMixCountOriginalHistory[dwThreadIndex][dwStatIndex] = dwOriginalMix;
|
|
|
|
|
|
WORK_COMPLETE:
|
|
|
|
// Pass through player list and return frames
|
|
pblSearch = This->m_blMixingActivePlayers.GetNext();
|
|
|
|
while( pblSearch != &This->m_blMixingActivePlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pCurrentPlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER( pCurrentPlayer );
|
|
|
|
DNASSERT( pCurrentPlayer->m_pSourceFrame[dwThreadIndex] );
|
|
|
|
pCurrentPlayer->CompleteRun( dwThreadIndex );
|
|
|
|
pblSearch = pblSearch->GetNext();
|
|
}
|
|
|
|
dwTickCountEnd = GetTickCount();
|
|
pStats->m_lCurrentRetTimeHistory[dwThreadIndex][dwStatIndex] = dwTickCountEnd - dwTickCountRetStart;
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXWORKER: [%d] Work complete", This->dwThreadIndex );
|
|
|
|
// Statistics update
|
|
InterlockedDecrement( &This->m_pServerObject->m_pStats->m_dwNumMixingThreadsActive );
|
|
|
|
SetEvent( This->hThreadIdle );
|
|
|
|
// Statistics update
|
|
pStats->m_dwMixingPassesTimeHistory[dwThreadIndex][dwStatIndex] = dwTickCountEnd - dwTickCountStart;
|
|
pStats->m_dwCurrentMixingHistoryLoc[dwThreadIndex]++;
|
|
pStats->m_dwCurrentMixingHistoryLoc[dwThreadIndex] %= MIXING_HISTORY;
|
|
|
|
}
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "MIXWORKER: [%d] Shutting down", This->dwThreadIndex );
|
|
|
|
SetEvent( This->hThreadDone );
|
|
|
|
return 0;
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::SpinWorkToThread"
|
|
//
|
|
// SpinWorkToThread
|
|
//
|
|
// This function performs the first step of a mixing server pass and then
|
|
// passes the work off to the specified thread
|
|
//
|
|
// Responsible for:
|
|
// 1. Updating the
|
|
// 2. running the list of players, determinging who they can hear
|
|
//
|
|
void CDirectVoiceServerEngine::SpinWorkToThread( LONG lThreadIndex )
|
|
{
|
|
CBilink *pblSearch = NULL, *pblSubSearch = NULL;
|
|
CDVCSPlayer *pCurrentPlayer = NULL, *pTmpPlayer = NULL, *pComparePlayer = NULL;
|
|
HRESULT hr;
|
|
PDVID pdvidTargets = NULL;
|
|
DWORD dwNumTargets = 0;
|
|
DWORD dwTargetIndex = 0;
|
|
|
|
DWORD dwTickCountStart = GetTickCount();
|
|
|
|
// Update the list of players from the pending lists to the individual bilinks
|
|
UpdateActiveMixingPendingList( lThreadIndex, &m_prWorkerControl[lThreadIndex].dwNumToMix );
|
|
|
|
// Pass 1 through player list.
|
|
//
|
|
// Reset state variables for specified thread, create any converters that need creating
|
|
pblSearch = m_prWorkerControl[lThreadIndex].m_blMixingActivePlayers.GetNext();
|
|
|
|
while( pblSearch != &m_prWorkerControl[lThreadIndex].m_blMixingActivePlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pTmpPlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER( pTmpPlayer );
|
|
|
|
pblSearch = pblSearch->GetNext();
|
|
|
|
// Reset for the next pass
|
|
pTmpPlayer->ResetForNextRun(lThreadIndex,TRUE);
|
|
|
|
// Resize the can hear array
|
|
pTmpPlayer->ResizeIfRequired( lThreadIndex, m_prWorkerControl[lThreadIndex].dwNumToMix );
|
|
|
|
// Lock the player -- only one person should be creating converter at a time
|
|
pTmpPlayer->Lock();
|
|
|
|
// Create outbound converter if required
|
|
if( !pTmpPlayer->IsOutBoundConverterInitialized() )
|
|
{
|
|
hr = pTmpPlayer->CreateOutBoundConverter( s_lpwfxMixerFormat, m_dvSessionDesc.guidCT );
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to create outbound converter hr=0x%x", hr );
|
|
DNASSERT( FALSE );
|
|
}
|
|
}
|
|
|
|
// Create inbound converter if required
|
|
if( !pTmpPlayer->IsInBoundConverterInitialized() )
|
|
{
|
|
hr = pTmpPlayer->CreateInBoundConverter( m_dvSessionDesc.guidCT, s_lpwfxMixerFormat );
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
DPFX(DPFPREP, DVF_ERRORLEVEL, "Failed to create converter" );
|
|
DNASSERT( FALSE );
|
|
}
|
|
}
|
|
pTmpPlayer->UnLock();
|
|
}
|
|
|
|
DNASSERT(m_prWorkerControl[lThreadIndex].m_blMixingSpeakingPlayers.IsEmpty());
|
|
DNASSERT(m_prWorkerControl[lThreadIndex].m_blMixingHearingPlayers.IsEmpty());
|
|
|
|
m_prWorkerControl[lThreadIndex].m_blMixingSpeakingPlayers.Initialize();
|
|
m_prWorkerControl[lThreadIndex].m_blMixingHearingPlayers.Initialize();
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "SST: 2" );
|
|
|
|
// Pass 2.
|
|
//
|
|
// For each player:
|
|
// 1. Figure out who they hear.
|
|
// 2. TODO: If they hear anyone, add them to the "to send to" list of people
|
|
// 3. TODO: If they hear > 1, add the people they hear to the "to decompress" list of people.
|
|
// 4. Setup the appropriate sequence # / msg # for the transmission
|
|
//
|
|
pblSearch = m_prWorkerControl[lThreadIndex].m_blMixingActivePlayers.GetNext();
|
|
|
|
while( pblSearch != &m_prWorkerControl[lThreadIndex].m_blMixingActivePlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pCurrentPlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER( pCurrentPlayer );
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "0x%x Can hear: ", pCurrentPlayer->GetPlayerID() );
|
|
|
|
pblSearch = pblSearch->GetNext();
|
|
|
|
pblSubSearch = m_prWorkerControl[lThreadIndex].m_blMixingActivePlayers.GetNext();
|
|
|
|
// Search the list of people in the session
|
|
while( pblSubSearch != &m_prWorkerControl[lThreadIndex].m_blMixingActivePlayers )
|
|
{
|
|
CBilinkPlusObject* pRealSubBilink = CONTAINING_OBJECT( pblSubSearch, CBilinkPlusObject, m_bl );
|
|
pComparePlayer = pRealSubBilink->m_pPlayer;
|
|
ASSERT_VPLAYER( pComparePlayer );
|
|
|
|
pblSubSearch = pblSubSearch->GetNext();
|
|
|
|
// This record contains a silent record -- ignore
|
|
if( pComparePlayer->m_pfSilence[lThreadIndex] )
|
|
continue;
|
|
|
|
// If this isn't the player themselves
|
|
if( pblSearch != pblSubSearch )
|
|
{
|
|
DNASSERT( pComparePlayer->m_pSourceFrame[lThreadIndex] );
|
|
pdvidTargets = pComparePlayer->m_pSourceFrame[lThreadIndex]->GetTargetList();
|
|
dwNumTargets = pComparePlayer->m_pSourceFrame[lThreadIndex]->GetNumTargets();
|
|
|
|
// The target of the subIndex user's frame is this user OR
|
|
// The user is in the group which is target of subIndex user's frame
|
|
|
|
for( dwTargetIndex = 0; dwTargetIndex < dwNumTargets; dwTargetIndex++ )
|
|
{
|
|
if( pCurrentPlayer->GetPlayerID() == pdvidTargets[dwTargetIndex] ||
|
|
m_lpSessionTransport->IsPlayerInGroup( pdvidTargets[dwTargetIndex], pCurrentPlayer->GetPlayerID() ) )
|
|
{
|
|
*((*(pCurrentPlayer->m_pppCanHear+lThreadIndex))+pCurrentPlayer->m_pdwHearCount[lThreadIndex]) = pComparePlayer;
|
|
pCurrentPlayer->m_pdwHearCount[lThreadIndex]++;
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "0x%x", pComparePlayer->GetPlayerID() );
|
|
|
|
// Setup the appropriate msg num / sequence number so when it's sent
|
|
// we ensure it gets re-assembled on the other side in the right order
|
|
if( pCurrentPlayer->m_pdwHearCount[lThreadIndex] == 1 )
|
|
{
|
|
if( pCurrentPlayer->m_bLastSent == FALSE )
|
|
{
|
|
pCurrentPlayer->m_bMsgNum++;
|
|
pCurrentPlayer->m_bSeqNum = 0;
|
|
pCurrentPlayer->m_bLastSent = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pCurrentPlayer->m_bSeqNum++;
|
|
}
|
|
|
|
pCurrentPlayer->m_pbMsgNumToSend[lThreadIndex] = pCurrentPlayer->m_bMsgNum;
|
|
pCurrentPlayer->m_pbSeqNumToSend[lThreadIndex] = pCurrentPlayer->m_bSeqNum;
|
|
|
|
pCurrentPlayer->AddToHearingList( lThreadIndex, &m_prWorkerControl[lThreadIndex].m_blMixingHearingPlayers );
|
|
}
|
|
// We can hear > 1 person, we need to mark each person as needing decompression
|
|
else if( pCurrentPlayer->m_pdwHearCount[lThreadIndex] > 1 )
|
|
{
|
|
if( !pComparePlayer->m_pfNeedsDecompression[lThreadIndex] )
|
|
{
|
|
// Add this player to the list of people who need to be decompressed
|
|
pComparePlayer->AddToSpeakingList( lThreadIndex, &m_prWorkerControl[lThreadIndex].m_blMixingSpeakingPlayers );
|
|
pComparePlayer->m_pfNeedsDecompression[lThreadIndex] = TRUE;
|
|
}
|
|
|
|
// Special case, we just transitioned to having > 1 people heard by this player,
|
|
// we should mark the first person we can hear for decompression as well
|
|
if( pCurrentPlayer->m_pdwHearCount[lThreadIndex] == 2 )
|
|
{
|
|
pTmpPlayer = (pCurrentPlayer->m_pppCanHear[lThreadIndex])[0];
|
|
ASSERT_VPLAYER( pTmpPlayer );
|
|
|
|
if( !pTmpPlayer->m_pfNeedsDecompression[lThreadIndex] )
|
|
{
|
|
pTmpPlayer->AddToSpeakingList( lThreadIndex, &m_prWorkerControl[lThreadIndex].m_blMixingSpeakingPlayers );
|
|
pTmpPlayer->m_pfNeedsDecompression[lThreadIndex] = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We need to break out of the loop as we only need to add an individual player once to the
|
|
// list of people a player can hear.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if( !pCurrentPlayer->m_pdwHearCount[lThreadIndex] )
|
|
{
|
|
pCurrentPlayer->m_bLastSent = FALSE;
|
|
}
|
|
else
|
|
{
|
|
pCurrentPlayer->m_bLastSent = TRUE;
|
|
}
|
|
}
|
|
|
|
m_pStats->m_dwPreMixingPassTimeHistoryLoc++;
|
|
m_pStats->m_dwPreMixingPassTimeHistoryLoc %= MIXING_HISTORY;
|
|
m_pStats->m_dwPreMixingPassTimeHistory[m_pStats->m_dwPreMixingPassTimeHistoryLoc] = GetTickCount() - dwTickCountStart;
|
|
|
|
SetEvent( m_prWorkerControl[lThreadIndex].hThreadDoWork );
|
|
}
|
|
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::MixingServerWakeupProc"
|
|
// MixingServerWakeupProc
|
|
//
|
|
// This function is called by the windows timer used by this
|
|
// class each time the timer goes off. The function signals
|
|
// a semaphore provided by the creator of the timer.
|
|
//
|
|
// Parameters:
|
|
// DWORD param - A recast pointer to a HANDLE
|
|
void CDirectVoiceServerEngine::MixingServerWakeupProc( void * pvUserData )
|
|
{
|
|
ReleaseSemaphore( *((HANDLE * ) pvUserData), 1, NULL );
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceServerEngine::HandleMixingReceive"
|
|
HRESULT CDirectVoiceServerEngine::HandleMixingReceive( CDVCSPlayer *pTargetPlayer, PDVPROTOCOLMSG_SPEECHWITHTARGET pdvSpeechWithtarget, DWORD dwSpeechSize, PBYTE pSourceSpeech )
|
|
{
|
|
HRESULT hr;
|
|
|
|
DPFX(DPFPREP, DVF_MIXER_DEBUG_LEVEL, "Mixing Server Speech Handler" );
|
|
|
|
ASSERT_VPLAYER(pTargetPlayer);
|
|
|
|
hr = pTargetPlayer->HandleMixingReceive( &pdvSpeechWithtarget->dvHeader, pSourceSpeech, dwSpeechSize, (PDVID) &pdvSpeechWithtarget[1], pdvSpeechWithtarget->dwNumTargets, m_dvSessionDesc.dwFlags & DVSESSION_SERVERCONTROLTARGET );
|
|
|
|
DPFX(DPFPREP, DVF_CLIENT_SEQNUM_DEBUG_LEVEL, "SEQ: Receive: Msg [%d] Seq [%d]", pdvSpeechWithtarget->dvHeader.bMsgNum, pdvSpeechWithtarget->dvHeader.bSeqNum );
|
|
|
|
return hr;
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceClientEngine::UpdateActiveMixingPendingList"
|
|
void CDirectVoiceServerEngine::UpdateActiveMixingPendingList( DWORD dwThreadIndex, DWORD *pdwNumActive)
|
|
{
|
|
CBilink *pblSearch;
|
|
CDVCSPlayer *pVoicePlayer;
|
|
|
|
DNEnterCriticalSection( &m_prWorkerControl[dwThreadIndex].m_csMixingAddList );
|
|
|
|
// Add players who are pending
|
|
pblSearch = m_prWorkerControl[dwThreadIndex].m_blMixingAddPlayers.GetNext();
|
|
|
|
while( pblSearch != &m_prWorkerControl[dwThreadIndex].m_blMixingAddPlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pVoicePlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER(pVoicePlayer);
|
|
|
|
pVoicePlayer->RemoveFromMixingList(dwThreadIndex);
|
|
|
|
pVoicePlayer->AddToMixingList( dwThreadIndex, &m_prWorkerControl[dwThreadIndex].m_blMixingActivePlayers );
|
|
|
|
pblSearch = m_prWorkerControl[dwThreadIndex].m_blMixingAddPlayers.GetNext();
|
|
}
|
|
|
|
DNASSERT( m_prWorkerControl[dwThreadIndex].m_blMixingAddPlayers.IsEmpty() );
|
|
|
|
DNLeaveCriticalSection( &m_prWorkerControl[dwThreadIndex].m_csMixingAddList );
|
|
|
|
*pdwNumActive = 0;
|
|
|
|
// Remove players who have disconnected
|
|
pblSearch = m_prWorkerControl[dwThreadIndex].m_blMixingActivePlayers.GetNext();
|
|
|
|
while( pblSearch != &m_prWorkerControl[dwThreadIndex].m_blMixingActivePlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pVoicePlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER(pVoicePlayer);
|
|
|
|
pblSearch = pblSearch->GetNext();
|
|
|
|
// If current player has disconnected, remove them from active list
|
|
// and release the reference the list has
|
|
if( pVoicePlayer->IsDisconnected() )
|
|
{
|
|
// Because players are removed from active list here, but not from hearing and/or speaking
|
|
// list you may end up in a situation where a player is talking, drops and during removal
|
|
// ends up in a state where they are not on the mixing list, but they are on the hearing
|
|
// and speaking lists. Except, the hearing and speaking lists are initialized for the
|
|
// next run in the spinworktothread. The result is when you go to delete the player
|
|
// object and attempt to release the player it will assert and/or crash because player
|
|
// still points to next person in the list (even though they don't point back). Causes
|
|
// bilink corruption.
|
|
pVoicePlayer->RemoveFromMixingList(dwThreadIndex);
|
|
pVoicePlayer->RemoveFromHearingList(dwThreadIndex);
|
|
pVoicePlayer->RemoveFromSpeakingList(dwThreadIndex);
|
|
pVoicePlayer->Release();
|
|
}
|
|
else
|
|
{
|
|
(*pdwNumActive)++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#undef DPF_MODNAME
|
|
#define DPF_MODNAME "CDirectVoiceClientEngine::CleanupMixingList"
|
|
void CDirectVoiceServerEngine::CleanupMixingList()
|
|
{
|
|
CBilink *pblSearch;
|
|
CDVCSPlayer *pVoicePlayer;
|
|
|
|
for( DWORD dwIndex = 0; dwIndex < m_dwNumMixingThreads; dwIndex++ )
|
|
{
|
|
DNEnterCriticalSection( &m_prWorkerControl[dwIndex].m_csMixingAddList );
|
|
|
|
// Add players who are pending
|
|
pblSearch = m_prWorkerControl[dwIndex].m_blMixingAddPlayers.GetNext();
|
|
|
|
while( pblSearch != &m_prWorkerControl[dwIndex].m_blMixingAddPlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pVoicePlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER(pVoicePlayer);
|
|
|
|
pblSearch = pblSearch->GetNext();
|
|
|
|
pVoicePlayer->RemoveFromMixingList(dwIndex);
|
|
pVoicePlayer->Release();
|
|
}
|
|
|
|
DNLeaveCriticalSection( &m_prWorkerControl[dwIndex].m_csMixingAddList );
|
|
|
|
DNASSERT( m_prWorkerControl[dwIndex].m_blMixingAddPlayers.IsEmpty() );
|
|
|
|
pblSearch = m_prWorkerControl[dwIndex].m_blMixingActivePlayers.GetNext();
|
|
|
|
while( pblSearch != &m_prWorkerControl[dwIndex].m_blMixingActivePlayers )
|
|
{
|
|
CBilinkPlusObject* pRealBilink = CONTAINING_OBJECT( pblSearch, CBilinkPlusObject, m_bl );
|
|
pVoicePlayer = pRealBilink->m_pPlayer;
|
|
ASSERT_VPLAYER(pVoicePlayer);
|
|
|
|
pblSearch = pblSearch->GetNext();
|
|
|
|
pVoicePlayer->RemoveFromMixingList(dwIndex);
|
|
pVoicePlayer->RemoveFromHearingList(dwIndex);
|
|
pVoicePlayer->RemoveFromSpeakingList(dwIndex);
|
|
pVoicePlayer->Release();
|
|
}
|
|
}
|
|
|
|
}
|