/*==========================================================================
 *
 *  Copyright (C) 1999 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:		loopback.cpp
 *  Content:	Implements the loopback portion of the full duplex test
 *
 *  History:
 *   Date		By		Reason
 *   ====		==		======
 * 09/10/99		pnewson	Created
 * 10/25/99		rodtoll	Removed lpszVoicePassword member from sessiondesc
 * 10/27/99		pnewson Fix: Bug #113936 - Wizard should reset the AGC level before loopback test 
 *						Note: this fix adds the DVCLIENTCONFIG_AUTOVOLUMERESET flag
 *  10/28/99	pnewson Bug #114176 updated DVSOUNDDEVICECONFIG struct
 *  11/04/99	pnewson Bug #115279 changed SendMessage to PostMessage to resolve some deadlocks
 *  11/30/99	pnewson	use default codec
 *						use devices passed to CheckAudioSetup, instead of default devices
 *  12/01/99	rodtoll	Added flag to cause wizard to auto-select the microphone
 *  01/14/2000	rodtoll	Updated with API changes  
 *  01/21/2000	pnewson	Updated for UI revisions
 *						Updated to support use of loopback tests for full duplex testing
 *  01/27/2000	rodtoll	Updated with API changes
 *  02/08/2000	rodtoll	Bug #131496 - Selecting DVTHRESHOLD_DEFAULT results in voice
 *						never being detected 
 *  03/03/2000	rodtoll	Updated to handle alternative gamevoice build.   
 *  04/19/2000	pnewson	    Error handling cleanup  
 * 04/21/2000  rodtoll   Bug #32952 Does not run on Win95 GOLD w/o IE4 installed
 * 06/21/2000	rodtoll	Updated to use new parameters
 * 06/28/2000	rodtoll	Prefix Bug #38022
 *  07/31/2000	rodtoll	Bug #39590 - SB16 class soundcards are passing when they should fail
 *						Half duplex code was being ignored in mic test portion. 
 *  08/28/2000	masonb  Voice Merge: Changed ccomutil.h to ccomutil.h
 * 08/31/2000 	rodtoll	Bug #43804 - DVOICE: dwSensitivity structure member is confusing - should be dwThreshold 
 *  11/29/2000	rodtoll	Bug #48348 - DPVOICE: Modify wizard to make use of DirectPlay8 as the transport. 
 *						NOTE: Now requires a TCP/IP adapter to be present (or at least loopback)
 * 02/04/2001	simonpow	Bug #354859 PREfast spotted errors (PostMessage return codes incorrectly
 *							treated as HRESULTs)
 * 04/12/2001	kareemc	WINBUG #360971 - Wizard Memory Leaks
 * 
 ***************************************************************************/

#include "dxvtlibpch.h"


#undef DPF_SUBCOMP
#define DPF_SUBCOMP DN_SUBCOMP_VOICE


// application defined message ids
//#define WMAPP_LOOPBACKRUNNING 	WM_USER + 1
//#define WMAPP_INPUTVOLUME		WM_USER + 2

// {53CA3FB7-4FD5-4a67-99E4-6F2496E6FEC2}
static const GUID GUID_LOOPBACKTEST = 
	{ 0x53ca3fb7, 0x4fd5, 0x4a67, { 0x99, 0xe4, 0x6f, 0x24, 0x96, 0xe6, 0xfe, 0xc2 } };

HRESULT StartDirectPlay( PDIRECTPLAY8SERVER* lplpdp8 );
HRESULT StopDirectPlay( PDIRECTPLAY8SERVER lpdp8 );

#undef DPF_MODNAME
#define DPF_MODNAME "DVMessageHandlerServer"
HRESULT PASCAL DVMessageHandlerServer( 
	LPVOID 		lpvUserContext,
	DWORD 		dwMessageType,
	LPVOID  	lpMessage
)
{
	DPF_ENTER();
	switch( dwMessageType )
	{
	case DVMSGID_CREATEVOICEPLAYER:
		break;
	case DVMSGID_DELETEVOICEPLAYER:
		break;
	case DVMSGID_SESSIONLOST:
		break;
	case DVMSGID_PLAYERVOICESTART:
		break;
	case DVMSGID_PLAYERVOICESTOP:
		break;
	case DVMSGID_RECORDSTART:
		break;
	case DVMSGID_RECORDSTOP:
		break;
	case DVMSGID_CONNECTRESULT:
		break;		
	case DVMSGID_DISCONNECTRESULT:
		break;
	case DVMSGID_INPUTLEVEL:
		break;
	case DVMSGID_OUTPUTLEVEL:
		break;
	default:
		break;
	}

	DPF_EXIT();
	return DV_OK;
}

