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

951 lines
25 KiB
C++

/*
Copyright (c) 1998-1999 Microsoft Corporation
*/
#include <windows.h>
#include <tapi3.h>
#include <strmif.h>
#include <mmsystem.h>
#include <uuids.h>
#include "callnot.h"
#include "resource.h"
//////////////////////////////////////////////////////////
// ANSMACH.EXE
//
// Sample application that handles incoming TAPI calls
// and uses the media streaming terminal to preform the
// functions of a simple answering machine.
//
// This sample was adapted from the T3IN sample.
//
// In order to receive incoming calls, the application must
// implement and register the outgoing ITCallNotification
// interface.
//
// This application will register to receive calls on
// all addresses that support at least the audio media type.
//
// NOTES:
// 1. This application is limited to working with one
// call at a time, and will not work correctly if
// multiple calls are present at the same time.
// 2. Some voice boards may have problems with small sample sizes
// (see SetAllocatorProperties below)
// 4. This works for half-duplex modems and voice boards with the
// wave MSP, as well as IP telephony with the H.323 MSP.
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
// constants
//////////////////////////////////////////////////////////
const DWORD MAXTERMINALS = 5;
//////////////////////////////////////////////////////////
// GLOBALS
//////////////////////////////////////////////////////////
bool g_fPlay;
bool g_fRecord;
HINSTANCE ghInst;
ITTAPI * gpTapi;
ITBasicCallControl * gpCall = NULL;
HWND ghDlg = NULL;
ITStream *g_pRecordStream = NULL;
ITTerminal *g_pPlayMedStrmTerm = NULL, *g_pRecordMedStrmTerm = NULL;
TCHAR gszTapi30[] = TEXT("TAPI 3.0 Answering Machine Sample");
CTAPIEventNotification * gpTAPIEventNotification;
ULONG gulAdvise;
//////////////////////////////////////////////////////////
// PROTOTYPES
//////////////////////////////////////////////////////////
INT_PTR
CALLBACK
MainDialogProc(
HWND hDlg,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
HRESULT
GetTerminal(
ITAddress *,
BSTR bstrMedia,
ITTerminal ** ppTerminal
);
HRESULT
RegisterTapiEventInterface();
HRESULT
ListenOnAddresses();
HRESULT
ListenOnThisAddress(
ITAddress * pAddress
);
HRESULT
AnswerTheCall();
HRESULT
DisconnectTheCall();
void
ReleaseTheCall();
void
DoMessage(
LPTSTR pszMessage
);
void
SetStatusMessage(
LPTSTR pszMessage
);
HRESULT
InitializeTapi();
void
ShutdownTapi();
//
// Telephone quality audio.
//
WAVEFORMATEX gwfx = {WAVE_FORMAT_PCM, 1, 8000, 16000, 2, 16, 0};
//////////////////////////////////////////////////////////
//
// FUNCTIONS
//
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
// WinMain
//////////////////////////////////////////////////////////
int
WINAPI
WinMain(
HINSTANCE hInst,
HINSTANCE hPrevInst,
LPSTR lpCmdLine,
int nCmdShow
)
{
if (__argc != 2)
{
MessageBox(NULL, "Usage: ansmach play|record|both", "ansmach", MB_OK);
return 0;
}
switch(__argv[1][0])
{
case 'p':
case 'P':
g_fPlay = true;
g_fRecord = false;
break;
case 'r':
case 'R':
g_fPlay = false;
g_fRecord = true;
break;
case 'b':
case 'B':
g_fPlay = true;
g_fRecord = true;
break;
default:
MessageBox(NULL, "Usage: ansmach play|record|both", "ansmach", MB_OK);
return 0;
break;
}
ghInst = hInst;
// need to coinit
if (!SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
{
return 0;
}
// do all tapi initialization
if (S_OK != InitializeTapi())
{
return 0;
}
// everything is initialized, so
// start the main dialog box
DialogBox(
ghInst,
MAKEINTRESOURCE(IDD_MAINDLG),
NULL,
MainDialogProc
);
//
// When EndDialog is called, we get here; clean up and exit.
//
ShutdownTapi();
CoUninitialize();
return 1;
}
//////////////////////////////////////////////////////////////
// InitializeTapi
//
// Various initializations
///////////////////////////////////////////////////////////////
HRESULT
InitializeTapi()
{
HRESULT hr;
// cocreate the TAPI object
hr = CoCreateInstance(
CLSID_TAPI,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITTAPI,
(LPVOID *)&gpTapi
);
if (hr != S_OK)
{
DoMessage(TEXT("CoCreateInstance on TAPI failed"));
return hr;
}
// call initialize. this must be called before
// any other tapi functions are called.
hr = gpTapi->Initialize();
if (S_OK != hr)
{
DoMessage(TEXT("TAPI failed to initialize"));
gpTapi->Release();
gpTapi = NULL;
return hr;
}
gpTAPIEventNotification = new CTAPIEventNotification;
hr = RegisterTapiEventInterface();
// Set the Event filter to only give us only the events we process
gpTapi->put_EventFilter(TE_CALLNOTIFICATION | TE_CALLSTATE | TE_CALLMEDIA);
// find all address objects that
// we will use to listen for calls on
hr = ListenOnAddresses();
if (S_OK != hr)
{
DoMessage(TEXT("Could not find any addresses to listen on"));
gpTapi->Release();
gpTapi = NULL;
return hr;
}
return S_OK;
}
///////////////////////////////////////////////////////////////
// ShutdownTapi
///////////////////////////////////////////////////////////////
void
ShutdownTapi()
{
//
// if there is still a call, disconnect and release it
//
DisconnectTheCall();
ReleaseTheCall();
//
// Sleep for a little while to give tapi a chance to finish
// disconnecting the call. Note that our dialog box has
// disappeared by now so the user has no idea that our process
// is still around. This step would not be necessary if the
// call were disconnected interactively as a separate operation
// from shutting down TAPI. The argument to Sleep is in
// milliseconds.
//
Sleep(5000);
//
// release main object.
//
if (NULL != gpTapi)
{
gpTapi->Shutdown();
gpTapi->Release();
}
}
///////////////////////////////////////////////////////////////////////////
// MainDlgProc
///////////////////////////////////////////////////////////////////////////
INT_PTR
CALLBACK
MainDialogProc(
HWND hDlg,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch (uMsg)
{
case WM_PRIVATETAPIEVENT:
{
OnTapiEvent(
(TAPI_EVENT) wParam,
(IDispatch *) lParam
);
return 0;
}
case WM_INITDIALOG:
{
// set up dialog
ghDlg = hDlg;
SetStatusMessage( TEXT("Waiting for a call..."));
return 0;
}
case WM_COMMAND:
{
if ( LOWORD(wParam) == IDCANCEL )
{
//
// quit
//
EndDialog( hDlg, 0 );
return 1;
}
switch ( LOWORD(wParam) )
{
// dial request
case IDC_ANSWER:
{
SetStatusMessage(TEXT("Answering..."));
// answer the call
if ( S_OK == AnswerTheCall() )
{
SetStatusMessage(TEXT("Connected"));
}
else
{
DoMessage(TEXT("Answer failed"));
SetStatusMessage(TEXT("Waiting for a call..."));
}
return 1;
}
// disconnect request
case IDC_DISCONNECT:
{
// If the remote party disconnected first, then the call was
// already disconnected and released, so do not disconnect
// it again.
if ( gpCall != NULL )
{
SetStatusMessage(TEXT("Disconnecting..."));
// disconnect the call
if (S_OK != DisconnectTheCall())
{
DoMessage(TEXT("Disconnect failed"));
}
}
return 1;
}
// connect notification
case IDC_CONNECTED:
{
SetStatusMessage(TEXT("Connected; waiting for media streams to run..."));
return 1;
}
// disconnected notification
case IDC_DISCONNECTED:
{
// release
ReleaseTheCall();
SetStatusMessage(TEXT("Waiting for a call..."));
return 1;
}
default:
return 0;
}
}
default:
return 0;
}
}
HRESULT
RegisterTapiEventInterface()
{
HRESULT hr = S_OK;
IConnectionPointContainer * pCPC;
IConnectionPoint * pCP;
hr = gpTapi->QueryInterface(
IID_IConnectionPointContainer,
(void **)&pCPC
);
if (!SUCCEEDED(hr))
{
return hr;
}
hr = pCPC->FindConnectionPoint(
IID_ITTAPIEventNotification,
&pCP
);
pCPC->Release();
if (!SUCCEEDED(hr))
{
return hr;
}
hr = pCP->Advise(
gpTAPIEventNotification,
&gulAdvise
);
pCP->Release();
return hr;
}
////////////////////////////////////////////////////////////////////////
// ListenOnAddresses
//
// This procedure will find all addresses that support audioin and audioout
// and will call ListenOnThisAddress to start listening on it.
////////////////////////////////////////////////////////////////////////
HRESULT
ListenOnAddresses()
{
HRESULT hr = S_OK;
IEnumAddress * pEnumAddress;
ITAddress * pAddress;
ITMediaSupport * pMediaSupport;
VARIANT_BOOL bSupport;
// enumerate the addresses
hr = gpTapi->EnumerateAddresses( &pEnumAddress );
if (S_OK != hr)
{
return hr;
}
while ( TRUE )
{
// get the next address
hr = pEnumAddress->Next( 1, &pAddress, NULL );
if (S_OK != hr)
{
break;
}
pAddress->QueryInterface( IID_ITMediaSupport, (void **)&pMediaSupport );
// does it support Audio
pMediaSupport->QueryMediaType(
TAPIMEDIATYPE_AUDIO,
&bSupport
);
if (bSupport)
{
// If it does then we'll listen.
hr = ListenOnThisAddress( pAddress );
if (S_OK != hr)
{
DoMessage(TEXT("Listen failed on an address"));
}
}
pMediaSupport->Release();
pAddress->Release();
}
pEnumAddress->Release();
return S_OK;
}
///////////////////////////////////////////////////////////////////
// ListenOnThisAddress
//
// We call RegisterCallNotifications to inform TAPI that we want
// notifications of calls on this address. We already resistered
// our notification interface with TAPI, so now we are just telling
// TAPI that we want calls from this address to trigger events on
// our existing notification interface.
//
///////////////////////////////////////////////////////////////////
HRESULT
ListenOnThisAddress(
ITAddress * pAddress
)
{
//
// RegisterCallNotifications takes a media type bitmap indicating
// the set of media types we are interested in. We know the
// address supports audio.
//
long lMediaTypes = TAPIMEDIATYPE_AUDIO;
HRESULT hr;
long lRegister;
hr = gpTapi->RegisterCallNotifications(
pAddress,
VARIANT_TRUE,
VARIANT_TRUE,
lMediaTypes,
0,
&lRegister
);
return hr;
}
/////////////////////////////////////////////////////////
// GetMediaStreamTerminal
//
// Create a media streaming terminal for the given
// direction.
//
/////////////////////////////////////////////////////////
HRESULT
GetMediaStreamTerminal(
ITAddress * pAddress,
TERMINAL_DIRECTION dir,
ITTerminal ** ppTerminal
)
{
HRESULT hr = S_OK;
ITTerminalSupport * pTerminalSupport;
BSTR bstrTerminalClass;
LPOLESTR lpTerminalClass;
AM_MEDIA_TYPE mt;
// get the terminal support interface
pAddress->QueryInterface( IID_ITTerminalSupport, (void **)&pTerminalSupport );
StringFromIID(
CLSID_MediaStreamTerminal,
&lpTerminalClass
);
bstrTerminalClass = SysAllocString ( lpTerminalClass );
CoTaskMemFree( lpTerminalClass );
hr = pTerminalSupport->CreateTerminal(
bstrTerminalClass,
TAPIMEDIATYPE_AUDIO,
dir,
ppTerminal
);
SysFreeString( bstrTerminalClass );
pTerminalSupport->Release();
if (FAILED(hr)) { return hr; }
if (TD_CAPTURE == dir)
{
//
// Now set the media format for the terminal.
//
// On addresses where a variety of formats are supported (such as
// Wave MSP addresses, which are used on most modems and voice boards),
// this call is mandatory or the terminal will not be able to connect.
//
// For other addresses, such as those implemented over IP, the format
// may be fixed / predetermined. In that case, this call will fail
// if the format is not the same as the predetermined format. To
// retrieve such a predetermined format, an application can use
// ITAMMediaFormat::get_MediaFormat.
//
// For the purposes of this example, we just set our preferred format
// and ignore any failure code. This is fine for both Wave MSP
// addresses and for addresses implemented over IP (which happens to
// use the same format the one we are setting here).
//
ITAMMediaFormat *pITFormat;
hr = (*ppTerminal)->QueryInterface(IID_ITAMMediaFormat, (void **)&pITFormat);
if (FAILED(hr)) { (*ppTerminal)->Release(); *ppTerminal = NULL; return hr; }
mt.majortype = MEDIATYPE_Audio;
mt.subtype = MEDIASUBTYPE_PCM;
mt.bFixedSizeSamples = TRUE;
mt.bTemporalCompression = FALSE;
mt.lSampleSize = 0;
mt.formattype = FORMAT_WaveFormatEx;
mt.pUnk = NULL;
mt.cbFormat = sizeof(WAVEFORMATEX);
mt.pbFormat = (BYTE*)&gwfx;
pITFormat->put_MediaFormat(&mt);
pITFormat->Release();
//
// optional - set the buffer size of fragments negotiated with rest of the graph
// this is only needed for applications that are not happy with the default fragment size
// (which depends on the MSP -- for the Wave MSP (used on most modems and voice boards)
// the default is 20 ms.
//
ITAllocatorProperties *pITAllocatorProperties;
hr = (*ppTerminal)->QueryInterface(IID_ITAllocatorProperties, (void **)&pITAllocatorProperties);
if (FAILED(hr)) { (*ppTerminal)->Release(); *ppTerminal = NULL; return hr; }
// Call to SetAllocatorProperties below illustrates how
// an app can control the number and size of buffers.
// A 30 ms sample size is most appropriate for IP (especailly G.723.1)
// (that's 480 bytes at 16-bit 8 KHz PCM); in fact you may get very
// poor audio quality with other settings.
// However, 30ms can cause poor audio quality on some voice boards --
// voice boards generally work best with large buffers.
// If this method is not called, the allocator properties
// suggested by the connecting filter may be used.
ALLOCATOR_PROPERTIES AllocProps;
AllocProps.cbBuffer = 480;
AllocProps.cBuffers = 5;
AllocProps.cbAlign = 1;
AllocProps.cbPrefix = 0;
hr = pITAllocatorProperties->SetAllocatorProperties(&AllocProps);
hr = pITAllocatorProperties->SetAllocateBuffers(TRUE);
BOOL bAllocateBuffer;
hr = pITAllocatorProperties->GetAllocateBuffers(&bAllocateBuffer);
pITAllocatorProperties->Release();
}
return hr;
}
/////////////////////////////////////////////////////////////////
// ReleaseTerminals
//
/////////////////////////////////////////////////////////////////
void
ReleaseTerminals(
ITTerminal ** ppTerminals,
LONG nNumTerminals
)
{
for (long i = 0; i < nNumTerminals; i ++)
{
if (ppTerminals[i])
{
ppTerminals[i]->Release();
}
}
}
/////////////////////////////////////////////////////////////////////
// Answer the call
/////////////////////////////////////////////////////////////////////
HRESULT
AnswerTheCall()
{
HRESULT hr;
ITCallInfo * pCallInfo;
ITAddress * pAddress;
g_pRecordStream = NULL;
g_pPlayMedStrmTerm = NULL;
g_pRecordMedStrmTerm = NULL;
if (NULL == gpCall)
{
return E_UNEXPECTED;
}
// get the address object of this call
gpCall->QueryInterface( IID_ITCallInfo, (void**)&pCallInfo );
pCallInfo->get_Address( &pAddress );
pCallInfo->Release();
ITStreamControl * pStreamControl;
// get the ITStreamControl interface for this call
hr = gpCall->QueryInterface(IID_ITStreamControl,
(void **) &pStreamControl);
// enumerate the streams
IEnumStream * pEnumStreams;
hr = pStreamControl->EnumerateStreams(&pEnumStreams);
pStreamControl->Release();
if (FAILED(hr))
{
pAddress->Release();
gpCall->Release();
gpCall = NULL;
return hr;
}
// for each stream
ITStream * pStream;
// Since we are interested in the audio capture and render streams only,
// the while loop terminates when either all the streams are done or
// when terminals for both the streams i.e. render and capture are created
// We will also make sure that even though terminals are created on the
// streams only one terminal will be selected to support half duplex lines
while ( ( S_OK == pEnumStreams->Next(1, &pStream, NULL) ) &&
( (g_pPlayMedStrmTerm == NULL) || (g_pRecordMedStrmTerm==NULL) ) )
{
TERMINAL_DIRECTION td;
hr = pStream->get_Direction(&td);
if ( SUCCEEDED(hr) )
{
//
// create the media terminals for this call
//
// Only one capture terminal will be created even if they are multiple capture streams
//
if ( (td == TD_CAPTURE) && g_fPlay && (g_pPlayMedStrmTerm == NULL))
{
GetMediaStreamTerminal(
pAddress,
TD_CAPTURE,
&g_pPlayMedStrmTerm
);
// the created terminal is selected on the stream if the user
// wishes to play or play and record. The play terminal has to be
// selected in any case before the record since the message will always
// be played before the record
hr = pStream->SelectTerminal(g_pPlayMedStrmTerm);
if ( FAILED (hr) )
{
::MessageBox(NULL,"Select Terminal for Play Failed", "Answer the call", MB_OK);
return hr;
}
}
// Create terminal for Render stream
// Note that we only create the terminal.. we do not select it on the stream
if ( (td == TD_RENDER) && g_fRecord && (g_pRecordMedStrmTerm == NULL))
{
GetMediaStreamTerminal(
pAddress,
TD_RENDER,
&g_pRecordMedStrmTerm
);
g_pRecordStream = pStream;
g_pRecordStream->AddRef();
}
}
pStream->Release();
}
// the record terminal is selected on the stream only if the play terminal is not already
// selected. This will happen only in the case when the user selects only record and no play
// Only one terminal is selected at a time inorder to support half-duplex lines
if (!g_fPlay)
{
hr = g_pRecordStream->SelectTerminal(g_pRecordMedStrmTerm);
if ( FAILED (hr) )
{
::MessageBox(NULL,"Select Terminal for Record Failed", "Answer the call", MB_OK);
return hr;
}
g_pRecordMedStrmTerm->Release();
g_pRecordStream->Release();
}
pEnumStreams->Release();
// release the address
pAddress->Release();
// answer the call
hr = gpCall->Answer();
if (S_OK != hr)
{
gpCall->Release();
gpCall = NULL;
return hr;
}
return hr;
}
//////////////////////////////////////////////////////////////////////
// DisconnectTheCall
//
// Disconnects the call
//////////////////////////////////////////////////////////////////////
HRESULT
DisconnectTheCall()
{
HRESULT hr = S_OK;
if (NULL != gpCall)
{
hr = gpCall->Disconnect( DC_NORMAL );
// Do not release the call yet, as that would prevent
// us from receiving the disconnected notification.
return hr;
}
return S_FALSE;
}
//////////////////////////////////////////////////////////////////////
// ReleaseTheCall
//
// Releases the call
//////////////////////////////////////////////////////////////////////
void
ReleaseTheCall()
{
if (NULL != gpCall)
{
gpCall->Release();
gpCall = NULL;
}
}
///////////////////////////////////////////////////////////////////
//
// HELPER FUNCTIONS
//
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
// DoMessage
///////////////////////////////////////////////////////////////////
void
DoMessage(
LPTSTR pszMessage
)
{
MessageBox(
ghDlg,
pszMessage,
gszTapi30,
MB_OK
);
}
//////////////////////////////////////////////////////////////////
// SetStatusMessage
//////////////////////////////////////////////////////////////////
void
SetStatusMessage(
LPTSTR pszMessage
)
{
SetDlgItemText(
ghDlg,
IDC_STATUS,
pszMessage
);
}