Windows2003-3790/termsrv/rdpclip/sndchan.cpp

5541 lines
143 KiB
C++

/////////////////////////////////////////////////////////////////////
//
// Module: sndchan.c
//
// Purpose: Server-side audio redirection communication
// module
//
// Copyright(C) Microsoft Corporation 2000
//
// History: 4-10-2000 vladimis [created]
//
/////////////////////////////////////////////////////////////////////
#include <windef.h>
#include <winsta.h>
#include <wtsapi32.h>
#include <pchannel.h>
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <msacm.h>
#include <aclapi.h>
#include <sha.h>
#include <rc4.h>
#include <rdpstrm.h>
//
// Include security headers for RNG functions
//
#define NO_INCLUDE_LICENSING 1
#include <tssec.h>
#include "sndchan.h"
#include "sndknown.h"
#define TSSND_REG_MAXBANDWIDTH_KEY L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Drivers32\\Terminal Server\\RDP"
#define TSSND_REG_MAXBANDWIDTH_VAL L"MaxBandwidth"
#define TSSND_REG_MINBANDWIDTH_VAL L"MinBandwidth"
#define TSSND_REG_DISABLEDGRAM_VAL L"DisableDGram"
#define TSSND_REG_ENABLEMP3_VAL L"EnableMP3Codec"
#define TSSND_REG_ALLOWCODECS L"AllowCodecs"
#define TSSND_REG_MAXDGRAM L"MaxDGram"
#define DEFAULT_RESPONSE_TIMEOUT 5000
#define TSSND_TRAINING_BLOCKSIZE 1024
//
// --- READ THIS IF YOU ARE ADDING FEATURES ---
// right now the encryption works only from server to client
// there's no data send from server to client
// if you read this in the future and you are planning to add
// data stream from client to server, PLEASE ENCRYPT IT !!!
// use SL_Encrypt function for that
//
#define MIN_ENCRYPT_LEVEL 2
#define STAT_COUNT 32
#define STAT_COUNT_INIT (STAT_COUNT - 8)
#define READ_EVENT 0
#define DISCONNECT_EVENT 1
#define RECONNECT_EVENT 2
#define DATAREADY_EVENT 3
#define DGRAM_EVENT 4
#define POWERWAKEUP_EVENT 5
#define POWERSUSPEND_EVENT 6
#define TOTAL_EVENTS 7
#define NEW_CODEC_COVER 90 // minimum percentage a new codec has to cover
// i.e if we are at 7kbps and the new meassurement is
// for 10kbps we are not switching to codec which
// does have more than NEW_CODEC_COVER * 10k / 100 bandwith
// requirement
//
// Data for enabling private codecs
// BUGBUG
// Legal issue ?!
//
#ifndef G723MAGICWORD1
#define G723MAGICWORD1 0xf7329ace
#endif
#ifndef G723MAGICWORD2
#define G723MAGICWORD2 0xacdeaea2
#endif
#ifndef VOXWARE_KEY
#define VOXWARE_KEY "35243410-F7340C0668-CD78867B74DAD857-AC71429AD8CAFCB5-E4E1A99E7FFD-371"
#endif
#define _RDPSNDWNDCLASS L"RDPSound window"
#ifdef _WIN32
#include <pshpack1.h>
#else
#ifndef RC_INVOKED
#pragma pack(1)
#endif
#endif
typedef struct msg723waveformat_tag {
WAVEFORMATEX wfx;
WORD wConfigWord;
DWORD dwCodeword1;
DWORD dwCodeword2;
} MSG723WAVEFORMAT;
typedef struct intelg723waveformat_tag {
WAVEFORMATEX wfx;
WORD wConfigWord;
DWORD dwCodeword1;
DWORD dwCodeword2;
} INTELG723WAVEFORMAT;
typedef struct tagVOXACM_WAVEFORMATEX
{
WAVEFORMATEX wfx;
DWORD dwCodecId;
DWORD dwMode;
char szKey[72];
} VOXACM_WAVEFORMATEX, *PVOXACM_WAVEFORMATEX, FAR *LPVOXACM_WAVEFORMATEX;
#define WAVE_FORMAT_WMAUDIO2 0x161
#ifdef _WIN32
#include <poppack.h>
#else
#ifndef RC_INVOKED
#pragma pack()
#endif
#endif
typedef struct {
SNDPROLOG Prolog;
UINT uiPrologReceived;
PVOID pBody;
UINT uiBodyAllocated;
UINT uiBodyReceived;
} SNDMESSAGE, *PSNDMESSAGE;
typedef struct _VCSNDFORMATLIST {
struct _VCSNDFORMATLIST *pNext;
HACMDRIVERID hacmDriverId;
WAVEFORMATEX Format;
// additional data for the format
} VCSNDFORMATLIST, *PVCSNDFORMATLIST;
typedef VOID (*PFNCONVERTER)( INT16 *, DWORD, DWORD * );
static HANDLE g_hVC = NULL; // virtual channel handle
BYTE g_Buffer[CHANNEL_CHUNK_LENGTH]; // receive buffer
UINT g_uiBytesInBuffer = 0; //
UINT g_uiBufferOffset = 0;
OVERLAPPED g_OverlappedRead; // overlapped structure
HANDLE g_hDataReadyEvent = NULL; // set by the client apps
HANDLE g_hStreamIsEmptyEvent = NULL; // set by this code
HANDLE g_hStreamMutex = NULL; // guard the stream data
HANDLE g_hStream = NULL; // stream handle
HANDLE g_hDisconnectEvent = NULL; // set for this VC
PSNDSTREAM g_Stream; // stream data pointer
BOOL g_bRunning = TRUE; // TRUE if running
BOOL g_bDeviceOpened = FALSE; // TRUE if device opened
BOOL g_bDisconnected = FALSE; // TRUE if disconnected
DWORD g_dwLineBandwidth = 0; // current bandwidth
DWORD g_dwCodecChangeThreshold = 10; // how mach the bandwith has to change in order
// to change the codec ( in percents )
// this number changes up to 50%
PSNDFORMATITEM *g_ppNegotiatedFormats = NULL; // list of formats
DWORD g_dwNegotiatedFormats = 0; // number of formats
DWORD g_dwCurrentFormat = 0; // current format Id
HACMDRIVERID g_hacmDriverId = NULL; // codec handles
HACMDRIVER g_hacmDriver = NULL;
HACMSTREAM g_hacmStream = NULL;
PFNCONVERTER g_pfnConverter = NULL; // intermidiate converter
DWORD g_dwDataRemain = 0;
BYTE g_pCnvPrevData[ TSSND_BLOCKSIZE ];
PVCSNDFORMATLIST g_pAllCodecsFormatList = NULL; // all available codecs
DWORD g_dwAllCodecsNumber = 0;
DWORD g_dwMaxBandwidth = (DWORD) -1; // options
DWORD g_dwMinBandwidth = 0;
DWORD g_dwDisableDGram = 0;
DWORD g_dwEnableMP3Codec = 0;
DWORD *g_AllowCodecs = NULL;
DWORD g_AllowCodecsSize = 0;
DWORD g_dwStatPing = 0; // statistics
DWORD g_dwStatLatency = 0;
DWORD g_dwBlocksOnTheNet = TSSND_BLOCKSONTHENET;
DWORD g_dwStatCount = STAT_COUNT_INIT;
DWORD g_dwPacketSize = 0;
HANDLE g_hPowerWakeUpEvent = NULL; // power events
HANDLE g_hPowerSuspendEvent = NULL;
BOOL g_bSuspended = FALSE;
BOOL g_bDeviceFailed = FALSE;
//
// datagram control
//
SOCKET g_hDGramSocket = INVALID_SOCKET;
DWORD g_dwDGramPort = 0;
DWORD g_dwDGramSize = 1460; // number good which is ok for LAN
u_long g_ulDGramAddress = 0;
DWORD g_EncryptionLevel = 3;
DWORD g_wClientVersion = 0;
DWORD g_HiBlockNo = 0;
BYTE g_EncryptKey[RANDOM_KEY_LENGTH + 4];
WSABUF g_wsabuf;
BYTE g_pDGramRecvData[128];
WSAOVERLAPPED g_WSAOverlapped;
const CHAR *ALV = "TSSNDD::ALV - ";
const CHAR *INF = "TSSNDD::INF - ";
const CHAR *WRN = "TSSNDD::WRN - ";
const CHAR *ERR = "TSSNDD::ERR - ";
const CHAR *FATAL = "TSSNDD::FATAL - ";
static HANDLE g_hThread = NULL;
//
// internal functions
//
BOOL
ChannelBlockWrite(
PVOID pBlock,
ULONG ulBlockSize
);
BOOL
VCSndAcquireStream(
VOID
);
BOOL
VCSndReleaseStream(
VOID
);
BOOL
_VCSndOpenConverter(
VOID
);
VOID
_VCSndCloseConverter(
VOID
);
VOID
_VCSndOrderFormatList(
PVCSNDFORMATLIST *ppFormatList,
DWORD *pdwNum
);
DWORD
_VCSndChooseProperFormat(
DWORD dwBandwidth
);
BOOL
_VCSndGetACMDriverId(
PSNDFORMATITEM pSndFmt
);
VOID
DGramRead(
HANDLE hDGramEvent,
PVOID *ppBuff,
DWORD *pdwRecvd
);
VOID
DGramReadComplete(
PVOID *ppBuff,
DWORD *pdwRecvd
);
#if !( TSSND_NATIVE_SAMPLERATE - 22050 )
//
// converters
// convert to the native format
//
#define CONVERTFROMNATIVETOMONO(_speed_) \
VOID \
_Convert##_speed_##Mono( \
INT16 *pSrc, \
DWORD dwSrcSize, \
DWORD *pdwDstSize ) \
{ \
DWORD dwDstSize; \
DWORD i; \
DWORD dwLeap; \
INT16 *pDest = pSrc; \
\
ASSERT( TSSND_NATIVE_SAMPLERATE >= _speed_ ); \
ASSERT( TSSND_NATIVE_CHANNELS == 2 ); \
\
dwDstSize = dwSrcSize * _speed_ / \
( TSSND_NATIVE_BLOCKALIGN * TSSND_NATIVE_SAMPLERATE ); \
\
for (i = 0, dwLeap = 0; \
i < dwDstSize; \
i ++) \
{ \
INT sum; \
\
sum = pSrc[0] + pSrc[1]; \
\
if (sum > 0x7FFF) \
sum = 0x7FFF; \
if (sum < -0x8000) \
sum = -0x8000; \
\
pDest[0] = (INT16)sum; \
pDest ++; \
\
dwLeap += 2 * TSSND_NATIVE_SAMPLERATE; \
pSrc += dwLeap / _speed_; \
dwLeap %= _speed_; \
} \
\
*pdwDstSize = dwDstSize * 2; \
}
#define CONVERTFROMNATIVETOSTEREO(_speed_) \
VOID \
_Convert##_speed_##Stereo( \
INT16 *pSrc, \
DWORD dwSrcSize, \
DWORD *pdwDstSize ) \
{ \
DWORD dwDstSize; \
DWORD i; \
DWORD dwLeap; \
INT16 *pDest = pSrc; \
\
ASSERT( TSSND_NATIVE_SAMPLERATE >= _speed_ ); \
\
dwDstSize = dwSrcSize * _speed_ / \
( TSSND_NATIVE_BLOCKALIGN * TSSND_NATIVE_SAMPLERATE ); \
for (i = 0, dwLeap = 0; \
i < dwDstSize; \
i ++) \
{ \
INT sum; \
\
pDest[0] = pSrc[0]; \
pDest ++; \
pDest[0] = pSrc[1]; \
pDest ++; \
\
dwLeap += 2 * TSSND_NATIVE_SAMPLERATE; \
pSrc += dwLeap / _speed_; \
dwLeap %= _speed_; \
} \
\
*pdwDstSize = dwDstSize * 4; \
}
VOID
_Convert11025Mono(
INT16 *pSrc,
DWORD dwSrcSize,
DWORD *pdwDstSize )
{
DWORD dwDstSize;
INT16 *pDest = pSrc;
ASSERT( TSSND_NATIVE_SAMPLERATE >= 11025 );
dwDstSize = dwSrcSize / ( TSSND_NATIVE_BLOCKALIGN * 2 );
*pdwDstSize = 2 * dwDstSize;
for (; dwDstSize; dwDstSize --)
{
INT sum = pSrc[0] + pSrc[1];
if (sum > 0x7FFF)
sum = 0x7FFF;
if (sum < -0x8000)
sum = -0x8000;
pDest[0] = (INT16)sum;
pDest ++;
pSrc += 4;
}
}
VOID
_Convert22050Mono(
INT16 *pSrc,
DWORD dwSrcSize,
DWORD *pdwDstSize )
{
DWORD dwDstSize;
INT16 *pDest = pSrc;
ASSERT( TSSND_NATIVE_SAMPLERATE >= 22050 );
dwDstSize = dwSrcSize / ( TSSND_NATIVE_BLOCKALIGN );
*pdwDstSize = 2 * dwDstSize;
for (; dwDstSize; dwDstSize --)
{
INT sum = pSrc[0] + pSrc[1];
if (sum > 0x7FFF)
sum = 0x7FFF;
if (sum < -0x8000)
sum = -0x8000;
pDest[0] = (INT16)sum;
pDest ++;
pSrc += 2;
}
}
VOID
_Convert11025Stereo(
INT16 *pSrc,
DWORD dwSrcSize,
DWORD *pdwDstSize )
{
DWORD dwDstSize;
INT16 *pDest = pSrc;
ASSERT( TSSND_NATIVE_SAMPLERATE >= 22050 );
dwDstSize = dwSrcSize / ( TSSND_NATIVE_BLOCKALIGN * 2 );
*pdwDstSize = 4 * dwDstSize;
for (; dwDstSize; dwDstSize --)
{
pDest[0] = pSrc[0];
pSrc ++;
pDest ++;
pDest[0] = pSrc[0];
pDest ++;
pSrc ++;
pSrc += 2;
}
}
//
// Make the actual code
//
CONVERTFROMNATIVETOMONO( 8000 )
CONVERTFROMNATIVETOMONO( 12000 )
CONVERTFROMNATIVETOMONO( 16000 )
CONVERTFROMNATIVETOSTEREO( 8000 )
CONVERTFROMNATIVETOSTEREO( 12000 )
CONVERTFROMNATIVETOSTEREO( 16000 )
#else
#pragma error
#endif
u_long
inet_addrW(
LPCWSTR szAddressW
)
{
CHAR szAddressA[32];
*szAddressA = 0;
WideCharToMultiByte(
CP_ACP,
0,
szAddressW,
-1,
szAddressA,
sizeof(szAddressA),
NULL, NULL);
return inet_addr(szAddressA);
}
/*
* create signature bits
*/
VOID
SL_Signature(
PBYTE pSig,
DWORD dwBlockNo
)
{
BYTE ShaBits[A_SHA_DIGEST_LEN];
A_SHA_CTX SHACtx;
ASSERT( A_SHA_DIGEST_LEN > RDPSND_SIGNATURE_SIZE );
A_SHAInit(&SHACtx);
*((DWORD *)(g_EncryptKey + RANDOM_KEY_LENGTH)) = dwBlockNo;
A_SHAUpdate(&SHACtx, (PBYTE)g_EncryptKey, sizeof(g_EncryptKey));
A_SHAFinal(&SHACtx, ShaBits);
memcpy( pSig, ShaBits, RDPSND_SIGNATURE_SIZE );
}
/*
* signature which verifies the audio bits
*/
VOID
SL_AudioSignature(
PBYTE pSig,
DWORD dwBlockNo,
PBYTE pData,
DWORD dwDataSize
)
{
BYTE ShaBits[A_SHA_DIGEST_LEN];
A_SHA_CTX SHACtx;
A_SHAInit(&SHACtx);
*((DWORD *)(g_EncryptKey + RANDOM_KEY_LENGTH)) = dwBlockNo;
A_SHAUpdate(&SHACtx, (PBYTE)g_EncryptKey, sizeof(g_EncryptKey));
A_SHAUpdate(&SHACtx, pData, dwDataSize );
A_SHAFinal(&SHACtx, ShaBits);
memcpy( pSig, ShaBits, RDPSND_SIGNATURE_SIZE );
}
/*
* encrypt/decrypt a block of data
*
*/
BOOL
SL_Encrypt( PBYTE pBits, DWORD BlockNo, DWORD dwBitsLen )
{
BYTE ShaBits[A_SHA_DIGEST_LEN];
RC4_KEYSTRUCT rc4key;
DWORD i;
PBYTE pbBuffer;
A_SHA_CTX SHACtx;
DWORD dw;
DWORD_PTR *pdwBits;
A_SHAInit(&SHACtx);
// SHA the bits
*((DWORD *)(g_EncryptKey + RANDOM_KEY_LENGTH)) = BlockNo;
A_SHAUpdate(&SHACtx, (PBYTE)g_EncryptKey, sizeof(g_EncryptKey));
A_SHAFinal(&SHACtx, ShaBits);
rc4_key(&rc4key, A_SHA_DIGEST_LEN, ShaBits);
rc4(&rc4key, dwBitsLen, pBits);
return TRUE;
}
BOOL
SL_SendKey( VOID )
{
SNDCRYPTKEY Key;
Key.Prolog.Type = SNDC_CRYPTKEY;
Key.Prolog.BodySize = sizeof( Key ) - sizeof( Key.Prolog );
Key.Reserved = 0;
memcpy( Key.Seed, g_EncryptKey, sizeof( Key.Seed ));
return ChannelBlockWrite( &Key, sizeof( Key ));
}
/*
* Function:
* _StatsCollect
*
* Description:
* Collects statistics for the line quality
*
*/
VOID
_StatsCollect(
DWORD dwTimeStamp
)
{
DWORD dwTimeDiff;
#if _DBG_STATS
TRC(INF, "_StatsCollect: time now=%x, stamp=%x\n",
GetTickCount() & 0xffff,
dwTimeStamp);
#endif
dwTimeDiff = (( GetTickCount() & 0xffff ) - dwTimeStamp ) & 0xffff;
// it is possible to receive time stamp
// with time before the packet was sent,
// this is because the client does adjusments to the time stamp
// i.e. subtracts the time when the packet was played
// catch and ignore this case
//
if ( dwTimeDiff > 0xf000 )
{
dwTimeDiff = 1;
}
if ( 0 == dwTimeDiff )
dwTimeDiff = 1;
if ( 0 == g_dwStatLatency )
g_dwStatLatency = dwTimeDiff;
else {
//
// increase by 30%
//
g_dwStatLatency = (( 7 * g_dwStatLatency ) + ( 3 * dwTimeDiff )) / 10;
}
g_dwStatCount ++;
}
/*
* Function:
* _StatsSendPing
*
* Description:
* Sends a ping packet to the client
*
*/
VOID
_StatSendPing(
VOID
)
{
//
// send a ping request
//
SNDTRAINING SndTraining;
SndTraining.Prolog.Type = SNDC_TRAINING;
SndTraining.Prolog.BodySize = sizeof( SndTraining ) -
sizeof( SndTraining.Prolog );
SndTraining.wTimeStamp = (UINT16)GetTickCount();
SndTraining.wPackSize = 0;
if ( INVALID_SOCKET != g_hDGramSocket &&
0 != g_dwDGramPort &&
0 != g_ulDGramAddress
)
{
struct sockaddr_in sin;
INT rc;
sin.sin_family = PF_INET;
sin.sin_port = (u_short)g_dwDGramPort;
sin.sin_addr.s_addr = g_ulDGramAddress;
rc = sendto(
g_hDGramSocket,
(LPSTR)&SndTraining,
sizeof( SndTraining ),
0,
(struct sockaddr *)&sin, // to address
sizeof(sin)
);
if (SOCKET_ERROR == rc)
{
TRC(ERR, "_StatsSendPing: sendto failed: %d\n",
WSAGetLastError());
}
} else {
BOOL bSuccess;
bSuccess = ChannelBlockWrite( &SndTraining, sizeof( SndTraining ));
if (!bSuccess)
{
TRC(ERR, "_StatSendPing: ChannelBlockWrite failed: %d\n",
GetLastError());
}
}
}
/*
* Function:
* _StatsCheckResample
*
* Description:
* Looks in the statistics and eventually changes the current
* codec
*/
BOOL
_StatsCheckResample(
VOID
)
{
BOOL rv = FALSE;
DWORD dwNewFmt;
DWORD dwNewLatency;
DWORD dwNewBandwidth;
DWORD dwLatDiff;
DWORD dwMsPerBlock;
DWORD dwBlocksOnTheNet;
DWORD dwCurrBandwith;
if (( g_dwStatCount % STAT_COUNT ) == STAT_COUNT / 2 )
_StatSendPing();
if ( g_dwStatCount < STAT_COUNT )
goto exitpt;
if ( g_dwStatPing >= g_dwStatLatency )
g_dwStatPing = g_dwStatLatency - 1;
dwNewLatency = ( g_dwStatLatency - g_dwStatPing / 2 );
if ( 0 == g_dwPacketSize )
{
TRC(INF, "_StatsCheckResample: invalid packet size\n");
goto resetpt;
}
dwNewBandwidth = g_dwPacketSize * 1000 / dwNewLatency;
if ( 0 == dwNewBandwidth )
{
TRC(INF, "_StatsCheckResample: invalid bandwidth\n");
goto resetpt;
}
TRC(INF, "_StatsCheckResample: latency=%d, bandwidth=%d\n",
dwNewLatency, dwNewBandwidth );
//
// g_dwBlocksOnTheNet is the latency in number of blocks
//
dwMsPerBlock = TSSND_BLOCKSIZE * 1000 / TSSND_NATIVE_AVGBYTESPERSEC;
dwBlocksOnTheNet = ((g_dwStatLatency + dwMsPerBlock / 2) / dwMsPerBlock + 2);
if ( dwBlocksOnTheNet > TSSND_BLOCKSONTHENET )
{
g_dwBlocksOnTheNet = TSSND_BLOCKSONTHENET;
} else {
g_dwBlocksOnTheNet = dwBlocksOnTheNet;
}
TRC( INF, "BlocksOnTheNet=%d\n", g_dwBlocksOnTheNet );
//
// check for at least 10% difference in the bandwidth
//
if ( dwNewBandwidth > g_dwMaxBandwidth )
dwNewBandwidth = g_dwMaxBandwidth;
if ( dwNewBandwidth < g_dwMinBandwidth )
dwNewBandwidth = g_dwMinBandwidth;
dwCurrBandwith = ( NULL != g_ppNegotiatedFormats[ g_dwCurrentFormat ] )?
g_ppNegotiatedFormats[ g_dwCurrentFormat ]->nAvgBytesPerSec:
g_dwLineBandwidth;
if ( dwCurrBandwith > dwNewBandwidth )
dwLatDiff = dwCurrBandwith - dwNewBandwidth;
else
dwLatDiff = dwNewBandwidth - dwCurrBandwith;
if ( dwLatDiff < g_dwCodecChangeThreshold * dwCurrBandwith / 100 )
goto resetpt;
//
// increment the threshold up to 50%
//
if ( g_dwCodecChangeThreshold < 50 )
{
g_dwCodecChangeThreshold += 5;
}
//
// try to choose another format
//
dwNewFmt = _VCSndChooseProperFormat( dwNewBandwidth );
if ( (DWORD)-1 != dwNewFmt &&
dwNewFmt != g_dwCurrentFormat )
{
INT step;
DWORD dwNextFmt;
//
// don't jump directly to the new format, just move
// towards it
//
step = ( dwNewFmt > g_dwCurrentFormat )?1:-1;
dwNextFmt = g_dwCurrentFormat + step;
while( dwNextFmt != dwNewFmt &&
NULL == g_ppNegotiatedFormats[dwNextFmt] )
{
dwNextFmt += step;
}
dwNewFmt = dwNextFmt;
}
if ( dwNewFmt == (DWORD)-1 ||
dwNewFmt == g_dwCurrentFormat )
goto resetpt;
TRC(INF, "_StatsCheckResample: new bandwidth=%d resampling\n",
dwNewBandwidth);
//
// resample, NOW
//
_VCSndCloseConverter();
if ( _VCSndGetACMDriverId( g_ppNegotiatedFormats[dwNewFmt] ))
{
g_dwLineBandwidth = dwNewBandwidth;
g_dwCurrentFormat = dwNewFmt;
g_dwDataRemain = 0;
}
_VCSndOpenConverter();
rv = TRUE;
resetpt:
g_dwStatLatency = 0;
g_dwStatCount = 0;
exitpt:
return rv;
}
/*
* Function:
* _StatReset
*
* Description:
* Resets the statistics
*
*/
VOID
_StatReset(
VOID
)
{
g_dwStatLatency = 0;
g_dwStatPing = 0;
g_dwStatCount = STAT_COUNT_INIT;
}
/*
* Function:
* ChannelOpen
*
* Description:
* Opens the virtual channel
*
*
*/
BOOL
ChannelOpen(
VOID
)
{
BOOL rv = FALSE;
if (!g_hVC)
g_hVC = WinStationVirtualOpen(
NULL,
LOGONID_CURRENT,
_SNDVC_NAME
);
rv = (g_hVC != NULL);
return rv;
}
/*
* Function:
* ChannelClose
*
* Description:
* Closes the virtual channel
*/
VOID
ChannelClose(
VOID
)
{
if (g_hVC)
{
CloseHandle(g_hVC);
g_hVC = NULL;
}
g_uiBytesInBuffer = 0;
g_uiBufferOffset = 0;
}
/*
* Function:
* ChannelBlockWrite
*
* Description:
* Writes a block thru the virtual channel
*
*/
BOOL
ChannelBlockWrite(
PVOID pBlock,
ULONG ulBlockSize
)
{
BOOL bSuccess = TRUE;
PCHAR pData = (PCHAR) pBlock;
ULONG ulBytesWritten;
ULONG ulBytesToWrite = ulBlockSize;
HANDLE hVC;
hVC = g_hVC;
if (!hVC)
{
TRC(ERR, "ChannelBlockWrite: vc handle is NULL\n");
bSuccess = FALSE;
goto exitpt;
}
while (bSuccess && ulBytesToWrite)
{
OVERLAPPED Overlapped;
Overlapped.hEvent = NULL;
Overlapped.Offset = 0;
Overlapped.OffsetHigh = 0;
bSuccess = WriteFile(
hVC,
pData,
ulBytesToWrite,
&ulBytesWritten,
&Overlapped
);
if (!bSuccess && ERROR_IO_PENDING == GetLastError())
bSuccess = GetOverlappedResult(
hVC,
&Overlapped,
&ulBytesWritten,
TRUE);
if (bSuccess)
{
TRC(ALV, "VirtualChannelWrite: Wrote %d bytes\n",
ulBytesWritten);
ulBytesToWrite -= ulBytesWritten;
pData += ulBytesWritten;
} else {
TRC(ERR, "VirtualChannelWrite failed, GetLastError=%d\n",
GetLastError());
}
}
exitpt:
return bSuccess;
}
/*
* Function:
* ChannelMessageWrite
*
* Description:
* Writes a two pieces message as a single one (uses ChannelBlockWrite)
*
*/
BOOL
ChannelMessageWrite(
PVOID pProlog,
ULONG ulPrologSize,
PVOID pBody,
ULONG ulBodySize
)
{
BOOL rv = FALSE;
if ( 0 != ulBodySize )
{
//
// create a new prolog message
// in which a UINT32 word is added at the end
// this word is the same as the first word of the prolog
// the client is aware of this and will reconstruct
// to the correct messages
//
PVOID pNewProlog;
__try {
pNewProlog = alloca( ulPrologSize + sizeof(UINT32) );
} __except((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH)
{
_resetstkoflw();
pNewProlog = NULL;
}
if ( NULL == pNewProlog )
{
TRC(ERR, "ChannelMessageWrite: alloca failed for %d bytes\n",
ulPrologSize + sizeof(UINT32) );
goto exitpt;
}
memcpy(pNewProlog, pProlog, ulPrologSize);
// replace the word, put SNDC_NONE in the body
//
ASSERT( ulBodySize >= sizeof(UINT32));
*(DWORD *)(((LPSTR)pNewProlog) + ulPrologSize) =
*(DWORD *)pBody;
*(DWORD *)pBody = SNDC_NONE;
pProlog = pNewProlog;
ulPrologSize += sizeof(UINT32);
}
rv = ChannelBlockWrite(
pProlog,
ulPrologSize
);
if (!rv)
{
TRC(ERR, "ChannelMessageWrite: failed while sending the prolog\n");
goto exitpt;
}
rv = ChannelBlockWrite(
pBody,
ulBodySize
);
if (!rv)
{
TRC(ERR, "ChannelMessageWrite: failed while sending the body\n");
}
exitpt:
return rv;
}
/*
* Function:
* ChannelBlockRead
*
* Description:
* Read a block, as much as possible
*
*/
BOOL
ChannelBlockRead(
PVOID pBlock,
ULONG ulBlockSize,
ULONG *pulBytesRead,
ULONG ulTimeout,
HANDLE hEvent
)
{
BOOL bSuccess = FALSE;
PCHAR pData = (PCHAR) pBlock;
ULONG ulBytesRead = 0;
HANDLE hVC;
hVC = g_hVC;
if (NULL == hVC)
{
TRC(ERR, "ChannelBlockRead: vc handle is invalid(NULL)\n");
goto exitpt;
}
if (NULL == pulBytesRead)
{
TRC(ERR, "ChannelBlockRead: pulBytesRead is NULL\n");
goto exitpt;
}
if (!g_uiBytesInBuffer)
{
g_OverlappedRead.hEvent = hEvent;
g_OverlappedRead.Offset = 0;
g_OverlappedRead.OffsetHigh = 0;
bSuccess = ReadFile(
hVC,
g_Buffer,
sizeof(g_Buffer),
(LPDWORD) &g_uiBytesInBuffer,
&g_OverlappedRead
);
if (ERROR_IO_PENDING == GetLastError())
{
bSuccess = FALSE;
goto exitpt;
}
if (!bSuccess)
{
TRC(ERR, "VirtualChannelRead failed, "
"GetLastError=%d\n",
GetLastError());
g_uiBytesInBuffer = 0;
} else {
TRC(ALV, "VirtualChannelRead: read %d bytes\n",
g_uiBytesInBuffer);
SetLastError(ERROR_SUCCESS);
}
}
if (g_uiBytesInBuffer)
{
ulBytesRead = (g_uiBytesInBuffer < ulBlockSize)
? g_uiBytesInBuffer : ulBlockSize;
memcpy(pData, g_Buffer + g_uiBufferOffset, ulBytesRead);
g_uiBufferOffset += ulBytesRead;
g_uiBytesInBuffer -= ulBytesRead;
bSuccess = TRUE;
}
// if the buffer is completed, zero the offset
//
if (0 == g_uiBytesInBuffer)
g_uiBufferOffset = 0;
TRC(ALV, "ChannelBlockRead: block size %d was read\n", ulBlockSize);
exitpt:
if (NULL != pulBytesRead)
*pulBytesRead = ulBytesRead;
return bSuccess;
}
/*
* Function:
* ChannelBlockReadComplete
*
* Description:
* Read completion
*
*/
BOOL
ChannelBlockReadComplete(
VOID
)
{
BOOL bSuccess = FALSE;
if (!g_hVC)
{
TRC(ERR, "ChannelBlockReadComplete: vc handle is invalid(NULL)\n");
goto exitpt;
}
bSuccess = GetOverlappedResult(
g_hVC,
&g_OverlappedRead,
(LPDWORD) &g_uiBytesInBuffer,
FALSE
);
if (bSuccess)
{
TRC(ALV, "VirtualChannelRead: read %d bytes\n",
g_uiBytesInBuffer);
;
} else {
TRC(ERR, "GetOverlappedResult failed, "
"GetLastError=%d\n",
GetLastError());
}
exitpt:
return bSuccess;
}
/*
* Function:
* ChannelCancelIo
*
* Description:
* Cancel the current IO
*
*/
BOOL
ChannelCancelIo(
VOID
)
{
BOOL rv = FALSE;
if (!g_hVC)
{
TRC(ERR, "ChannelCancelIo: vc handle is invalid(NULL)\n");
goto exitpt;
}
rv = CancelIo(g_hVC);
if (rv)
SetLastError(ERROR_IO_INCOMPLETE);
exitpt:
return rv;
}
/*
* Function:
* ChannelReceiveMessage
*
* Description:
* Attempts to read two piece message,
* returns TRUE if the whole message is received
*
*/
BOOL
ChannelReceiveMessage(
PSNDMESSAGE pSndMessage,
HANDLE hReadEvent
)
{
BOOL rv = FALSE;
HANDLE hVC = g_hVC;
UINT uiBytesReceived = 0;
ASSERT( NULL != pSndMessage );
ASSERT( NULL != hReadEvent );
if (NULL == hVC)
{
TRC(ERR, "ChannelReceiveMessage: VC is NULL\n");
goto exitpt;
}
//
// loop until PENDING or message is received
//
do {
if (pSndMessage->uiPrologReceived < sizeof(pSndMessage->Prolog))
{
if (ChannelBlockRead(
((LPSTR)(&pSndMessage->Prolog)) +
pSndMessage->uiPrologReceived,
sizeof(pSndMessage->Prolog) -
pSndMessage->uiPrologReceived,
(ULONG*) &uiBytesReceived,
DEFAULT_VC_TIMEOUT,
hReadEvent
))
{
pSndMessage->uiPrologReceived += uiBytesReceived;
}
else
{
if (ERROR_IO_PENDING != GetLastError())
{
// Perform cleanup
//
pSndMessage->uiPrologReceived = 0;
}
goto exitpt;
}
}
// Reallocate a new body if needed
//
if (pSndMessage->uiBodyAllocated < pSndMessage->Prolog.BodySize)
{
PVOID pBody;
pBody = (NULL == pSndMessage->pBody)?
TSMALLOC(pSndMessage->Prolog.BodySize):
TSREALLOC(pSndMessage->pBody,
pSndMessage->Prolog.BodySize);
if ( NULL == pBody && NULL != pSndMessage->pBody )
{
TSFREE( pSndMessage->pBody );
}
pSndMessage->pBody = pBody;
if (!pSndMessage->pBody)
{
TRC(ERR, "ChannelMessageRead: can't allocate %d bytes\n",
pSndMessage->Prolog.BodySize);
pSndMessage->uiBodyAllocated = 0;
goto exitpt;
} else
pSndMessage->uiBodyAllocated = pSndMessage->Prolog.BodySize;
}
// Receive the body
//
if (pSndMessage->uiBodyReceived < pSndMessage->Prolog.BodySize)
{
if (ChannelBlockRead(
((LPSTR)(pSndMessage->pBody)) + pSndMessage->uiBodyReceived,
pSndMessage->Prolog.BodySize - pSndMessage->uiBodyReceived,
(ULONG*) &uiBytesReceived,
DEFAULT_VC_TIMEOUT,
hReadEvent
))
{
pSndMessage->uiBodyReceived += uiBytesReceived;
}
else
{
if (ERROR_IO_PENDING != GetLastError())
{
// Perform cleanup
//
pSndMessage->uiPrologReceived = 0;
pSndMessage->uiBodyReceived = 0;
}
goto exitpt;
}
}
// check if the message is received
//
} while (pSndMessage->uiBodyReceived != pSndMessage->Prolog.BodySize);
rv = TRUE;
exitpt:
return rv;
}
/*
* Function:
* VCSndDataArrived
*
* Description:
* Arrived message demultiplexer
*
*/
VOID
VCSndDataArrived(
PSNDMESSAGE pSndMessage
)
{
if (pSndMessage->Prolog.BodySize &&
NULL == pSndMessage->pBody)
{
TRC(ERR, "_VCSndDataArrived: pBody is NULL\n");
goto exitpt;
}
// first, get the stream
//
if (!VCSndAcquireStream())
{
TRC(FATAL, "VCSndDataArrived: somebody is holding the "
"Stream mutext for too long\n");
ASSERT(0);
goto exitpt;
}
switch (pSndMessage->Prolog.Type)
{
case SNDC_WAVECONFIRM:
{
PSNDWAVECONFIRM pSndConfirm;
if ( pSndMessage->Prolog.BodySize <
sizeof( *pSndConfirm ) - sizeof( SNDPROLOG ))
{
TRC( ERR, "VCSndDataArrived: Invalid confirmation received\n" );
break;
}
pSndConfirm = (PSNDWAVECONFIRM)
(((LPSTR)pSndMessage->pBody) -
sizeof(pSndMessage->Prolog));
_StatsCollect( pSndConfirm->wTimeStamp );
TRC(ALV, "VCSndDataArrived: SNDC_WAVECONFIRM, block no %d\n",
pSndConfirm->cConfirmedBlockNo);
if ( (BYTE)(g_Stream->cLastBlockSent -
pSndConfirm->cConfirmedBlockNo) > TSSND_BLOCKSONTHENET )
{
TRC(WRN, "VCSndDataArrived: confirmation for block #%d "
"which wasn't sent. Last sent=%d. DROPPING !!!\n",
pSndConfirm->cConfirmedBlockNo,
g_Stream->cLastBlockSent);
break;
}
if ( (BYTE)(pSndConfirm->cConfirmedBlockNo -
g_Stream->cLastBlockConfirmed) < TSSND_BLOCKSONTHENET )
{
// move the mark
//
g_Stream->cLastBlockConfirmed = pSndConfirm->cConfirmedBlockNo + 1;
} else {
TRC(WRN, "VCSndDataArrived: difference in confirmed blocks "
"last=%d, this one=%d\n",
g_Stream->cLastBlockConfirmed,
pSndConfirm->cConfirmedBlockNo
);
}
PulseEvent(g_hStreamIsEmptyEvent);
}
break;
case SNDC_TRAINING:
{
PSNDTRAINING pSndTraining;
DWORD dwLatency;
if ( pSndMessage->Prolog.BodySize <
sizeof ( *pSndTraining ) - sizeof ( pSndTraining->Prolog ))
{
TRC(ERR, "VCSndDataArrived: SNDC_TRAINING invalid length "
"for the body=%d\n",
pSndMessage->Prolog.BodySize );
break;
}
pSndTraining = (PSNDTRAINING)
(((LPSTR)pSndMessage->pBody) -
sizeof(pSndMessage->Prolog));
if ( 0 != pSndTraining->wPackSize )
{
TRC(INF, "VCSndDataArrived: SNDC_TRAINING received (ignoring)\n");
//
// these type of messages are handled
// in _VCSndLineVCTraining(), bail out
//
break;
}
dwLatency = (GetTickCount() & 0xffff) - pSndTraining->wTimeStamp;
TRC(INF, "VCSndDataArrived: SNDC_TRAINING Latency=%d\n",
dwLatency );
//
// increase by 30%
//
if ( 0 == g_dwStatPing )
g_dwStatPing = dwLatency;
else
g_dwStatPing = (( 7 * g_dwStatPing ) + ( 3 * dwLatency )) / 10;
}
break;
case SNDC_FORMATS:
//
// this is handled in VCSndNegotiateWaveFormat()
//
TRC(INF, "VCSndDataArrived: SNDC_FORMATS reveived (ignoring)\n");
break;
default:
{
TRC(ERR, "_VCSndDataArrived: unknow message received: %d\n",
pSndMessage->Prolog.Type);
ASSERT(0);
}
}
VCSndReleaseStream();
exitpt:
;
}
/*
* Function:
* VCSndAcquireStream
*
* Description:
* Locks the stream
*
*/
BOOL
VCSndAcquireStream(
VOID
)
{
BOOL rv = FALSE;
DWORD dwres;
if (NULL == g_hStream ||
NULL == g_Stream)
{
TRC(FATAL, "VCSndAcquireStream: the stream handle is NULL\n");
goto exitpt;
}
if (NULL == g_hStreamMutex)
{
TRC(FATAL, "VCSndAcquireStream: the stream mutex is NULL\n");
goto exitpt;
}
dwres = WaitForSingleObject(g_hStreamMutex, DEFAULT_VC_TIMEOUT);
if (WAIT_TIMEOUT == dwres ||
WAIT_ABANDONED == dwres )
{
TRC(ERR, "VCSndAcquireStream: "
"timed out waiting for the stream mutex or owner crashed=%d\n", dwres );
//
// possible app crash
//
ASSERT(0);
goto exitpt;
}
rv = TRUE;
exitpt:
return rv;
}
/*
* Function:
* VCSndReleaseStream
*
* Description:
* Release the stream data
*
*/
BOOL
VCSndReleaseStream(
VOID
)
{
BOOL rv = TRUE;
ASSERT(NULL != g_hStream);
ASSERT(NULL != g_Stream);
ASSERT(NULL != g_hStreamMutex);
if (!ReleaseMutex(g_hStreamMutex))
rv = FALSE;
return rv;
}
/*
* Function:
* _DGramOpen
*
* Description:
* Opens a datagram socket
*
*/
VOID
_DGramOpen(
VOID
)
{
// create a datagram socket if needed
//
if (INVALID_SOCKET == g_hDGramSocket)
{
g_hDGramSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == g_hDGramSocket)
TRC(ERR, "_DGramOpen: failed to crate dgram socket: %d\n",
WSAGetLastError());
else
TRC(ALV, "_DGramOpen: datagram socket created\n");
}
// get the max datagram size
//
if (INVALID_SOCKET != g_hDGramSocket)
{
UINT optval = 0;
UINT optlen = sizeof(optval);
getsockopt(g_hDGramSocket,
SOL_SOCKET,
SO_MAX_MSG_SIZE,
(LPSTR)(&optval),
(int *) &optlen);
TRC(ALV, "_DGramOpen: max allowed datagram: %d\n",
optval);
optval = (optval < TSSND_BLOCKSIZE)?optval:TSSND_BLOCKSIZE;
// align the dgram to DWORD
//
optval /= sizeof(DWORD);
optval *= sizeof(DWORD);
if ( optval < RDPSND_MIN_FRAG_SIZE )
{
g_dwDGramSize = 0;
} else if ( optval < g_dwDGramSize )
{
g_dwDGramSize = optval;
TRC( INF, "DGram size downgraded to %d\n", g_dwDGramSize );
}
TRC(ALV, "_DGramOpen: max datagram size: %d\n",
optval);
// get client's ip address
//
{
WINSTATIONCLIENT ClientData;
ULONG ulReturnLength;
BOOL rc;
u_long ulDGramClientAddress;
rc = WinStationQueryInformation(
SERVERNAME_CURRENT,
LOGONID_CURRENT,
WinStationClient,
&ClientData,
sizeof(ClientData),
&ulReturnLength);
if (rc)
{
g_EncryptionLevel = ClientData.EncryptionLevel;
if (PF_INET == ClientData.ClientAddressFamily)
{
TRC(ALV, "_VCSndSendOpenDevice: client address is: %S\n",
ClientData.ClientAddress);
ulDGramClientAddress = inet_addrW(ClientData.ClientAddress);
if (INADDR_NONE != ulDGramClientAddress)
g_ulDGramAddress = ulDGramClientAddress;
else
TRC(ERR, "_VCSndSendOpenDevice: client address is NONE\n");
}
else
TRC(ERR, "_VCSndSendOpenDevice: "
"Invalid address family: %d\n",
ClientData.ClientAddressFamily);
} else
TRC(ERR, "_VCSndSendOpenDevice: "
"WinStationQueryInformation failed. %d\n",
GetLastError());
}
}
}
VOID
_FillWithGarbage(
PVOID pBuff,
DWORD dwSize
)
{
PBYTE pbBuff = (PBYTE)pBuff;
for ( ; dwSize; pbBuff++, dwSize-- )
{
pbBuff[0] = (BYTE)rand();
}
}
/*
* Function:
* _VCSndReadRegistry
*
* Description:
* Reads current options
*/
VOID
_VCSndReadRegistry(
VOID
)
{
DWORD rv = (DWORD) -1;
DWORD sysrc;
HKEY hkey = NULL;
DWORD dwKeyType;
DWORD dwKeyLen;
WINSTATIONCONFIG config;
ULONG Length = 0;
DWORD dw;
sysrc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
TSSND_REG_MAXBANDWIDTH_KEY,
0, // reserved
KEY_READ,
&hkey);
if ( ERROR_SUCCESS != sysrc )
{
TRC(WRN, "_VCSndReadRegistry: "
"RegOpenKeyEx failed: %d\n",
sysrc );
goto exitpt;
}
dwKeyType = REG_DWORD;
dwKeyLen = sizeof( rv );
sysrc = RegQueryValueEx( hkey,
TSSND_REG_MAXBANDWIDTH_VAL,
NULL, // reserved
&dwKeyType,
(LPBYTE)&rv,
&dwKeyLen);
if ( ERROR_SUCCESS != sysrc )
{
TRC(WRN, "_VCSndReadRegistry: "
"RegQueryValueEx failed: %d\n",
sysrc );
} else {
g_dwMaxBandwidth = rv;
}
sysrc = RegQueryValueEx( hkey,
TSSND_REG_MINBANDWIDTH_VAL,
NULL, // reserved
&dwKeyType,
(LPBYTE)&rv,
&dwKeyLen);
if ( ERROR_SUCCESS != sysrc )
{
TRC(ALV, "_VCSndReadRegistry: "
"RegQueryValueEx failed: %d\n",
sysrc );
} else {
g_dwMinBandwidth = rv;
}
sysrc = RegQueryValueEx( hkey,
TSSND_REG_DISABLEDGRAM_VAL,
NULL, // reserved
&dwKeyType,
(LPBYTE)&rv,
&dwKeyLen);
if ( ERROR_SUCCESS != sysrc )
{
TRC(ALV, "_VCSndReadRegistry: "
"RegQueryValueEx failed: %d\n",
sysrc );
} else {
g_dwDisableDGram = rv;
}
sysrc = RegQueryValueEx( hkey,
TSSND_REG_ENABLEMP3_VAL,
NULL, // reserved
&dwKeyType,
(LPBYTE)&rv,
&dwKeyLen);
if ( ERROR_SUCCESS != sysrc )
{
TRC(WRN, "_VCSndReadRegistry: "
"RegQueryValueEx failed: %d\n",
sysrc );
} else {
g_dwEnableMP3Codec = rv;
}
sysrc = RegQueryValueEx( hkey,
TSSND_REG_MAXDGRAM,
NULL,
&dwKeyType,
(LPBYTE)&rv,
&dwKeyLen );
if ( ERROR_SUCCESS != sysrc )
{
TRC( WRN, "_VCSndReadRegistry: "
"RegQueryValueEx failed for \"%s\": %d\n",
TSSND_REG_MAXDGRAM, sysrc );
} else {
if ( rv < g_dwDGramSize && rv >= RDPSND_MIN_FRAG_SIZE )
{
g_dwDGramSize = rv;
TRC( INF, "DGram size forced to %d\n", g_dwDGramSize );
}
}
dwKeyLen = 0;
sysrc = RegQueryValueEx( hkey,
TSSND_REG_ALLOWCODECS,
NULL,
&dwKeyType,
NULL,
&dwKeyLen );
if ( ERROR_MORE_DATA != sysrc || REG_BINARY != dwKeyType )
{
TRC( ALV, "_VCSndReadRegistry: "
"RegQueryValueEx failed for AllowCodecs: %d\n",
sysrc );
} else {
if ( NULL != g_AllowCodecs )
TSFREE( g_AllowCodecs );
g_AllowCodecs = (DWORD *)TSMALLOC( dwKeyLen );
if ( NULL == g_AllowCodecs )
{
TRC( WRN, "_VCSndReadRegistry: "
"malloc failed for %d bytes\n",
dwKeyLen );
} else {
sysrc = RegQueryValueEx( hkey,
TSSND_REG_ALLOWCODECS,
NULL,
&dwKeyType,
(LPBYTE)g_AllowCodecs,
&dwKeyLen );
if ( ERROR_SUCCESS != sysrc )
{
TRC( WRN, "_VCSndReadRegistry: "
"RegQueryValueEx failed: %d\n",
sysrc );
TSFREE( g_AllowCodecs );
g_AllowCodecs = NULL;
g_AllowCodecsSize = 0;
} else {
g_AllowCodecsSize = dwKeyLen;
}
}
}
exitpt:
if ( NULL != hkey )
RegCloseKey( hkey );
}
/*
* Function:
* _VCSndLineVCTraining
*
* Description:
* Meassures the line speed thru the virtual channel
*
*/
DWORD
_VCSndLineVCTraining(
HANDLE hReadEvent
)
{
PSNDTRAINING pSndTraining;
SNDMESSAGE SndMessage;
DWORD dwSuggestedBaudRate;
DWORD dwLatency;
PSNDTRAINING pSndTrainingResp;
memset(&SndMessage, 0, sizeof(SndMessage));
dwLatency = 0;
if (NULL == hReadEvent)
{
TRC(ERR, "_VCSndLineVCTraining: hReadEvent is NULL\n");
goto exitpt;
}
__try
{
pSndTraining = (PSNDTRAINING) alloca( TSSND_TRAINING_BLOCKSIZE );
} __except((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH)
{
_resetstkoflw();
pSndTraining = NULL;
}
if (NULL == pSndTraining)
{
TRC(ERR, "_VCSndLineVCTraining: can't alloca %d bytes\n",
TSSND_TRAINING_BLOCKSIZE);
goto exitpt;
}
_FillWithGarbage( pSndTraining, TSSND_TRAINING_BLOCKSIZE);
pSndTraining->Prolog.Type = SNDC_TRAINING;
pSndTraining->Prolog.BodySize = TSSND_TRAINING_BLOCKSIZE -
sizeof (pSndTraining->Prolog);
pSndTraining->wTimeStamp = (UINT16)GetTickCount();
pSndTraining->wPackSize = (UINT16)TSSND_TRAINING_BLOCKSIZE;
//
// send the packet
//
if (!ChannelBlockWrite(pSndTraining, TSSND_TRAINING_BLOCKSIZE))
{
TRC(ERR, "_VCSndLineVCTraining: failed to send a block: %d\n",
GetLastError());
goto exitpt;
}
//
// wait for response to arrive
//
do {
SndMessage.uiPrologReceived = 0;
SndMessage.uiBodyReceived = 0;
while(!ChannelReceiveMessage(&SndMessage, hReadEvent))
{
if (ERROR_IO_PENDING == GetLastError())
{
DWORD dwres;
HANDLE ahEvents[2];
ahEvents[0] = hReadEvent;
ahEvents[1] = g_hDisconnectEvent;
dwres = WaitForMultipleObjects(
sizeof(ahEvents)/sizeof(ahEvents[0]), // count
ahEvents, // events
FALSE, // wait all
DEFAULT_RESPONSE_TIMEOUT);
if (WAIT_TIMEOUT == dwres ||
WAIT_OBJECT_0 + 1 == dwres)
{
TRC(WRN, "_VCSndLineVCTraining: timeout "
"waiting for response\n");
ChannelCancelIo();
ResetEvent(hReadEvent);
goto exitpt;
}
ChannelBlockReadComplete();
ResetEvent(hReadEvent);
} else
if (ERROR_SUCCESS != GetLastError())
{
TRC(ERR, "_VCSndLineVCTraining: "
"ChannelReceiveMessage failed: %d\n",
GetLastError());
goto exitpt;
}
}
} while ( SNDC_TRAINING != SndMessage.Prolog.Type ||
sizeof(SNDTRAINING) - sizeof(SNDPROLOG) <
SndMessage.Prolog.BodySize);
TRC(ALV, "_VCSndLineVCTraining: response received\n");
pSndTrainingResp = (PSNDTRAINING)
(((LPSTR)SndMessage.pBody) -
sizeof(SndMessage.Prolog));
//
// calculate latency (nonzero)
//
dwLatency = ((WORD)GetTickCount()) - pSndTrainingResp->wTimeStamp + 1;
exitpt:
TRC(INF, "_VCSndLineVCTraining: dwLatency = %d\n",
dwLatency);
if (0 != dwLatency)
{
//
// the latency is in miliseconds, so compute it bytes per seconds
// and get nonzero result
//
dwSuggestedBaudRate = 1 + (1000 * ( pSndTrainingResp->wPackSize +
sizeof( *pSndTraining ))
/ dwLatency);
}
else
dwSuggestedBaudRate = 0;
TRC(INF, "_VCSndLineVCTraining: dwSuggestedBaudRate = %d\n",
dwSuggestedBaudRate);
if (NULL != SndMessage.pBody)
TSFREE(SndMessage.pBody);
return dwSuggestedBaudRate;
}
/*
* Function:
* _VCSndLineDGramTraining
*
* Description:
* Meassures the line speed thru UDP channel
*
*/
DWORD
_VCSndLineDGramTraining(
HANDLE hDGramEvent
)
{
PSNDTRAINING pSndTraining;
PSNDTRAINING pSndTrainingResp;
struct sockaddr_in sin;
DWORD dwRetries;
DWORD dwSuggestedBaudRate;
DWORD dwDGramLatency = 0;
INT sendres;
DWORD dwPackSize;
DWORD dwRespSize;
dwDGramLatency = 0;
if (NULL == hDGramEvent)
{
TRC(ERR, "_VCSndLineDGramTraining: hDGramEvent is NULL\n");
goto exitpt;
}
if (INVALID_SOCKET == g_hDGramSocket ||
0 == g_dwDGramPort ||
g_dwDGramSize < sizeof(*pSndTraining) ||
0 == g_ulDGramAddress)
{
TRC(ERR, "_VCSndLineDGramTraining: no dgram support. Can't train the line\n");
goto exitpt;
}
dwPackSize = ( g_dwDGramSize < TSSND_TRAINING_BLOCKSIZE )?
g_dwDGramSize:
TSSND_TRAINING_BLOCKSIZE;
__try
{
pSndTraining = (PSNDTRAINING) alloca( dwPackSize );
} __except((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH)
{
_resetstkoflw();
pSndTraining = NULL;
}
if (NULL == pSndTraining)
{
TRC(ERR, "_VCSndLineDGramTraining: can't alloca %d bytes\n",
dwPackSize);
goto exitpt;
}
_FillWithGarbage( pSndTraining, dwPackSize );
//
// send a block and measure the time when it will arrive
//
// prepare the to address
//
sin.sin_family = PF_INET;
sin.sin_port = (u_short)g_dwDGramPort;
sin.sin_addr.s_addr = g_ulDGramAddress;
pSndTraining->Prolog.Type = SNDC_TRAINING;
pSndTraining->Prolog.BodySize = (UINT16)( dwPackSize -
sizeof (pSndTraining->Prolog));
pSndTraining->wPackSize = (UINT16)TSSND_TRAINING_BLOCKSIZE;
dwRetries = 2 * DEFAULT_RESPONSE_TIMEOUT / 1000;
do {
pSndTraining->wTimeStamp = (WORD)GetTickCount();
//
// send the datagram
// the type is SNDC_WAVE but the structure is of SNDWAVE
// wTimeStamp contains the sending time
//
sendres = sendto(
g_hDGramSocket,
(LPSTR)pSndTraining,
dwPackSize,
0, // flags
(struct sockaddr *)&sin, // to address
sizeof(sin)
);
if (SOCKET_ERROR == sendres)
{
TRC(ERR, "_VCSndLineDGramTraining: sendto failed: %d\n",
WSAGetLastError());
goto exitpt;
}
//
// wait for a response
//
do {
pSndTrainingResp = NULL;
dwRespSize = 0;
DGramRead( hDGramEvent, (PVOID*) &pSndTrainingResp, &dwRespSize );
if ( NULL == pSndTrainingResp )
{
DWORD dwres;
HANDLE ahEvents[2];
ahEvents[0] = hDGramEvent;
ahEvents[1] = g_hDisconnectEvent;
dwres = WaitForMultipleObjects(
sizeof(ahEvents)/sizeof(ahEvents[0]), // count
ahEvents, // events
FALSE, // wait all
1000);
if ( WAIT_OBJECT_0 + 1 == dwres )
{
TRC(WRN, "_VCSndLineDGramTraining: disconnected\n");
goto exitpt;
}
if (WAIT_TIMEOUT == dwres)
{
TRC(WRN, "_VCSndLineDGramTraining: timeout "
"waiting for response\n");
goto try_again;
}
DGramReadComplete( (PVOID*) &pSndTrainingResp, &dwRespSize );
}
} while ( NULL == pSndTrainingResp ||
sizeof( *pSndTrainingResp ) != dwRespSize ||
SNDC_TRAINING != pSndTrainingResp->Prolog.Type ||
sizeof(SNDTRAINING) - sizeof(SNDPROLOG) <
pSndTrainingResp->Prolog.BodySize );
TRC(ALV, "_VCSndLineDGramTraining: response received\n");
break;
try_again:
dwRetries --;
} while (0 != dwRetries);
if (0 != dwRetries)
{
//
// calculate latency (nonzero)
//
dwDGramLatency = ((WORD)GetTickCount()) -
pSndTrainingResp->wTimeStamp + 1;
}
exitpt:
TRC(INF, "_VCSndLineDGramTraining: dwDGramLatency = %d\n",
dwDGramLatency);
if (0 != dwDGramLatency)
{
//
// the latency is in miliseconds, so compute it bytes per seconds
// and get nonzero result
//
dwSuggestedBaudRate = 1 + (1000 * ( pSndTrainingResp->wPackSize +
sizeof( *pSndTrainingResp ))
/ dwDGramLatency);
}
else
dwSuggestedBaudRate = 0;
TRC(INF, "_VCSndLineDGramTraining: dwSuggestedBaudRate = %d\n",
dwSuggestedBaudRate);
return dwSuggestedBaudRate;
}
//
// puts code licensing codes into the header
//
BOOL
_VCSndFixHeader(
PWAVEFORMATEX pFmt
)
{
BOOL rv = FALSE;
switch (pFmt->wFormatTag)
{
case WAVE_FORMAT_MSG723:
ASSERT(pFmt->cbSize == 10);
if ( pFmt->cbSize == 10 )
{
((MSG723WAVEFORMAT *) pFmt)->dwCodeword1 = G723MAGICWORD1;
((MSG723WAVEFORMAT *) pFmt)->dwCodeword2 = G723MAGICWORD2;
rv = TRUE;
}
break;
case WAVE_FORMAT_MSRT24:
//
// assume call control will take care of the other
// params ?
//
ASSERT(pFmt->cbSize == sizeof( VOXACM_WAVEFORMATEX ) - sizeof( WAVEFORMATEX ) );
if ( sizeof( VOXACM_WAVEFORMATEX ) - sizeof( WAVEFORMATEX ) == pFmt->cbSize )
{
VOXACM_WAVEFORMATEX *pVOX = (VOXACM_WAVEFORMATEX *)pFmt;
ASSERT( strlen( VOXWARE_KEY ) + 1 == sizeof( pVOX->szKey ));
strncpy( pVOX->szKey, VOXWARE_KEY, sizeof( pVOX->szKey ));
rv = TRUE;
}
break;
// this format eats too much from the CPU
//
case WAVE_FORMAT_MPEGLAYER3:
if ( g_dwEnableMP3Codec )
rv = TRUE;
break;
case WAVE_FORMAT_WMAUDIO2:
if ( g_dwEnableMP3Codec )
{
rv = TRUE;
}
break;
default:
rv = TRUE;
}
return rv;
}
/*
* Function:
* _VCSndFindSuggestedConverter
*
* Description:
* Searches for intermidiate converter
*
*/
BOOL
_VCSndFindSuggestedConverter(
HACMDRIVERID hadid,
LPWAVEFORMATEX pDestFormat,
LPWAVEFORMATEX pInterrimFmt,
PFNCONVERTER *ppfnConverter
)
{
BOOL rv = FALSE;
MMRESULT mmres;
HACMDRIVER hacmDriver = NULL;
PFNCONVERTER pfnConverter = NULL;
HACMSTREAM hacmStream = NULL;
ASSERT( NULL != pDestFormat );
ASSERT( NULL != hadid );
ASSERT( NULL != pInterrimFmt );
*ppfnConverter = NULL;
//
// first, open the destination acm driver
//
mmres = acmDriverOpen(&hacmDriver, hadid, 0);
if ( MMSYSERR_NOERROR != mmres )
{
TRC(ERR, "_VCSndFindSuggestedConverter: can't "
"open the acm driver: %d\n",
mmres);
goto exitpt;
}
//
// first probe with the native format
// if it passes, we don't need intermidiate
// format converter
//
pInterrimFmt->wFormatTag = WAVE_FORMAT_PCM;
pInterrimFmt->nChannels = TSSND_NATIVE_CHANNELS;
pInterrimFmt->nSamplesPerSec = TSSND_NATIVE_SAMPLERATE;
pInterrimFmt->nAvgBytesPerSec = TSSND_NATIVE_AVGBYTESPERSEC;
pInterrimFmt->nBlockAlign = TSSND_NATIVE_BLOCKALIGN;
pInterrimFmt->wBitsPerSample = TSSND_NATIVE_BITSPERSAMPLE;
pInterrimFmt->cbSize = 0;
mmres = acmStreamOpen(
&hacmStream,
hacmDriver,
pInterrimFmt,
pDestFormat,
NULL, // filter
0, // callback
0, // dwinstance
ACM_STREAMOPENF_NONREALTIME
);
if ( MMSYSERR_NOERROR == mmres )
{
//
// format is supported
//
rv = TRUE;
goto exitpt;
} else {
TRC(ALV, "_VCSndFindSuggestedConverter: format is not supported\n");
}
//
// find a suggested intermidiate PCM format
//
mmres = acmFormatSuggest(
hacmDriver,
pDestFormat,
pInterrimFmt,
sizeof( *pInterrimFmt ),
ACM_FORMATSUGGESTF_WFORMATTAG
);
if ( MMSYSERR_NOERROR != mmres )
{
TRC(ALV, "_VCSndFindSuggestedConverter: can't find "
"interrim format: %d\n",
mmres);
goto exitpt;
}
if ( 16 != pInterrimFmt->wBitsPerSample ||
( 1 != pInterrimFmt->nChannels &&
2 != pInterrimFmt->nChannels) ||
( 8000 != pInterrimFmt->nSamplesPerSec &&
11025 != pInterrimFmt->nSamplesPerSec &&
12000 != pInterrimFmt->nSamplesPerSec &&
16000 != pInterrimFmt->nSamplesPerSec &&
22050 != pInterrimFmt->nSamplesPerSec)
)
{
TRC(ALV, "_VCSndFindSuggestedConverter: not supported "
"interrim format. Details:\n");
TRC(ALV, "Channels - %d\n", pInterrimFmt->nChannels);
TRC(ALV, "SamplesPerSec - %d\n", pInterrimFmt->nSamplesPerSec);
TRC(ALV, "AvgBytesPerSec - %d\n", pInterrimFmt->nAvgBytesPerSec);
TRC(ALV, "BlockAlign - %d\n", pInterrimFmt->nBlockAlign);
TRC(ALV, "BitsPerSample - %d\n", pInterrimFmt->wBitsPerSample);
goto exitpt;
}
if ( 1 == pInterrimFmt->nChannels )
{
switch ( pInterrimFmt->nSamplesPerSec )
{
case 8000: pfnConverter = _Convert8000Mono; break;
case 11025: pfnConverter = _Convert11025Mono; break;
case 12000: pfnConverter = _Convert12000Mono; break;
case 16000: pfnConverter = _Convert16000Mono; break;
case 22050: pfnConverter = _Convert22050Mono; break;
default:
ASSERT( 0 );
}
} else {
switch ( pInterrimFmt->nSamplesPerSec )
{
case 8000: pfnConverter = _Convert8000Stereo; break;
case 11025: pfnConverter = _Convert11025Stereo; break;
case 12000: pfnConverter = _Convert12000Stereo; break;
case 16000: pfnConverter = _Convert16000Stereo; break;
case 22050: pfnConverter = NULL; break;
default:
ASSERT( 0 );
}
}
//
// probe with this format
//
mmres = acmStreamOpen(
&hacmStream,
hacmDriver,
pInterrimFmt,
pDestFormat,
NULL, // filter
0, // callback
0, // dwinstance
ACM_STREAMOPENF_NONREALTIME
);
if ( MMSYSERR_NOERROR != mmres )
{
TRC(ALV, "_VCSndFindSuggestedConverter: probing the suggested "
"format failed: %d\n",
mmres);
goto exitpt;
}
TRC(ALV, "_VCSndFindSuggestedConverter: found intermidiate PCM format\n");
TRC(ALV, "Channels - %d\n", pInterrimFmt->nChannels);
TRC(ALV, "SamplesPerSec - %d\n", pInterrimFmt->nSamplesPerSec);
TRC(ALV, "AvgBytesPerSec - %d\n", pInterrimFmt->nAvgBytesPerSec);
TRC(ALV, "BlockAlign - %d\n", pInterrimFmt->nBlockAlign);
TRC(ALV, "BitsPerSample - %d\n", pInterrimFmt->wBitsPerSample);
rv = TRUE;
exitpt:
if ( NULL != hacmStream )
acmStreamClose( hacmStream, 0 );
if ( NULL != hacmDriver )
acmDriverClose( hacmDriver, 0 );
*ppfnConverter = pfnConverter;
return rv;
}
/*
* Function:
* VCSndEnumAllCodecFormats
*
* Description:
* Creates a list of all codecs/formats
*
*/
BOOL
VCSndEnumAllCodecFormats(
PVCSNDFORMATLIST *ppFormatList,
DWORD *pdwNumberOfFormats
)
{
BOOL rv = FALSE;
PVCSNDFORMATLIST pIter;
PVCSNDFORMATLIST pPrev;
PVCSNDFORMATLIST pNext;
MMRESULT mmres;
DWORD dwNum = 0;
UINT count, codecsize;
ASSERT( ppFormatList );
ASSERT( pdwNumberOfFormats );
*ppFormatList = NULL;
//
// convert the known format list to a linked list
//
for ( count = 0, codecsize = 0; count < sizeof( KnownFormats ); count += codecsize )
{
PWAVEFORMATEX pSndFmt = (PWAVEFORMATEX)(KnownFormats + count);
codecsize = sizeof( WAVEFORMATEX ) + pSndFmt->cbSize;
//
// skip mp3s if disabled
//
if (( WAVE_FORMAT_MPEGLAYER3 == pSndFmt->wFormatTag ||
WAVE_FORMAT_WMAUDIO2 == pSndFmt->wFormatTag ) &&
!g_dwEnableMP3Codec )
{
continue;
}
UINT entrysize = sizeof( VCSNDFORMATLIST ) + pSndFmt->cbSize;
PVCSNDFORMATLIST pNewEntry;
pNewEntry = (PVCSNDFORMATLIST) TSMALLOC( entrysize );
if ( NULL != pNewEntry )
{
memcpy( &pNewEntry->Format, pSndFmt, codecsize );
pNewEntry->hacmDriverId = NULL;
pNewEntry->pNext = *ppFormatList;
*ppFormatList = pNewEntry;
}
}
//
// additional codecs
// these are codecs not included initially, it reads them from the registry
// see AllowCodecs initialization
//
for ( count = 0, codecsize = 0; count < g_AllowCodecsSize ; count += codecsize )
{
PWAVEFORMATEX pSndFmt = (PWAVEFORMATEX)(((PBYTE)g_AllowCodecs) + count);
codecsize = sizeof( WAVEFORMATEX ) + pSndFmt->cbSize;
if ( codecsize + count > g_AllowCodecsSize )
{
TRC( ERR, "Invalid size of additional codec\n" );
break;
}
//
// skip mp3s if disabled
//
if (( WAVE_FORMAT_MPEGLAYER3 == pSndFmt->wFormatTag ||
WAVE_FORMAT_WMAUDIO2 == pSndFmt->wFormatTag ) &&
!g_dwEnableMP3Codec )
{
continue;
}
UINT entrysize = sizeof( VCSNDFORMATLIST ) + pSndFmt->cbSize;
PVCSNDFORMATLIST pNewEntry;
pNewEntry = (PVCSNDFORMATLIST) TSMALLOC( entrysize );
if ( NULL != pNewEntry )
{
memcpy( &pNewEntry->Format, pSndFmt, codecsize );
pNewEntry->hacmDriverId = NULL;
pNewEntry->pNext = *ppFormatList;
*ppFormatList = pNewEntry;
}
}
//
// add the native format
//
pIter = (PVCSNDFORMATLIST) TSMALLOC( sizeof( *pIter ) );
if ( NULL != pIter )
{
pIter->Format.wFormatTag = WAVE_FORMAT_PCM;
pIter->Format.nChannels = TSSND_NATIVE_CHANNELS;
pIter->Format.nSamplesPerSec = TSSND_NATIVE_SAMPLERATE;
pIter->Format.nAvgBytesPerSec = TSSND_NATIVE_AVGBYTESPERSEC;
pIter->Format.nBlockAlign = TSSND_NATIVE_BLOCKALIGN;
pIter->Format.wBitsPerSample = TSSND_NATIVE_BITSPERSAMPLE;
pIter->Format.cbSize = 0;
pIter->hacmDriverId = NULL;
pIter->pNext = *ppFormatList;
*ppFormatList = pIter;
}
if (NULL == *ppFormatList)
{
TRC(WRN, "VCSndEnumAllCodecFormats: failed to add formats\n");
goto exitpt;
}
_VCSndOrderFormatList( ppFormatList, &dwNum );
//
// number of formats is passed as UINT16, delete all after those
//
if ( dwNum > 0xffff )
{
DWORD dwLimit = 0xfffe;
pIter = *ppFormatList;
while ( 0 != dwLimit )
{
pIter = pIter->pNext;
dwLimit --;
}
pNext = pIter->pNext;
pIter->pNext = NULL;
pIter = pNext;
while( NULL != pIter )
{
pNext = pIter->pNext;
TSFREE( pNext );
pIter = pNext;
}
dwNum = 0xffff;
}
rv = TRUE;
exitpt:
if (!rv)
{
//
// in case of error free the allocated list of formats
//
pIter = *ppFormatList;
while( NULL != pIter )
{
PVCSNDFORMATLIST pNext = pIter->pNext;
TSFREE( pIter );
pIter = pNext;
}
*ppFormatList = NULL;
}
*pdwNumberOfFormats = dwNum;
return rv;
}
BOOL
CALLBACK
acmDriverEnumCallbackGetACM(
HACMDRIVERID hadid,
DWORD_PTR dwInstance,
DWORD fdwSupport
)
{
BOOL rv = TRUE;
MMRESULT mmres;
ASSERT(dwInstance);
ASSERT( NULL != hadid );
if ( (0 != ( fdwSupport & ACMDRIVERDETAILS_SUPPORTF_CODEC ) ||
0 != ( fdwSupport & ACMDRIVERDETAILS_SUPPORTF_CONVERTER )))
{
//
// a codec found
//
ACMFORMATTAGDETAILS fdwDetails;
PVCSNDFORMATLIST pFmt = (PVCSNDFORMATLIST)dwInstance;
fdwDetails.cbStruct = sizeof( fdwDetails );
fdwDetails.fdwSupport = 0;
fdwDetails.dwFormatTag = pFmt->Format.wFormatTag;
mmres = acmFormatTagDetails( (HACMDRIVER)hadid, &fdwDetails, ACM_FORMATTAGDETAILSF_FORMATTAG );
if ( MMSYSERR_NOERROR == mmres )
{
WAVEFORMATEX WaveFormat; // dummy parameter
PFNCONVERTER pfnConverter; // dummy parameter
if ( _VCSndFindSuggestedConverter(
(HACMDRIVERID)hadid,
&(pFmt->Format),
&WaveFormat,
&pfnConverter ))
{
pFmt->hacmDriverId = hadid;
rv = FALSE;
}
}
}
//
// continue to the next driver
//
return rv;
}
BOOL
_VCSndGetACMDriverId( PSNDFORMATITEM pSndFmt )
{
DWORD rv = FALSE;
PVCSNDFORMATLIST pIter;
//
// Find the acm format id
//
for( pIter = g_pAllCodecsFormatList; NULL != pIter; pIter = pIter->pNext )
{
if (pIter->Format.wFormatTag == pSndFmt->wFormatTag &&
pIter->Format.nChannels == pSndFmt->nChannels &&
pIter->Format.nSamplesPerSec == pSndFmt->nSamplesPerSec &&
pIter->Format.nAvgBytesPerSec == pSndFmt->nAvgBytesPerSec &&
pIter->Format.nBlockAlign == pSndFmt->nBlockAlign &&
pIter->Format.wBitsPerSample == pSndFmt->wBitsPerSample &&
pIter->Format.cbSize == pSndFmt->cbSize &&
0 == memcmp((&pIter->Format) + 1, pSndFmt + 1, pIter->Format.cbSize))
{
//
// format is found
//
DWORD_PTR dwp = (DWORD_PTR)pIter;
MMRESULT mmres;
if ( NULL != pIter->hacmDriverId )
{
// found already
rv = TRUE;
break;
}
mmres = acmDriverEnum(
acmDriverEnumCallbackGetACM,
(DWORD_PTR)dwp,
0
);
if ( MMSYSERR_NOERROR == mmres )
{
if ( NULL != pIter->hacmDriverId )
{
rv = TRUE;
} else {
ASSERT( 0 );
}
}
break;
}
}
return rv;
}
/*
* Function:
* _VCSndChooseProperFormat
*
* Description:
* Chooses the closest format to a given bandwidth
*
*/
DWORD
_VCSndChooseProperFormat(
DWORD dwBandwidth
)
{
// choose a format from the list
// closest to the measured bandwidth
//
DWORD i;
DWORD fmt = (DWORD)-1;
DWORD lastgood = (DWORD)-1;
if ( NULL == g_ppNegotiatedFormats )
{
TRC(ERR, "_VCSndChooseProperFormat: no negotiated formats\n");
goto exitpt;
}
for( i = 0; i < g_dwNegotiatedFormats; i++ )
{
if ( NULL == g_ppNegotiatedFormats[i] )
{
continue;
}
lastgood = i;
if ( dwBandwidth != g_dwLineBandwidth )
{
//
// we are looking for new codec here, make sure we cover at least 90%
// of the requested bandwith
//
if ( g_ppNegotiatedFormats[i]->nAvgBytesPerSec <= dwBandwidth * NEW_CODEC_COVER / 100 )
{
fmt = i;
break;
}
} else if ( g_ppNegotiatedFormats[i]->nAvgBytesPerSec <= dwBandwidth )
{
fmt = i;
break;
}
}
//
// get the last format inc case that all format are not
// suitable for our bandwidth
//
if ( (DWORD)-1 == fmt && 0 != g_dwNegotiatedFormats )
{
fmt = lastgood;
}
ASSERT( fmt != (DWORD)-1 );
exitpt:
return fmt;
}
/*
* Function:
* _VCSndOrderFormatList
*
* Description:
* Order all formats in descendant order
*
*/
VOID
_VCSndOrderFormatList(
PVCSNDFORMATLIST *ppFormatList,
DWORD *pdwNum
)
{
PVCSNDFORMATLIST pFormatList;
PVCSNDFORMATLIST pLessThan;
PVCSNDFORMATLIST pPrev;
PVCSNDFORMATLIST pNext;
PVCSNDFORMATLIST pIter;
PVCSNDFORMATLIST pIter2;
DWORD dwNum = 0;
ASSERT ( NULL != ppFormatList );
pFormatList = *ppFormatList;
pLessThan = NULL;
//
// fill both lists
//
pIter = pFormatList;
while ( NULL != pIter )
{
pNext = pIter->pNext;
pIter->pNext = NULL;
//
// descending order
//
pIter2 = pLessThan;
pPrev = NULL;
while ( NULL != pIter2 &&
pIter2->Format.nAvgBytesPerSec >
pIter->Format.nAvgBytesPerSec )
{
pPrev = pIter2;
pIter2 = pIter2->pNext;
}
pIter->pNext = pIter2;
if ( NULL == pPrev )
pLessThan = pIter;
else
pPrev->pNext = pIter;
pIter = pNext;
dwNum ++;
}
*ppFormatList = pLessThan;
if ( NULL != pdwNum )
*pdwNum = dwNum;
}
/*
* Function:
* _VCSndLineTraining
*
* Description:
* Meassures the line bandwidth
*
*/
BOOL
_VCSndLineTraining(
HANDLE hReadEvent,
HANDLE hDGramEvent
)
{
BOOL rv = FALSE;
DWORD dwLineBandwidth = 0;
_DGramOpen();
//
// test this line while it's hot
//
if ( !g_dwDisableDGram )
{
dwLineBandwidth = _VCSndLineDGramTraining( hDGramEvent );
}
if (0 == dwLineBandwidth || g_dwDisableDGram)
{
TRC(WRN, "_VCSndLineTraining: no bandwidth trough UDP\n");
g_ulDGramAddress = 0;
g_dwDGramPort = 0;
g_EncryptionLevel = 0;
dwLineBandwidth = _VCSndLineVCTraining( hReadEvent );
if (0 == dwLineBandwidth)
{
TRC(WRN, "_VCSndLineTraining: no bandwidth "
"trough VC either. GIVING up\n");
goto exitpt;
}
} else {
if ( g_wClientVersion == 1 )
g_EncryptionLevel = 0;
}
//
// check for encryption
//
if ( g_EncryptionLevel >= MIN_ENCRYPT_LEVEL )
{
TRC( INF, "Encryption enabled\n" );
if ( TSRNG_GenerateRandomBits( g_EncryptKey, RANDOM_KEY_LENGTH))
{
SL_SendKey();
} else {
TRC( ERR, "_VCSndLineTraining: failing to generate random numbers. GIVING up\n" );
goto exitpt;
}
}
//
// check for limitations
//
if ((DWORD)-1 != g_dwMaxBandwidth &&
dwLineBandwidth > g_dwMaxBandwidth )
{
dwLineBandwidth = g_dwMaxBandwidth;
TRC(INF, "Bandwidth limited up to %d\n",
dwLineBandwidth );
}
if ( dwLineBandwidth < g_dwMinBandwidth )
{
dwLineBandwidth = g_dwMinBandwidth;
TRC(INF, "Bandwidth limited to at least %d\n",
dwLineBandwidth );
}
rv = TRUE;
exitpt:
g_dwLineBandwidth = dwLineBandwidth;
return rv;
}
/*
* Function:
* VCSndNegotiateWaveFormat
*
* Description:
* Requests the client for a list of supported formats
*
*/
BOOL
VCSndNegotiateWaveFormat(
HANDLE hReadEvent,
HANDLE hDGramEvent
)
{
BOOL rv = FALSE;
PVCSNDFORMATLIST pIter;
BOOL bSuccess;
PSNDFORMATMSG pSndFormats;
PSNDFORMATMSG pSndResp;
PSNDFORMATITEM pSndFmt;
SNDMESSAGE SndMessage;
DWORD msgsize;
DWORD maxsize;
DWORD i;
DWORD dwNewFmt;
DWORD dwSoundCaps = 0;
DWORD dwVolume;
DWORD dwPitch;
DWORD BestChannels;
DWORD BestSamplesPerSec;
DWORD BestBitsPerSample;
DWORD dwPacketSize;
BOOL bWMADetected = FALSE;
//
// clean the previously negotiated format
//
if (NULL != g_ppNegotiatedFormats)
{
DWORD i;
for ( i = 0; i < g_dwNegotiatedFormats; i++ )
{
if ( NULL != g_ppNegotiatedFormats[i] )
TSFREE( g_ppNegotiatedFormats[i] );
}
TSFREE( g_ppNegotiatedFormats );
g_ppNegotiatedFormats = NULL;
g_dwNegotiatedFormats = 0;
g_hacmDriverId = NULL;
}
memset( &SndMessage, 0, sizeof( SndMessage ));
//
// get the list of all codec formats
//
if ( NULL == g_pAllCodecsFormatList )
{
bSuccess = VCSndEnumAllCodecFormats(
&g_pAllCodecsFormatList,
&g_dwAllCodecsNumber
);
if (!bSuccess)
goto exitpt;
}
//
// create a packet huge enough to hold all formats
//
msgsize = sizeof( *pSndFormats ) +
sizeof( SNDFORMATITEM ) * g_dwAllCodecsNumber;
//
// calculate the extra data needed by all format
//
for( maxsize = 0, pIter = g_pAllCodecsFormatList;
NULL != pIter;
pIter = pIter->pNext )
{
msgsize += pIter->Format.cbSize;
if (maxsize < pIter->Format.cbSize)
maxsize = pIter->Format.cbSize;
}
__try {
pSndFormats = (PSNDFORMATMSG) alloca( msgsize );
} __except((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH)
{
_resetstkoflw();
pSndFormats = NULL;
}
if ( NULL == pSndFormats )
{
TRC(ERR, "VCSndNegotiateWaveFormat: alloca failed for %d bytes\n",
msgsize);
goto exitpt;
}
pSndFormats->Prolog.Type = SNDC_FORMATS;
pSndFormats->Prolog.BodySize = (UINT16)( msgsize - sizeof( pSndFormats->Prolog ));
pSndFormats->wNumberOfFormats = (UINT16)g_dwAllCodecsNumber;
pSndFormats->cLastBlockConfirmed = g_Stream->cLastBlockConfirmed;
pSndFormats->wVersion = RDPSND_PROTOCOL_VERSION;
for ( i = 0, pSndFmt = (PSNDFORMATITEM) (pSndFormats + 1),
pIter = g_pAllCodecsFormatList;
i < g_dwAllCodecsNumber;
i++,
pSndFmt = (PSNDFORMATITEM)
(((LPSTR)pSndFmt) +
sizeof( *pSndFmt ) + pSndFmt->cbSize),
pIter = pIter->pNext )
{
ASSERT(NULL != pIter);
pSndFmt->wFormatTag = pIter->Format.wFormatTag;
pSndFmt->nChannels = pIter->Format.nChannels;
pSndFmt->nSamplesPerSec = pIter->Format.nSamplesPerSec;
pSndFmt->nAvgBytesPerSec = pIter->Format.nAvgBytesPerSec;
pSndFmt->nBlockAlign = pIter->Format.nBlockAlign;
pSndFmt->wBitsPerSample = pIter->Format.wBitsPerSample;
pSndFmt->cbSize = pIter->Format.cbSize;
//
// copy the rest of the format data
//
memcpy( pSndFmt + 1, (&pIter->Format) + 1, pSndFmt->cbSize );
}
bSuccess = ChannelBlockWrite( pSndFormats, msgsize );
if (!bSuccess)
{
TRC(ERR, "VCSndNegotiateWaveFormat: ChannelBlockWrite failed: %d\n",
GetLastError());
goto exitpt;
}
do {
//
// Wait for response with a valid message
//
SndMessage.uiPrologReceived = 0;
SndMessage.uiBodyReceived = 0;
while(!ChannelReceiveMessage(&SndMessage, hReadEvent))
{
if (ERROR_IO_PENDING == GetLastError())
{
DWORD dwres;
HANDLE ahEvents[2];
ahEvents[0] = hReadEvent;
ahEvents[1] = g_hDisconnectEvent;
dwres = WaitForMultipleObjects(
sizeof(ahEvents)/sizeof(ahEvents[0]), // count
ahEvents, // events
FALSE, // wait all
DEFAULT_VC_TIMEOUT);
if (WAIT_TIMEOUT == dwres ||
WAIT_OBJECT_0 + 1 == dwres)
{
TRC(WRN, "VCSndNegotiateWaveFormat: timeout "
"waiting for response\n");
ChannelCancelIo();
ResetEvent(hReadEvent);
goto exitpt;
}
ChannelBlockReadComplete();
ResetEvent(hReadEvent);
} else
if (ERROR_SUCCESS != GetLastError())
{
TRC(ERR, "VCSndNegotiateWaveFormat: "
"ChannelReceiveMessage failed: %d\n",
GetLastError());
goto exitpt;
}
}
} while (SNDC_FORMATS != SndMessage.Prolog.Type);
if (SndMessage.Prolog.BodySize <
sizeof( SNDFORMATMSG ) - sizeof( SNDPROLOG ))
{
TRC(ERR, "VCSndNegotiateWaveFormat: SNDC_FORMAT message "
"invalid body size: %d\n",
SndMessage.Prolog.BodySize );
}
pSndResp = (PSNDFORMATMSG)
(((LPSTR)SndMessage.pBody) - sizeof( SNDPROLOG ));
// save the capabilities
//
dwSoundCaps = pSndResp->dwFlags;
dwVolume = pSndResp->dwVolume;
dwPitch = pSndResp->dwPitch;
g_dwDGramPort = pSndResp->wDGramPort;
g_wClientVersion = pSndResp->wVersion;
//
// Expect at least one format returned
//
if (SndMessage.Prolog.BodySize <
sizeof( SNDFORMATITEM ) +
sizeof( SNDFORMATMSG ) - sizeof( SNDPROLOG ))
{
TRC(ERR, "VCSndNegotiateWaveFormat: SNDC_FORMAT message "
"w/ invalid size (%d). No supported formats\n",
SndMessage.Prolog.BodySize );
goto exitpt;
}
//
// train this line
//
if ( 0 != ( dwSoundCaps & TSSNDCAPS_ALIVE ))
{
if (!_VCSndLineTraining( hReadEvent, hDGramEvent ))
{
TRC( WRN, "VCSndIo: can't talk to the client, go silent\n" );
dwSoundCaps = 0;
}
}
//
// allocate a new list
//
g_dwNegotiatedFormats = pSndResp->wNumberOfFormats;
g_ppNegotiatedFormats = (PSNDFORMATITEM*) TSMALLOC( sizeof( g_ppNegotiatedFormats[0] ) *
g_dwNegotiatedFormats );
memset( g_ppNegotiatedFormats, 0,
sizeof( g_ppNegotiatedFormats[0] ) * g_dwNegotiatedFormats );
if ( NULL == g_ppNegotiatedFormats )
{
TRC(ERR, "VCSndNegotiateWaveFormat: can't allocate %d bytes\n",
sizeof( g_ppNegotiatedFormats[0] ) * g_dwNegotiatedFormats);
goto exitpt;
}
//
// allocate space for each entry in the list
//
for ( i = 0; i < g_dwNegotiatedFormats; i ++ )
{
g_ppNegotiatedFormats[i] = (PSNDFORMATITEM) TSMALLOC( sizeof( **g_ppNegotiatedFormats ) +
maxsize);
if ( NULL == g_ppNegotiatedFormats[i] )
{
TRC(ERR, "VCSndNegotiateWaveFormat: can't allocate %d bytes\n",
sizeof( **g_ppNegotiatedFormats ) + maxsize );
goto exitpt;
}
}
//
// Fill in the global list
//
pSndFmt = (PSNDFORMATITEM)(pSndResp + 1);
dwPacketSize = sizeof( SNDPROLOG ) + SndMessage.Prolog.BodySize - sizeof( *pSndResp );
for( i = 0; i < g_dwNegotiatedFormats; i++)
{
DWORD adv = sizeof( *pSndFmt ) + pSndFmt->cbSize;
if ( adv > dwPacketSize )
{
TRC( ERR, "VCSndNegotiateWaveFormat: invalid response packet size\n" );
ASSERT( 0 );
goto exitpt;
}
if ( pSndFmt->cbSize > maxsize )
{
TRC(ERR, "VCSndNegotiateWaveFormat: invalid format size\n" );
ASSERT( 0 );
goto exitpt;
}
memcpy( g_ppNegotiatedFormats[i],
pSndFmt,
sizeof( *g_ppNegotiatedFormats[0] ) + pSndFmt->cbSize );
//
// advance to the next format
//
pSndFmt = (PSNDFORMATITEM)(((LPSTR)pSndFmt) + adv);
dwPacketSize -= adv;
}
ASSERT( 0 == dwPacketSize );
//
// prune the formats
// ie, don't allow 8khZ mono codec to be between
// 22kHz and 11kHz stereo
// This is the order of imporatnce: frquency, channels, bits, bytes/s
//
#if DBG
TRC( INF, "======== Pruning formats =========. num=%d\n\n", g_dwNegotiatedFormats );
#endif
BestSamplesPerSec = 0;
BestChannels = 0;
BestBitsPerSample = 0;
for( i = g_dwNegotiatedFormats; i > 0; i-- )
{
PSNDFORMATITEM pDest = g_ppNegotiatedFormats[i - 1];
if ( WAVE_FORMAT_MPEGLAYER3 == pDest->wFormatTag && bWMADetected )
{
goto bad_codec;
}
if ( WAVE_FORMAT_WMAUDIO2 == pDest->wFormatTag )
{
bWMADetected = TRUE;
}
//
// a complex check for the order
//
if ( BestSamplesPerSec > pDest->nSamplesPerSec )
{
goto bad_codec;
} else if ( BestSamplesPerSec == pDest->nSamplesPerSec )
{
if ( BestChannels > pDest->nChannels )
{
goto bad_codec;
#if 0
} else if ( BestChannels == pDest->nChannels )
{
if ( BestBitsPerSample > pDest->wBitsPerSample )
{
goto bad_codec;
}
#endif
}
}
//
// good codec, keep it
//
BestChannels = pDest->nChannels;
BestBitsPerSample = pDest->wBitsPerSample;
BestSamplesPerSec = pDest->nSamplesPerSec;
#if 0
TRC(INF, "GOOD ag=%d chans=%d rate=%d, bps=%d, bpsamp=%d\n",
pDest->wFormatTag, pDest->nChannels, pDest->nSamplesPerSec, pDest->nAvgBytesPerSec, pDest->wBitsPerSample
);
#endif
continue;
bad_codec:
//
// bad codec, delete
//
#if 0
TRC(INF, "BAD ag=%d chans=%d rate=%d, bps=%d, bpsamp=%d\n",
pDest->wFormatTag, pDest->nChannels, pDest->nSamplesPerSec, pDest->nAvgBytesPerSec, pDest->wBitsPerSample
);
#endif
TSFREE( pDest );
g_ppNegotiatedFormats[i - 1] = NULL;
}
//
// choose the first format as a default
//
dwNewFmt = _VCSndChooseProperFormat( g_dwLineBandwidth );
if (dwNewFmt != (DWORD) -1)
{
//
// get a valid driver id
//
_VCSndGetACMDriverId( g_ppNegotiatedFormats[ dwNewFmt ] );
g_dwCurrentFormat = dwNewFmt;
//
// correct the new bandwidth
//
g_dwLineBandwidth = g_ppNegotiatedFormats[ dwNewFmt ]->nAvgBytesPerSec;
}
//
// remember the stream settings
//
if ( 0 != dwSoundCaps )
{
//
// set the remote sound caps
//
if (VCSndAcquireStream())
{
g_Stream->dwSoundCaps = dwSoundCaps;
g_Stream->dwVolume = dwVolume;
g_Stream->dwPitch = dwPitch;
VCSndReleaseStream();
}
}
rv = TRUE;
exitpt:
// don't forget to free the body of the eventually received message
//
if ( NULL != SndMessage.pBody )
TSFREE( SndMessage.pBody );
//
// in case of error cleanup the negotiated format
//
if ( !rv && NULL != g_ppNegotiatedFormats )
{
for ( i = 0; i < g_dwNegotiatedFormats; i++ )
{
if ( NULL != g_ppNegotiatedFormats[i] )
TSFREE( g_ppNegotiatedFormats[i] );
}
TSFREE( g_ppNegotiatedFormats );
g_ppNegotiatedFormats = NULL;
g_dwNegotiatedFormats = 0;
g_hacmDriverId = NULL;
}
return rv;
}
/*
* Function:
* _VCSndFindNativeFormat
*
* Description:
* returns the ID of the native format ( no codecs available )
*
*/
DWORD
_VCSndFindNativeFormat(
VOID
)
{
PSNDFORMATITEM pIter;
DWORD rv = 0;
if ( NULL == g_ppNegotiatedFormats )
{
TRC(ERR, "_VCSndFindNativeFormat: no format cache\n");
goto exitpt;
}
for( rv = 0; rv < g_dwNegotiatedFormats; rv++ )
{
pIter = g_ppNegotiatedFormats[ rv ];
if (pIter->wFormatTag == WAVE_FORMAT_PCM &&
pIter->nChannels == TSSND_NATIVE_CHANNELS &&
pIter->nSamplesPerSec == TSSND_NATIVE_SAMPLERATE &&
pIter->nAvgBytesPerSec == TSSND_NATIVE_AVGBYTESPERSEC &&
pIter->nBlockAlign == TSSND_NATIVE_BLOCKALIGN &&
pIter->wBitsPerSample == TSSND_NATIVE_BITSPERSAMPLE &&
pIter->cbSize == 0)
//
// format is found
//
break;
}
ASSERT( rv < g_dwNegotiatedFormats );
exitpt:
return rv;
}
/*
* Function:
* _VCSndOpenConverter
*
* Description:
* Opens a codec
*
*/
BOOL
_VCSndOpenConverter(
VOID
)
{
BOOL rv = FALSE;
MMRESULT mmres;
WAVEFORMATEX NativeFormat;
PSNDFORMATITEM pSndFmt;
PVCSNDFORMATLIST pIter;
BOOL bSucc;
//
// assert that these wasn't opened before
//
ASSERT(NULL == g_hacmDriver);
ASSERT(NULL == g_hacmStream);
if ( NULL == g_ppNegotiatedFormats )
{
TRC(INF, "_VCSndOpenConverter: no acm format specified\n");
goto exitpt;
}
//
// Find the acm format id
//
pSndFmt = g_ppNegotiatedFormats[ g_dwCurrentFormat ];
for( pIter = g_pAllCodecsFormatList; NULL != pIter; pIter = pIter->pNext )
{
if (pIter->Format.wFormatTag == pSndFmt->wFormatTag &&
pIter->Format.nChannels == pSndFmt->nChannels &&
pIter->Format.nSamplesPerSec == pSndFmt->nSamplesPerSec &&
pIter->Format.nAvgBytesPerSec == pSndFmt->nAvgBytesPerSec &&
pIter->Format.nBlockAlign == pSndFmt->nBlockAlign &&
pIter->Format.wBitsPerSample == pSndFmt->wBitsPerSample &&
pIter->Format.cbSize == pSndFmt->cbSize &&
0 == memcmp((&pIter->Format) + 1, pSndFmt + 1, pIter->Format.cbSize)
)
{
//
// format is found
//
g_hacmDriverId = pIter->hacmDriverId;
break;
}
}
if (NULL == g_hacmDriverId)
{
TRC(ERR, "_VCSndOpenConverter: acm driver id was not found\n");
goto exitpt;
}
TRC(INF, "_VCSndOpenConverter: format received is:\n");
TRC(INF, "FormatTag - %d\n", pSndFmt->wFormatTag);
TRC(INF, "Channels - %d\n", pSndFmt->nChannels);
TRC(INF, "SamplesPerSec - %d\n", pSndFmt->nSamplesPerSec);
TRC(INF, "AvgBytesPerSec - %d\n", pSndFmt->nAvgBytesPerSec);
TRC(INF, "BlockAlign - %d\n", pSndFmt->nBlockAlign);
TRC(INF, "BitsPerSample - %d\n", pSndFmt->wBitsPerSample);
TRC(INF, "cbSize - %d\n", pSndFmt->cbSize);
TRC(INF, "acmFormatId - %p\n", g_hacmDriverId);
mmres = acmDriverOpen(
&g_hacmDriver,
g_hacmDriverId,
0
);
if (MMSYSERR_NOERROR != mmres)
{
TRC(ERR, "_VCSndOpenConverter: unable to open acm driver: %d\n",
mmres);
goto exitpt;
}
TRC(ALV, "_VCSndOpenConverter: Driver is open, DriverId = %p\n",
g_hacmDriverId);
//
// first, find a suggested format for this converter
//
pSndFmt = g_ppNegotiatedFormats[ g_dwCurrentFormat ];
if ( WAVE_FORMAT_PCM == pSndFmt->wFormatTag &&
TSSND_NATIVE_CHANNELS == pSndFmt->nChannels &&
TSSND_NATIVE_SAMPLERATE == pSndFmt->nSamplesPerSec &&
TSSND_NATIVE_AVGBYTESPERSEC == pSndFmt->nAvgBytesPerSec &&
TSSND_NATIVE_BLOCKALIGN == pSndFmt->nBlockAlign &&
TSSND_NATIVE_BITSPERSAMPLE == pSndFmt->wBitsPerSample &&
0 == pSndFmt->cbSize )
{
TRC(INF, "_VCSndOpenConverter: opening native format, no converter\n");
goto exitpt;
}
bSucc = _VCSndFindSuggestedConverter(
g_hacmDriverId,
(LPWAVEFORMATEX)pSndFmt,
&NativeFormat,
&g_pfnConverter);
if (!bSucc)
{
TRC(FATAL, "_VCSndOpenConverter: can't find a suggested format\n");
goto exitpt;
}
TRC(ALV, "_VCSndOpenConverter: SOURCE format is:\n");
TRC(ALV, "FormatTag - %d\n", NativeFormat.wFormatTag);
TRC(ALV, "Channels - %d\n", NativeFormat.nChannels);
TRC(ALV, "SamplesPerSec - %d\n", NativeFormat.nSamplesPerSec);
TRC(ALV, "AvgBytesPerSec - %d\n", NativeFormat.nAvgBytesPerSec);
TRC(ALV, "BlockAlign - %d\n", NativeFormat.nBlockAlign);
TRC(ALV, "BitsPerSample - %d\n", NativeFormat.wBitsPerSample);
TRC(ALV, "cbSize - %d\n", NativeFormat.cbSize);
TRC(ALV, "_VCSndOpenConverter: DESTINATION format is:\n");
TRC(ALV, "FormatTag - %d\n", pSndFmt->wFormatTag);
TRC(ALV, "Channels - %d\n", pSndFmt->nChannels);
TRC(ALV, "SamplesPerSec - %d\n", pSndFmt->nSamplesPerSec);
TRC(ALV, "AvgBytesPerSec - %d\n", pSndFmt->nAvgBytesPerSec);
TRC(ALV, "BlockAlign - %d\n", pSndFmt->nBlockAlign);
TRC(ALV, "BitsPerSample - %d\n", pSndFmt->wBitsPerSample);
TRC(ALV, "cbSize - %d\n", pSndFmt->cbSize);
mmres = acmStreamOpen(
&g_hacmStream,
g_hacmDriver,
&NativeFormat,
(LPWAVEFORMATEX)pSndFmt,
NULL, // no filter
0, // no callback
0, // no callback instance
ACM_STREAMOPENF_NONREALTIME
);
if (MMSYSERR_NOERROR != mmres)
{
TRC(ERR, "_VCSndOpenConverter: unable to open acm stream: %d\n",
mmres);
goto exitpt;
}
g_dwDataRemain = 0;
rv = TRUE;
exitpt:
if (!rv)
{
_VCSndCloseConverter();
g_dwCurrentFormat = _VCSndFindNativeFormat();
}
return rv;
}
/*
* Function:
* _VCSndCloseConverter
*
* Description:
* Closes the codec
*
*/
VOID
_VCSndCloseConverter(
VOID
)
{
if (g_hacmStream)
{
acmStreamClose( g_hacmStream, 0 );
g_hacmStream = NULL;
}
if (g_hacmDriver)
{
acmDriverClose( g_hacmDriver, 0 );
g_hacmDriver = NULL;
}
}
/*
* Function:
* _VCSndConvert
*
* Description:
* Convert a block
*
*/
BOOL
_VCSndConvert(
PBYTE pSrc,
DWORD dwSrcSize,
PBYTE pDest,
DWORD *pdwDestSize )
{
BOOL rv = FALSE;
ACMSTREAMHEADER acmStreamHdr;
MMRESULT mmres, mmres2;
DWORD dwDestSize = 0;
DWORD dwSrcBlockAlign = 0;
DWORD dwNewDestSize = 0;
PBYTE pbRemDst;
DWORD dwRemDstLength;
ASSERT( NULL != g_hacmStream );
ASSERT( NULL != pdwDestSize );
//
// check if we have interrim converter
// use it, if so
//
if ( NULL != g_pfnConverter )
{
DWORD dwInterrimSize;
g_pfnConverter( (INT16 *)pSrc,
dwSrcSize,
&dwInterrimSize );
dwSrcSize = dwInterrimSize;
}
//
// compute the destination size
//
mmres = acmStreamSize(
g_hacmStream,
dwSrcSize,
&dwNewDestSize,
ACM_STREAMSIZEF_SOURCE
);
if ( MMSYSERR_NOERROR != mmres )
{
TRC(ERR, "_VCSndConvert: acmStreamSize failed: %d\n",
mmres);
g_dwDataRemain = 0;
goto go_convert;
}
//
// align the source to a block of the destination
// the remainder put in a buffer for consequentive use
//
mmres = acmStreamSize(
g_hacmStream,
g_ppNegotiatedFormats[ g_dwCurrentFormat ]->nBlockAlign,
&dwSrcBlockAlign,
ACM_STREAMSIZEF_DESTINATION
);
if ( MMSYSERR_NOERROR != mmres )
{
TRC(ALV, "_VCSndConvert: acmStreamSize failed for dst len: %d\n",
mmres);
g_dwDataRemain = 0;
goto go_convert;
}
dwNewDestSize += g_ppNegotiatedFormats[ g_dwCurrentFormat ]->nBlockAlign;
if ( dwNewDestSize > *pdwDestSize )
{
TRC( FATAL, "_VCSndConvert: dest size(%d) "
"bigger than passed buffer(%d)\n",
dwNewDestSize,
*pdwDestSize);
goto exitpt;
}
*pdwDestSize = dwNewDestSize;
if ( dwSrcBlockAlign <= g_dwDataRemain )
g_dwDataRemain = 0;
go_convert:
//
// prepare the acm stream header
//
memset( &acmStreamHdr, 0, sizeof( acmStreamHdr ));
acmStreamHdr.cbStruct = sizeof( acmStreamHdr );
acmStreamHdr.pbDst = pDest;
acmStreamHdr.cbDstLength = *pdwDestSize;
//
// first convert the remainding data from the previous call
// add the data needed to complete one block
//
if ( 0 != g_dwDataRemain)
{
DWORD dwDataToCopy = dwSrcBlockAlign - g_dwDataRemain;
memcpy( g_pCnvPrevData + g_dwDataRemain,
pSrc,
dwDataToCopy);
pSrc += dwDataToCopy;
ASSERT( dwSrcSize > dwDataToCopy );
dwSrcSize -= dwDataToCopy;
acmStreamHdr.pbSrc = g_pCnvPrevData;
acmStreamHdr.cbSrcLength = dwSrcBlockAlign;
mmres = acmStreamPrepareHeader(
g_hacmStream,
&acmStreamHdr,
0
);
if ( MMSYSERR_NOERROR != mmres )
{
TRC(ERR, "_VCSndConvert: acmStreamPrepareHeader failed: %d\n",
mmres);
goto exitpt;
}
mmres = acmStreamConvert(
g_hacmStream,
&acmStreamHdr,
ACM_STREAMCONVERTF_BLOCKALIGN
);
mmres2 = acmStreamUnprepareHeader(
g_hacmStream,
&acmStreamHdr,
0
);
ASSERT( mmres == MMSYSERR_NOERROR );
if ( MMSYSERR_NOERROR != mmres )
{
TRC(ERR, "_VCSndConvert: acmStreamConvert failed: %d\n",
mmres );
} else {
dwDestSize += acmStreamHdr.cbDstLengthUsed;
acmStreamHdr.cbSrcLengthUsed= 0;
acmStreamHdr.pbDst += acmStreamHdr.cbDstLengthUsed;
acmStreamHdr.cbDstLength -= acmStreamHdr.cbDstLengthUsed;
acmStreamHdr.cbDstLengthUsed= 0;
}
}
//
// if we don't have fully aligned block
// skip this conversion, but don't forget to save
// this block
//
if (dwSrcSize < dwSrcBlockAlign)
{
g_dwDataRemain = dwSrcSize;
memcpy( g_pCnvPrevData, pSrc, g_dwDataRemain );
rv = TRUE;
goto exitpt;
}
pbRemDst = acmStreamHdr.pbDst;
dwRemDstLength = acmStreamHdr.cbDstLength;
acmStreamHdr.pbSrc = pSrc;
acmStreamHdr.cbSrcLength = dwSrcSize;
acmStreamHdr.fdwStatus = 0;
mmres = acmStreamPrepareHeader(
g_hacmStream,
&acmStreamHdr,
0
);
if ( MMSYSERR_NOERROR != mmres )
{
TRC(ERR, "_VCSndConvert: can't prepare header: %d\n",
mmres);
goto exitpt;
}
while (acmStreamHdr.cbSrcLength > dwSrcBlockAlign)
{
mmres = acmStreamConvert(
g_hacmStream,
&acmStreamHdr,
ACM_STREAMCONVERTF_BLOCKALIGN
);
if ( MMSYSERR_NOERROR != mmres )
{
TRC(ERR, "_VCSndConvert: acmStreamConvert failed: %d\n",
mmres);
goto exitpt;
}
//
// advance the buffer positions
//
acmStreamHdr.pbSrc += acmStreamHdr.cbSrcLengthUsed;
acmStreamHdr.pbDst += acmStreamHdr.cbDstLengthUsed;
acmStreamHdr.cbSrcLength -= acmStreamHdr.cbSrcLengthUsed;
acmStreamHdr.cbDstLength -= acmStreamHdr.cbDstLengthUsed;
dwDestSize += acmStreamHdr.cbDstLengthUsed;
acmStreamHdr.cbSrcLengthUsed= 0;
acmStreamHdr.cbDstLengthUsed= 0;
}
rv = TRUE;
//
// save the unaligned data
//
if ( 0 != dwSrcBlockAlign )
{
g_dwDataRemain = acmStreamHdr.cbSrcLength;
memcpy( g_pCnvPrevData, acmStreamHdr.pbSrc, g_dwDataRemain );
}
//
// restore the header and unprepare
//
acmStreamHdr.pbSrc = pSrc;
acmStreamHdr.pbDst = pbRemDst;
acmStreamHdr.cbSrcLength = dwSrcSize;
acmStreamHdr.cbSrcLengthUsed= 0;
acmStreamHdr.cbDstLength = dwRemDstLength;
acmStreamHdr.cbDstLengthUsed= 0;
mmres = acmStreamUnprepareHeader(
g_hacmStream,
&acmStreamHdr,
0
);
ASSERT( mmres == MMSYSERR_NOERROR );
exitpt:
*pdwDestSize = dwDestSize;
return rv;
}
/*
* Function:
* _VCSndCheckDevice
*
* Description:
* Initializes capabilities negotiations with the client
*
*/
VOID
_VCSndCheckDevice(
HANDLE hReadEvent,
HANDLE hDGramEvent
)
{
SNDPROLOG Prolog;
BOOL bSuccess;
//
// no sound caps yet
//
g_Stream->dwSoundCaps = 0;
//
// find the best compression for this audio line
//
if ( !VCSndNegotiateWaveFormat( hReadEvent, hDGramEvent ))
{
ChannelClose();
}
}
/*
* Function:
* _VCSndCloseDevice
*
* Description:
* Closes the remote device
*
*/
VOID
_VCSndCloseDevice(
VOID
)
{
if (!VCSndAcquireStream())
{
TRC(FATAL, "_VCSndCloseDevice: Can't acquire stream mutex. exit\n");
goto exitpt;
}
// disable the local audio
//
g_Stream->dwSoundCaps = 0;
g_bDeviceOpened = FALSE;
g_dwLineBandwidth = 0;
VCSndReleaseStream();
exitpt:
;
}
/*
* Function:
* _VCSndSendWave
*
* Description:
* Sends a wave data to the client using UDP
*
*/
BOOL
_VCSndSendWaveDGram(
BYTE cBlockNo,
PVOID pData,
DWORD dwDataSize
)
{
BOOL bSucc = FALSE;
struct sockaddr_in sin;
INT sendres;
PSNDWAVE pSndWave;
//
// encrypt the packet if necessary
//
if ( g_EncryptionLevel >= MIN_ENCRYPT_LEVEL )
if ( !SL_Encrypt( (PBYTE)pData, ( g_HiBlockNo << 8 ) + cBlockNo, dwDataSize ))
goto exitpt;
__try {
pSndWave = (PSNDWAVE) alloca(g_dwDGramSize);
} __except((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH)
{
_resetstkoflw();
pSndWave = NULL;
TRC(ERR, "_VCSndSendWaveDGram: alloca generate exception: %d\n",
GetExceptionCode());
}
if (NULL == pSndWave)
goto exitpt;
pSndWave->Prolog.Type = (g_EncryptionLevel >= MIN_ENCRYPT_LEVEL)?SNDC_WAVEENCRYPT:SNDC_WAVE;
pSndWave->wFormatNo = (UINT16)g_dwCurrentFormat;
pSndWave->wTimeStamp = (UINT16)GetTickCount();
pSndWave->dwBlockNo = (g_HiBlockNo << 8) + cBlockNo;
// prepare the to address
//
sin.sin_family = PF_INET;
sin.sin_port = (u_short)g_dwDGramPort;
sin.sin_addr.s_addr = g_ulDGramAddress;
// Send the block in chunks of dwDGramSize
//
while (dwDataSize)
{
DWORD dwWaveDataLen;
dwWaveDataLen = (dwDataSize + sizeof(*pSndWave)
< g_dwDGramSize)
?
dwDataSize:
g_dwDGramSize - sizeof(*pSndWave);
pSndWave->Prolog.BodySize = (UINT16)
( sizeof(*pSndWave) -
sizeof(pSndWave->Prolog) +
dwWaveDataLen );
memcpy(pSndWave + 1, pData, dwWaveDataLen);
sendres = sendto(g_hDGramSocket,
(LPSTR)pSndWave,
sizeof(*pSndWave) + dwWaveDataLen,
0, // flags
(struct sockaddr *)&sin, // to address
sizeof(sin));
if (SOCKET_ERROR == sendres)
{
TRC(ERR, "_VCSndSendWaveDGram: sendto failed: %d\n",
WSAGetLastError());
goto exitpt;
}
g_dwPacketSize = sizeof(*pSndWave) + dwWaveDataLen;
dwDataSize -= dwWaveDataLen;
pData = ((LPSTR)(pData)) + dwWaveDataLen;
}
bSucc = TRUE;
exitpt:
return bSucc;
}
BOOL
_VCSndSendWaveDGramInFrags(
BYTE cBlockNo,
PVOID pData,
DWORD dwDataSize
)
{
BOOL bSucc = FALSE;
struct sockaddr_in sin;
INT sendres;
PSNDUDPWAVE pWave;
PSNDUDPWAVELAST pLast;
PBYTE pSource;
PBYTE pEnd;
DWORD dwFragSize;
DWORD dwNumFrags;
DWORD count;
DWORD dwSize;
PVOID pBuffer;
UINT16 wStartTime;
ASSERT( CanUDPFragment( g_wClientVersion ) && dwDataSize <= 0x8000 + RDPSND_SIGNATURE_SIZE );
__try {
pBuffer = _alloca( g_dwDGramSize );
} __except((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH)
{
_resetstkoflw();
pBuffer = 0;
}
if ( NULL == pBuffer )
{
goto exitpt;
}
//
// calculate number of frags etc
//
dwFragSize = g_dwDGramSize - sizeof( *pLast );
dwNumFrags = dwDataSize / dwFragSize;
pSource = (PBYTE)pData;
pEnd = pSource + dwDataSize;
wStartTime = (UINT16)GetTickCount();
// prepare the to address
//
sin.sin_family = PF_INET;
sin.sin_port = (u_short)g_dwDGramPort;
sin.sin_addr.s_addr = g_ulDGramAddress;
//
// make sure we can fit all the frag counters
//
ASSERT( dwNumFrags < 0x7fff );
if ( 0 != dwNumFrags )
{
pWave = (PSNDUDPWAVE)pBuffer;
pWave->Type = SNDC_UDPWAVE;
pWave->cBlockNo = cBlockNo;
for( count = 0; count < dwNumFrags; count++ )
{
PBYTE pDest = (PBYTE)(&pWave->cFragNo);
dwSize = sizeof( *pWave ) + dwFragSize;
if ( count >= RDPSND_FRAGNO_EXT )
{
*pDest = (BYTE)(((count >> 8) & (~RDPSND_FRAGNO_EXT)) | RDPSND_FRAGNO_EXT);
pDest++;
*pDest = (BYTE)(count & 0xff);
dwSize++;
} else {
*pDest = (BYTE)count;
}
pDest++;
memcpy( pDest, pSource, dwFragSize );
sendres = sendto(
g_hDGramSocket,
(LPSTR)pWave,
dwSize,
0, // flags
(struct sockaddr *)&sin, // to address
sizeof(sin));
if (SOCKET_ERROR == sendres)
{
TRC(ERR, "_VCSndSendWaveDGramInFrags: sendto failed: %d\n",
WSAGetLastError());
goto exitpt;
}
pSource += dwFragSize;
}
}
ASSERT( pSource <= pEnd );
//
// send the last fragment with all the extra info
//
pLast = (PSNDUDPWAVELAST)pBuffer;
pLast->Type = SNDC_UDPWAVELAST;
pLast->wTotalSize = (UINT16)dwDataSize;
pLast->wTimeStamp = wStartTime;
pLast->wFormatNo = (UINT16)g_dwCurrentFormat;
pLast->dwBlockNo = (g_HiBlockNo << 8) + cBlockNo;
dwSize = PtrToLong( (PVOID)( pEnd - pSource ));
memcpy( pLast + 1, pSource, dwSize );
sendres = sendto(
g_hDGramSocket,
(LPSTR)pLast,
dwSize + sizeof( *pLast ),
0,
(struct sockaddr *)&sin,
sizeof(sin)
);
if (SOCKET_ERROR == sendres)
{
TRC(ERR, "_VCSndSendWaveDGramInFrags: sendto failed: %d\n",
WSAGetLastError());
goto exitpt;
}
g_dwPacketSize = dwNumFrags * sizeof( *pWave ) + sizeof( *pLast ) + dwDataSize;
bSucc = TRUE;
exitpt:
return bSucc;
}
BOOL
_VCSndSendWaveVC(
BYTE cBlockNo,
PVOID pData,
DWORD dwDataSize
)
{
BOOL bSucc = FALSE;
SNDWAVE Wave;
//
// send this block through the virtual channel
//
TRC(ALV, "_VCSndSendWave: sending through VC\n");
Wave.Prolog.Type = SNDC_WAVE;
Wave.cBlockNo = cBlockNo;
Wave.wFormatNo = (UINT16)g_dwCurrentFormat;
Wave.wTimeStamp = (UINT16)GetTickCount();
Wave.Prolog.BodySize = (UINT16)( sizeof(Wave) - sizeof(Wave.Prolog) +
dwDataSize );
bSucc = ChannelMessageWrite(
&Wave,
sizeof(Wave),
pData,
dwDataSize
);
g_dwPacketSize = sizeof(Wave) + Wave.Prolog.BodySize;
if (!bSucc)
{
TRC(ERR, "_VCSndSendWave: failed to send wave: %d\n",
GetLastError());
}
return bSucc;
}
/*
* Function:
* _VCSndSendWave
*
* Description:
* Sends a wave data to the client
*
*/
BOOL
_VCSndSendWave(
BYTE cBlockNo,
PVOID pData,
DWORD dwDataSize
)
{
BOOL bSucc = FALSE;
static BYTE s_pDest[ TSSND_BLOCKSIZE + RDPSND_SIGNATURE_SIZE ];
PBYTE pDest = s_pDest + RDPSND_SIGNATURE_SIZE;
#if _SIM_RESAMPLE
//
// resample randomly
//
if ( NULL != g_ppNegotiatedFormats && 0 == (cBlockNo % 32) )
{
_VCSndCloseConverter();
g_dwCurrentFormat = rand() % g_dwNegotiatedFormats;
g_dwDataRemain = 0;
_VCSndOpenConverter();
}
#endif
_StatsCheckResample();
if ( NULL != g_hacmStream )
{
DWORD dwDestSize = TSSND_BLOCKSIZE;
if (!_VCSndConvert( (PBYTE) pData, dwDataSize, pDest, &dwDestSize ))
{
TRC(ERR, "_VCSndSendWave: conversion failed\n");
goto exitpt;
} else {
pData = pDest;
dwDataSize = dwDestSize;
}
} else {
//
// no conversion
// use the data as it is
// copy it in the s_pDest buffer
//
ASSERT( dwDataSize <= TSSND_BLOCKSIZE );
memcpy( pDest, pData, dwDataSize );
}
if (
INVALID_SOCKET != g_hDGramSocket &&
0 != g_dwDGramPort &&
0 != g_dwDGramSize &&
0 != g_ulDGramAddress &&
0 != g_dwLineBandwidth // if this is 0, we don't have UDP
)
{
// Send a datagram
//
if ( !CanUDPFragment( g_wClientVersion ) &&
dwDataSize + sizeof( SNDWAVE ) + RDPSND_SIGNATURE_SIZE > g_dwDGramSize )
{
//
// if the wave doesn't fit in the UDP packet use VCs
//
bSucc = _VCSndSendWaveVC( cBlockNo, pData, dwDataSize );
} else {
//
// add signature if necessary
//
if ( IsDGramWaveSigned( g_wClientVersion ))
{
if ( !IsDGramWaveAudioSigned( g_wClientVersion ))
{
SL_Signature( s_pDest, (g_HiBlockNo << 8) + cBlockNo );
} else {
SL_AudioSignature( s_pDest, (g_HiBlockNo << 8) + cBlockNo,
(PBYTE)pData, dwDataSize );
}
pData = s_pDest;
dwDataSize += RDPSND_SIGNATURE_SIZE;
}
if ( CanUDPFragment( g_wClientVersion ) &&
dwDataSize + sizeof( SNDWAVE ) > g_dwDGramSize )
{
bSucc = _VCSndSendWaveDGramInFrags( cBlockNo, pData, dwDataSize );
} else {
bSucc = _VCSndSendWaveDGram( cBlockNo, pData, dwDataSize );
}
}
} else {
bSucc = _VCSndSendWaveVC( cBlockNo, pData, dwDataSize );
}
TRC(ALV, "_VCSndSendWave: BlockNo: %d sent\n", cBlockNo);
exitpt:
return bSucc;
}
INT
WSInit(
VOID
)
{
WORD versionRequested;
WSADATA wsaData;
int intRC;
versionRequested = MAKEWORD(1, 1);
intRC = WSAStartup(versionRequested, &wsaData);
if (intRC != 0)
{
TRC(ERR, "Failed to initialize WinSock rc:%d\n", intRC);
}
return intRC;
}
/*
* Function:
* DGramRead
*
* Description:
* Reads an UDP message (datagram)
*
*/
VOID
DGramRead(
HANDLE hDGramEvent,
PVOID *ppBuff,
DWORD *pdwSize
)
{
DWORD dwRecvd;
DWORD dwFlags;
INT rc;
if ( INVALID_SOCKET == g_hDGramSocket )
goto exitpt;
ASSERT( NULL != hDGramEvent );
do {
memset(&g_WSAOverlapped, 0, sizeof(g_WSAOverlapped));
g_WSAOverlapped.hEvent = hDGramEvent;
dwRecvd = 0;
dwFlags = 0;
g_wsabuf.len = sizeof( g_pDGramRecvData );
g_wsabuf.buf = (char *) g_pDGramRecvData;
rc = WSARecvFrom(
g_hDGramSocket,
&g_wsabuf,
1,
&dwRecvd,
&dwFlags,
NULL, // no from address
NULL,
&g_WSAOverlapped,
NULL); // no completion routine
if ( 0 == rc )
{
//
// data received
//
SNDMESSAGE SndMsg;
if ( NULL != ppBuff && NULL != pdwSize )
{
//
// pass the data to the caller
//
*ppBuff = g_pDGramRecvData;
*pdwSize = dwRecvd;
goto exitpt;
}
if ( dwRecvd < sizeof( SNDPROLOG ))
{
TRC(WRN, "DGramRead: invalid message received: len=%d\n",
dwRecvd );
continue;
}
memcpy( &SndMsg.Prolog, g_pDGramRecvData, sizeof( SNDPROLOG ));
SndMsg.pBody = g_pDGramRecvData + sizeof( SNDPROLOG );
TRC(ALV, "DGramRead: data received\n");
// parse this packet
//
VCSndDataArrived( &SndMsg );
}
}
while ( SOCKET_ERROR != rc );
exitpt:
;
}
/*
* Function:
* DGramReadCompletion
*
* Description:
* Datagram read completion
*
*/
VOID
DGramReadComplete(
PVOID *ppBuff,
DWORD *pdwSize
)
{
BOOL rc;
SNDMESSAGE SndMsg;
DWORD dwFlags = 0;
DWORD dwRecvd = 0;
ASSERT( INVALID_SOCKET != g_hDGramSocket );
rc = WSAGetOverlappedResult(
g_hDGramSocket,
&g_WSAOverlapped,
&dwRecvd,
FALSE,
&dwFlags
);
if ( !rc )
{
TRC(ERR, "DGramReadComplete: WSAGetOverlappedResult failed=%d\n",
WSAGetLastError());
goto exitpt;
}
//
// data received
//
if ( dwRecvd < sizeof( SNDPROLOG ))
{
TRC(WRN, "DGramReadComplete: invalid message received: len=%d\n",
dwRecvd );
goto exitpt;
}
if ( NULL != ppBuff && NULL != pdwSize )
{
//
// pass the data to the caller
//
*ppBuff = g_pDGramRecvData;
*pdwSize = dwRecvd;
goto exitpt;
}
memcpy( &SndMsg.Prolog, g_pDGramRecvData, sizeof( SNDPROLOG ));
SndMsg.pBody = g_pDGramRecvData + sizeof( SNDPROLOG );
TRC(ALV, "DGramReadComplete: data received\n");
// parse this packet
//
VCSndDataArrived( &SndMsg );
exitpt:
;
}
/*
* Add an ACE to object security descriptor
*/
DWORD
AddAceToObjectsSecurityDescriptor (
HANDLE hObject, // handle to object
SE_OBJECT_TYPE ObjectType, // type of object
LPTSTR pszTrustee, // trustee for new ACE
TRUSTEE_FORM TrusteeForm, // format of TRUSTEE structure
DWORD dwAccessRights, // access mask for new ACE
ACCESS_MODE AccessMode, // type of ACE
DWORD dwInheritance // inheritance flags for new ACE
)
{
DWORD dwRes;
PACL pOldDACL = NULL, pNewDACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS ea;
if (NULL == hObject)
return ERROR_INVALID_PARAMETER;
// Get a pointer to the existing DACL.
dwRes = GetSecurityInfo(hObject, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, &pOldDACL, NULL, &pSD);
if (ERROR_SUCCESS != dwRes)
{
TRC( ERR, "GetSecurityInfo Error %u\n", dwRes );
goto exitpt;
}
// Initialize an EXPLICIT_ACCESS structure for the new ACE.
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = dwAccessRights;
ea.grfAccessMode = AccessMode;
ea.grfInheritance= dwInheritance;
ea.Trustee.TrusteeForm = TrusteeForm;
ea.Trustee.ptstrName = pszTrustee;
// Create a new ACL that merges the new ACE
// into the existing DACL.
dwRes = SetEntriesInAcl(1, &ea, pOldDACL, &pNewDACL);
if (ERROR_SUCCESS != dwRes)
{
TRC( ERR, "SetEntriesInAcl Error %u\n", dwRes );
goto exitpt;
}
// Attach the new ACL as the object's DACL.
dwRes = SetSecurityInfo(hObject, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, pNewDACL, NULL);
if (ERROR_SUCCESS != dwRes)
{
TRC( ERR, "SetSecurityInfo Error %u\n", dwRes );
goto exitpt;
}
exitpt:
if(pSD != NULL)
LocalFree((HLOCAL) pSD);
if(pNewDACL != NULL)
LocalFree((HLOCAL) pNewDACL);
return dwRes;
}
/*
* Add "system" account with full control over this handle
*
*/
BOOL
_ObjectAllowSystem(
HANDLE h
)
{
BOOL rv = FALSE;
PSID pSidSystem;
SID_IDENTIFIER_AUTHORITY AuthorityNt = SECURITY_NT_AUTHORITY;
DWORD dw;
if (!AllocateAndInitializeSid(&AuthorityNt, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pSidSystem))
{
TRC( ERR, "AllocateAndInitializeSid failed: %d\n",
GetLastError() );
goto exitpt;
}
ASSERT(IsValidSid(pSidSystem));
dw = AddAceToObjectsSecurityDescriptor (
h, // handle to object
SE_KERNEL_OBJECT, // type of object
(LPTSTR)pSidSystem, // trustee for new ACE
TRUSTEE_IS_SID, // format of TRUSTEE structure
GENERIC_ALL, // access mask for new ACE
GRANT_ACCESS, // type of ACE
0 // inheritance flags for new ACE
);
if ( ERROR_SUCCESS != dw )
{
TRC( ERR, "AddAceToObjectsSecurityDescriptor failed=%d\n", dw );
goto exitpt;
}
rv = TRUE;
exitpt:
return rv;
}
VOID
_SignalInitializeDone(
VOID
)
{
HANDLE hInitEvent = OpenEvent( EVENT_MODIFY_STATE,
FALSE,
TSSND_WAITTOINIT );
g_Stream->dwSoundCaps |= TSSNDCAPS_INITIALIZED;
if ( NULL != hInitEvent )
{
PulseEvent( hInitEvent );
CloseHandle( hInitEvent );
}
TRC( INF, "Audio host is ready!\n" );
}
/*
* Function:
* VCSndIoThread
*
* Description:
* Main entry pint for this thread
*
*/
INT
WINAPI
VCSndIoThread(
PVOID pParam
)
{
HANDLE ahEvents[TOTAL_EVENTS];
HANDLE hReadEvent = NULL;
HANDLE hDGramEvent = NULL;
SNDMESSAGE SndMessage;
DWORD dwres;
ULONG logonId;
HANDLE hReconnectEvent = NULL;
WCHAR szEvName[64];
BYTE i;
_VCSndReadRegistry();
memset (&SndMessage, 0, sizeof(SndMessage));
WSInit();
// create the global/local events
//
g_hDataReadyEvent = CreateEvent(NULL,
FALSE,
FALSE,
TSSND_DATAREADYEVENT);
g_hStreamIsEmptyEvent = CreateEvent(NULL,
FALSE,
TRUE,
TSSND_STREAMISEMPTYEVENT);
g_hStreamMutex = CreateMutex(NULL, FALSE, TSSND_STREAMMUTEX);
hReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
hDGramEvent = WSACreateEvent();
if (NULL == g_hDataReadyEvent ||
NULL == g_hStreamIsEmptyEvent ||
NULL == g_hStreamMutex ||
NULL == hReadEvent ||
NULL == hDGramEvent)
{
TRC(FATAL, "VCSndIoThread: no events\n");
goto exitpt;
}
// adjust privileges on the events
//
if (!_ObjectAllowSystem( g_hDataReadyEvent ))
goto exitpt;
if (!_ObjectAllowSystem( g_hStreamIsEmptyEvent ))
goto exitpt;
if (!_ObjectAllowSystem( g_hStreamMutex ))
goto exitpt;
// create the stream
//
g_hStream = CreateFileMapping(
INVALID_HANDLE_VALUE, //PG.SYS
NULL, // security
PAGE_READWRITE,
0, // Size high
sizeof(*g_Stream), // Size low
TSSND_STREAMNAME // mapping name
);
if (NULL == g_hStream)
{
TRC(FATAL, "DllInstanceInit: failed to create mapping: %d\n",
GetLastError());
goto exitpt;
}
if (!_ObjectAllowSystem( g_hStream ))
goto exitpt;
g_Stream = (PSNDSTREAM) MapViewOfFile(
g_hStream,
FILE_MAP_ALL_ACCESS,
0, 0, // offset
sizeof(*g_Stream)
);
if (NULL == g_Stream)
{
TRC(ERR, "VCSndIoThread: "
"can't map the stream view: %d\n",
GetLastError());
goto exitpt;
}
// Initialize the stream
//
if (VCSndAcquireStream())
{
memset(g_Stream, 0, sizeof(*g_Stream) - sizeof(g_Stream->pSndData));
memset(g_Stream->pSndData, 0x00000000, sizeof(g_Stream->pSndData));
g_Stream->cLastBlockConfirmed = g_Stream->cLastBlockSent - 1;
//
// no socket created, so far
//
g_hDGramSocket = INVALID_SOCKET;
VCSndReleaseStream();
} else {
TRC(FATAL, "VCSndIoThread, can't map the stream: %d, aborting\n",
GetLastError());
goto exitpt;
}
if (!ProcessIdToSessionId(GetCurrentProcessId(), &logonId))
{
TRC(FATAL, "VCSndIoThread: failed to het session Id. %d\n",
GetLastError());
goto exitpt;
}
// create disconnect/reconnect events
//
wcsncpy(szEvName, L"RDPSound-Disconnect", sizeof(szEvName)/sizeof(szEvName[0]));
g_hDisconnectEvent = CreateEvent(NULL, FALSE, FALSE, szEvName);
if (NULL == g_hDisconnectEvent)
{
TRC(FATAL, "VCSndIoThread: can't create disconnect event. %d\n",
GetLastError());
goto exitpt;
}
wcsncpy(szEvName, L"RDPSound-Reconnect", sizeof(szEvName)/sizeof(szEvName[0]));
hReconnectEvent = CreateEvent(NULL, FALSE, FALSE, szEvName);
if (NULL == hReconnectEvent)
{
TRC(FATAL, "VCSndIoThread: can't create reconnect event. %d\n",
GetLastError());
goto exitpt;
}
if (!ChannelOpen())
{
TRC(FATAL, "VCSndIoThread: unable to open virtual channel\n");
goto exitpt;
}
ahEvents[READ_EVENT] = hReadEvent;
ahEvents[DISCONNECT_EVENT] = g_hDisconnectEvent;
ahEvents[RECONNECT_EVENT] = hReconnectEvent;
ahEvents[DATAREADY_EVENT] = g_hDataReadyEvent;
ahEvents[DGRAM_EVENT] = hDGramEvent;
ahEvents[POWERWAKEUP_EVENT] = g_hPowerWakeUpEvent;
ahEvents[POWERSUSPEND_EVENT] = g_hPowerSuspendEvent;
_VCSndCheckDevice( hReadEvent, hDGramEvent );
// Check the channel for data
//
while (ChannelReceiveMessage(&SndMessage, hReadEvent))
{
VCSndDataArrived(&SndMessage);
SndMessage.uiPrologReceived = 0;
SndMessage.uiBodyReceived = 0;
}
DGramRead( hDGramEvent, NULL, NULL );
//
// signal all workers waiting for initialize
//
_SignalInitializeDone();
// main loop
//
while (g_bRunning)
{
DWORD dwNumEvents = sizeof(ahEvents)/sizeof(ahEvents[0]);
dwres = WaitForMultipleObjectsEx(
dwNumEvents, // count
ahEvents, // events
FALSE, // wait all
DEFAULT_RESPONSE_TIMEOUT,
FALSE // non alertable
);
if (!g_bRunning)
TRC(ALV, "VCSndIoThread: time to exit\n");
if (WAIT_TIMEOUT != dwres)
TRC(ALV, "VCSndIoThread: an event was fired\n");
if (READ_EVENT == dwres)
//
// data is ready to read
//
{
ChannelBlockReadComplete();
ResetEvent(ahEvents[0]);
// Check the channel for data
//
while (ChannelReceiveMessage(&SndMessage, hReadEvent))
{
VCSndDataArrived(&SndMessage);
SndMessage.uiPrologReceived = 0;
SndMessage.uiBodyReceived = 0;
}
} else if (( DISCONNECT_EVENT == dwres ) || // disconnect event
( POWERSUSPEND_EVENT == dwres )) // suspend event
{
// disconnect event
//
TRC(INF, "VCSndIoThread: DISCONNECTED\n");
_VCSndCloseDevice();
_VCSndCloseConverter();
ChannelClose();
_StatReset();
if ( DISCONNECT_EVENT == dwres )
{
g_bDisconnected = TRUE;
} else {
g_bSuspended = TRUE;
}
continue;
} else if (( RECONNECT_EVENT == dwres ) || // reconnect event
( POWERWAKEUP_EVENT == dwres ))
{
// reconnect event
//
if ( POWERWAKEUP_EVENT == dwres )
{
// power wakeup event
// here, we may have not received suspend event, but in that case we
// had failed to send, so check the g_bDeviceFailed flag and act only if it is on
if ( g_bDisconnected )
{
// no reason to process power wake up if we are not remote
//
g_bSuspended = FALSE;
continue;
}
if ( !g_bSuspended && !g_bDeviceFailed )
{
// if neither of these happend
// then we don't care
continue;
}
}
TRC(INF, "VCSndIoThread: RECONNECTED\n");
if (!ChannelOpen())
{
TRC(FATAL, "VCSndIoThread: unable to open virtual channel\n");
} else {
_VCSndCheckDevice( hReadEvent, hDGramEvent );
//
// start the receive loop again
//
if (ChannelReceiveMessage(&SndMessage, hReadEvent))
{
VCSndDataArrived(&SndMessage);
SndMessage.uiPrologReceived = 0;
SndMessage.uiBodyReceived = 0;
}
if ( RECONNECT_EVENT == dwres )
{
g_bDisconnected = FALSE;
} else {
g_bSuspended = FALSE;
}
g_bDeviceFailed = FALSE;
// kick the player
//
PulseEvent( g_hStreamIsEmptyEvent );
_SignalInitializeDone();
}
} else if ( DGRAM_EVENT == dwres )
{
//
// DGram ready
//
DGramReadComplete( NULL, NULL );
//
// atttempt more read
//
DGramRead( hDGramEvent, NULL, NULL );
}
// Check for data available from the apps
//
if (!VCSndAcquireStream())
{
TRC(FATAL, "VCSndIoThread: somebody is holding the "
"Stream mutext for too long\n");
continue;
}
// if this was a reconnect
// roll back to the last block sent
//
if ( RECONNECT_EVENT == dwres)
{
//
// clean this chunk for the next mix
//
memset( g_Stream->pSndData, 0, TSSND_MAX_BLOCKS * TSSND_BLOCKSIZE );
g_Stream->cLastBlockConfirmed = g_Stream->cLastBlockSent;
}
//
// if we have not received confirmation
// for the packets sent, just give up and continue
//
if (WAIT_TIMEOUT == dwres &&
g_bDeviceOpened &&
g_Stream->cLastBlockSent != g_Stream->cLastBlockConfirmed)
{
BYTE cCounter;
TRC(WRN, "VCSndIoThread: not received confirmation for blocks "
"between %d and %d\n",
g_Stream->cLastBlockConfirmed,
g_Stream->cLastBlockSent);
for ( cCounter = g_Stream->cLastBlockConfirmed;
cCounter != (BYTE)(g_Stream->cLastBlockSent + 1);
cCounter++)
{
_StatsCollect(( GetTickCount() - DEFAULT_RESPONSE_TIMEOUT ) &
0xffff );
}
//
// end of the loop
//
g_Stream->cLastBlockConfirmed = g_Stream->cLastBlockSent;
//
// kick the player
//
PulseEvent(g_hStreamIsEmptyEvent);
}
// Check for control commands
//
// Volume
//
if ( g_bDeviceOpened && g_Stream->bNewVolume &&
0 != (g_Stream->dwSoundCaps & TSSNDCAPS_VOLUME))
{
SNDSETVOLUME SetVolume;
TRC(ALV, "VCSndIoThread: new volume\n");
SetVolume.Prolog.Type = SNDC_SETVOLUME;
SetVolume.Prolog.BodySize = sizeof(SetVolume) - sizeof(SetVolume.Prolog);
SetVolume.dwVolume = g_Stream->dwVolume;
ChannelBlockWrite(
&SetVolume,
sizeof(SetVolume)
);
g_Stream->bNewVolume = FALSE;
}
// Pitch
//
if ( g_bDeviceOpened && g_Stream->bNewPitch &&
0 != (g_Stream->dwSoundCaps & TSSNDCAPS_PITCH))
{
SNDSETPITCH SetPitch;
TRC(ALV, "VCSndIoThread: new pitch\n");
SetPitch.Prolog.Type = SNDC_SETPITCH;
SetPitch.Prolog.BodySize = sizeof(SetPitch) - sizeof(SetPitch.Prolog);
SetPitch.dwPitch = g_Stream->dwPitch;
ChannelBlockWrite(
&SetPitch,
sizeof(SetPitch)
);
g_Stream->bNewPitch = FALSE;
}
// Check for data available from the apps
//
if (g_Stream->cLastBlockSent != g_Stream->cLastBlockQueued &&
(BYTE)(g_Stream->cLastBlockSent - g_Stream->cLastBlockConfirmed) <
g_dwBlocksOnTheNet
)
{
// Aha, here's some data to send
//
TRC(ALV, "VCSndIoThread: will send some data\n");
if (g_bDisconnected || g_bSuspended || g_bDeviceFailed)
{
TRC(ALV, "Device is disconnected. ignore the packets\n");
g_Stream->cLastBlockSent = g_Stream->cLastBlockQueued;
g_Stream->cLastBlockConfirmed = g_Stream->cLastBlockSent - 1;
PulseEvent( g_hStreamIsEmptyEvent );
} else
if (!g_bDeviceOpened)
{
// send an "open device" command
//
SNDPROLOG Prolog;
//
// first, try to open the acm converter
//
_VCSndOpenConverter();
//
// if we failed width the converter will
// send in native format
//
g_bDeviceOpened = TRUE;
}
for (i = g_Stream->cLastBlockSent;
i != g_Stream->cLastBlockQueued &&
(BYTE)(g_Stream->cLastBlockSent -
g_Stream->cLastBlockConfirmed) <
g_dwBlocksOnTheNet;
i++)
{
BOOL bSucc;
// TRC( INF, "Sending block # %d, last conf=%d, last queued=%d\n", i, g_Stream->cLastBlockConfirmed, g_Stream->cLastBlockSent );
bSucc = _VCSndSendWave(
i, // block no
((LPSTR)g_Stream->pSndData) +
((i % TSSND_MAX_BLOCKS) * TSSND_BLOCKSIZE),
TSSND_BLOCKSIZE
);
//
// clean this chunk for the next mix
//
memset(g_Stream->pSndData +
(i % TSSND_MAX_BLOCKS) *
TSSND_BLOCKSIZE,
0x00000000,
TSSND_BLOCKSIZE);
if ( 0xff == i )
g_HiBlockNo++;
if (bSucc)
{
g_Stream->cLastBlockSent = i + 1;
}
else
{
TRC(WRN, "VCSndIoThread: failed to send, "
"disabling the device\n");
//
// act the same way as DISCONNECT
//
_VCSndCloseDevice();
_VCSndCloseConverter();
ChannelClose();
g_Stream->cLastBlockConfirmed =
g_Stream->cLastBlockSent = g_Stream->cLastBlockQueued;
_StatReset();
g_bDeviceFailed = TRUE;
//
// Break this loop
//
break;
}
}
}
// Check if there's no more data
// if so, close the remote device
//
if (g_bDeviceOpened &&
g_Stream->cLastBlockQueued == g_Stream->cLastBlockSent &&
g_Stream->cLastBlockSent == g_Stream->cLastBlockConfirmed)
{
SNDPROLOG Prolog;
TRC(ALV, "VCSndIoThread: no more data, closing the device\n");
_VCSndCloseConverter();
Prolog.Type = SNDC_CLOSE;
Prolog.BodySize = 0;
ChannelBlockWrite(&Prolog, sizeof(Prolog));
g_bDeviceOpened = FALSE;
}
VCSndReleaseStream();
}
exitpt:
ChannelClose();
if (NULL != hReadEvent)
CloseHandle(hReadEvent);
if (NULL != hDGramEvent)
WSACloseEvent( hDGramEvent );
if (SndMessage.pBody)
TSFREE(SndMessage.pBody);
if (NULL != hReconnectEvent)
CloseHandle(hReconnectEvent);
if (NULL != g_hDisconnectEvent)
CloseHandle(g_hDisconnectEvent);
if (NULL != g_hStreamIsEmptyEvent)
CloseHandle(g_hStreamIsEmptyEvent);
if (VCSndAcquireStream())
{
//
// mark the device dead
//
g_Stream->dwSoundCaps = TSSNDCAPS_TERMINATED;
VCSndReleaseStream();
_SignalInitializeDone();
}
if (NULL != g_Stream)
{
if (INVALID_SOCKET != g_hDGramSocket)
closesocket(g_hDGramSocket);
UnmapViewOfFile(g_Stream);
}
if (NULL != g_hStream)
CloseHandle(g_hStream);
if (NULL != g_hStreamMutex)
CloseHandle(g_hStreamMutex);
// clean the previously negotiated format
//
if (NULL != g_ppNegotiatedFormats)
{
DWORD i;
for ( i = 0; i < g_dwNegotiatedFormats; i++ )
{
if ( NULL != g_ppNegotiatedFormats[i] )
TSFREE( g_ppNegotiatedFormats[i] );
}
TSFREE( g_ppNegotiatedFormats );
}
//
// cleanup the format list
//
if ( NULL != g_pAllCodecsFormatList )
{
PVCSNDFORMATLIST pIter;
pIter = g_pAllCodecsFormatList;
while( NULL != pIter )
{
PVCSNDFORMATLIST pNext = pIter->pNext;
TSFREE( pIter );
pIter = pNext;
}
}
if ( NULL != g_AllowCodecs )
{
TSFREE( g_AllowCodecs );
g_AllowCodecs = NULL;
g_AllowCodecsSize = 0;
}
WSACleanup();
TRC(INF, "VCSndIoThread: EXIT !\n");
return 0;
}
/////////////////////////////////////////////////////////////////////
//
// Startup code
//
/////////////////////////////////////////////////////////////////////
VOID
TSSNDD_Term(
VOID
)
{
if ( NULL == g_hThread )
return;
g_bRunning = FALSE;
//
// kick the io thread
//
if (NULL != g_hDataReadyEvent)
SetEvent(g_hDataReadyEvent);
if ( NULL != g_hThread )
{
WaitForSingleObject(g_hThread, DEFAULT_VC_TIMEOUT);
CloseHandle(g_hThread);
g_hThread = NULL;
}
if (NULL != g_hDataReadyEvent)
{
CloseHandle(g_hDataReadyEvent);
g_hDataReadyEvent = NULL;
}
if ( NULL != g_hPowerWakeUpEvent )
{
CloseHandle( g_hPowerWakeUpEvent );
g_hPowerWakeUpEvent = NULL;
}
if ( NULL != g_hPowerSuspendEvent )
{
CloseHandle( g_hPowerSuspendEvent );
g_hPowerSuspendEvent = NULL;
}
}
LRESULT
TSSNDD_PowerMessage(
WPARAM wParam,
LPARAM lParam
)
{
switch( wParam )
{
case PBT_APMSUSPEND:
//
// signal only if connected
//
if ( NULL != g_hPowerSuspendEvent )
{
SetEvent( g_hPowerSuspendEvent );
}
break;
case PBT_APMRESUMEAUTOMATIC:
case PBT_APMRESUMECRITICAL:
case PBT_APMRESUMESUSPEND:
//
// signal only if not connected
//
if ( NULL != g_hPowerWakeUpEvent )
{
SetEvent( g_hPowerWakeUpEvent );
}
break;
}
return TRUE;
}
LRESULT
CALLBACK
_VCSndWndProc(
HWND hwnd,
UINT uiMessage,
WPARAM wParam,
LPARAM lParam
)
{
LRESULT rv = 0;
switch( uiMessage )
{
case WM_CREATE:
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_ENDSESSION:
TSSNDD_Term();
break;
case WM_POWERBROADCAST:
rv = TSSNDD_PowerMessage( wParam, lParam );
break;
default:
rv = DefWindowProc(hwnd, uiMessage, wParam, lParam);
}
return rv;
}
BOOL
TSSNDD_Loop(
HINSTANCE hInstance
)
{
BOOL rv = FALSE;
WNDCLASS wc;
DWORD dwLastErr;
HWND hWnd = NULL;
MSG msg;
memset(&wc, 0, sizeof(wc));
wc.lpfnWndProc = _VCSndWndProc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)GetStockObject(HOLLOW_BRUSH);
wc.lpszClassName = _RDPSNDWNDCLASS;
if (!RegisterClass (&wc) &&
(dwLastErr = GetLastError()) &&
dwLastErr != ERROR_CLASS_ALREADY_EXISTS)
{
TRC(ERR,
"TSSNDD_Loop: Can't register class. GetLastError=%d\n",
GetLastError());
goto exitpt;
}
hWnd = CreateWindow(
_RDPSNDWNDCLASS,
_RDPSNDWNDCLASS, // Window name
WS_OVERLAPPEDWINDOW, // dwStyle
0, // x
0, // y
100, // nWidth
100, // nHeight
NULL, // hWndParent
NULL, // hMenu
hInstance,
NULL); // lpParam
if (!hWnd)
{
TRC(ERR, "TSSNDD_Loop: Failed to create message window: %d\n",
GetLastError());
goto exitpt;
}
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
rv = TRUE;
exitpt:
return rv;
}
BOOL
TSSNDD_Init(
)
{
BOOL rv = FALSE;
DWORD dwThreadId;
g_bRunning = TRUE;
if ( NULL == g_hPowerWakeUpEvent )
{
g_hPowerWakeUpEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
if ( NULL == g_hPowerWakeUpEvent )
{
TRC( FATAL, "TSSNDD_Init: failed to create power wakeup notification message: %d\n", GetLastError() );
goto exitpt;
}
}
if ( NULL == g_hPowerSuspendEvent )
{
g_hPowerSuspendEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
if ( NULL == g_hPowerSuspendEvent )
{
TRC( FATAL, "TSSNDD_Init: failed to create power suspend notification message: %d\n", GetLastError() );
goto exitpt;
}
}
g_hThread = CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)VCSndIoThread,
NULL,
0,
&dwThreadId
);
if (NULL == g_hThread)
{
TRC(FATAL, "WinMain: can't create thread: %d. Aborting\n",
GetLastError());
goto exitpt;
}
rv = TRUE;
exitpt:
return rv;
}
/////////////////////////////////////////////////////////////////////
//
// Tracing
//
/////////////////////////////////////////////////////////////////////
VOID
_cdecl
_DebugMessage(
LPCSTR szLevel,
LPCSTR szFormat,
...
)
{
CHAR szBuffer[256];
va_list arglist;
if (szLevel == ALV)
return;
va_start (arglist, szFormat);
_vsnprintf (szBuffer, RTL_NUMBER_OF(szBuffer), szFormat, arglist);
va_end (arglist);
szBuffer[ RTL_NUMBER_OF( szBuffer ) - 1 ] = 0;
OutputDebugStringA(szLevel);
OutputDebugStringA(szBuffer);
}