#undef DPF_MODNAME
#define DPF_MODNAME "DVMessageHandlerClient"
HRESULT PASCAL DVMessageHandlerClient( 
	LPVOID 		lpvUserContext,
	DWORD 		dwMessageType,
	LPVOID    	lpMessage
)
{
	DPF_EXIT();

	HWND hwndDialog;
	HWND hwndPeakMeter;
	HWND hwndSlider;
	LONG lRet;
	HRESULT hr;
	CSupervisorInfo* lpsinfo;
	PDVMSG_INPUTLEVEL pdvInputLevel;
	PDVMSG_OUTPUTLEVEL pdvOutputLevel;

	lpsinfo = (CSupervisorInfo*)lpvUserContext;

	if( lpsinfo )
	{
		if( !lpsinfo->GetLoopbackRunning() )
			return DV_OK;
	}

	
	switch( dwMessageType )
	{
	case DVMSGID_CREATEVOICEPLAYER:
		break;
	case DVMSGID_DELETEVOICEPLAYER:
		break;
	case DVMSGID_SESSIONLOST:
		break;
	case DVMSGID_PLAYERVOICESTART:
		break;
	case DVMSGID_PLAYERVOICESTOP:
		break;
	case DVMSGID_RECORDSTART:
		if (lpsinfo == NULL)
		{
			break;
		}

		// forward the message along to the appropriate window
		lpsinfo->GetHWNDDialog(&hwndDialog);
		if (!PostMessage(hwndDialog, WM_APP_RECORDSTART, 0, 0))
		{
			DPFX(DPFPREP, DVF_ERRORLEVEL, "PostMessage failed, code: %i", GetLastError());
			break;	// no error return, just continue
		}
		break;
		
	case DVMSGID_RECORDSTOP:
		if (lpsinfo == NULL)
		{
			break;
		}
		
		// forward the message along to the appropriate window
		lpsinfo->GetHWNDDialog(&hwndDialog);
		if (!PostMessage(hwndDialog, WM_APP_RECORDSTOP, 0, 0))
		{
			DPFX(DPFPREP, DVF_ERRORLEVEL, "PostMessage failed, code: %i", GetLastError());
			break;	// no error return, just continue
		}
		break;
		
	case DVMSGID_CONNECTRESULT:
		break;		
	case DVMSGID_DISCONNECTRESULT:
		break;
	case DVMSGID_INPUTLEVEL:
		if (lpsinfo == NULL)
		{
			break;
		}
		// update the peak meter
		lpsinfo->GetHWNDInputPeak(&hwndPeakMeter);
		if (IsWindow(hwndPeakMeter))
		{
			pdvInputLevel = (PDVMSG_INPUTLEVEL) lpMessage;
			if (!PostMessage(hwndPeakMeter, PM_SETCUR, 0, pdvInputLevel->dwPeakLevel ))
			{
				DPFX(DPFPREP, DVF_ERRORLEVEL, "PostMessage failed, code: %i", GetLastError());
				break;	// no error return, just continue
			}
		}

		// update the volume slider
		lpsinfo->GetHWNDInputVolumeSlider(&hwndSlider);
		if (IsWindow(hwndSlider))
		{
			pdvInputLevel = (PDVMSG_INPUTLEVEL) lpMessage;
			if (!PostMessage(hwndSlider, TBM_SETPOS, 1, DBToAmpFactor(DSBVOLUME_MAX)-DBToAmpFactor(pdvInputLevel->lRecordVolume)))
			{
				DPFX(DPFPREP, DVF_ERRORLEVEL, "PostMessage failed, code: %i", GetLastError());
				break;	// no error return, just continue
			}
		}
		
		break;
		
	case DVMSGID_OUTPUTLEVEL:
		if (lpsinfo == NULL)
		{
			break;
		}
		// update the peak meter
		lpsinfo->GetHWNDOutputPeak(&hwndPeakMeter);
		if (IsWindow(hwndPeakMeter))
		{
			pdvOutputLevel = (PDVMSG_OUTPUTLEVEL) lpMessage;
			if (!PostMessage(hwndPeakMeter, PM_SETCUR, 0, pdvOutputLevel->dwPeakLevel ))
			{
				DPFX(DPFPREP, DVF_ERRORLEVEL, "PostMessage failed, code: %i", GetLastError());
				break;	// no error return, just continue
			}
		}

		// update the volume slider
		lpsinfo->GetHWNDOutputVolumeSlider(&hwndSlider);
		if (IsWindow(hwndSlider))
		{
			DWORD dwVolume = 0;	// Set to 0 to avoid PREFIX bug

			// Get the current waveOut volume and set the slider to that position
			hr = lpsinfo->GetWaveOutVolume(&dwVolume);
			if (FAILED(hr))
			{
				// couldn't get the volume - set the slider to top
				PostMessage(hwndSlider, TBM_SETPOS, 1, DBToAmpFactor(DSBVOLUME_MAX) - DBToAmpFactor(DSBVOLUME_MAX));
				// disable the slider
				PostMessage(hwndSlider, WM_CANCELMODE, 0, 0 );
			}
			else
			{
				PostMessage(hwndSlider, TBM_SETPOS, 1, (DBToAmpFactor(DSBVOLUME_MAX) - DBToAmpFactor(DSBVOLUME_MIN)) - dwVolume);
			}
		}
		
		break;

	default:
		break;
	}

	DPF_EXIT();
	return DV_OK;
}

