windows-nt/Source/XPSP1/NT/net/tapi/skywalker/ipconf/msp/confcall.cpp
2020-09-26 16:20:57 +08:00

2943 lines
69 KiB
C++

/*++
Copyright (c) 1997 Microsoft Corporation
Module Name:
confcall.cpp
Abstract:
This module contains implementation of CIPConfMSPCall.
Author:
Mu Han (muhan) 5-September-1998
--*/
#include "stdafx.h"
#include <confpdu.h>
CIPConfMSPCall::CIPConfMSPCall()
: m_fLocalInfoRetrieved(FALSE),
m_fShutDown(FALSE),
m_dwIPInterface(INADDR_ANY),
m_LoopbackMode(MM_NO_LOOPBACK),
m_hAudioRTPSession(NULL),
m_hVideoRTPSession(NULL),
m_pIAudioDuplexController(NULL),
m_fCallStarted(FALSE),
m_pApplicationID(NULL),
m_pApplicationGUID(NULL),
m_pSubIDs(NULL),
m_pCallQCRelay(NULL)
{
ZeroMemory(m_InfoItems, sizeof(m_InfoItems));
}
CIPConfMSPCall::~CIPConfMSPCall()
{
if (m_pApplicationID)
{
SysFreeString(m_pApplicationID);
}
if (m_pApplicationGUID)
{
SysFreeString(m_pApplicationGUID);
}
if (m_pSubIDs)
{
SysFreeString(m_pSubIDs);
}
if (m_pCallQCRelay)
{
delete m_pCallQCRelay;
}
}
STDMETHODIMP CIPConfMSPCall::CreateStream(
IN long lMediaType,
IN TERMINAL_DIRECTION Direction,
IN OUT ITStream ** ppStream
)
{
// This MSP doesn't support creating new streams on the fly.
return TAPI_E_NOTSUPPORTED;
}
STDMETHODIMP CIPConfMSPCall::RemoveStream(
IN ITStream * pStream
)
{
// This MSP doesn't support removing streams either.
return TAPI_E_NOTSUPPORTED;
}
HRESULT CIPConfMSPCall::InitializeLocalParticipant()
/*++
Routine Description:
This function uses the RTP filter to find out the local information that
will be used in the call. The infomation is stored in a local participant
object.
Arguments:
Return Value:
HRESULT.
--*/
{
m_fLocalInfoRetrieved = FALSE;
// Create the RTP fitler.
IRtpSession *pIRtpSession;
HRESULT hr = CoCreateInstance(
__uuidof(MSRTPSourceFilter),
NULL,
CLSCTX_INPROC_SERVER | CLSCTX_NO_CODE_DOWNLOAD,
__uuidof(IRtpSession),
(void **) &pIRtpSession
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "can't create RTP filter for local info. %x", hr));
return hr;
}
// Get the available local SDES info from the filter.
WCHAR Buffer[MAX_PARTICIPANT_TYPED_INFO_LENGTH + 1];
for (int i = 0; i < NUM_SDES_ITEMS; i ++)
{
DWORD dwLen = MAX_PARTICIPANT_TYPED_INFO_LENGTH;
hr = pIRtpSession->GetSdesInfo(
RTPSDES_CNAME + i,
Buffer,
&dwLen,
0 // local participant
);
if (SUCCEEDED(hr) && dwLen > 0)
{
_ASSERT(dwLen <= MAX_PARTICIPANT_TYPED_INFO_LENGTH);
// allocate memory to store the string.
m_InfoItems[i] = (WCHAR *)malloc((dwLen) * sizeof(WCHAR));
if (m_InfoItems[i] == NULL)
{
LOG((MSP_ERROR, "out of mem for local info"));
pIRtpSession->Release();
return E_OUTOFMEMORY;
}
lstrcpynW(m_InfoItems[i], Buffer, dwLen);
}
}
pIRtpSession->Release();
m_fLocalInfoRetrieved = TRUE;
return S_OK;
}
HRESULT CIPConfMSPCall::Init(
IN CMSPAddress * pMSPAddress,
IN MSP_HANDLE htCall,
IN DWORD dwReserved,
IN DWORD dwMediaType
)
/*++
Routine Description:
This method is called when the call is first created. It sets
up the streams based on the mediatype specified.
Arguments:
pMSPAddress - The pointer to the address object.
htCall - The handle to the Call in TAPI's space.
Used in sending events.
dwReserved - Reserved.
dwMediaType - The media type of this call.
Return Value:
HRESULT.
--*/
{
LOG((MSP_TRACE,
"IPConfMSP call %x initialize entered,"
" pMSPAddress:%x, htCall %x, dwMediaType %x",
this, pMSPAddress, htCall, dwMediaType
));
#ifdef DEBUG_REFCOUNT
if (g_lStreamObjects != 0)
{
LOG((MSP_ERROR, "Number of Streams alive: %d", g_lStreamObjects));
// DebugBreak();
}
#endif
// initialize the participant array so that the array is not NULL.
if (!m_Participants.Grow())
{
LOG((MSP_ERROR, "out of mem for participant list"));
return E_OUTOFMEMORY;
}
// Call the base class's init.
HRESULT hr= CMSPCallMultiGraph::Init(
pMSPAddress,
htCall,
dwReserved,
dwMediaType
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "MSPCallMultiGraph init failed:%x", hr));
return hr;
}
// create the quality control relay for this call.
m_pCallQCRelay = new CCallQualityControlRelay ();
if (NULL == m_pCallQCRelay)
{
LOG((MSP_ERROR, "call init: failed to create call quality control relay:%x", hr));
return E_OUTOFMEMORY;
}
// initialize qc relay, a thread will be started
if (FAILED (hr = m_pCallQCRelay->Initialize (this)))
{
LOG ((MSP_ERROR, "call init: failed to initialize qc relay. %x", hr));
return hr;
}
// create streams based on the media types.
if (dwMediaType & TAPIMEDIATYPE_AUDIO)
{
ITStream * pStream;
// create a stream object.
hr = InternalCreateStream(TAPIMEDIATYPE_AUDIO, TD_RENDER, &pStream);
if (FAILED(hr))
{
LOG((MSP_ERROR, "create audio render stream failed:%x", hr));
return hr;
}
// The stream is already in our array, we don't need this pointer.
pStream->Release();
// create a stream object.
hr = InternalCreateStream(TAPIMEDIATYPE_AUDIO, TD_CAPTURE, &pStream);
if (FAILED(hr))
{
LOG((MSP_ERROR, "create audio capture stream failed:%x", hr));
return hr;
}
// The stream is already in our array, we don't need this pointer.
pStream->Release();
}
if (dwMediaType & TAPIMEDIATYPE_VIDEO)
{
ITStream * pStream;
// create a stream object.
hr = InternalCreateStream(TAPIMEDIATYPE_VIDEO, TD_RENDER, &pStream);
if (FAILED(hr))
{
LOG((MSP_ERROR, "create video render stream failed:%x", hr));
return hr;
}
// The stream is already in our array, we don't need this pointer.
pStream->Release();
// create a stream object.
hr = InternalCreateStream(TAPIMEDIATYPE_VIDEO, TD_CAPTURE, &pStream);
if (FAILED(hr))
{
LOG((MSP_ERROR, "create video capture stream failed:%x", hr));
return hr;
}
// The stream is already in our array, we don't need this pointer.
pStream->Release();
}
DWORD dwLoopback = 0;
if (TRUE == ::GetRegValue(gszMSPLoopback, &dwLoopback) && dwLoopback != 0)
{
m_LoopbackMode = MULTICAST_LOOPBACK_MODE(dwLoopback);
}
m_fShutDown = FALSE;
return S_OK;
}
HRESULT CIPConfMSPCall::ShutDown()
/*++
Routine Description:
Shutdown the call.
Arguments:
Return Value:
HRESULT.
--*/
{
InternalShutDown();
// acquire the lock on call.
m_lock.Lock();
for (int i = 0; i < NUM_SDES_ITEMS; i ++)
{
if (m_InfoItems[i])
{
free(m_InfoItems[i]);
m_InfoItems[i] = NULL;
}
}
m_lock.Unlock();
return S_OK;
}
HRESULT CIPConfMSPCall::InternalShutDown()
/*++
Routine Description:
First call the base class's shutdown and then release all the participant
objects.
Arguments:
Return Value:
HRESULT.
--*/
{
LOG((MSP_TRACE, "ConfMSPCall.InternalShutdown, entered"));
// acquire the lock on the call.
m_lock.Lock();
if (m_fShutDown)
{
LOG((MSP_TRACE, "ConfMSPCall::InterShutdown, already shutdown"));
m_lock.Unlock ();
return S_OK;
}
m_fShutDown = TRUE;
if (m_pCallQCRelay)
{
m_pCallQCRelay->Shutdown ();
}
int i;
// Shutdown all the streams
for (i = m_Streams.GetSize() - 1; i >= 0; i --)
{
UnregisterWaitEvent(i);
((CMSPStream*)m_Streams[i])->ShutDown();
}
m_ThreadPoolWaitBlocks.RemoveAll();
// release all the streams
for (i = m_Streams.GetSize() - 1; i >= 0; i --)
{
m_Streams[i]->Release();
}
m_Streams.RemoveAll();
if (m_pIAudioDuplexController)
{
m_pIAudioDuplexController->Release();
m_pIAudioDuplexController = NULL;
}
m_lock.Unlock();
// release all the participants
m_ParticipantLock.Lock();
for (i = 0; i < m_Participants.GetSize(); i ++)
{
m_Participants[i]->Release();
}
m_Participants.RemoveAll();
m_ParticipantLock.Unlock();
return S_OK;
}
template <class T>
HRESULT CreateStreamHelper(
IN T * pT,
IN HANDLE hAddress,
IN CIPConfMSPCall* pMSPCall,
IN IMediaEvent * pGraph,
IN DWORD dwMediaType,
IN TERMINAL_DIRECTION Direction,
OUT ITStream ** ppITStream
)
/*++
Routine Description:
Create a stream object and initialize it. This method is called internally
to create a stream object of different class.
Arguments:
hAddress - the handle to the address object.
pCall - the call object.
pGraph - the filter graph for this stream.
dwMediaType - the media type of the stream.
Direction - the direction of the steam.
ppITStream - the interface on this stream object.
Return Value:
HRESULT.
--*/
{
ENTER_FUNCTION ("CreateStreamHelper");
CComObject<T> * pCOMMSPStream;
HRESULT hr;
hr = ::CreateCComObjectInstance(&pCOMMSPStream);
if (NULL == pCOMMSPStream)
{
LOG((MSP_ERROR, "CreateMSPStream:could not create stream:%x", hr));
return hr;
}
// get the interface pointer.
hr = pCOMMSPStream->_InternalQueryInterface(
__uuidof(ITStream),
(void **)ppITStream
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "CreateMSPStream:QueryInterface failed: %x", hr));
delete pCOMMSPStream;
return hr;
}
// Initialize the object.
hr = pCOMMSPStream->Init(
hAddress,
pMSPCall,
pGraph,
dwMediaType,
Direction
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "CreateMSPStream:call init failed: %x", hr));
(*ppITStream)->Release();
return hr;
}
// retrieve inner call quality control
IInnerCallQualityControl * pIInnerCallQC;
if (FAILED (hr = pMSPCall->_InternalQueryInterface (
__uuidof (IInnerCallQualityControl),
(void **)&pIInnerCallQC
)))
{
LOG ((MSP_ERROR, "%s failed to retrieve inner call qc relay: %x", __fxName, hr));
(*ppITStream)->Release ();
return hr;
}
// retrieve inner stream quality control
IInnerStreamQualityControl *pIInnerStreamQC;
if (FAILED (hr = (*ppITStream)->QueryInterface (
__uuidof (IInnerStreamQualityControl),
(void **)&pIInnerStreamQC
)))
{
LOG ((MSP_ERROR, "%s failed to retrieve inner stream qc relay: %x", __fxName, hr));
pIInnerCallQC->Release ();
(*ppITStream)->Release ();
return hr;
}
// store inner call qc
if (FAILED (hr = pIInnerStreamQC->LinkInnerCallQC (pIInnerCallQC)))
{
LOG ((MSP_ERROR, "%s failed to setup inner call qc on stream, %x", __fxName, hr));
pIInnerCallQC->Release ();
pIInnerStreamQC->Release ();
(*ppITStream)->Release ();
return hr;
}
// register inner stream qc on the call
hr = pIInnerCallQC->RegisterInnerStreamQC (pIInnerStreamQC);
pIInnerStreamQC->Release ();
pIInnerCallQC->Release ();
if (FAILED (hr))
{
LOG ((MSP_ERROR, "%s failed to register inner stream qc relay: %x", __fxName, hr));
(*ppITStream)->Release ();
return hr;
}
return S_OK;
}
HRESULT CIPConfMSPCall::CreateStreamObject(
IN DWORD dwMediaType,
IN TERMINAL_DIRECTION Direction,
IN IMediaEvent * pGraph,
IN ITStream ** ppStream
)
/*++
Routine Description:
Create a media stream object based on the mediatype and direction.
Arguments:
pMediaType - TAPI3 media type.
Direction - direction of this stream.
IMediaEvent - The filter graph used in this stream.
ppStream - the return pointer of the stream interface
Return Value:
HRESULT.
--*/
{
LOG((MSP_TRACE, "CreateStreamObject, entered"));
HRESULT hr = S_OK;
ITStream * pIMSPStream = NULL;
// Create a stream object based on the media type.
if (dwMediaType == TAPIMEDIATYPE_AUDIO)
{
if (Direction == TD_RENDER)
{
CStreamAudioRecv *pAudioRecv = NULL;
hr = ::CreateStreamHelper(
pAudioRecv,
m_pMSPAddress,
this,
pGraph,
TAPIMEDIATYPE_AUDIO,
TD_RENDER,
&pIMSPStream
);
LOG((MSP_TRACE, "create audio receive:%x, hr:%x", pIMSPStream,hr));
if (FAILED(hr))
{
LOG((MSP_ERROR, "create stream failed. %x", hr));
return hr;
}
if (FAILED(hr = InitFullDuplexControler()))
{
LOG((MSP_ERROR, "Create full duplex controller failed. %x", hr));
}
else
{
((CStreamAudioRecv *)pIMSPStream)->
SetFullDuplexController(m_pIAudioDuplexController);
}
}
else if (Direction == TD_CAPTURE)
{
CStreamAudioSend *pAudioSend = NULL;
hr = ::CreateStreamHelper(
pAudioSend,
m_pMSPAddress,
this,
pGraph,
TAPIMEDIATYPE_AUDIO,
TD_CAPTURE,
&pIMSPStream
);
LOG((MSP_TRACE, "create audio send:%x, hr:%x", pIMSPStream,hr));
if (FAILED(hr))
{
LOG((MSP_ERROR, "create stream failed. %x", hr));
return hr;
}
if (FAILED(hr = InitFullDuplexControler()))
{
LOG((MSP_ERROR, "Create full duplex controller failed. %x", hr));
}
else
{
((CStreamAudioSend *)pIMSPStream)->
SetFullDuplexController(m_pIAudioDuplexController);
}
}
else
{
return TAPI_E_INVALIDDIRECTION;
}
}
else if (dwMediaType == TAPIMEDIATYPE_VIDEO)
{
if (Direction == TD_RENDER)
{
CStreamVideoRecv *pVideoRecv = NULL;
hr = ::CreateStreamHelper(
pVideoRecv,
m_pMSPAddress,
this,
pGraph,
TAPIMEDIATYPE_VIDEO,
TD_RENDER,
&pIMSPStream
);
LOG((MSP_TRACE, "create video Recv:%x, hr:%x", pIMSPStream,hr));
if (FAILED(hr))
{
LOG((MSP_ERROR, "create stream failed. %x", hr));
return hr;
}
}
else if (Direction == TD_CAPTURE)
{
CStreamVideoSend *pVideoSend = NULL;
hr = ::CreateStreamHelper(
pVideoSend,
m_pMSPAddress,
this,
pGraph,
TAPIMEDIATYPE_VIDEO,
TD_CAPTURE,
&pIMSPStream
);
LOG((MSP_TRACE, "create video send:%x, hr:%x", pIMSPStream,hr));
if (FAILED(hr))
{
LOG((MSP_ERROR, "create stream failed. %x", hr));
return hr;
}
}
else
{
return TAPI_E_INVALIDDIRECTION;
}
}
else
{
return TAPI_E_INVALIDMEDIATYPE;
}
*ppStream = pIMSPStream;
return S_OK;
}
DWORD CIPConfMSPCall::FindInterfaceByName(IN WCHAR *pMachineName)
/*++
Routine Description:
Given the machine name of the originator, find out which local interface
can be used to reach that machine.
Arguments:
pMachineName - The machine name of the originator.
Return Value:
INADDR_NONE - nothing can be found.
valid IP - succeeded.
--*/
{
char buffer[MAXIPADDRLEN + 1];
if (WideCharToMultiByte(
GetACP(),
0,
pMachineName,
-1,
buffer,
MAXIPADDRLEN,
NULL,
NULL
) == 0)
{
LOG((MSP_ERROR, "can't convert originator's address:%ws", pMachineName));
return INADDR_NONE;
}
DWORD dwAddr;
if ((dwAddr = inet_addr(buffer)) != INADDR_NONE)
{
dwAddr = ntohl(dwAddr);
LOG((MSP_INFO, "originator's IP:%x", dwAddr));
return ((CIPConfMSP *)m_pMSPAddress)->FindLocalInterface(dwAddr);
}
struct hostent * pHost;
// attempt to lookup hostname
pHost = gethostbyname(buffer);
// validate pointer
if (pHost == NULL)
{
LOG((MSP_WARN, "can't resolve address:%s", buffer));
return INADDR_NONE;
}
// for each of the addresses returned, find the local interface.
for (DWORD i = 0; TRUE; i ++)
{
if (pHost->h_addr_list[i] == NULL)
{
break;
}
// retrieve host address from structure
dwAddr = ntohl(*(unsigned long *)pHost->h_addr_list[i]);
LOG((MSP_INFO, "originator's IP:%x", dwAddr));
DWORD dwInterface =
((CIPConfMSP *)m_pMSPAddress)->FindLocalInterface(dwAddr);
if (dwInterface != INADDR_NONE)
{
return dwInterface;
}
}
return INADDR_NONE;
}
HRESULT CIPConfMSPCall::CheckOrigin(
IN ITSdp * pITSdp,
OUT BOOL * pFlag,
OUT DWORD * pdwIP
)
/*++
Routine Description:
Check to see if the current user is the originator of the conference.
If he is, he can send to a receive only conference.
Arguments:
pITSdp - a pointer to the ITSdp interface.
pFlag - The result.
pdwIP - The local IP interface that should be used to reach the originator.
Return Value:
HRESULT.
--*/
{
const DWORD MAXUSERNAMELEN = 127;
DWORD dwUserNameLen = MAXUSERNAMELEN;
WCHAR szUserName[MAXUSERNAMELEN+1];
// determine the name of the current user
if (!GetUserNameW(szUserName, &dwUserNameLen))
{
LOG((MSP_ERROR, "cant' get user name. %x", GetLastError()));
return E_UNEXPECTED;
}
LOG((MSP_INFO, "current user: %ws", szUserName));
// find out if the current user is the originator of the conference.
BSTR Originator = NULL;
HRESULT hr = pITSdp->get_Originator(&Originator);
if (FAILED(hr))
{
LOG((MSP_ERROR, "cant' get originator. %x", hr));
return hr;
}
LOG((MSP_INFO, "originator: %ws", Originator));
*pFlag = (_wcsnicmp(szUserName, Originator, lstrlenW(szUserName)) == 0);
SysFreeString(Originator);
// Get the machine IP address of the originator.
BSTR MachineAddress = NULL;
hr = pITSdp->get_MachineAddress(&MachineAddress);
if (FAILED(hr))
{
LOG((MSP_ERROR, "cant' get MachineAddress. %x", hr));
return hr;
}
LOG((MSP_INFO, "MachineAddress: %ws", MachineAddress));
DWORD dwIP = FindInterfaceByName(MachineAddress);
SysFreeString(MachineAddress);
*pdwIP = dwIP;
LOG((MSP_INFO, "Interface to use:%x", *pdwIP));
return S_OK;
}
HRESULT GetAddress(
IN IUnknown * pIUnknown,
OUT DWORD * pdwAddress,
OUT DWORD * pdwTTL,
OUT BSTR * ppKey,
OUT LONG * plBandwidth,
OUT LONG * plConfBandwidth = NULL
)
/*++
Routine Description:
Get the IP address and TTL value from a connection. It is a "c=" line
in the SDP blob.
Arguments:
pIUnknow - an object that might contain connection information.
pdwAddress - the mem address to store the IP address.
pdwTTL - the mem address to store the TTL value.
plBandwidth - maximum bandwidth
Return Value:
HRESULT.
--*/
{
// query for the ITConnection i/f
CComPtr<ITConnection> pITConnection;
HRESULT hr = pIUnknown->QueryInterface(__uuidof(ITConnection), (void **)&pITConnection);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get connection interface. %x", hr));
return hr;
}
// clear
if (plConfBandwidth != NULL) *plConfBandwidth = QCDEFAULT_QUALITY_UNSET;
// get bandwidth
const WCHAR * const AS = L"AS";
const WCHAR * const CT = L"CT";
BSTR pModifier = NULL;
DOUBLE bandwidth;
if (FAILED (hr = pITConnection->get_BandwidthModifier (&pModifier)))
{
*plBandwidth = QCDEFAULT_QUALITY_UNSET;
// bandwidth modifier may not be presented
// LOG ((MSP_TRACE, "get bandwidth modifiler. %x", hr));
}
else if (_wcsnicmp (AS, pModifier, lstrlenW (AS)) != 0)
{
// if not application specific
*plBandwidth = QCDEFAULT_QUALITY_UNSET;
// check conference-wide bandwidth limit
if (_wcsnicmp (CT, pModifier, lstrlenW (CT)) == 0)
{
if (plConfBandwidth)
{
if (FAILED (hr = pITConnection->get_Bandwidth (&bandwidth)))
{
*plConfBandwidth = QCDEFAULT_QUALITY_UNSET;
LOG ((MSP_ERROR, "get conf bandwidth. %x", hr));
}
else
*plConfBandwidth = (LONG)(bandwidth * 1000);
}
}
}
else if (FAILED (hr = pITConnection->get_Bandwidth (&bandwidth)))
{
*plBandwidth = QCDEFAULT_QUALITY_UNSET;
LOG ((MSP_ERROR, "get bandwidth. %x", hr));
}
else
*plBandwidth = (LONG)(bandwidth * 1000);
if (pModifier)
{
SysFreeString (pModifier);
pModifier = NULL;
}
// get the start address,
BSTR StartAddress = NULL;
hr = pITConnection->get_StartAddress(&StartAddress);
if (FAILED(hr))
{
LOG((MSP_WARN, "get start address. %x", hr));
return hr;
}
// Get the IP address from the string.
const DWORD MAXIPADDRLEN = 20;
char Buffer[MAXIPADDRLEN+1];
// first convert the string to ascii.
Buffer[0] = '\0';
if (!WideCharToMultiByte(
CP_ACP,
0,
StartAddress,
-1,
Buffer,
MAXIPADDRLEN,
NULL,
NULL
))
{
LOG((MSP_ERROR, "converting address. %ws", StartAddress));
SysFreeString(StartAddress);
return E_UNEXPECTED;
}
SysFreeString(StartAddress);
// convert the string to DWORD IP address.
DWORD dwIP = ntohl(inet_addr(Buffer));
if (dwIP == INADDR_NONE)
{
LOG((MSP_ERROR, "invalid IP address. %s", Buffer));
return E_UNEXPECTED;
}
// get the TTL value.
BYTE Ttl;
hr = pITConnection->get_Ttl(&Ttl);
if (FAILED(hr))
{
LOG((MSP_ERROR, "can't get TTL."));
return hr;
}
// get the Encryption key.
const WCHAR * const CLEAR = L"clear";
VARIANT_BOOL fKeyValid;
BSTR bstrKeyType = NULL;
if (*ppKey)
SysFreeString (*ppKey);
*ppKey = NULL;
if (FAILED (hr = pITConnection->GetEncryptionKey (&bstrKeyType, &fKeyValid, ppKey)))
{
LOG((MSP_WARN, "can't get EncryptionKey. %x", hr));
}
else if (_wcsnicmp (CLEAR, bstrKeyType, lstrlenW (CLEAR)) != 0)
{
if (*ppKey)
{
SysFreeString (*ppKey);
*ppKey = NULL;
}
}
if (bstrKeyType)
SysFreeString (bstrKeyType);
*pdwAddress = dwIP;
*pdwTTL = Ttl;
return S_OK;
}
HRESULT CheckAttributes(
IN IUnknown * pIUnknown,
OUT BOOL * pbSendOnly,
OUT BOOL * pbRecvOnly,
OUT DWORD * pdwMSPerPacket,
OUT BOOL * pbCIF
)
/*++
Routine Description:
Check the direction of the media, find out if it is send only or
receive only.
Arguments:
pIUnknow - an object that might have a attribute list.
pbSendOnly - the mem address to store the returned BOOL.
pbRecvOnly - the mem address to store the returned BOOL.
pbCIF - if CIF is used for video.
Return Value:
HRESULT.
--*/
{
// query for the ITAttributeList i/f
CComPtr<ITAttributeList> pIAttList;
HRESULT hr = pIUnknown->QueryInterface(__uuidof(ITAttributeList), (void **)&pIAttList);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get attribute interface. %x", hr));
return hr;
}
// get the number of attributes
long lCount;
hr = pIAttList->get_Count(&lCount);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get attribute count. %x", hr));
return hr;
}
*pbRecvOnly = FALSE;
*pbSendOnly = FALSE;
*pdwMSPerPacket = 0;
*pbCIF = FALSE;
const WCHAR * const SENDONLY = L"sendonly";
const WCHAR * const RECVONLY = L"recvonly";
const WCHAR * const FORMAT = L"fmtp";
const WCHAR * const PTIME = L"ptime:";
const WCHAR * const CIF = L" CIF=";
for (long i = 1; i <= lCount; i ++)
{
// get the attributes and check if sendonly of recvonly is specified.
BSTR Attribute = NULL;
hr = pIAttList->get_Item(i, &Attribute);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get attribute item. %x", hr));
return hr;
}
if (_wcsnicmp(SENDONLY, Attribute, lstrlen(SENDONLY)) == 0)
{
*pbSendOnly = TRUE;
}
else if (_wcsnicmp(RECVONLY, Attribute, lstrlen(RECVONLY)) == 0)
{
*pbRecvOnly = TRUE;
}
else if (_wcsnicmp(PTIME, Attribute, lstrlen(PTIME)) == 0)
{
// read the number of milliseconds per packet.
*pdwMSPerPacket = (DWORD)_wtol(Attribute + lstrlen(PTIME));
// RFC 1890 only requires an app to support 200ms packets.
if (*pdwMSPerPacket > 200)
{
// invalid tag, we just use our default.
*pdwMSPerPacket = 0;
}
}
else if (_wcsnicmp(FORMAT, Attribute, lstrlen(FORMAT)) == 0)
{
if (wcsstr(Attribute, CIF))
{
*pbCIF = TRUE;
}
}
SysFreeString(Attribute);
}
return S_OK;
}
HRESULT CIPConfMSPCall::ProcessMediaItem(
IN ITMedia * pITMedia,
IN DWORD dwMediaTypeMask,
OUT DWORD * pdwMediaType,
OUT WORD * pwPort,
OUT DWORD * pdwPayloadTypes,
IN OUT DWORD * pdwNumPayLoadType
)
/*++
Routine Description:
Process a "m=" line, find out the media type, port, and payload type.
Arguments:
dwMediaTypeMask - the media type of this call.
pdwMediaType - return the media type of this media item.
pwPort - return the port number used for this media.
pdwPayloadType - an array to store the RTP payload types.
pdwNumPayLoadType - The size of the above array. When return, it is the
number of payload types read.
Return Value:
HRESULT.
S_FALSE - everything is all right but the media type is not needed.
--*/
{
// get the name of the media.
BSTR MediaName = NULL;
HRESULT hr = pITMedia->get_MediaName(&MediaName);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get media name. %x", hr));
return hr;
}
LOG((MSP_INFO, "media name: %ws", MediaName));
// check if the media is audio or video.
const WCHAR * const AUDIO = L"audio";
const WCHAR * const VIDEO = L"video";
const DWORD NAMELEN = 5;
DWORD dwMediaType = 0;
if (_wcsnicmp(AUDIO, MediaName, NAMELEN) == 0)
{
dwMediaType = TAPIMEDIATYPE_AUDIO;
}
else if (_wcsnicmp(VIDEO, MediaName, NAMELEN) == 0)
{
dwMediaType = TAPIMEDIATYPE_VIDEO;
}
SysFreeString(MediaName);
// check if the call wants this media type.
if ((dwMediaType & dwMediaTypeMask) == 0)
{
// We don't need this media type in this call.
LOG((MSP_INFO, "media skipped."));
return S_FALSE;
}
// get start port
long lStartPort;
hr = pITMedia->get_StartPort(&lStartPort);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get start port. %x", hr));
return hr;
}
// get the transport Protocol
BSTR TransportProtocol = NULL;
hr = pITMedia->get_TransportProtocol(&TransportProtocol);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get transport Protocol. %x", hr));
return hr;
}
// varify that the protocol is RTP.
const WCHAR * const RTP = L"RTP";
const DWORD PROTOCOLLEN = 3;
if (_wcsnicmp(RTP, TransportProtocol, PROTOCOLLEN) != 0)
{
LOG((MSP_ERROR, "wrong transport Protocol:%ws", TransportProtocol));
SysFreeString(TransportProtocol);
return S_FALSE;
}
SysFreeString(TransportProtocol);
// get the format code list
VARIANT Variant;
VariantInit(&Variant);
hr = pITMedia->get_FormatCodes(&Variant);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get format codes. %x", hr));
return hr;
}
// Verify that the SafeArray is in proper shape.
if(SafeArrayGetDim(V_ARRAY(&Variant)) != 1)
{
LOG((MSP_ERROR, "wrong dimension for the format code. %x", hr));
VariantClear(&Variant);
return E_UNEXPECTED;
}
long lLowerBound;
long lUpperBound;
if (FAILED(hr = SafeArrayGetLBound(V_ARRAY(&Variant), 1, &lLowerBound))
|| FAILED(hr = SafeArrayGetUBound(V_ARRAY(&Variant), 1, &lUpperBound)))
{
LOG((MSP_ERROR, "Can't get the array bounds. %x", hr));
VariantClear(&Variant);
return E_UNEXPECTED;
}
DWORD dwNumFormats = 0;
for (long l = lLowerBound; l <= lUpperBound && dwNumFormats < *pdwNumPayLoadType; l ++)
{
BSTR Format = NULL;
hr = SafeArrayGetElement(V_ARRAY(&Variant), &l, &Format);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get format code. %x", hr));
continue;
}
LOG((MSP_INFO, "format code: %ws", Format));
pdwPayloadTypes[dwNumFormats ++] = (DWORD)_wtoi(Format);
SysFreeString(Format);
}
// clear the variant because we don't need it any more
VariantClear(&Variant);
*pdwMediaType = dwMediaType;
*pwPort = (WORD)lStartPort;
*pdwNumPayLoadType = dwNumFormats;
return S_OK;
}
HRESULT CIPConfMSPCall::ConfigStreamsBasedOnSDP(
IN ITSdp * pITSdp,
IN DWORD dwAudioQOSLevel,
IN DWORD dwVideoQOSLevel
)
/*++
Routine Description:
Configure the streams based on the information in the SDP blob.
Arguments:
pITSdp - the SDP object. It contains parsed information.
Return Value:
HRESULT.
--*/
{
// find out if the current user is the originator of the conference.
BOOL fIsOriginator;
DWORD dwLocalInterface = INADDR_NONE;
HRESULT hr = CheckOrigin(pITSdp, &fIsOriginator, &dwLocalInterface);
if (FAILED(hr))
{
LOG((MSP_ERROR, "check origin. %x", hr));
return hr;
}
LOG((MSP_INFO, "Local interface: %x", dwLocalInterface));
// get the start IP address and TTL value from the connection.
DWORD dwIPGlobal, dwTTLGlobal;
BSTR bstrKeyGlobal = NULL;
LONG lbandwidth, lConfBandwidth;
hr = GetAddress(pITSdp, &dwIPGlobal, &dwTTLGlobal, &bstrKeyGlobal, &lbandwidth, &lConfBandwidth);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get global address. %x", hr));
return hr;
}
CLock lock(m_lock);
// store conference bandwidth
if (FAILED (m_pCallQCRelay->SetConfBitrate (lConfBandwidth)))
{
LOG ((MSP_ERROR, "bandwidth is out of range %d", lConfBandwidth));
}
// find out if this conference is sendonly or recvonly.
BOOL fSendOnlyGlobal = FALSE, fRecvOnlyGlobal = FALSE, fCIF = FALSE;
DWORD dwMSPerPacket;
hr = CheckAttributes(
pITSdp, &fSendOnlyGlobal, &fRecvOnlyGlobal, &dwMSPerPacket, &fCIF);
if (FAILED(hr))
{
LOG((MSP_ERROR, "check global attributes. %x", hr));
return hr;
}
// get the media information
CComPtr<ITMediaCollection> pICollection;
hr = pITSdp->get_MediaCollection(&pICollection);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get the media collection. %x", hr));
return hr;
}
// find out how many media sessions are in the blobl.
long lCount;
hr = pICollection->get_Count(&lCount);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get number of media items. %x", hr));
return hr;
}
if (lCount > 0)
{
// change the call into connected state since the SDP is OK.
// We are going to set up each every streams next.
SendTSPMessage(CALL_CONNECTED, 0);
}
DWORD dwNumSucceeded = 0;
// for each media session, get info configure a stream.
for(long i=1; i <= lCount; i++)
{
// get the media item first.
ITMedia *pITMedia;
hr = pICollection->get_Item(i, &pITMedia);
if (FAILED(hr))
{
LOG((MSP_ERROR, "get media item. %x", hr));
continue;
}
DWORD dwMediaType;
STREAMSETTINGS Setting;
ZeroMemory(&Setting, sizeof(STREAMSETTINGS));
// find out the information about the media. Here we pass in the media
// type of call so that we won't wasting time reading the attributes
// for a media type we don't need.
DWORD dwNumPayloadTypes = sizeof(Setting.PayloadTypes)
/ sizeof(Setting.PayloadTypes[0]);
hr = ProcessMediaItem(
pITMedia,
m_dwMediaType,
&dwMediaType,
&Setting.wRTPPortRemote,
Setting.PayloadTypes,
&dwNumPayloadTypes
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "process media. %x", hr));
continue;
}
Setting.dwNumPayloadTypes = dwNumPayloadTypes;
// if the return value is S_FALSE from the previous call, this media
// type is not needed for the call.
if (hr != S_OK)
{
// the media is not needed.
continue;
}
if (dwMediaType == TAPIMEDIATYPE_AUDIO)
{
Setting.dwQOSLevel = dwAudioQOSLevel;
Setting.phRTPSession = &m_hAudioRTPSession;
}
else
{
Setting.dwQOSLevel = dwVideoQOSLevel;
Setting.phRTPSession = &m_hVideoRTPSession;
}
// Get the local connect information.
DWORD dwIP, dwTTL;
BSTR bstrKey = NULL;
hr = GetAddress(pITMedia, &dwIP, &dwTTL, &bstrKey, &lbandwidth);
if (FAILED(hr))
{
LOG((MSP_WARN, "no local address, use global one", hr));
Setting.dwIPRemote = dwIPGlobal;
Setting.dwTTL = dwTTLGlobal;
Setting.lBandwidth = QCDEFAULT_QUALITY_UNSET;
}
else
{
Setting.dwIPRemote = dwIP;
Setting.dwTTL = dwTTL;
Setting.lBandwidth = lbandwidth;
}
// find out if this media is sendonly or recvonly.
BOOL fSendOnly = FALSE, fRecvOnly = FALSE, fCIF = FALSE;
hr = CheckAttributes(
pITMedia, &fSendOnly, &fRecvOnly, &dwMSPerPacket, &fCIF);
if (FAILED(hr))
{
LOG((MSP_ERROR, "check local attributes. %x", hr));
}
fSendOnly = fSendOnly || fSendOnlyGlobal;
fRecvOnly = (fRecvOnly || fRecvOnlyGlobal) && (!fIsOriginator);
Setting.dwMSPerPacket = dwMSPerPacket;
Setting.fCIF = fCIF;
// The media item is not needed after this point.
pITMedia->Release();
// Go through the existing streams and find out if any stream
// can be configured.
// Note: we are not creating any new streams now. We might want to
// do it in the future if we want to support two sessions of the
// same media type.
m_fCallStarted = TRUE;
for (long j = 0; j < m_Streams.GetSize(); j ++)
{
CIPConfMSPStream* pStream = (CIPConfMSPStream*)m_Streams[j];
if ((pStream->MediaType() != dwMediaType)
|| pStream->IsConfigured()
|| (fSendOnly && pStream->Direction() == TD_RENDER)
|| (fRecvOnly && pStream->Direction() == TD_CAPTURE)
)
{
// this stream should not be configured.
continue;
}
// set the local interface that the call should bind to.
Setting.dwIPLocal = m_dwIPInterface;
if ((m_dwIPInterface == INADDR_ANY)
&& (dwLocalInterface != INADDR_NONE))
{
Setting.dwIPLocal = dwLocalInterface;
}
// set the loopback mode of the stream.
Setting.LoopbackMode = m_LoopbackMode;
// set the qos application IDS.
Setting.pApplicationID = m_pApplicationID;
Setting.pApplicationGUID = m_pApplicationGUID;
Setting.pSubIDs = m_pSubIDs;
// configure the stream, it will not be started.
hr = pStream->Configure(Setting, (bstrKey) ? bstrKey : bstrKeyGlobal);
if (FAILED(hr))
{
LOG((MSP_ERROR, "configure stream failed. %x", hr));
}
}
SysFreeString(bstrKey);
}
SysFreeString(bstrKeyGlobal);
// after configuring the streams, start them.
for (int j = 0; j < m_Streams.GetSize(); j ++)
{
CIPConfMSPStream* pStream = (CIPConfMSPStream*)m_Streams[j];
// start the stream.
hr = pStream->FinishConfigure();
if (SUCCEEDED(hr))
{
dwNumSucceeded ++;
}
}
if (dwNumSucceeded == 0)
{
LOG((MSP_ERROR, "No media succeeded."));
return E_FAIL;
}
return S_OK;
}
HRESULT CIPConfMSPCall::ParseSDP(
IN WCHAR * pSDP,
IN DWORD dwAudioQOSLevel,
IN DWORD dwVideoQOSLevel
)
/*++
Routine Description:
Parse the SDP string. The function uses the SdpConferenceBlob object
to parse the string.
Arguments:
pSDP - the SDP string.
dwAudioQOSLevel - the QOS requirement for audio.
dwVideoQOSLevel - the QOS requirement for video.
Return Value:
HRESULT.
--*/
{
// co-create an sdp conference blob component
// query for the ITConferenceBlob interface
CComPtr<ITConferenceBlob> pIConfBlob;
HRESULT hr = ::CoCreateInstance(
CLSID_SdpConferenceBlob,
NULL,
CLSCTX_INPROC_SERVER | CLSCTX_NO_CODE_DOWNLOAD,
__uuidof(ITConferenceBlob),
(void **)&pIConfBlob
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "creating a SDPBlob object. %x", hr));
return hr;
}
// conver the sdp into a BSTR to use the interface.
BSTR bstrSDP = SysAllocString(pSDP);
if (bstrSDP == NULL)
{
LOG((MSP_ERROR, "out of mem converting SDP to a BSTR."));
return E_OUTOFMEMORY;
}
// Parse the SDP string.
hr = pIConfBlob->Init(NULL, BCS_ASCII, bstrSDP);
// the string is not needed any more.
SysFreeString(bstrSDP);
if (FAILED(hr))
{
LOG((MSP_ERROR, "parse the SDPBlob object. %x", hr));
return hr;
}
// Get the ITSdp interface.
CComPtr<ITSdp> pITSdp;
hr = pIConfBlob->QueryInterface(__uuidof(ITSdp), (void **)&pITSdp);
if (FAILED(hr))
{
LOG((MSP_ERROR, "can't get the ITSdp interface. %x", hr));
return hr;
}
// check main sdp validity
VARIANT_BOOL IsValid;
hr = pITSdp->get_IsValid(&IsValid);
if (FAILED(hr))
{
LOG((MSP_ERROR, "can't get the valid flag on the SDP %x", hr));
return hr;
}
if (!IsValid)
{
LOG((MSP_ERROR, "the SDP is not valid %x", hr));
return E_FAIL;
}
return ConfigStreamsBasedOnSDP(
pITSdp,
dwAudioQOSLevel,
dwVideoQOSLevel
);
}
HRESULT CIPConfMSPCall::SendTSPMessage(
IN TSP_MSP_COMMAND command,
IN DWORD dwParam1,
IN DWORD dwParam2
) const
/*++
Routine Description:
Send the TSP a message from the MSP.
Arguments:
command - the command to be sent.
dwParam1 - the first DWORD used in the command.
dwParam2 - the second DWORD used in the command.
Return Value:
HRESULT.
--*/
{
LOG((MSP_TRACE, "SendTSPMessage, command %d, dwParam1 %d, dwParam2",
command, dwParam1, dwParam2));
// first allocate the memory.
MSPEVENTITEM* pEventItem = AllocateEventItem(sizeof(MSG_TSPMSPDATA));
if (pEventItem == NULL)
{
LOG((MSP_ERROR, "No memory for the TSPMSP data"));
return E_OUTOFMEMORY;
}
// Fill in the necessary fields for the event structure.
pEventItem->MSPEventInfo.dwSize =
sizeof(MSP_EVENT_INFO) + sizeof(MSG_TSPMSPDATA);
pEventItem->MSPEventInfo.Event = ME_TSP_DATA;
pEventItem->MSPEventInfo.hCall = m_htCall;
// Fill in the data for the TSP.
pEventItem->MSPEventInfo.MSP_TSP_DATA.dwBufferSize = sizeof(MSG_TSPMSPDATA);
MSG_TSPMSPDATA *pData = (MSG_TSPMSPDATA *)
pEventItem->MSPEventInfo.MSP_TSP_DATA.pBuffer;
pData->command = command;
switch (command)
{
case CALL_DISCONNECTED:
pData->CallDisconnected.dwReason = dwParam1;
break;
case CALL_QOS_EVENT:
pData->QosEvent.dwEvent = dwParam1;
pData->QosEvent.dwMediaMode = dwParam2;
break;
case CALL_CONNECTED:
break;
default:
LOG((MSP_ERROR, "Wrong command type for TSP"));
FreeEventItem(pEventItem);
return E_UNEXPECTED;
}
HRESULT hr = m_pMSPAddress->PostEvent(pEventItem);
if (FAILED(hr))
{
LOG((MSP_ERROR, "Post event failed %x", hr));
FreeEventItem(pEventItem);
return hr;
}
return S_OK;
}
HRESULT CIPConfMSPCall::CheckUnusedStreams()
/*++
Routine Description:
Find out which streams are not used and send tapi events about them.
Arguments:
Return Value:
HRESULT.
--*/
{
LOG((MSP_TRACE, "CheckUnusedStreams"));
CLock lock(m_lock);
for (long j = 0; j < m_Streams.GetSize(); j ++)
{
CIPConfMSPStream* pStream = (CIPConfMSPStream*)m_Streams[j];
if (pStream->IsConfigured())
{
// find the next.
continue;
}
MSPEVENTITEM* pEventItem = AllocateEventItem();
if (pEventItem == NULL)
{
LOG((MSP_ERROR, "No memory for the TSPMSP data"));
return E_OUTOFMEMORY;
}
// Fill in the necessary fields for the event structure.
pEventItem->MSPEventInfo.dwSize = sizeof(MSP_EVENT_INFO);;
pEventItem->MSPEventInfo.Event = ME_CALL_EVENT;
pEventItem->MSPEventInfo.hCall = m_htCall;
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.Type = CALL_STREAM_NOT_USED;
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.Cause = CALL_CAUSE_REMOTE_REQUEST;
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.pStream = m_Streams[j];
// Addref to prevent it from going away.
m_Streams[j]->AddRef();
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.pTerminal = NULL;
pEventItem->MSPEventInfo.MSP_CALL_EVENT_INFO.hrError= 0;
// send the event to tapi.
HRESULT hr = m_pMSPAddress->PostEvent(pEventItem);
if (FAILED(hr))
{
LOG((MSP_ERROR, "Post event failed %x", hr));
FreeEventItem(pEventItem);
return hr;
}
}
return S_OK;
}
DWORD WINAPI CIPConfMSPCall::WorkerCallbackDispatcher(VOID *pContext)
/*++
Routine Description:
Because Parsing the SDP and configure the streams uses a lot of COM
stuff, we can't rely on the RPC thread the calls into the MSP to
receive the TSP data. So, we let our own working thread do the work.
This method is the callback function for the queued work item. It
just gets the call object from the context structure and calls a method
on the call object to handle the work item.
Arguments:
pContext - A pointer to a CALLWORKITEM structure.
Return Value:
HRESULT.
--*/
{
_ASSERTE(!IsBadReadPtr(pContext, sizeof CALLWORKITEM));
CALLWORKITEM *pItem = (CALLWORKITEM *)pContext;
pItem->pCall->ProcessWorkerCallBack(pItem->Buffer, pItem->dwLen);
pItem->pCall->MSPCallRelease();
free(pItem);
return NOERROR;
}
DWORD CIPConfMSPCall::ProcessWorkerCallBack(
IN PBYTE pBuffer,
IN DWORD dwSize
)
/*++
Routine Description:
This function handles the work item given by the TSP.
Arguments:
pBuffer - a buffer that contains a TSP_MSP command block.
dwSize - the size of the buffer.
Return Value:
NOERROR.
--*/
{
LOG((MSP_TRACE, "PreocessWorkerCallBAck"));
_ASSERTE(!IsBadReadPtr(pBuffer, dwSize));
MSG_TSPMSPDATA * pData = (MSG_TSPMSPDATA *)pBuffer;
HRESULT hr;
switch (pData->command)
{
case CALL_START:
// Parse the SDP contained in the command block.
hr = ParseSDP(pData->CallStart.szSDP,
pData->CallStart.dwAudioQOSLevel,
pData->CallStart.dwVideoQOSLevel
);
if (FAILED(hr))
{
// disconnect the call if someting terrible happend.
SendTSPMessage(CALL_DISCONNECTED, 0);
LOG((MSP_ERROR, "parsing theSDPBlob object. %x", hr));
return NOERROR;
}
// go through the streams and send events if they are not used.
hr = CheckUnusedStreams();
if (FAILED(hr))
{
LOG((MSP_ERROR, "start the streams failed. %x", hr));
}
break;
case CALL_STOP:
InternalShutDown();
break;
}
return NOERROR;
}
HRESULT CIPConfMSPCall::ReceiveTSPCallData(
IN PBYTE pBuffer,
IN DWORD dwSize
)
/*++
Routine Description:
This function handles the work item given by the TSP.
Arguments:
pBuffer - a buffer that contains a TSP_MSP command block.
dwSize - the size of the buffer.
Return Value:
NOERROR.
--*/
{
LOG((MSP_TRACE,
"ReceiveTSPCallData, pBuffer %x, dwSize %d", pBuffer, dwSize));
MSG_TSPMSPDATA * pData = (MSG_TSPMSPDATA *)pBuffer;
switch (pData->command)
{
case CALL_START:
// make sure the string is valid.
if ((IsBadReadPtr(pData->CallStart.szSDP,
(pData->CallStart.dwSDPLen + 1) * sizeof (WCHAR)))
|| (pData->CallStart.szSDP[pData->CallStart.dwSDPLen] != 0))
{
LOG((MSP_ERROR, "the TSP data is invalid."));
return E_UNEXPECTED;
}
LOG((MSP_INFO, "SDP string\n%ws", pData->CallStart.szSDP));
break;
case CALL_STOP:
break;
default:
LOG((MSP_ERROR,
"wrong command received from the TSP:%x", pData->command));
return E_UNEXPECTED;
}
// allocate a work item structure for our worker thread.
CALLWORKITEM *pItem = (CALLWORKITEM *)malloc(sizeof(CALLWORKITEM) + dwSize);
if (pItem == NULL)
{
// Disconnect the call because of out of memory.
SendTSPMessage(CALL_DISCONNECTED, 0);
LOG((MSP_ERROR, "out of memory for work item."));
return E_OUTOFMEMORY;
}
this->MSPCallAddRef();
pItem->pCall = this;
pItem->dwLen = dwSize;
CopyMemory(pItem->Buffer, pBuffer, dwSize);
// post a work item to our worker thread.
HRESULT hr = g_Thread.QueueWorkItem(
WorkerCallbackDispatcher, // the callback
pItem, // the context.
FALSE // sync (FALSE means asyn)
);
if (FAILED(hr))
{
if (pData->command == CALL_START)
{
// Disconnect the call because we can't handle the work.
SendTSPMessage(CALL_DISCONNECTED, 0);
}
this->MSPCallRelease();
free(pItem);
LOG((MSP_ERROR, "queue work item failed."));
}
return hr;
}
STDMETHODIMP CIPConfMSPCall::EnumerateParticipants(
OUT IEnumParticipant ** ppEnumParticipant
)
/*++
Routine Description:
This method returns an enumerator to the participants.
Arguments:
ppEnumParticipant - the memory location to store the returned pointer.
Return Value:
S_OK
E_POINTER
E_OUTOFMEMORY
--*/
{
LOG((MSP_TRACE,
"EnumerateParticipants entered. ppEnumParticipant:%p", ppEnumParticipant));
//
// Check parameters.
//
if (IsBadWritePtr(ppEnumParticipant, sizeof(VOID *)))
{
LOG((MSP_ERROR, "CIPConfMSPCall::EnumerateParticipants - "
"bad pointer argument - exit E_POINTER"));
return E_POINTER;
}
//
// First see if this call has been shut down.
// acquire the lock before accessing the Participant object list.
//
CLock lock(m_ParticipantLock);
if (m_Participants.GetData() == NULL)
{
LOG((MSP_ERROR, "CIPConfMSPCall::EnumerateParticipants - "
"call appears to have been shut down - exit E_UNEXPECTED"));
// This call has been shut down.
return E_UNEXPECTED;
}
//
// Create an enumerator object.
//
HRESULT hr = CreateParticipantEnumerator(
m_Participants.GetData(), // the begin itor
m_Participants.GetData() + m_Participants.GetSize(), // the end itor,
ppEnumParticipant
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "CIPConfMSPCall::EnumerateParticipants - "
"create enumerator object failed, %x", hr));
return hr;
}
LOG((MSP_TRACE, "CIPConfMSPCall::EnumerateParticipants - exit S_OK"));
return hr;
}
STDMETHODIMP CIPConfMSPCall::get_Participants(
OUT VARIANT * pVariant
)
{
LOG((MSP_TRACE, "CIPConfMSPCall::get_Participants - enter"));
//
// Check parameters.
//
if ( IsBadWritePtr(pVariant, sizeof(VARIANT) ) )
{
LOG((MSP_ERROR, "CIPConfMSPCall::get_Participants - "
"bad pointer argument - exit E_POINTER"));
return E_POINTER;
}
//
// See if this call has been shut down. Acquire the lock before accessing
// the Participant object list.
//
CLock lock(m_ParticipantLock);
if (m_Participants.GetData() == NULL)
{
LOG((MSP_ERROR, "CIPConfMSPCall::get_Participants - "
"call appears to have been shut down - exit E_UNEXPECTED"));
// This call has been shut down.
return E_UNEXPECTED;
}
//
// create the collection object - see mspcoll.h
//
HRESULT hr = CreateParticipantCollection(
m_Participants.GetData(), // the begin itor
m_Participants.GetData() + m_Participants.GetSize(), // the end itor,
m_Participants.GetSize(), // the size
pVariant
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "CIPConfMSPCall::get_Participants - "
"create collection failed - exit 0x%08x", hr));
return hr;
}
LOG((MSP_TRACE, "CIPConfMSPCall::get_Participants - exit S_OK"));
return S_OK;
}
// IMulticastControl methods
STDMETHODIMP CIPConfMSPCall::get_LoopbackMode (
OUT MULTICAST_LOOPBACK_MODE * pMode
)
{
if (pMode == NULL)
{
return E_INVALIDARG;
}
*pMode = m_LoopbackMode;
return S_OK;
}
STDMETHODIMP CIPConfMSPCall::put_LoopbackMode (
IN MULTICAST_LOOPBACK_MODE mode
)
{
if (mode < MM_NO_LOOPBACK || mode > MM_SELECTIVE_LOOPBACK)
{
return E_INVALIDARG;
}
m_LoopbackMode = mode;
return S_OK;
}
// ITLocalParticipant methods, called by the app.
STDMETHODIMP CIPConfMSPCall::get_LocalParticipantTypedInfo(
IN PARTICIPANT_TYPED_INFO InfoType,
OUT BSTR * ppInfo
)
/*++
Routine Description:
Get a information item for the local participant. This information is
sent out to other participants in the conference.
Arguments:
InfoType - The type of the information asked.
ppInfo - the mem address to store a BSTR.
Return Value:
S_OK,
E_INVALIDARG,
E_POINTER,
E_OUTOFMEMORY,
TAPI_E_NOITEMS
*/
{
LOG((MSP_TRACE, "CParticipant get info, type:%d", InfoType));
if (InfoType > PTI_PRIVATE || InfoType < PTI_CANONICALNAME)
{
LOG((MSP_ERROR, "CParticipant get info - invalid type:%d", InfoType));
return E_INVALIDARG;
}
if (IsBadWritePtr(ppInfo, sizeof(BSTR)))
{
LOG((MSP_ERROR, "CParticipant get info - exit E_POINTER"));
return E_POINTER;
}
// check if we have that info.
CLock lock(m_lock);
if (!m_fLocalInfoRetrieved)
{
HRESULT hr = InitializeLocalParticipant();
if (FAILED(hr))
{
return hr;
}
}
int index = (int)InfoType;
if (m_InfoItems[index] == NULL)
{
LOG((MSP_INFO, "no local participant info item for %d", InfoType));
return TAPI_E_NOITEMS;
}
// make a BSTR out of it.
BSTR pName = SysAllocString(m_InfoItems[index]);
if (pName == NULL)
{
LOG((MSP_ERROR, "CParticipant get info - exit out of mem"));
return E_POINTER;
}
// return the BSTR.
*ppInfo = pName;
return S_OK;
}
// ITLocalParticipant methods, called by the app.
STDMETHODIMP CIPConfMSPCall::put_LocalParticipantTypedInfo(
IN PARTICIPANT_TYPED_INFO InfoType,
IN BSTR pInfo
)
/*++
Routine Description:
Set a information item for the local participant. This information is
sent out to other participants in the conference.
Arguments:
InfoType - The type of the information item.
pInfo - the information item.
Return Value:
S_OK,
E_INVALIDARG,
E_POINTER,
E_OUTOFMEMORY,
TAPI_E_NOITEMS
*/
{
LOG((MSP_TRACE, "set local info, type:%d", InfoType));
if (InfoType > PTI_PRIVATE || InfoType < PTI_CANONICALNAME)
{
LOG((MSP_ERROR, "set local info - invalid type:%d", InfoType));
return E_INVALIDARG;
}
if (IsBadStringPtr(pInfo, MAX_PARTICIPANT_TYPED_INFO_LENGTH))
{
LOG((MSP_ERROR, "set local info, bad ptr:%p", pInfo));
return E_POINTER;
}
DWORD dwStringLen = lstrlenW(pInfo);
if (dwStringLen > MAX_PARTICIPANT_TYPED_INFO_LENGTH)
{
LOG((MSP_ERROR, "local info too long"));
return E_INVALIDARG;
}
// check if we have that info.
CLock lock(m_lock);
if (m_fCallStarted)
{
return TAPI_E_INVALCALLSTATE;
}
if (!m_fLocalInfoRetrieved)
{
HRESULT hr = InitializeLocalParticipant();
if (FAILED(hr))
{
return hr;
}
}
int index = (int)InfoType;
if (m_InfoItems[index] != NULL)
{
if (lstrcmpW(m_InfoItems[index], pInfo) == 0)
{
// The info is the same as what we are using.
return S_OK;
}
// the infomation is different, release the old info.
free(m_InfoItems[index]);
m_InfoItems[index] = NULL;
}
// save the info.
m_InfoItems[index] = (WCHAR *)malloc((dwStringLen + 1)* sizeof(WCHAR));
if (m_InfoItems[index] == NULL)
{
LOG((MSP_ERROR, "out of mem for local info"));
return E_OUTOFMEMORY;
}
lstrcpynW(m_InfoItems[index], pInfo, dwStringLen + 1);
//
// The info is new, we need to set it on the streams.
//
for (int i = 0; i < m_Streams.GetSize(); i ++)
{
((CIPConfMSPStream*)m_Streams[i])->SetLocalParticipantInfo(
InfoType,
m_InfoItems[index],
dwStringLen
);
}
return S_OK;
}
STDMETHODIMP CIPConfMSPCall::SetQOSApplicationID (
IN BSTR pApplicationID,
IN BSTR pApplicationGUID,
IN BSTR pSubIDs
)
/*++
Routine Description:
This method is called by the App to set the QOS specific application ID.
It can only be called before the call is connected.
Arguments:
pApplicationID - the Application ID.
pSubIDs - the SubIDs that will be appended to the end of policy locator.
Return Value:
S_OK
E_OUTOFMEMORY
--*/
{
CLock lock(m_lock);
if (m_fCallStarted)
{
return TAPI_E_INVALCALLSTATE;
}
if (pSubIDs!=NULL && lstrlenW(pSubIDs)>MAX_QOS_ID_LEN)
{
return E_INVALIDARG;
}
if (pApplicationID!=NULL && lstrlenW(pApplicationID)>MAX_QOS_ID_LEN)
{
return E_INVALIDARG;
}
try
{
if (m_pApplicationID) SysFreeString(m_pApplicationID);
m_pApplicationID = SysAllocString(pApplicationID);
}
catch(...)
{
return E_POINTER;
}
if (m_pApplicationID == NULL)
{
return E_OUTOFMEMORY;
}
try
{
if (m_pApplicationGUID)
{
SysFreeString(m_pApplicationGUID);
m_pApplicationGUID = NULL;
}
if (m_pSubIDs)
{
SysFreeString(m_pSubIDs);
m_pSubIDs = NULL;
}
if (pApplicationGUID)
{
m_pApplicationGUID = SysAllocString(pApplicationGUID);
}
if (pSubIDs)
{
m_pSubIDs = SysAllocString(pSubIDs);
}
}
catch(...)
{
SysFreeString(m_pApplicationID);
m_pApplicationID = NULL;
if (m_pApplicationGUID)
{
SysFreeString(m_pApplicationGUID);
}
m_pApplicationGUID = NULL;
if (m_pSubIDs)
{
SysFreeString(m_pSubIDs);
}
m_pSubIDs = NULL;
return E_POINTER;
}
if ((pApplicationGUID!=NULL && m_pApplicationGUID==NULL) ||
(pSubIDs!=NULL && m_pSubIDs==NULL))
{
return E_OUTOFMEMORY;
}
return S_OK;
}
HRESULT CIPConfMSPCall::NewParticipant(
IN ITStream * pITStream,
IN DWORD dwSSRC,
IN DWORD dwSendRecv,
IN DWORD dwMediaType,
IN WCHAR * szCName,
OUT ITParticipant ** ppITParticipant
)
/*++
Routine Description:
This method is called by a stream object when a new participant appears.
It looks throught the call's participant list, if the partcipant is
already in the list, it returns the pointer to the object. If it is not
found, a new object will be created and added into the list.
Arguments:
pITStream - the stream object.
dwSSRC - the SSRC of the participant in the stream.
dwSendRecv - a sender or a receiver.
dwMediaType - the media type of the stream.
szCName - the canonical name of the participant.
ppITParticipant - the address to store the returned pointer.
Return Value:
S_OK
E_OUTOFMEMORY
--*/
{
CLock lock(m_ParticipantLock);
HRESULT hr;
// First check to see if the participant is in our list. If he is already
// in the list, just return the object.
int index;
if (m_Participants.FindByCName(szCName, &index))
{
hr = ((CParticipant *)m_Participants[index])->
AddStream(pITStream, dwSSRC, dwSendRecv, dwMediaType);
if (FAILED(hr))
{
LOG((MSP_ERROR, "can not add a stream to a participant:%x", hr));
return hr;
}
*ppITParticipant = m_Participants[index];
(*ppITParticipant)->AddRef();
return S_OK;
}
// create a new participant object.
CComObject<CParticipant> * pCOMParticipant;
hr = ::CreateCComObjectInstance(&pCOMParticipant);
if (NULL == pCOMParticipant)
{
LOG((MSP_ERROR, "can not create a new participant:%x", hr));
return hr;
}
ITParticipant* pITParticipant;
// get the interface pointer.
hr = pCOMParticipant->_InternalQueryInterface(
__uuidof(ITParticipant),
(void **)&pITParticipant
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "Participant QueryInterface failed: %x", hr));
delete pCOMParticipant;
return hr;
}
// Initialize the object.
hr = pCOMParticipant->Init(
szCName, pITStream, dwSSRC, dwSendRecv, dwMediaType
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "Create participant:call init failed: %x", hr));
pITParticipant->Release();
return hr;
}
// Add the Participant into our list of Participants.
if (!m_Participants.InsertAt(index, pITParticipant))
{
pITParticipant->Release();
LOG((MSP_ERROR, "out of memory in adding a Participant."));
return E_OUTOFMEMORY;
}
// AddRef the interface pointer and return it.
pITParticipant->AddRef();
*ppITParticipant = pITParticipant;
SendParticipantEvent(PE_NEW_PARTICIPANT, pITParticipant);
return S_OK;
}
HRESULT CIPConfMSPCall::ParticipantLeft(
IN ITParticipant * pITParticipant
)
/*++
Routine Description:
This method is called by a stream object when a participant left the
conference.
Arguments:
pITParticipant - the participant that left.
Return Value:
S_OK
--*/
{
m_ParticipantLock.Lock();
BOOL fRemoved = m_Participants.Remove(pITParticipant);
m_ParticipantLock.Unlock();
if (fRemoved)
{
SendParticipantEvent(PE_PARTICIPANT_LEAVE, pITParticipant);
pITParticipant->Release();
}
else
{
LOG((MSP_ERROR, "can't remove Participant %p", pITParticipant));
}
return S_OK;
}
void CIPConfMSPCall::SendParticipantEvent(
IN PARTICIPANT_EVENT Event,
IN ITParticipant * pITParticipant,
IN ITSubStream * pITSubStream
) const
/*++
Routine Description:
This method is called by a stream object to send a participant related
event to the app.
Arguments:
Event - the event code.
pITParticipant - the participant object.
pITSubStream - the substream object, if any.
Return Value:
nothing.
--*/
{
if (pITParticipant)
{
LOG((MSP_TRACE, "SendParticipantEvent, event %s, participant:%ws",
ParticipantEventString[Event],
((CParticipant*)pITParticipant)->Name()
));
}
else
{
LOG((MSP_TRACE, "SendParticipantEvent, event %s, participant: null (local)",
ParticipantEventString[Event]
));
}
// Create a private event object.
CComPtr<IDispatch> pEvent;
HRESULT hr = CreateParticipantEvent(
Event,
pITParticipant,
pITSubStream,
&pEvent
);
if (FAILED(hr))
{
LOG((MSP_ERROR, "create event returned: %x", hr));
return;
}
MSPEVENTITEM* pEventItem = AllocateEventItem();
if (pEventItem == NULL)
{
LOG((MSP_ERROR, "No memory for the TSPMSP data"));
return;
}
// Fill in the necessary fields for the event structure.
pEventItem->MSPEventInfo.dwSize = sizeof(MSP_EVENT_INFO);;
pEventItem->MSPEventInfo.Event = ME_PRIVATE_EVENT;
pEventItem->MSPEventInfo.hCall = m_htCall;
pEventItem->MSPEventInfo.MSP_PRIVATE_EVENT_INFO.pEvent = pEvent;
pEventItem->MSPEventInfo.MSP_PRIVATE_EVENT_INFO.lEventCode = Event;
pEvent->AddRef();
// send the event to tapi.
hr = m_pMSPAddress->PostEvent(pEventItem);
if (FAILED(hr))
{
LOG((MSP_ERROR, "Post event failed %x", hr));
pEvent->Release();
FreeEventItem(pEventItem);
}
}
VOID CIPConfMSPCall::HandleGraphEvent(
IN MSPSTREAMCONTEXT * pContext
)
{
long lEventCode;
LONG_PTR lParam1, lParam2; // win64 fix
HRESULT hr = pContext->pIMediaEvent->GetEvent(&lEventCode, &lParam1, &lParam2, 0);
if (FAILED(hr))
{
LOG((MSP_ERROR, "Can not get the actual event. %x", hr));
return;
}
LOG((MSP_EVENT, "ProcessGraphEvent, code:%d param1:%x param2:%x",
lEventCode, lParam1, lParam2));
if (lEventCode == EC_PALETTE_CHANGED
|| lEventCode == EC_VIDEO_SIZE_CHANGED)
{
LOG((MSP_EVENT, "event %d ignored", lEventCode));
return;
}
//
// Create an event data structure that we will pass to the worker thread.
//
MULTI_GRAPH_EVENT_DATA * pData;
pData = new MULTI_GRAPH_EVENT_DATA;
if (pData == NULL)
{
LOG((MSP_ERROR, "Out of memory for event data."));
return;
}
pData->pCall = this;
pData->pITStream = pContext->pITStream;
pData->lEventCode = lEventCode;
pData->lParam1 = lParam1;
pData->lParam2 = lParam2;
//
// also pass an addref'ed pointer to IMediaEvent, so that whoever processes
// the message has the opportunity to free event parameters
//
pData->pIMediaEvent = pContext->pIMediaEvent;
pData->pIMediaEvent->AddRef();
//
// Make sure the call and stream don't go away while we handle the event.
// but use our special inner object addref for the call
//
pData->pCall->MSPCallAddRef();
pData->pITStream->AddRef();
//
// Queue an async work item to call ProcessGraphEvent.
//
hr = g_Thread.QueueWorkItem(AsyncMultiGraphEvent,
(void *) pData,
FALSE); // asynchronous
if (FAILED(hr))
{
LOG((MSP_ERROR, "QueueWorkItem failed, return code:%x", hr));
pData->pCall->MSPCallRelease();
pData->pITStream->Release();
//
// no one is going to free event params and release the IMediaEvent
// pointer, so do it here
//
pContext->pIMediaEvent->FreeEventParams(lEventCode, lParam1, lParam2);
pData->pIMediaEvent->Release();
delete pData;
}
}
HRESULT CIPConfMSPCall::InitFullDuplexControler()
/*++
Routine Description:
This method creates the full-duplex controller object used to control
audio devices.
Arguments:
NONE
Return Value:
HRESULT.
--*/
{
ENTER_FUNCTION("CIPConfMSPCall::InitFullDuplexControler");
LOG((MSP_TRACE, "%s entered", __fxName));
CLock lock(m_lock);
if (m_pIAudioDuplexController != NULL)
{
return S_OK;
}
HRESULT hr;
IAudioDuplexController *pIAudioDuplexController;
if (FAILED(hr = CoCreateInstance(
__uuidof(TAPIAudioDuplexController),
NULL,
CLSCTX_INPROC_SERVER | CLSCTX_NO_CODE_DOWNLOAD,
__uuidof(IAudioDuplexController),
(void **) &pIAudioDuplexController
)))
{
LOG((MSP_ERROR, "%s, create AudioDuplexController failed. hr=%x",
__fxName, hr));
return hr;
}
m_pIAudioDuplexController = pIAudioDuplexController;
return S_OK;
}
/*++
Routine Description:
ITCallQualityControl method. Delegated to quality control relay
--*/
STDMETHODIMP
CIPConfMSPCall::GetRange (
IN CallQualityProperty Property,
OUT long *plMin,
OUT long *plMax,
OUT long *plSteppingDelta,
OUT long *plDefault,
OUT TAPIControlFlags *plFlags
)
{
return m_pCallQCRelay->GetRange (Property, plMin, plMax, plSteppingDelta, plDefault, plFlags);
}
/*++
Routine Description:
ITCallQualityControl method. Delegated to quality control relay
--*/
STDMETHODIMP
CIPConfMSPCall::Get (
IN CallQualityProperty Property,
OUT long *plValue,
OUT TAPIControlFlags *plFlags
)
{
return m_pCallQCRelay->Get (Property, plValue, plFlags);
}
/*++
Routine Description:
ITCallQualityControl method. Delegated to quality control relay
--*/
STDMETHODIMP
CIPConfMSPCall::Set (
IN CallQualityProperty Property,
IN long lValue,
IN TAPIControlFlags lFlags
)
{
return m_pCallQCRelay->Set (Property, lValue, lFlags);
}
/*++
Routine Description:
IInnerCallQualityControl method. Delegated to quality control relay
--*/
STDMETHODIMP_(ULONG)
CIPConfMSPCall::InnerCallAddRef (VOID)
{
return this->MSPCallAddRef ();
}
STDMETHODIMP_(ULONG)
CIPConfMSPCall::InnerCallRelease (VOID)
{
return this->MSPCallRelease ();
}
STDMETHODIMP
CIPConfMSPCall::RegisterInnerStreamQC (
IN IInnerStreamQualityControl *pIInnerStreamQC
)
{
return m_pCallQCRelay->RegisterInnerStreamQC (
pIInnerStreamQC
);
}
/*++
Routine Description:
IInnerCallQualityControl method. Delegated to quality control relay
--*/
STDMETHODIMP
CIPConfMSPCall::DeRegisterInnerStreamQC (
IN IInnerStreamQualityControl *pIInnerStreamQC
)
{
return m_pCallQCRelay->DeRegisterInnerStreamQC (
pIInnerStreamQC
);
}
/*++
Routine Description:
IInnerCallQualityControl method. Delegated to quality control relay
--*/
STDMETHODIMP
CIPConfMSPCall::ProcessQCEvent (
IN QCEvent event,
IN DWORD dwParam
)
{
return m_pCallQCRelay->ProcessQCEvent (event, dwParam);
}