2441 lines
55 KiB
C++
2441 lines
55 KiB
C++
|
|
/*
|
|
|
|
Copyright (c) 1999 Microsoft Corporation
|
|
|
|
|
|
Module Name:
|
|
|
|
TAPISend.cpp
|
|
|
|
|
|
Abstract:
|
|
|
|
This sample illustrates use of Media Streaming Terminal for sending audio.
|
|
|
|
The application makes a TAPI call to the address specified in the command
|
|
line and uses Media Streaming Terminal to send the media content of a wav
|
|
file, also specified in the command line, to the remote machibe.
|
|
|
|
*/
|
|
|
|
|
|
#include "common.h"
|
|
#include "avifilereader.h"
|
|
|
|
|
|
//
|
|
// global TAPI object
|
|
//
|
|
|
|
ITTAPI *g_pTapi = NULL;
|
|
|
|
|
|
//
|
|
// a global flag, set true to request stop streaming
|
|
//
|
|
|
|
BOOL g_bExitRequested = FALSE;
|
|
|
|
|
|
//
|
|
// possible address types strings and corresponding LINEADDRESSTYPE_ constants
|
|
//
|
|
|
|
char *g_szAddressTypes[] =
|
|
{"PHONENUMBER", "CONFERENCE", "EMAIL", "MACHINE", "IP"};
|
|
|
|
|
|
long g_nAddressTypeConstants[] =
|
|
{ LINEADDRESSTYPE_PHONENUMBER,
|
|
LINEADDRESSTYPE_SDP,
|
|
LINEADDRESSTYPE_EMAILNAME,
|
|
LINEADDRESSTYPE_DOMAINNAME,
|
|
LINEADDRESSTYPE_IPADDRESS };
|
|
|
|
//
|
|
// number of different address types
|
|
//
|
|
|
|
const UINT g_nNumberOfAddressTypes =
|
|
sizeof(g_szAddressTypes)/sizeof(g_szAddressTypes[0]);
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LogMessage
|
|
//
|
|
//
|
|
// log a message using printf
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogMessage(IN CHAR *pszFormat, ... )
|
|
{
|
|
|
|
//
|
|
// output buffer -- note: hardcoded limit
|
|
//
|
|
|
|
char szBuffer[1280];
|
|
|
|
|
|
//
|
|
// get current time
|
|
//
|
|
|
|
SYSTEMTIME SystemTime;
|
|
|
|
GetLocalTime(&SystemTime);
|
|
|
|
|
|
//
|
|
// format thread id and time
|
|
//
|
|
|
|
sprintf( szBuffer, "[%lx]:[%02u:%02u:%02u.%03u]::",
|
|
GetCurrentThreadId(),
|
|
SystemTime.wHour,
|
|
SystemTime.wMinute,
|
|
SystemTime.wSecond,
|
|
SystemTime.wMilliseconds);
|
|
|
|
|
|
//
|
|
// get the actual message
|
|
//
|
|
|
|
va_list vaArguments;
|
|
|
|
va_start(vaArguments, pszFormat);
|
|
vsprintf(&(szBuffer[strlen(szBuffer)]), pszFormat, vaArguments);
|
|
va_end(vaArguments);
|
|
|
|
|
|
//
|
|
// log the buffer with the terminating carriage return
|
|
//
|
|
|
|
strcat(szBuffer, "\n");
|
|
|
|
printf(szBuffer);
|
|
}
|
|
|
|
|
|
#define LogError LogMessage
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LogFormat
|
|
//
|
|
// use LogMessage to log wave format
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void LogFormat(IN WAVEFORMATEX *pWaveFormat)
|
|
{
|
|
LogMessage(" Format: ");
|
|
LogMessage(" tag: %u", pWaveFormat->wFormatTag);
|
|
LogMessage(" channels: %u", pWaveFormat->nChannels);
|
|
LogMessage(" samples/sec: %lu", pWaveFormat->nSamplesPerSec);
|
|
LogMessage(" align: %u", pWaveFormat->nBlockAlign);
|
|
LogMessage(" bits/sample: %u", pWaveFormat->wBitsPerSample);
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// AllocateMemory
|
|
//
|
|
// use win32 heap api to allocate memory on the application's heap
|
|
// and zero the allocated memory
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void *AllocateMemory(SIZE_T nMemorySize)
|
|
{
|
|
|
|
|
|
//
|
|
// use HeapAlloc to allocate and clear memory
|
|
//
|
|
|
|
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nMemorySize);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FreeMemory
|
|
//
|
|
// use win32 heap api to free memory previously allocated on the application's
|
|
// heap
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void FreeMemory(void *pMemory)
|
|
{
|
|
|
|
//
|
|
// get size of the allocated memory
|
|
//
|
|
|
|
DWORD nMemorySize = HeapSize(GetProcessHeap(), 0, pMemory);
|
|
|
|
if (-1 == nMemorySize)
|
|
{
|
|
LogError("FreeMemory: failed to get size of the memory block %p",
|
|
pMemory);
|
|
|
|
//
|
|
// don't exit -- try freeing anyway
|
|
//
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// fill memory with 0xdd's before freeing, so it is easier to debug
|
|
// failures caused by using pointer to deallocated memory
|
|
//
|
|
|
|
if (0 != pMemory)
|
|
{
|
|
FillMemory(pMemory, nMemorySize, 0xdd);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// use HeapFree to free memory. use return code to log the result, but
|
|
// do not return it to the caller
|
|
//
|
|
|
|
BOOL bFreeSuccess = HeapFree(GetProcessHeap(), 0, pMemory);
|
|
|
|
if (FALSE == bFreeSuccess)
|
|
{
|
|
LogError("FreeMemory: HeapFree failed");
|
|
|
|
//
|
|
// if this assertion fires, it is likely there is a problem with the
|
|
// memory we are trying to deallocate. Was it allocated using heapalloc
|
|
// and on the same heap? Is this a valid pointer?
|
|
//
|
|
|
|
_ASSERTE(FALSE);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// InitializeTAPI()
|
|
//
|
|
// create and initialize the TAPI object
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT InitializeTAPI()
|
|
{
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
LogMessage("InitializeTAPI: started");
|
|
|
|
|
|
//
|
|
// create the TAPI object
|
|
//
|
|
|
|
hr = CoCreateInstance(
|
|
CLSID_TAPI,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_ITTAPI,
|
|
(LPVOID *)&g_pTapi
|
|
);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("InitializeTAPI: CoCreateInstance on TAPI failed");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// must initialize tapi object before using it
|
|
//
|
|
|
|
LogMessage("InitializeTAPI: calling ITTAPI::Initialize()");
|
|
|
|
hr = g_pTapi->Initialize();
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("InitializeTAPI: TAPI failed to initialize");
|
|
|
|
g_pTapi->Release();
|
|
g_pTapi = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// setting event filtering -- this is required for synchronous
|
|
// ITBasicCallControl->Connect to work
|
|
//
|
|
|
|
LogMessage("InitializeTAPI: calling ITTAPI::put_EventFilter()");
|
|
|
|
hr = g_pTapi->put_EventFilter(TE_CALLSTATE);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("InitializeTAPI: TAPI failed to put event filtering");
|
|
|
|
g_pTapi->Shutdown();
|
|
|
|
g_pTapi->Release();
|
|
g_pTapi = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
LogMessage("InitializeTAPI: succeeded");
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ShutdownTAPI
|
|
//
|
|
// shutdown and release the tapi object
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void ShutdownTAPI()
|
|
{
|
|
|
|
LogMessage("ShutdownTAPI: started");
|
|
|
|
|
|
if (NULL != g_pTapi)
|
|
{
|
|
|
|
g_pTapi->Shutdown();
|
|
|
|
g_pTapi->Release();
|
|
g_pTapi = NULL;
|
|
}
|
|
|
|
|
|
LogMessage("ShutdownTAPI: completed");
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// IsValidAudioFile
|
|
//
|
|
// returns TRUE if the file specified by pszFileName
|
|
// exists and is a valid audio file
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
BOOL IsValidAudioFile(IN char *pszFileName)
|
|
{
|
|
|
|
//
|
|
// open the file
|
|
//
|
|
|
|
CAVIFileReader FileReader;
|
|
|
|
HRESULT hr = FileReader.Initialize(pszFileName);
|
|
|
|
|
|
//
|
|
// see if it is a valid audio file
|
|
//
|
|
|
|
if ((FAILED(hr)) || !FileReader.IsValidAudioFile())
|
|
{
|
|
LogError("IsValidAudioFile: file [%s] does not exist or is not "
|
|
"a valid wav file", pszFileName);
|
|
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
LogMessage("IsValidAudioFile: file [%s] is a valid audio file",
|
|
pszFileName);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FindAddress
|
|
//
|
|
// find an address of the requested type that supports audio. returns S_OK
|
|
// if address found, failure otherwise
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT FindAddress(IN long nAddressType,
|
|
OUT ITAddress **ppAddress)
|
|
{
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
|
|
//
|
|
// don't return garbage even if we fail
|
|
//
|
|
|
|
*ppAddress = NULL;
|
|
|
|
|
|
//
|
|
// enumerate all available addresses
|
|
//
|
|
|
|
IEnumAddress *pEnumAddress = NULL;
|
|
|
|
hr = g_pTapi->EnumerateAddresses(&pEnumAddress);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("FindAddress: Failed to enumerate addresses");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// walk through the enumeration of addresses and look for those which are
|
|
// of requested type and supports audio
|
|
//
|
|
|
|
while (TRUE)
|
|
{
|
|
//
|
|
// get the next address from the enumeration
|
|
//
|
|
|
|
ITAddress *pAddress = NULL;
|
|
|
|
hr = pEnumAddress->Next( 1, &pAddress, NULL );
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
|
|
//
|
|
// no more addresses
|
|
//
|
|
|
|
LogMessage("FindAddress: no more addresses");
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// we got an address. check its capabilities
|
|
//
|
|
|
|
ITAddressCapabilities *pAddressCaps = NULL;
|
|
|
|
hr = pAddress->QueryInterface( IID_ITAddressCapabilities,
|
|
(void**)&pAddressCaps );
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
|
|
LogError("FindAddress: "
|
|
"Failed to QI address for address capabilities");
|
|
|
|
//
|
|
// just continue to the next address
|
|
//
|
|
|
|
pAddress->Release();
|
|
pAddress = NULL;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// is this the right address type?
|
|
//
|
|
|
|
long nType = 0;
|
|
|
|
hr = pAddressCaps->get_AddressCapability(AC_ADDRESSTYPES, &nType);
|
|
|
|
|
|
pAddressCaps->Release();
|
|
pAddressCaps = NULL;
|
|
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("FindAddress: Failed to get_AddressCapability");
|
|
|
|
pAddress->Release();
|
|
pAddress = NULL;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
if (nType & nAddressType)
|
|
{
|
|
|
|
//
|
|
// this address is of the right type. does it support audio?
|
|
//
|
|
|
|
ITMediaSupport *pMediaSupport = NULL;
|
|
|
|
hr = pAddress->QueryInterface(IID_ITMediaSupport,
|
|
(void **)&pMediaSupport);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
|
|
//
|
|
// continue to the next address
|
|
//
|
|
|
|
LogError("FindAddress: "
|
|
"failed to qi address for ITMediaSupport");
|
|
|
|
pAddress->Release();
|
|
pAddress = NULL;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
VARIANT_BOOL bAudioSupported = VARIANT_FALSE;
|
|
|
|
hr = pMediaSupport->QueryMediaType(TAPIMEDIATYPE_AUDIO,
|
|
&bAudioSupported);
|
|
|
|
pMediaSupport->Release();
|
|
pMediaSupport = NULL;
|
|
|
|
if (SUCCEEDED(hr) && (VARIANT_TRUE == bAudioSupported))
|
|
{
|
|
|
|
LogMessage("FindAddress: address found");
|
|
|
|
|
|
//
|
|
// log the name of this address
|
|
//
|
|
|
|
BSTR bstrAddressName = NULL;
|
|
|
|
hr = pAddress->get_AddressName(&bstrAddressName);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("FindAddress: failed to get address name");
|
|
}
|
|
else
|
|
{
|
|
LogMessage(" %S\n", bstrAddressName);
|
|
}
|
|
|
|
SysFreeString(bstrAddressName);
|
|
|
|
|
|
//
|
|
// will use this address
|
|
//
|
|
|
|
*ppAddress = pAddress;
|
|
|
|
break;;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// can't use this address. release it
|
|
//
|
|
|
|
pAddress->Release();
|
|
pAddress = NULL;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// done with the enumeration. release.
|
|
//
|
|
|
|
pEnumAddress->Release();
|
|
pEnumAddress = NULL;
|
|
|
|
|
|
//
|
|
// log a message if no address was found
|
|
//
|
|
|
|
if (NULL == *ppAddress)
|
|
{
|
|
LogError("FindAddress: no address found");
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
|
|
LogMessage("FindAddress: completed");
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// GetAddressType
|
|
//
|
|
// convert a address type specified by pszRequestedAddressType
|
|
// to tapi LINEADDRESSTYPE_ constants (LINEADDRESSTYPE_PHONENUMBER,
|
|
// LINEADDRESSTYPE_IPADDRESS, etc).
|
|
//
|
|
// returns E_FAIL if the string does not correspond to a valid address type
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT GetAddressType(IN char *pszRequestedAddressType,
|
|
IN OUT long *pnAddressType)
|
|
{
|
|
|
|
//
|
|
// match address type specified by the user to one of the known
|
|
// address types
|
|
//
|
|
|
|
*pnAddressType = 0;
|
|
|
|
|
|
for (int i = 0; i < g_nNumberOfAddressTypes; i++)
|
|
{
|
|
|
|
if (0 == _stricmp(g_szAddressTypes[i], pszRequestedAddressType))
|
|
{
|
|
|
|
//
|
|
// get the address type constant corresponding to the string
|
|
//
|
|
|
|
*pnAddressType = g_nAddressTypeConstants[i];
|
|
|
|
LogMessage("GetAddressType: "
|
|
"matched address type [%s] to address type [%d]",
|
|
pszRequestedAddressType, *pnAddressType);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
LogError("GetAddressType: unrecognized address type [%s]",
|
|
pszRequestedAddressType);
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CreateBSTRfromString
|
|
//
|
|
// create a bstr from a string supplied. the caller is responsible for
|
|
// freeng the returned string by calling SysFreeString.
|
|
//
|
|
// returns the allocated string or NULL if failed.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
BSTR CreateBSTRfromString(IN char *pszString)
|
|
{
|
|
|
|
//
|
|
// convert to wchar so we can create bstr
|
|
//
|
|
// allocate buffer for resulting string of wchars
|
|
//
|
|
|
|
size_t nStringLength = strlen(pszString) + 1;
|
|
|
|
WCHAR *pwsString = (WCHAR *)AllocateMemory(sizeof(WCHAR) * nStringLength);
|
|
|
|
if (NULL == pwsString)
|
|
{
|
|
LogError("CreateBSTRfromString: "
|
|
"failed to allocate memory for address string.");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// convert to wchar
|
|
//
|
|
|
|
int rc = MultiByteToWideChar(CP_ACP,
|
|
0,
|
|
pszString,
|
|
-1,
|
|
pwsString,
|
|
nStringLength);
|
|
|
|
if (0 == rc)
|
|
{
|
|
LogError("CreateBSTRfromString: Failed to convert char to wchar");
|
|
|
|
FreeMemory(pwsString);
|
|
pwsString = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// create bstr
|
|
//
|
|
|
|
BSTR bstr = SysAllocString(pwsString);
|
|
|
|
|
|
//
|
|
// no longer needed, deallocate
|
|
//
|
|
|
|
FreeMemory(pwsString);
|
|
pwsString = NULL;
|
|
|
|
return bstr;
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CreateAndConnectCall
|
|
//
|
|
// make a call using the address object specified, to the address specified
|
|
//
|
|
// if successful, returns S_OK and connected call, error otherwise
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT CreateAndConnectCall(IN ITAddress *pAddress,
|
|
IN char *pszDestinationAddress,
|
|
IN long nAddressType,
|
|
OUT ITBasicCallControl **ppCall)
|
|
{
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
|
|
//
|
|
// don't return garbage
|
|
//
|
|
|
|
*ppCall = NULL;
|
|
|
|
|
|
//
|
|
// create a call on the address
|
|
//
|
|
|
|
BSTR bstrDestinationAddress = CreateBSTRfromString(pszDestinationAddress);
|
|
|
|
ITBasicCallControl *pCall = NULL;
|
|
|
|
hr = pAddress->CreateCall(bstrDestinationAddress,
|
|
nAddressType,
|
|
TAPIMEDIATYPE_AUDIO,
|
|
&pCall);
|
|
|
|
SysFreeString (bstrDestinationAddress);
|
|
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateAndConnectCall: Failed to create a call");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// call created. attempt to connect synchronously
|
|
//
|
|
|
|
LogMessage("CreateAndConnectCall: attempting a synchronous connect");
|
|
|
|
hr = pCall->Connect(VARIANT_TRUE);
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
|
|
LogError("CreateAndConnectCall: failed to connect, hr = 0x%lx", hr);
|
|
|
|
|
|
pCall->Disconnect(DC_NORMAL);
|
|
|
|
|
|
//
|
|
// we don't need the call object if it failed to connect
|
|
//
|
|
|
|
pCall->Release();
|
|
pCall = NULL;
|
|
|
|
hr = E_FAIL;
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// call is successful. return the call object
|
|
//
|
|
|
|
LogMessage("CreateAndConnectCall: call connected successfully");
|
|
|
|
*ppCall = pCall;
|
|
}
|
|
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Call
|
|
//
|
|
// make a call to the specified address on the first address object
|
|
// of the requested type that supports audio
|
|
//
|
|
// if successful, returns S_OK and connected call, error otherwise
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT Call(IN char *szDestinationAddress,
|
|
IN char *szAddressType,
|
|
OUT ITBasicCallControl **ppCall)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
|
|
//
|
|
// we don't want to return garbage even if we fail
|
|
//
|
|
|
|
*ppCall = NULL;
|
|
|
|
|
|
//
|
|
// find address type
|
|
//
|
|
|
|
long nAddressType = 0;
|
|
|
|
hr = GetAddressType(szAddressType, &nAddressType);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("Call: failed to recognize address type %s", szAddressType);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// find an address for this address type that supports audio
|
|
//
|
|
|
|
ITAddress *pAddress = NULL;
|
|
|
|
hr = FindAddress(nAddressType, &pAddress);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("Call: failed to find an address with audio for type %s",
|
|
szAddressType);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// have the address. create and connect call
|
|
//
|
|
|
|
ITBasicCallControl *pCall = NULL;
|
|
|
|
hr = CreateAndConnectCall(pAddress,
|
|
szDestinationAddress,
|
|
nAddressType,
|
|
&pCall);
|
|
|
|
pAddress->Release();
|
|
pAddress = NULL;
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("Call: Failed to create and connect call");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// we have a connected call. return it.
|
|
//
|
|
|
|
*ppCall = pCall;
|
|
|
|
LogMessage("Call: succeeded.");
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FindAudioStream
|
|
//
|
|
// given a call, return the first outgoing audio stream.
|
|
//
|
|
// returns S_OK if successful, error otherwise
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT FindAudioStream( IN ITBasicCallControl *pCall,
|
|
OUT ITStream **ppStream)
|
|
{
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
LogMessage("FindAudioStream: started");
|
|
|
|
|
|
//
|
|
// don't return garbage
|
|
//
|
|
|
|
*ppStream = NULL;
|
|
|
|
|
|
//
|
|
// enumerate streams on the call
|
|
//
|
|
|
|
|
|
|
|
//
|
|
// get the ITStreamControl interface for this call
|
|
//
|
|
|
|
ITStreamControl *pStreamControl = NULL;
|
|
|
|
hr = pCall->QueryInterface(IID_ITStreamControl,
|
|
(void **) &pStreamControl);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("FindAudioStream: failed to QI call for ITStreamControl");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// enumerate the streams
|
|
//
|
|
|
|
IEnumStream *pEnumStreams = NULL;
|
|
|
|
hr = pStreamControl->EnumerateStreams(&pEnumStreams);
|
|
|
|
pStreamControl->Release();
|
|
pStreamControl = NULL;
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateAndSelectMST: failed to enumerate streams on call");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// walk through the streams on the call
|
|
// return the first outgoing audio stream
|
|
//
|
|
|
|
while (TRUE)
|
|
{
|
|
ITStream *pStream = NULL;
|
|
|
|
hr = pEnumStreams->Next(1, &pStream, NULL);
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
//
|
|
// no more streams
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// check the stream's direction
|
|
//
|
|
|
|
TERMINAL_DIRECTION td;
|
|
|
|
hr = pStream->get_Direction(&td);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateAndSelectMST: Failed to get stream direction");
|
|
|
|
pStream->Release();
|
|
pStream = NULL;
|
|
|
|
|
|
//
|
|
// proceed to the next stream, if any
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// is the stream of the right direction?
|
|
//
|
|
|
|
if (TD_CAPTURE != td)
|
|
{
|
|
|
|
//
|
|
// incoming stream. we need outgoing.
|
|
// release the stream and continue
|
|
//
|
|
|
|
pStream->Release();
|
|
pStream = NULL;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// check the stream's media type
|
|
//
|
|
|
|
long lMediaType = 0;
|
|
|
|
hr = pStream->get_MediaType(&lMediaType);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateAndSelectMST: Failed to get media type");
|
|
|
|
pStream->Release();
|
|
pStream = NULL;
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Does this stream have the right media type?
|
|
// Streams are defined as having exactly one media type
|
|
// (not a bitmask).
|
|
//
|
|
|
|
if ( lMediaType == TAPIMEDIATYPE_AUDIO )
|
|
{
|
|
|
|
LogMessage("FindAudioStream: stream found");
|
|
|
|
|
|
//
|
|
// this is what we need, so stop looking
|
|
//
|
|
|
|
*ppStream = pStream;
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
pStream->Release();
|
|
pStream = NULL;
|
|
|
|
} // while (walking through the call's streams)
|
|
|
|
|
|
|
|
//
|
|
// release stream enumeration
|
|
//
|
|
|
|
pEnumStreams->Release();
|
|
pEnumStreams = NULL;
|
|
|
|
|
|
//
|
|
// return the error code, depending on whether we found a stream or not
|
|
//
|
|
|
|
if (NULL == *ppStream)
|
|
{
|
|
LogMessage("FindAudioStream: didn't find an audio stream");
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
LogMessage("FindAudioStream: succeded");
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CreateCaptureMediaStreamingTerminal
|
|
//
|
|
// create media streaming terminal for outgoing data
|
|
//
|
|
// returns the created terminal or NULL if failed
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
ITTerminal *CreateCaptureMediaStreamingTerminal(IN ITBasicCallControl *pCall)
|
|
{
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
|
|
//
|
|
// get the call's call info so we can get the call's address
|
|
//
|
|
|
|
ITCallInfo *pCallInfo = NULL;
|
|
|
|
hr = pCall->QueryInterface(IID_ITCallInfo, (void**)&pCallInfo);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateCaptureMediaStreamingTerminal: "
|
|
"failed to qi call for ITCallInfo");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// now we can get the address
|
|
//
|
|
|
|
ITAddress *pAddress = NULL;
|
|
|
|
hr = pCallInfo->get_Address(&pAddress);
|
|
|
|
pCallInfo->Release();
|
|
pCallInfo = NULL;
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateCaptureMediaStreamingTerminal: "
|
|
"failed to get call's address");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// get the terminal support interface
|
|
//
|
|
|
|
ITTerminalSupport *pTerminalSupport = NULL;
|
|
|
|
hr = pAddress->QueryInterface( IID_ITTerminalSupport,
|
|
(void **)&pTerminalSupport );
|
|
|
|
|
|
pAddress->Release();
|
|
pAddress = NULL;
|
|
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateCaptureMediaStreamingTerminal: "
|
|
"failed to QI pAddress for ITTerminalSupport");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// get string for the terminal's class id
|
|
//
|
|
|
|
LPOLESTR psTerminalClass = NULL;
|
|
|
|
hr = StringFromIID(CLSID_MediaStreamTerminal, &psTerminalClass);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateCaptureMediaStreamingTerminal: "
|
|
"Failed to generate string from terminal's class id");
|
|
|
|
pTerminalSupport->Release();
|
|
pTerminalSupport = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// make bstr out of the class id
|
|
//
|
|
|
|
BSTR bstrTerminalClass = SysAllocString (psTerminalClass);
|
|
|
|
|
|
//
|
|
// free the string returned by StringFromIID
|
|
//
|
|
|
|
CoTaskMemFree(psTerminalClass);
|
|
psTerminalClass = NULL;
|
|
|
|
|
|
if (NULL == bstrTerminalClass)
|
|
{
|
|
|
|
LogError("CreateCaptureMediaStreamingTerminal: "
|
|
"Failed to allocate BSTR for terminal class");
|
|
|
|
pTerminalSupport->Release();
|
|
pTerminalSupport = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// create media streaming terminal
|
|
//
|
|
|
|
ITTerminal *pTerminal = NULL;
|
|
|
|
hr = pTerminalSupport->CreateTerminal(bstrTerminalClass,
|
|
TAPIMEDIATYPE_AUDIO,
|
|
TD_CAPTURE,
|
|
&pTerminal);
|
|
|
|
|
|
//
|
|
// release resources no longer needed
|
|
//
|
|
|
|
SysFreeString(bstrTerminalClass);
|
|
|
|
pTerminalSupport->Release();
|
|
pTerminalSupport = NULL;
|
|
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateCaptureMediaStreamingTerminal: "
|
|
"failed to create media streaming terminal hr = 0x%lx", hr);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// successfully created media streaming terminal. return.
|
|
//
|
|
|
|
LogMessage("CreateCaptureMediaStreamingTerminal: "
|
|
"Terminal created successfully");
|
|
|
|
return pTerminal;
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// SetTerminalFormat
|
|
//
|
|
// tell media streaming terminal the format of the data we are going to provide
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT SetTerminalFormat(IN ITTerminal *pTerminal,
|
|
IN WAVEFORMATEX *pWaveFormat)
|
|
{
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
|
|
//
|
|
// log format requested
|
|
//
|
|
|
|
LogMessage("SetTerminalFormat: starting.");
|
|
LogFormat(pWaveFormat);
|
|
|
|
|
|
ITAMMediaFormat *pIMediaFormat = NULL;
|
|
|
|
hr = pTerminal->QueryInterface(IID_ITAMMediaFormat,
|
|
(void **)&pIMediaFormat);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
|
|
LogError("SetTerminalFormat: Failed to set terminal format");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// fill the media format structure
|
|
//
|
|
|
|
AM_MEDIA_TYPE MediaType;
|
|
|
|
ZeroMemory(&MediaType, sizeof(AM_MEDIA_TYPE));
|
|
|
|
MediaType.majortype = MEDIATYPE_Audio;
|
|
MediaType.subtype = MEDIASUBTYPE_PCM;
|
|
MediaType.bFixedSizeSamples = TRUE;
|
|
MediaType.bTemporalCompression = FALSE;
|
|
MediaType.lSampleSize = pWaveFormat->nBlockAlign;
|
|
MediaType.formattype = FORMAT_WaveFormatEx;
|
|
MediaType.pUnk = NULL;
|
|
|
|
MediaType.cbFormat = sizeof(WAVEFORMATEX) +
|
|
pWaveFormat->cbSize;
|
|
|
|
MediaType.pbFormat = (BYTE*)pWaveFormat;
|
|
|
|
|
|
//
|
|
// set the requested format
|
|
//
|
|
|
|
hr = pIMediaFormat->put_MediaFormat(&MediaType);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
|
|
//
|
|
// try to see what format the terminal wanted
|
|
//
|
|
|
|
LogError("SetTerminalFormat: failed to set format");
|
|
|
|
AM_MEDIA_TYPE *pMediaFormat = NULL;
|
|
|
|
HRESULT hr2 = pIMediaFormat->get_MediaFormat(&pMediaFormat);
|
|
|
|
|
|
if (SUCCEEDED(hr2))
|
|
{
|
|
|
|
if (pMediaFormat->formattype == FORMAT_WaveFormatEx)
|
|
{
|
|
|
|
//
|
|
// log the terminal's format
|
|
//
|
|
|
|
LogError("SetTerminalFormat: terminal's format is");
|
|
LogFormat((WAVEFORMATEX*) pMediaFormat->pbFormat);
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
LogError("SetTerminalFormat: "
|
|
"terminal's format is not WAVEFORMATEX");
|
|
}
|
|
|
|
|
|
//
|
|
// note: we are responsible for deallocating the format returned by
|
|
// get_MediaFormat
|
|
//
|
|
|
|
DeleteMediaType(pMediaFormat);
|
|
|
|
} // succeeded to get terminal's format
|
|
else
|
|
{
|
|
|
|
LogError("SetTerminalFormat: failed to get terminal's format");
|
|
|
|
}
|
|
|
|
} // failed to set format
|
|
|
|
|
|
pIMediaFormat->Release();
|
|
pIMediaFormat = NULL;
|
|
|
|
LogMessage("SetTerminalFormat: completed");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// SetAllocatorProperties
|
|
//
|
|
// sets allocator properties to the terminal
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT SetAllocatorProperties(IN ITTerminal *pTerminal)
|
|
{
|
|
|
|
//
|
|
// different buffer sizes may produce different sound quality, depending
|
|
// on the underlying transport that is being used.
|
|
//
|
|
// this function illustrates how an app can control the number and size of
|
|
// buffers. A multiple of 30 ms (480 bytes at 16-bit 8 KHz PCM) is the most
|
|
// appropriate sample size for IP (especailly G.723.1).
|
|
//
|
|
// However, small buffers can cause poor audio quality on some voice boards.
|
|
//
|
|
// If this method is not called, the allocator properties suggested by the
|
|
// connecting filter will be used.
|
|
//
|
|
// Note: do not set allocator properties in the applications unless you are
|
|
// sure that sound quality will not degrade as a result. Some MSPs can have
|
|
// their own preferred allocator properties, and will not be able to
|
|
// provide the best quality if the app sets its own properties, different
|
|
// from what is preferred by the msp.
|
|
//
|
|
// Also note that ITAllocatorProperties::SetBufferSize allows the app to
|
|
// specify preferred size of the buffer allocated to the application
|
|
// without affecting terminal's allocator properties.
|
|
//
|
|
|
|
LogMessage("SetAllocatorProperties: starting.");
|
|
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
|
|
//
|
|
// get ITAllocator properties interface on the terminal
|
|
//
|
|
|
|
ITAllocatorProperties *pITAllocatorProperties = NULL;
|
|
|
|
|
|
hr = pTerminal->QueryInterface(IID_ITAllocatorProperties,
|
|
(void **)&pITAllocatorProperties);
|
|
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("SetAllocatorProperties: "
|
|
"failed to QI terminal for ITAllocatorProperties");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// configure allocator properties
|
|
//
|
|
|
|
ALLOCATOR_PROPERTIES AllocProps;
|
|
|
|
AllocProps.cbBuffer = 4800;
|
|
AllocProps.cBuffers = 5;
|
|
AllocProps.cbAlign = 1;
|
|
AllocProps.cbPrefix = 0;
|
|
|
|
|
|
hr = pITAllocatorProperties->SetAllocatorProperties(&AllocProps);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("SetAllocatorProperties: failed to set allocator properties. "
|
|
"hr = 0x%lx", hr);
|
|
|
|
pITAllocatorProperties->Release();
|
|
pITAllocatorProperties = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// ask media streaming terminal to allocate buffers for us.
|
|
// TRUE is the default, so strictly speaking, we didn't have to call
|
|
// this method.
|
|
//
|
|
|
|
hr = pITAllocatorProperties->SetAllocateBuffers(TRUE);
|
|
|
|
|
|
pITAllocatorProperties->Release();
|
|
pITAllocatorProperties = NULL;
|
|
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("SetAllocatorProperties: failed to SetAllocateBuffers, "
|
|
"hr = 0x%lx", hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// succeeded setting allocator properties
|
|
//
|
|
|
|
LogMessage("SetAllocatorProperties: completed");
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ReadFileIntoTerminal
|
|
//
|
|
// read data from the file and submit it to media streaming terminal
|
|
//
|
|
// exit when finished reading the file, user requested exit by pressing
|
|
// ctrl+break, or the connection broke
|
|
//
|
|
// returns S_FALSE when finished streaming the file, S_OK if user requested
|
|
// exit, failure otherwise
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT ReadFileIntoTerminal(IN CAVIFileReader *pFileReader,
|
|
IN ITTerminal *pPlaybackTerminal)
|
|
{
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
LogMessage("ReadFileIntoTerminal: started.");
|
|
|
|
|
|
Sleep(20000);
|
|
|
|
|
|
//
|
|
// get terminal's IMediaStream interface
|
|
//
|
|
|
|
IMediaStream *pTerminalMediaStream = NULL;
|
|
|
|
hr = pPlaybackTerminal->QueryInterface(IID_IMediaStream,
|
|
(void**)&pTerminalMediaStream);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("ReadFileIntoTerminal: "
|
|
"failed to QI terminal for IMediaStream");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// create a queue (STL deque) that will hold all the samples that we ever
|
|
// submitted to media streaming terminal
|
|
//
|
|
// we need this so when we are finished reading the file, we can go through
|
|
// the list of all the samples that we have submitted and make sure mst
|
|
// is finished processing them
|
|
//
|
|
// note that samples get reused, the same samples will be put in the queue
|
|
// more than once. so the size of the queue will be proportional to the
|
|
// size of the file being played. this might cause problems if
|
|
// the file is big or the source of the samples is unlimited (live audio
|
|
// feed). In this case, the logic can be modified to only enqueue each
|
|
// sample once, by comparing against existing queue entries.
|
|
//
|
|
|
|
std::deque<IStreamSample*> SampleQ;
|
|
|
|
|
|
//
|
|
// count the number of samples that have been submitted
|
|
//
|
|
|
|
ULONG nSampleCount = 0;
|
|
|
|
|
|
//
|
|
// keep reading samples from file and sending them.
|
|
// (until user requests exit, there is no more data, or failure)
|
|
//
|
|
|
|
while (!g_bExitRequested)
|
|
{
|
|
|
|
//
|
|
// allocate a sample on the terminal's media stream
|
|
//
|
|
// Note: the call to AllocateSample() will block if we filled all the
|
|
// samples with data, and there are no more samples for us to fill
|
|
// (waiting for media streaming terminal to process samples we have
|
|
// submitted). When MST is is done with at least one sample, the call
|
|
// will return. This logic will ensure that MST always has work and is
|
|
// never starved for samples.
|
|
//
|
|
|
|
IStreamSample *pStreamSample = NULL;
|
|
|
|
hr = pTerminalMediaStream->AllocateSample(0, &pStreamSample);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("ReadFileIntoTerminal: "
|
|
"failed to allocate a sample on terminal's stream");
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// get IMemoryData on the sample so we can get to the sample's memory
|
|
// data
|
|
//
|
|
|
|
IMemoryData *pSampleMemoryData = NULL;
|
|
|
|
hr = pStreamSample->QueryInterface(IID_IMemoryData,
|
|
(void**)&pSampleMemoryData);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("ReadFileIntoTerminal: failed to qi sample for IMemoryData");
|
|
|
|
pStreamSample->Release();
|
|
pStreamSample = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// get to the sample's memory buffer
|
|
//
|
|
|
|
DWORD nBufferSize = 0;
|
|
|
|
BYTE *pBuffer = NULL;
|
|
|
|
hr = pSampleMemoryData->GetInfo(&nBufferSize, &pBuffer, NULL);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
|
|
LogError("ReadFileIntoTerminal: "
|
|
"failed to get info on sample's memory");
|
|
|
|
pStreamSample->Release();
|
|
pStreamSample = NULL;
|
|
|
|
pSampleMemoryData->Release();
|
|
pSampleMemoryData = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// read file into memory buffer provided by the sample
|
|
//
|
|
|
|
LONG nBytesRead = 0;
|
|
|
|
hr = pFileReader->Read(pBuffer, nBufferSize, &nBytesRead);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("ReadFileIntoTerminal: failed to read data from file");
|
|
|
|
pStreamSample->Release();
|
|
pStreamSample = NULL;
|
|
|
|
pSampleMemoryData->Release();
|
|
pSampleMemoryData = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
else if (S_FALSE == hr)
|
|
{
|
|
//
|
|
// no more data
|
|
//
|
|
|
|
LogMessage("ReadFileIntoTerminal: finished reading file");
|
|
|
|
pStreamSample->Release();
|
|
pStreamSample = NULL;
|
|
|
|
pSampleMemoryData->Release();
|
|
pSampleMemoryData = NULL;
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// tell the sample how many useful bytes are in the sample's buffer
|
|
//
|
|
|
|
hr = pSampleMemoryData->SetActual(nBytesRead);
|
|
|
|
pSampleMemoryData->Release();
|
|
pSampleMemoryData = NULL;
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
|
|
LogError("ReadFileIntoTerminal: failed to SetActual (%ld bytes) "
|
|
"on the sample.", nBytesRead);
|
|
|
|
pStreamSample->Release();
|
|
pStreamSample = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// we are done with the sample. now let media streaming terminal
|
|
// process it asynchronously. when the terminal is finished with
|
|
// the sample, this sample will be returned to us from the call
|
|
// to AllocateSample()
|
|
//
|
|
|
|
hr = pStreamSample->Update(SSUPDATE_ASYNC, NULL, NULL, 0);
|
|
|
|
|
|
//
|
|
// with some MSPs, starting the stream can be done asynchronously, so
|
|
// there may be a delay between the time when terminal is selected
|
|
// (or call connected) and the time when the stream becomes usable.
|
|
//
|
|
// attempting to use the stream before the stream is active would
|
|
// result in the Update() returning error VFW_E_NOT_COMMITTED.
|
|
//
|
|
// Usually an application would not start using the stream until
|
|
// it gets media event CME_STREAM_ACTIVE. This requires the app
|
|
// to register a callback interface by calling
|
|
// ITTAPI::RegisterCallNotifications. Refer to documentation and other
|
|
// samples for more details on how this is done.
|
|
//
|
|
// To keep things simple, this sample doesn't do event processing.
|
|
// To deal with the problem of using the stream before it becomes
|
|
// active, we retry Update() until we succeed.
|
|
//
|
|
// Note that there is still a danger that the stream becomes
|
|
// disconnected before we process the first sample, in which case
|
|
// we will be stuck in a loop, which can be exited when the user
|
|
// presses ctrl+break
|
|
//
|
|
|
|
while ( (hr == VFW_E_NOT_COMMITTED)
|
|
&& (0 == nSampleCount)
|
|
&& !g_bExitRequested )
|
|
{
|
|
LogMessage("ReadFileIntoTerminal: "
|
|
"Update returned VFW_E_NOT_COMMITTED. "
|
|
"Likely cause: stream not yet started. Retrying.");
|
|
|
|
Sleep( 1000 );
|
|
|
|
hr = pStreamSample->Update(SSUPDATE_ASYNC, NULL, NULL, 0);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("ReadFileIntoTerminal: failed to Update the sample");
|
|
|
|
pStreamSample->Release();
|
|
pStreamSample = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// the sample was submitted successfully. update count
|
|
//
|
|
|
|
nSampleCount++;
|
|
|
|
if (nSampleCount == 300)
|
|
{
|
|
|
|
LogError("ReadFileIntoTerminal: sleeping 10 seconds");
|
|
|
|
Sleep(30000);
|
|
|
|
LogError("ReadFileIntoTerminal: woke up");
|
|
}
|
|
|
|
//
|
|
// keep the sample we have just submitted. on exit, we will wait
|
|
// for it to be processed by mst
|
|
//
|
|
|
|
SampleQ.push_back(pStreamSample);
|
|
|
|
} // file reading/sample-filling loop
|
|
|
|
|
|
LogMessage("ReadFileIntoTerminal: processed %lu samples", nSampleCount);
|
|
|
|
|
|
//
|
|
// walk through the list of all the samples we have submitted and wait for
|
|
// each sample to be done
|
|
//
|
|
|
|
while (!SampleQ.empty())
|
|
{
|
|
|
|
//
|
|
// get and remove a sample from the queue
|
|
//
|
|
|
|
IStreamSample *pStreamSample = SampleQ.front();
|
|
|
|
SampleQ.pop_front();
|
|
|
|
|
|
//
|
|
// wait for the Media Streaming Terminal to finish
|
|
// processing the sample
|
|
//
|
|
|
|
pStreamSample->CompletionStatus(COMPSTAT_WAIT, INFINITE);
|
|
|
|
|
|
//
|
|
// ignore the error code -- release the sample in any case
|
|
//
|
|
|
|
pStreamSample->Release();
|
|
pStreamSample = NULL;
|
|
|
|
}
|
|
|
|
|
|
LogMessage("ReadFileIntoTerminal: released all submitted samples");
|
|
|
|
|
|
//
|
|
// tell media streaming terminal's stream that there is no more data
|
|
//
|
|
|
|
pTerminalMediaStream->SendEndOfStream(0);
|
|
|
|
|
|
//
|
|
// ignore the error code
|
|
//
|
|
|
|
pTerminalMediaStream->Release();
|
|
pTerminalMediaStream = NULL;
|
|
|
|
|
|
//
|
|
// if we disconnect the call right away, the call may be dropped before
|
|
// receiver gets all the samples. An application should wait for
|
|
// STREAM_INACTIVE media event before disconnecting the call.
|
|
//
|
|
// Since, for simplicity, we are not processing events in this sample,
|
|
// wait several seconds to give the receiver a little time to complete
|
|
// processing.
|
|
//
|
|
|
|
LogMessage("ReadFileIntoTerminal: Sleeping to give the receiver time "
|
|
"to process everything we have sent.");
|
|
|
|
Sleep(7500);
|
|
|
|
|
|
LogMessage("ReadFileIntoTerminal: completed");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CreateAndSelectTerminal
|
|
//
|
|
// creates a media streaming terminal for capture, sets requested format,
|
|
// sets allocator properties, and selects the terminal on the call's first
|
|
// outgoing audio stream.
|
|
//
|
|
// returns S_OK and terminal if success
|
|
// error if failure
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT CreateAndSelectTerminal(IN ITBasicCallControl *pCall,
|
|
IN WAVEFORMATEX *pWaveFormat,
|
|
OUT ITTerminal **ppTerminal)
|
|
{
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
|
|
//
|
|
// don't return garbage
|
|
//
|
|
|
|
*ppTerminal = NULL;
|
|
|
|
|
|
|
|
//
|
|
// find an outgoing audio stream on the call
|
|
//
|
|
|
|
ITStream *pStream = NULL;
|
|
|
|
hr = FindAudioStream(pCall, &pStream);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateAndSelectTerminal: failed to find an outgoing audio stream");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// create media streaming terminal
|
|
//
|
|
|
|
ITTerminal *pTerminal = NULL;
|
|
|
|
pTerminal = CreateCaptureMediaStreamingTerminal(pCall);
|
|
|
|
|
|
if (NULL == pTerminal)
|
|
{
|
|
|
|
LogError("CreateAndSelectTerminal: Failed to create media streaming terminal");
|
|
|
|
pStream->Release();
|
|
pStream = NULL;
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// tell media streaming terminal format of the data
|
|
// we are going to send. If the terminal cannot handle this format
|
|
// return an error
|
|
//
|
|
|
|
hr = SetTerminalFormat(pTerminal, pWaveFormat);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogMessage("CreateAndSelectTerminal: "
|
|
"terminal does not support requested format");
|
|
|
|
pStream->Release();
|
|
pStream = NULL;
|
|
|
|
pTerminal->Release();
|
|
pTerminal = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// set allocator properties.
|
|
//
|
|
// calling ITAllocatorProperties::SetAllocatorProperties with the
|
|
// properties that are not optimal for the MSP in use can result
|
|
// in loss of sound quality.
|
|
//
|
|
// So make sure that you only call this function if you know you
|
|
// need it.
|
|
//
|
|
// Do not use ITAllocatorProperties::SetAllocatorProperties to set
|
|
// the size of the buffer you want to get when you fill samples,
|
|
// ITAllocatorProperties::SetBufferSize will accomplish that without
|
|
// affecting terminal's allocator properties.
|
|
//
|
|
|
|
// hr = SetAllocatorProperties(pTerminal);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
|
|
//
|
|
// not fatal -- we are still likely to successfully stream data
|
|
//
|
|
|
|
LogMessage("CreateAndSelectTerminal: "
|
|
"failed to set allocator properties. continuing.");
|
|
}
|
|
|
|
|
|
//
|
|
// select the terminal on the stream
|
|
//
|
|
|
|
hr = pStream->SelectTerminal(pTerminal);
|
|
|
|
|
|
//
|
|
// don't need the stream anymore
|
|
//
|
|
|
|
pStream->Release();
|
|
pStream = NULL;
|
|
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("CreateAndSelectTerminal: Failed to select terminal on the stream");
|
|
|
|
pTerminal->Release();
|
|
pTerminal = NULL;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// if everything went smoothly pTerminal has a pointer to configured
|
|
// and selected terminal. otherwise pTerminal is null and all resources
|
|
// have been released
|
|
//
|
|
|
|
*ppTerminal = pTerminal;
|
|
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// StreamFile
|
|
//
|
|
// use TAPI to connect to the remote machine to stream file
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT StreamFile(IN char *szFileName,
|
|
IN char *szAddressString,
|
|
IN char *szAddressType)
|
|
{
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
|
|
LogMessage("StreamFile: file [%s] address [%s] address type [%s]",
|
|
szFileName, szAddressString, szAddressType);
|
|
|
|
|
|
//
|
|
// check if the file is valid
|
|
//
|
|
|
|
if (!IsValidAudioFile(szFileName))
|
|
{
|
|
|
|
LogError("StreamFile: file not valid [%s]", szFileName);
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
|
|
//
|
|
// initialize COM libraries -- used by TAPI
|
|
//
|
|
|
|
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
|
|
|
if ( FAILED(hr))
|
|
{
|
|
LogError("StreamFile: Failed to CoInitialize");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// create and initialize TAPI
|
|
//
|
|
|
|
hr = InitializeTAPI();
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
|
|
//
|
|
// try to make a call
|
|
//
|
|
|
|
ITBasicCallControl *pCall = NULL;
|
|
|
|
hr = Call(szAddressString, szAddressType, &pCall);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
|
|
//
|
|
// construct the file reader object
|
|
// to be used to read file's data
|
|
//
|
|
// note: If you want to play more than one file in one call, reuse
|
|
// the same terminal for the duration of the call. Under
|
|
// Windows 2000, all data submitted via the Media Streaming Terminal
|
|
// during the call must have the same format. Changing a Media
|
|
// Streaming Terminal's format after it is initially configured will
|
|
// always return an error code, regardless of the OS version.
|
|
// Unselecting a Media Streaming Terminal and creating and selecting
|
|
// a new terminal on the same stream from the same call with a
|
|
// different format is not supported on Windows 2000. This may,
|
|
// however, be supported on other versions of Windows. For the latest
|
|
// information on which versions of Windows support this, please
|
|
// refer to the latest Platform SDK documentation.
|
|
//
|
|
|
|
CAVIFileReader FileReader;
|
|
|
|
hr = FileReader.Initialize(szFileName);
|
|
|
|
|
|
//
|
|
// get the file's format. remember to deallocate when done.
|
|
//
|
|
|
|
WAVEFORMATEX *pWaveFormat = NULL;
|
|
|
|
if (SUCCEEDED(hr) &&
|
|
SUCCEEDED(hr = FileReader.GetFormat(&pWaveFormat)))
|
|
{
|
|
|
|
//
|
|
// create and configure a media streaming terminal and select it on this call
|
|
//
|
|
//
|
|
|
|
ITTerminal *pPlaybackTerminal = NULL;
|
|
|
|
hr = CreateAndSelectTerminal(pCall, pWaveFormat, &pPlaybackTerminal);
|
|
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
|
|
//
|
|
// use the terminal to send the file
|
|
//
|
|
|
|
hr = ReadFileIntoTerminal(&FileReader, pPlaybackTerminal);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("StreamFile: failed to ReadFileIntoTerminal");
|
|
}
|
|
else
|
|
{
|
|
LogError("StreamFile: succeeded");
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// release the terminal, we no longer need it
|
|
//
|
|
|
|
pPlaybackTerminal->Release();
|
|
pPlaybackTerminal = NULL;
|
|
|
|
}
|
|
else
|
|
{
|
|
LogError("StreamFile: failed to create and select terminal");
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// no longer need wave format. free memory.
|
|
//
|
|
|
|
FreeMemory(pWaveFormat);
|
|
pWaveFormat = NULL;
|
|
|
|
} // got file format
|
|
else
|
|
{
|
|
LogError("StreamFile: failed to get file's format");
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// there is not much we can do if disconnect fails,
|
|
// so ignore its return code
|
|
//
|
|
|
|
pCall->Disconnect(DC_NORMAL);
|
|
|
|
pCall->Release();
|
|
pCall = NULL;
|
|
|
|
} // call connected
|
|
else
|
|
{
|
|
|
|
LogError("StreamFile: failed to connect to %s", szAddressString);
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// tapi has been initialized. shutdown now.
|
|
//
|
|
|
|
ShutdownTAPI();
|
|
|
|
} // initialized tapi
|
|
else
|
|
{
|
|
|
|
LogError("StreamFile: Failed to initialize TAPI");
|
|
|
|
}
|
|
|
|
|
|
CoUninitialize();
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// HelpScreen
|
|
//
|
|
// this function displays usage information
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void HelpScreen()
|
|
{
|
|
|
|
printf("Usage:\n\n"
|
|
" TAPISend filename address addresstype\n\n"
|
|
" where addresstype is [ ");
|
|
|
|
|
|
for (int i = 0; i < g_nNumberOfAddressTypes - 1; ++i)
|
|
{
|
|
printf("%s | ", g_szAddressTypes[i]);
|
|
}
|
|
|
|
printf("%s ]\n", g_szAddressTypes[i]);
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CtrlHandler
|
|
//
|
|
// handler for ctrl+break, close, logoff and shutdown.
|
|
//
|
|
// sets g_bExitRequested flag signaling shutdown. this ensures graceful exit
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
BOOL CtrlHandler(DWORD nEventType)
|
|
{
|
|
|
|
//
|
|
// are we in the middle of shutting down?
|
|
//
|
|
|
|
if (TRUE == g_bExitRequested)
|
|
{
|
|
LogMessage("CtrlHandler: shutdown is already in progress");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// any exit event (close, ctrl+break/C, logoff, shutdown)
|
|
// is a signal for the application to exit.
|
|
//
|
|
|
|
switch (nEventType)
|
|
{
|
|
|
|
case CTRL_C_EVENT:
|
|
case CTRL_CLOSE_EVENT:
|
|
case CTRL_BREAK_EVENT:
|
|
case CTRL_LOGOFF_EVENT:
|
|
case CTRL_SHUTDOWN_EVENT:
|
|
|
|
LogMessage("CtrlHandler: Initiating shutdown.");
|
|
|
|
|
|
//
|
|
// signal shutdown
|
|
//
|
|
|
|
g_bExitRequested = TRUE;
|
|
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// main
|
|
//
|
|
// usage: TAPISend filename address addresstype
|
|
//
|
|
// returns 0 if success, 1 if failure
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
int __cdecl main(int argc, char* argv[])
|
|
{
|
|
|
|
LogMessage("main: started");
|
|
|
|
|
|
//
|
|
// validate arguments
|
|
//
|
|
|
|
if (argc != 4)
|
|
{
|
|
HelpScreen();
|
|
|
|
LogMessage("main: invalid arguments, exiting.");
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
//
|
|
// we want to handle ctrl+c and ctrl+break events so we can cleanup on exit
|
|
// proceed even in case of failure
|
|
//
|
|
|
|
SetConsoleCtrlHandler( (PHANDLER_ROUTINE)CtrlHandler, TRUE);
|
|
|
|
|
|
//
|
|
// open the file, connect to the remote machine and stream the file over
|
|
//
|
|
|
|
HRESULT hr = StreamFile(argv[1], argv[2], argv[3]);
|
|
|
|
|
|
//
|
|
// exiting... we no longer want to handle ctrl+c and ctrl+break
|
|
//
|
|
|
|
SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, FALSE);
|
|
|
|
|
|
//
|
|
// was file streaming successful?
|
|
//
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
LogError("main: Failed to stream file");
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
LogMessage("main: completed");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|