#undef DPF_MODNAME
#define DPF_MODNAME "StartDirectPlay"
HRESULT StartDirectPlay( PDIRECTPLAY8SERVER* lplpdp8 )
{
	HRESULT hr = DPN_OK;
	LONG lRet = S_OK;
    PDIRECTPLAY8ADDRESS pDeviceAddress = NULL;
    PDIRECTPLAY8SERVER pdp8Server = NULL;
    DPN_APPLICATION_DESC dpnApplicationDesc;

	DPF_ENTER();

	*lplpdp8 = NULL;
	
    hr = COM_CoCreateInstance(
    	CLSID_DirectPlay8Server, 
    	NULL, 
    	CLSCTX_INPROC_SERVER, 
    	IID_IDirectPlay8Server, 
    	(void **)lplpdp8);
    if (FAILED(hr))
    {
    	*lplpdp8 = NULL;
    	DPFX(DPFPREP, DVF_ERRORLEVEL, "CoCreateInstance for DirectPlay failed, code: %i", hr);
    	goto error_cleanup;
    }

    pdp8Server = *lplpdp8;

    hr = COM_CoCreateInstance( 
        CLSID_DirectPlay8Address, 
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_IDirectPlay8Address,
        (void **)&pDeviceAddress );

    if( FAILED( hr ) )
    {
        pDeviceAddress = NULL;
        DPFX(DPFPREP, DVF_ERRORLEVEL, "CoCreateInstance for DirectPlay8Address failed, code: 0x%x", hr );
        goto error_cleanup;
    }

	// 
	// NOTE: This now causes the wizard to require TCP/IP to be installed.
	// (doesn't have to be dialed up -- as long as local loopback is available)
	//
	// Eventually build a loopback SP for directplay8.
	//
	// TODO: Allow this to fall back to other SPs or use loopback SP
	//
    hr = pDeviceAddress->SetSP( &CLSID_DP8SP_TCPIP );

    if( FAILED( hr ) )
    {
        DPFX(DPFPREP,  DVF_ERRORLEVEL, "Failed setting SP for address, code: 0x%x", hr );
        goto error_cleanup;
    }

    hr = pdp8Server->Initialize( NULL, DVMessageHandlerServer, 0 );

    if( FAILED( hr ) )
    {
        DPFX(DPFPREP,  DVF_ERRORLEVEL, "Failed initializing directplay layer, code: 0x%x", hr );
        goto error_cleanup;
    }

    ZeroMemory( &dpnApplicationDesc, sizeof( DPN_APPLICATION_DESC ) );
    dpnApplicationDesc.dwSize = sizeof( DPN_APPLICATION_DESC );
    dpnApplicationDesc.guidApplication = GUID_LOOPBACKTEST;
    dpnApplicationDesc.dwFlags = DPNSESSION_NODPNSVR | DPNSESSION_CLIENT_SERVER;

    hr = pdp8Server->Host( 
                        &dpnApplicationDesc, 
                        &pDeviceAddress, 
                        1, 
                        NULL,
                        NULL,
                        NULL, 
                        0 );

    if( FAILED( hr ) )
    {
        DPFX(DPFPREP,  DVF_ERRORLEVEL, "Failed to host on directplay layer, code: 0x%x", hr );
        goto error_cleanup;
    }

    pDeviceAddress->Release();

	DPF_EXIT();
	return S_OK;

error_cleanup:
	if (*lplpdp8 != NULL)
	{
	    pdp8Server->Close(0);
	    pdp8Server->Release();
		*lplpdp8 = NULL;
	}

	if( pDeviceAddress )
	{
		pDeviceAddress->Release();
	}

	DPF_EXIT();
	return hr;
}

