2025-04-27 07:49:33 -04:00

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;
}