////////////////////////////////////////////////////////////////////// // // // Copyright (c) 1998-2001 Microsoft Corporation // // // Fileterm.cpp // // Sample application that handles incoming TAPI calls // and uses the file terminals to perform the // functions of a simple answering machine. // // This sample was adapted from the ANSMACH 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 work with one // call at a time - all other calls will be rejected // 2. This works for half-duplex modems and voice boards with the // wave MSP, as well as IP telephony with the H.323 MSP. // ////////////////////////////////////////////////////////////////////// #include #include #include #include "FileTerm.h" #include "callnot.h" #include "resource.h" ////////////////////////////////////////////////////////// // GLOBALS ////////////////////////////////////////////////////////// // // Application dialog window handle // HWND g_hDlg = NULL; // // TAPI object, Call Object // ITTAPI * g_pTapi = NULL; ITBasicCallControl * g_pCall = NULL; // // File Terminals // ITTerminal *g_pPlayFileTerm = NULL; ITTerminal *g_pRecordFileTerm = NULL; // // Connection point cookie // ULONG g_ulAdvise = 0; ////////////////////////////////////////////////////////// // // FUNCTIONS // ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// // wWinMain - UNICODE application ////////////////////////////////////////////////////////// int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow ) { // // need to coinitialize // if (!SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) { return 0; } // // do all tapi initialization // if (S_OK == InitializeTapi()) { // // everything is initialized, so start the main dialog box // DialogBox( hInst, MAKEINTRESOURCE(IDD_MAINDLG), NULL, MainDialogProc ); } // // When EndDialog is called, we get here; clean up and exit. // CoUninitialize(); return 1; } ////////////////////////////////////////////////////////////// // InitializeTapi // // Create TAPI object, register callback object, // register addresses for call notifications // /////////////////////////////////////////////////////////////// HRESULT InitializeTapi() { HRESULT hr; // // cocreate the TAPI object // hr = CoCreateInstance( CLSID_TAPI, NULL, CLSCTX_INPROC_SERVER, IID_ITTAPI, (LPVOID *)&g_pTapi ); if (FAILED(hr)) { DoMessage(_T("InitializeTapi: CoCreateInstance on TAPI failed")); return hr; } // // call initialize. this must be called before any other tapi functions are called. // hr = g_pTapi->Initialize(); if (FAILED(hr)) { g_pTapi->Release(); g_pTapi = NULL; DoMessage(_T("InitializeTapi: TAPI failed to initialize")); return hr; } // //register event callback object // hr = RegisterTapiEventInterface(); if (FAILED(hr)) { // // shutdown and release TAPI // g_pTapi->Shutdown(); g_pTapi->Release(); g_pTapi = NULL; DoMessage(_T("InitializeTapi: RegisterTapiEventInterface failed")); return hr; } // // put the Event filter to only give us only the events we process // hr = g_pTapi->put_EventFilter(TE_CALLNOTIFICATION | TE_CALLSTATE | TE_CALLMEDIA | TE_FILETERMINAL); if (FAILED(hr)) { // // unregister callback // UnRegisterTapiEventInterface(); // // shutdown and release TAPI // g_pTapi->Shutdown(); g_pTapi->Release(); g_pTapi = NULL; DoMessage(_T("InitializeTapi: put_EventFilter failed")); return hr; } // find all address objects that // we will use to listen for calls on hr = ListenOnAddresses(); if (FAILED(hr)) { // // unregister callback // UnRegisterTapiEventInterface(); // // shutdown and release TAPI // g_pTapi->Shutdown(); g_pTapi->Release(); g_pTapi = NULL; DoMessage(_T("InitializeTapi: ListenOnAddresses failed")); } return hr; } /////////////////////////////////////////////////////////////// // ShutdownTapi // // Called when application exits // Disconnects the call and releases all internal objects // /////////////////////////////////////////////////////////////// void ShutdownTapi() { // // unregister callback // UnRegisterTapiEventInterface(); // // if there is still a call, disconnect and release it // DisconnectTheCall(); // // // ReleaseTheCall(); // //wait for TAPI to cool off (2 sec. may be too short) //maybe there was an event sent by PostMessage but not processed yet // { // //we need an event for MsgWaitForMultipleObjectsEx // HANDLE hDumbEvent = CreateEvent(NULL, TRUE, FALSE, NULL); while(NULL != hDumbEvent) { DWORD dwStatus; MSG msg; dwStatus=::MsgWaitForMultipleObjectsEx(1, &hDumbEvent, TAPI_TIMEOUT, QS_ALLPOSTMESSAGE, MWMO_ALERTABLE); if(dwStatus != WAIT_TIMEOUT) { while(TRUE == ::PeekMessage(&msg, g_hDlg,//only from post thread NULL, NULL, PM_REMOVE)) { // //if not TAPI event translate and dispatch it // if(msg.message != WM_PRIVATETAPIEVENT) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } // //TAPI event // else { if((IDispatch *) msg.lParam != NULL) { //release the event ((IDispatch *) msg.lParam)->Release(); msg.lParam = NULL; } } } } else { ::CloseHandle(hDumbEvent); hDumbEvent = NULL; } } } // // release main TAPI object. // if (NULL != g_pTapi) { g_pTapi->Shutdown(); g_pTapi->Release(); g_pTapi = NULL; } } /////////////////////////////////////////////////////////////////////////// // MainDlgProc // // Process dialog messages // /////////////////////////////////////////////////////////////////////////// INT_PTR CALLBACK MainDialogProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch (uMsg) { case WM_PRIVATETAPIEVENT: { // //Process TAPI event // OnTapiEvent( (TAPI_EVENT) wParam, (IDispatch *) lParam ); return 0; } case WM_INITDIALOG: { // // store dialog window handle // g_hDlg = hDlg; SetStatusMessage( _T("Waiting for a call...")); return 0; } case WM_COMMAND: { if ( LOWORD(wParam) == IDCANCEL ) { ShutdownTapi(); // // quit // EndDialog( hDlg, 0 ); return 1; } switch ( LOWORD(wParam) ) { // // offering notification // case IDC_ANSWER: { SetStatusMessage(_T("Answering...")); // answer the call if ( S_OK == AnswerTheCall() ) { SetStatusMessage(_T("Connecting...")); } else { DoMessage(_T("Answer failed")); SetStatusMessage(_T("Waiting for a call...")); } return 1; } // // connect notification // case IDC_CONNECTED: { SetStatusMessage(_T("Connected; waiting for file playback terminal to run...")); return 1; } // // disconnected notification // case IDC_DISCONNECTED: { // // release internal call objetcs // ReleaseTheCall(); SetStatusMessage(_T("Waiting for a call...")); return 1; } default: return 0; } } case WM_TIMER: { // //disconnect the call when maximum record time is reached //it will unselect the terminal too // if ( g_pCall != NULL ) { SetStatusMessage(_T("Disconnecting...")); // // disconnect the call // if (S_OK != DisconnectTheCall()) { DoMessage(_T("Disconnect failed")); } } return 1; } default: return 0; } } /////////////////////////////////////////////////////////////////////////////// // // RegisterTapiEventInterface // // register call notification object used to receive events // fired by TAPI object // // The event notification interface is registered with TAPI 3.1 // through the IConnectionPoint mechanism. // For more information on IConnectionPoint, and IConnectiontPointContainer // please refer to the COM documentation. // /////////////////////////////////////////////////////////////////////////////// HRESULT RegisterTapiEventInterface() { HRESULT hr = S_OK; IConnectionPointContainer * pCPC = NULL; IConnectionPoint * pCP = NULL; hr = g_pTapi->QueryInterface( IID_IConnectionPointContainer, (void **)&pCPC ); if (!SUCCEEDED(hr)) { DoMessage(_T("RegisterTapiEventInterface: ") _T("Failed to get IConnectionPointContainer on TAPI")); return hr; } hr = pCPC->FindConnectionPoint( IID_ITTAPIEventNotification, &pCP ); pCPC->Release(); if (!SUCCEEDED(hr)) { DoMessage(_T("RegisterTapiEventInterface: Failed to get a connection point for ") _T("ITTAPIEventNotification")); return hr; } // //create event callback object // CTAPIEventNotification* pTAPIEventNotification = new CTAPIEventNotification; if(NULL == pTAPIEventNotification) { DoMessage(_T("RegisterTapiEventInterface: Could not create CTAPIEventNotification")); return E_OUTOFMEMORY; } // //advise the callback object - it will be released by Unadvise // hr = pCP->Advise( pTAPIEventNotification, &g_ulAdvise ); pCP->Release(); // // whether Advise failed or succeeded - Advise already addrefed the object, // we no longer need a reference to the callback object // pTAPIEventNotification->Release(); pTAPIEventNotification = NULL; if (FAILED(hr)) { DoMessage(_T("RegisterTapiEventInterface: Advise failed")); } return hr; } /////////////////////////////////////////////////////////////////////////////// // // UnRegisterTapiEventInterface // // Unadvise call notification object. // // The event notification interface was registered with TAPI 3.1 // through the IConnectionPoint mechanism. // For more information on IConnectionPoint, and IConnectiontPointContainer // please refer to the COM documentation. // /////////////////////////////////////////////////////////////////////////////// HRESULT UnRegisterTapiEventInterface() { HRESULT hr = S_OK; IConnectionPointContainer *pCPC = NULL; IConnectionPoint *pCP = NULL; // // get connection point container on the tapi object // hr = g_pTapi->QueryInterface(IID_IConnectionPointContainer, (void **)&pCPC); if (FAILED(hr)) { DoMessage(_T("UnRegisterTapiEventInterface: ") _T("Failed to get IConnectionPointContainer on TAPI")); return hr; } // // find connection point for our interface // hr = pCPC->FindConnectionPoint(IID_ITTAPIEventNotification, &pCP); pCPC->Release(); pCPC = NULL; if (FAILED(hr)) { DoMessage(_T("UnRegisterTapiEventInterface: Failed to get a connection point for ") _T("ITTAPIEventNotification")); return hr; } // // unregister the callback // hr = pCP->Unadvise(g_ulAdvise); pCP->Release(); pCP = NULL; g_ulAdvise = 0; if (FAILED(hr)) { DoMessage(_T("UnRegisterTapiEventInterface: Unadvise failed")); } return hr; } //////////////////////////////////////////////////////////////////////// // ListenOnAddresses // // This function will find all addresses that support audio in and audio out // and will call ListenOnThisAddress to start listening on it. //////////////////////////////////////////////////////////////////////// HRESULT ListenOnAddresses() { HRESULT hr = S_OK; IEnumAddress * pEnumAddress = NULL; ITAddress * pAddress = NULL; ITMediaSupport * pMediaSupport = NULL; VARIANT_BOOL bSupport = VARIANT_FALSE; BOOL bAddressFound = FALSE; // enumerate the addresses hr = g_pTapi->EnumerateAddresses( &pEnumAddress ); if (FAILED(hr)) { DoMessage(_T("ListenOnAddresses: EnumerateAddresses failed")); return hr; } // // loop for all addresses // while ( S_OK == pEnumAddress->Next( 1, &pAddress, NULL ) ) { hr = pAddress->QueryInterface( IID_ITMediaSupport, (void **)&pMediaSupport ); if(FAILED(hr)) { pAddress->Release(); continue; } // // does it support Audio // hr = pMediaSupport->QueryMediaType( TAPIMEDIATYPE_AUDIO, &bSupport ); pMediaSupport->Release(); if(FAILED(hr)) { pAddress->Release(); continue; } if (bSupport) { // // If it does support then we'll listen. // hr = ListenOnThisAddress( pAddress ); if (SUCCEEDED(hr)) { bAddressFound = TRUE; } } pAddress->Release(); } // //clean up // pEnumAddress->Release(); if(!bAddressFound) { DoMessage(_T("ListenOnAddresses: not address found")); return E_FAIL; } else { 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( IN 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 lRegister = 0; return g_pTapi->RegisterCallNotifications( pAddress, VARIANT_FALSE, VARIANT_TRUE, TAPIMEDIATYPE_AUDIO, 0, &lRegister ); } ///////////////////////////////////////////////////////////////////// // // Answers the call // It will create and select the Playback terminal // ///////////////////////////////////////////////////////////////////// HRESULT AnswerTheCall() { // // check if call available // if (NULL == g_pCall) { DoMessage( _T("AnswerTheCall: call not available")); return E_UNEXPECTED; } // // it should not happen, but if you have already the terminal release it // if(NULL != g_pPlayFileTerm) { g_pPlayFileTerm->Release(); g_pPlayFileTerm = NULL; } // // prepare to request the terminal - need ITBasicCallControl2 interface // ITBasicCallControl2* pITBCC2 = NULL; HRESULT hr = g_pCall->QueryInterface( IID_ITBasicCallControl2, (void**)&pITBCC2 ); if(FAILED(hr)) { DoMessage( _T("AnswerTheCall: QI ITBasicCallControl2 failed")); g_pCall->Disconnect(DC_NORMAL); return hr; } // // prepare to request the terminal - need CLSID BSTR // BSTR bstrCLSID = NULL; hr = StringFromCLSID(CLSID_FilePlaybackTerminal, &bstrCLSID); if(FAILED(hr)) { pITBCC2->Release(); DoMessage( _T("AnswerTheCall: StringFromCLSID failed")); g_pCall->Disconnect(DC_NORMAL); return hr; } // //request the terminal using right media type and direction // hr = pITBCC2->RequestTerminal(bstrCLSID, TAPIMEDIATYPE_AUDIO, TD_CAPTURE, &g_pPlayFileTerm); // //clean up // ::CoTaskMemFree(bstrCLSID); if(FAILED(hr)) { pITBCC2->Release(); DoMessage( _T("AnswerTheCall: RequestTerminal failed")); g_pCall->Disconnect(DC_NORMAL); return hr; } // // prepare to put the file name // BSTR bstrFileName = ::SysAllocString(PLAY_FILENAME); if(NULL == bstrFileName) { g_pPlayFileTerm->Release(); g_pPlayFileTerm = NULL; pITBCC2->Release(); DoMessage( _T("AnswerTheCall: SysAllocString for play list failed")); g_pCall->Disconnect(DC_NORMAL); return E_OUTOFMEMORY; } // // call helper method for put_PlayList // hr = PutPlayList(g_pPlayFileTerm, bstrFileName); // // free the file name // ::SysFreeString(bstrFileName); if(FAILED(hr)) { g_pPlayFileTerm->Release(); g_pPlayFileTerm = NULL; pITBCC2->Release(); DoMessage( _T("AnswerTheCall: PutPlayList failed")); g_pCall->Disconnect(DC_NORMAL); return hr; } // //select the terminal using ITBasicCallControl2 method // hr = pITBCC2->SelectTerminalOnCall(g_pPlayFileTerm); if(FAILED(hr)) { g_pPlayFileTerm->Release(); g_pPlayFileTerm = NULL; pITBCC2->Release(); DoMessage( _T("AnswerTheCall: SelectTerminalOnCall failed")); g_pCall->Disconnect(DC_NORMAL); return hr; } // //finally answer the call // hr = g_pCall->Answer(); if(FAILED(hr)) { g_pPlayFileTerm->Release(); g_pPlayFileTerm = NULL; pITBCC2->Release(); DoMessage( _T("AnswerTheCall: Answer failed")); g_pCall->Disconnect(DC_NORMAL); return hr; } // //if we are here we are OK // pITBCC2->Release(); return S_OK; } ////////////////////////////////////////////////////////////////////// // DisconnectTheCall // // Disconnects the call - the objects will be released when // processing disconnect notification // ////////////////////////////////////////////////////////////////////// HRESULT DisconnectTheCall() { // //disconnect the call // if (NULL != g_pCall) { // // we will release the call and terminals on disconnected notification. // return g_pCall->Disconnect( DC_NORMAL ); } return E_FAIL; } ////////////////////////////////////////////////////////////////////// // ReleaseTheCall // // Releases the objects used for call processing // ////////////////////////////////////////////////////////////////////// void ReleaseTheCall() { // //do not the timer anymore anymore // KillTimer(g_hDlg, TIMER_ID); // //release the terminals // if(NULL != g_pPlayFileTerm) { g_pPlayFileTerm->Release(); g_pPlayFileTerm = NULL; } if(NULL != g_pRecordFileTerm) { g_pRecordFileTerm->Release(); g_pRecordFileTerm = NULL; } // //release the call object - this will unselect all selected terminals // if (NULL != g_pCall) { g_pCall->Release(); g_pCall = NULL; } } /////////////////////////////////////////////////////////////////// // // HELPER FUNCTIONS // /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// // // DoMessage - pops up a message box // /////////////////////////////////////////////////////////////////// void DoMessage( IN LPWSTR pszMessage) { ::MessageBox( g_hDlg, pszMessage, MSGBOX_NAME, MB_OK ); } ////////////////////////////////////////////////////////////////// // // SetStatusMessage - change the status // ////////////////////////////////////////////////////////////////// void SetStatusMessage( IN LPWSTR pszMessage) { ::SetDlgItemText( g_hDlg, IDC_STATUS, pszMessage ); } ////////////////////////////////////////////////////////////////// // // PutPlayList - prepare the variant for put_PlayList method // and invoke the method // ////////////////////////////////////////////////////////////////// HRESULT PutPlayList( IN ITTerminal *pITTerminal, IN BSTR bstrFileName) { // //check if really have a terminal // if(NULL == pITTerminal) { DoMessage( _T("PutPlayList: playback terminal NULL")); return E_UNEXPECTED; } // // Get ITMediaPlayback interface - only playback terminal object // exposes this interface // ITMediaPlayback* pMediaPlayback = NULL; HRESULT hr = pITTerminal->QueryInterface( IID_ITMediaPlayback, (void**)&pMediaPlayback); if(FAILED(hr)) { DoMessage( _T("PutPlayList: QI ITMediaPlayback failed")); return hr; } // //VARIANT to be passed to put_PlayList // VARIANT varPlaylist; VariantInit(&varPlaylist); // //Prepare SAFEARRAYBOUND for SAFEARRAY //Put file name into array with one element // SAFEARRAYBOUND DimensionBounds; DimensionBounds.lLbound = 1; // //number of files in play list - modify for more files // DimensionBounds.cElements = 1; // // Put file name into array at index 1 - see lLbound // long lArrayPos = 1; // //variant that will hold the BSTR - it will be added to SAFEARRAY // VARIANT* pvarArrayEntry = new VARIANT; if( pvarArrayEntry == NULL) { DoMessage( _T("PutPlayList: new VARIANT failed")); return E_OUTOFMEMORY; } VariantInit(pvarArrayEntry); // // Create SAFEARRAY // SAFEARRAY *pPlayListArray = NULL; pPlayListArray = SafeArrayCreate( VT_VARIANT, 1, &DimensionBounds); if( pPlayListArray == NULL) { DoMessage( _T("PutPlayList: SafeArrayCreate failed")); delete pvarArrayEntry; return E_OUTOFMEMORY; } // //repeat this for each file you want to add //you need to increment lArrayPos // pvarArrayEntry->vt = VT_BSTR; pvarArrayEntry->bstrVal = ::SysAllocString(bstrFileName); SafeArrayPutElement( pPlayListArray, &lArrayPos, pvarArrayEntry); VariantClear(pvarArrayEntry); // //prepare the variant for put_PlayList // V_VT(&varPlaylist) = VT_ARRAY | VT_VARIANT; V_ARRAY(&varPlaylist) = pPlayListArray; // //finally put play list // hr = pMediaPlayback->put_PlayList(varPlaylist); if(FAILED(hr)) { DoMessage( _T("PutPlayList: put_PlayList failed")); delete pvarArrayEntry; return E_OUTOFMEMORY; } // //clean up // delete pvarArrayEntry; pMediaPlayback->Release(); VariantClear(&varPlaylist); return hr; }