#undef DPF_MODNAME
#define DPF_MODNAME "StopDirectPlay"
HRESULT StopDirectPlay(PDIRECTPLAY8SERVER lpdp8)
{

	DPF_ENTER();

	// Kill the session
	if (lpdp8 != NULL)
	{
        lpdp8->Close(0);

        lpdp8->Release();
	}

	
	DPF_EXIT();
	return S_OK;

}

#undef DPF_MODNAME
#define DPF_MODNAME "StartLoopback"
HRESULT StartLoopback(
	LPDIRECTPLAYVOICESERVER* lplpdvs, 
	LPDIRECTPLAYVOICECLIENT* lplpdvc,
	PDIRECTPLAY8SERVER* lplpdp8, 
	LPVOID lpvCallbackContext,
	HWND hwndAppWindow,
	GUID guidCaptureDevice,
	GUID guidRenderDevice,
	DWORD dwFlags)
{
	HRESULT hr;
	DWORD dwSize = 0;
	DVCLIENTCONFIG dvcc;
	DVSOUNDDEVICECONFIG dvsdc;
	DVID dvidAllPlayers = DVID_ALLPLAYERS;	
	PBYTE pDeviceConfigBuffer = NULL;
	PDVSOUNDDEVICECONFIG pdvsdc = NULL;
	BOOL fVoiceSessionStarted = FALSE;
	BOOL fClientConnected = FALSE;

	DPF_ENTER();

	*lplpdvs = NULL;
	*lplpdvc = NULL;

	hr = COM_CoCreateInstance(
		DPVOICE_CLSID_DPVOICE, 
		NULL, 
		CLSCTX_INPROC_SERVER, 
		IID_IDirectPlayVoiceServer, 
		(void **)lplpdvs);
	if (FAILED(hr))
	{
		*lplpdvs = NULL;
		DPFX(DPFPREP, DVF_ERRORLEVEL, "CoCreateInstance failed, code: %i", hr);
		goto error_cleanup;
	}

	hr = (*lplpdvs)->Initialize(*lplpdp8, DVMessageHandlerServer, lpvCallbackContext, NULL, 0);
	if (FAILED(hr))
	{
		DPFX(DPFPREP, DVF_ERRORLEVEL, "IDirectPlayVoiceServer::Initialize failed, code: %i", hr);
		goto error_cleanup;
	}

	DVSESSIONDESC dvSessionDesc;

	dvSessionDesc.dwSize = sizeof( DVSESSIONDESC );
	dvSessionDesc.dwBufferAggressiveness = DVBUFFERAGGRESSIVENESS_DEFAULT;
	dvSessionDesc.dwBufferQuality = DVBUFFERQUALITY_DEFAULT;
	dvSessionDesc.dwFlags = 0;
	dvSessionDesc.dwSessionType = DVSESSIONTYPE_ECHO;
	// Note this compression type is used for its short frame size so
	// we can quickly detect lockups.
    dvSessionDesc.guidCT = DPVCTGUID_NONE;     

	hr = (*lplpdvs)->StartSession( &dvSessionDesc, 0 );
	if (FAILED(hr))
	{
		DPFX(DPFPREP, DVF_ERRORLEVEL, "IDirectPlayVoiceServer::StartSession failed, code: %i", hr);
		goto error_cleanup;
	}
	fVoiceSessionStarted = TRUE;
	
	hr = COM_CoCreateInstance(
		DPVOICE_CLSID_DPVOICE, 
		NULL, 
		CLSCTX_INPROC_SERVER, 
		IID_IDirectPlayVoiceClient, 
		(void **)lplpdvc);
	if (FAILED(hr))
	{
		*lplpdvc = NULL;
		DPFX(DPFPREP, DVF_ERRORLEVEL, "CoCreateInstance failed, code: %i", hr);
		goto error_cleanup;
	}

	hr = (*lplpdvc)->Initialize(*lplpdp8, DVMessageHandlerClient, lpvCallbackContext, NULL, 0);
	if (FAILED(hr))
	{
		DPFX(DPFPREP, DVF_ERRORLEVEL, "IDirectPlayVoiceClient::Initialize failed, code: %i", hr);
		goto error_cleanup;
	}

	dvsdc.dwSize = sizeof( DVSOUNDDEVICECONFIG );
	dvsdc.hwndAppWindow = hwndAppWindow;
	dvsdc.dwFlags = DVSOUNDCONFIG_AUTOSELECT;
	if (dwFlags & DVSOUNDCONFIG_HALFDUPLEX)
	{
		// The caller wants a half duplex session.
		dvsdc.dwFlags |= DVSOUNDCONFIG_HALFDUPLEX;
	}
	if (dwFlags & DVSOUNDCONFIG_TESTMODE)
	{
		// The caller wants a test mode session.
		dvsdc.dwFlags |= DVSOUNDCONFIG_TESTMODE;
	}
	dvsdc.guidCaptureDevice = guidCaptureDevice;
	dvsdc.guidPlaybackDevice = guidRenderDevice;
	dvsdc.lpdsPlaybackDevice = NULL;
	dvsdc.lpdsCaptureDevice = NULL;
	dvsdc.dwMainBufferFlags = 0;
	dvsdc.dwMainBufferPriority = 0;
	dvsdc.lpdsMainBuffer = NULL;

	dvcc.dwSize = sizeof( DVCLIENTCONFIG );
	dvcc.dwFlags = 
		DVCLIENTCONFIG_AUTOVOICEACTIVATED | 
		DVCLIENTCONFIG_AUTORECORDVOLUME | DVCLIENTCONFIG_AUTOVOLUMERESET |
		DVCLIENTCONFIG_PLAYBACKMUTE;  // we don't want the user to hear his/her voice right away
	dvcc.dwThreshold = DVTHRESHOLD_UNUSED;
	dvcc.lPlaybackVolume = DSBVOLUME_MAX;
	dvcc.lRecordVolume = DSBVOLUME_MAX;
	dvcc.dwNotifyPeriod = 50;
	dvcc.dwBufferQuality = DVBUFFERQUALITY_DEFAULT;
	dvcc.dwBufferAggressiveness = DVBUFFERAGGRESSIVENESS_DEFAULT;

	hr = (*lplpdvc)->Connect( &dvsdc, &dvcc, DVFLAGS_SYNC|DVFLAGS_NOQUERY );
	if (FAILED(hr))
	{
		DPFX(DPFPREP, DVF_ERRORLEVEL, "IDirectPlayVoiceClient::Connect failed, code: %i", hr);
		goto error_cleanup;
	}
	fClientConnected = TRUE;

	hr = (*lplpdvc)->SetTransmitTargets(&dvidAllPlayers, 1 , 0);
	if (FAILED(hr))
	{
		DPFX(DPFPREP, DVF_ERRORLEVEL, "IDirectPlayVoiceClient::SetTransmitTargets failed, code: %i", hr);
		goto error_cleanup;
	}
	
	dwSize = 0;
	hr = (*lplpdvc)->GetSoundDeviceConfig(pdvsdc, &dwSize);
	if( hr != DVERR_BUFFERTOOSMALL )
	{
		DPFX(DPFPREP,  DVF_ERRORLEVEL, "IDirectPlayVoiceClient::GetSoundDeviceConfig failed, hr: %i", hr);
		goto error_cleanup;
	}

	pDeviceConfigBuffer = new BYTE[dwSize];
	if( pDeviceConfigBuffer == NULL )
	{
		DPFX(DPFPREP,  DVF_ERRORLEVEL, "Memory alloc failure" );
		hr = DVERR_OUTOFMEMORY;
		goto error_cleanup;
	}

	pdvsdc = (PDVSOUNDDEVICECONFIG) pDeviceConfigBuffer;
	pdvsdc->dwSize = sizeof( DVSOUNDDEVICECONFIG );

	hr = (*lplpdvc)->GetSoundDeviceConfig(pdvsdc, &dwSize );
	if (FAILED(hr))
	{
		DPFX(DPFPREP,  DVF_ERRORLEVEL, "IDirectPlayVoiceClient::GetSoundDeviceConfig failed, hr: %i", hr);
		goto error_cleanup;
	}

	// If we're looking for full duplex fail and notify caller if we get half duplex
	if( !(dwFlags & DVSOUNDCONFIG_HALFDUPLEX) )
	{
		if (pdvsdc->dwFlags & DVSOUNDCONFIG_HALFDUPLEX)
		{
			DPFX(DPFPREP,  DVF_ERRORLEVEL, "We received a half duplex when we expected full duplex" );
			// It only started up in half duplex. Notify the caller.
			hr = DV_HALFDUPLEX;
			goto error_cleanup;
		}
	} 

	if( pdvsdc->dwFlags & DVSOUNDCONFIG_HALFDUPLEX )
	{
		DPFX(DPFPREP,  DVF_INFOLEVEL, "StartLoopBack() returning DV_HALFDUPLEX flags=0x%x dwFlags = 0x%x", pdvsdc->dwFlags, dwFlags );
		// it started in fullduplex, notify the caller
		delete [] pDeviceConfigBuffer;
		DPF_EXIT();
		return DV_HALFDUPLEX;
	}
	else
	{
		DPFX(DPFPREP,  DVF_INFOLEVEL, "StartLoopBack() returning DV_FULLDUPLEX flags=0x%x dwFlags = 0x%x", pdvsdc->dwFlags, dwFlags );		
		// it started in fullduplex, notify the caller
		delete [] pDeviceConfigBuffer;
		DPF_EXIT();
		return DV_FULLDUPLEX;
	}

error_cleanup:

	if (pDeviceConfigBuffer != NULL)
	{
		delete [] pDeviceConfigBuffer;
		pDeviceConfigBuffer = NULL;
	}

	if (*lplpdvc != NULL)
	{
		if (fClientConnected)
		{
			(*lplpdvc)->Disconnect(DVFLAGS_SYNC);
			fClientConnected = FALSE;
		}
		(*lplpdvc)->Release();
		*lplpdvc = NULL;
	}

	if (*lplpdvs != NULL)
	{
		if (fVoiceSessionStarted)
		{
			(*lplpdvs)->StopSession(0);
			fVoiceSessionStarted = FALSE;
		}
		(*lplpdvs)->Release();
		*lplpdvs = NULL;
	}

	DPFX(DPFPREP,  DVF_ERRORLEVEL, "StartLoopback() returning hr=0x%x", hr );
	
	DPF_EXIT();
	return hr;
}

#undef DPF_MODNAME
#define DPF_MODNAME "StopLoopback"
HRESULT StopLoopback(
	LPDIRECTPLAYVOICESERVER lpdvs, 
	LPDIRECTPLAYVOICECLIENT lpdvc,
	PDIRECTPLAY8SERVER lpdp8 )
{
	HRESULT hr;
	LONG lRet;
	BOOL fRet;
	BOOL fClientConnected = TRUE;
	BOOL fVoiceSessionRunning = TRUE;
	
	DPF_ENTER();

	if (lpdvc != NULL)
	{
		hr = lpdvc->Disconnect(DVFLAGS_SYNC);
		fClientConnected = FALSE;
		if (FAILED(hr))
		{
			DPFX(DPFPREP, DVF_ERRORLEVEL, "IDirectPlayVoiceClient::Disconnect failed, hr: %i", hr);
			goto error_cleanup;
		}
		
		lpdvc->Release();
		lpdvc = NULL;
	}

	if (lpdvs != NULL)
	{
		hr = lpdvs->StopSession(0);
		fVoiceSessionRunning = FALSE;
		if (FAILED(hr))
		{
			DPFX(DPFPREP, DVF_ERRORLEVEL, "IDirectPlayVoiceServer::StopSession failed, hr: %i", hr);
			goto error_cleanup;
		}
		lpdvs->Release();
		lpdvs = NULL;
	}

	DPF_EXIT();
	return S_OK;

error_cleanup:
	if (lpdvc != NULL)
	{
		if (fClientConnected)
		{
			lpdvc->Disconnect(DVFLAGS_SYNC);
			fClientConnected = FALSE;
		}
		lpdvc->Release();
		lpdvc = NULL;
	}

	if (lpdvs != NULL)
	{
		if (fVoiceSessionRunning)
		{
			lpdvs->StopSession(0);
			fVoiceSessionRunning = FALSE;
		}
		lpdvs->Release();
		lpdvs = NULL;
	}

	DPF_EXIT();
	return hr;
}