1331 lines
41 KiB
C++
1331 lines
41 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright © Microsoft Corporation. All rights reserved
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// SimpleTelephony.EXE
|
|
//
|
|
// Sample application that handles incoming TAPI calls
|
|
// and does some speech recognition and text-to-speech.
|
|
//
|
|
// 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.
|
|
//
|
|
// NOTE: This application is limited to working with one
|
|
// call at at time, and will not work correctly if multiple
|
|
// calls are present at the same time.
|
|
//
|
|
// NOTE: The call-handling sequence in this application is
|
|
// short, so it is fine to make it all the way through to
|
|
// the end of the call even if the caller hung up in the
|
|
// middle. If your telephony app involves a lengthy
|
|
// call-handling sequence, you need a separate thread to
|
|
// listen for TAPI's CS_DISCONNECT message; otherwise your
|
|
// app will have no way of being notified of the hang-up,
|
|
// and your app will appear to have hung.
|
|
//////////////////////////////////////////////////////////
|
|
|
|
/****************************************************************************
|
|
* Operator.cpp
|
|
* Implementation of the COperator class for the SimpleTelephony
|
|
* project.
|
|
*****************************************************************************/
|
|
|
|
#include <windows.h>
|
|
#include <atlbase.h>
|
|
#include <sapi.h>
|
|
#include <sperror.h>
|
|
#include <sphelper.h>
|
|
#include <tapi3.h> // In general, you need to have the
|
|
// Microsoft Platform SDK installed
|
|
// in order to have this file
|
|
#include "operator.h"
|
|
#include "resource.h"
|
|
|
|
#define CALLER_TIMEOUT 8000 // Wait 8 seconds before giving up on a caller
|
|
|
|
/**********************************************************
|
|
* WinMain *
|
|
*---------*
|
|
* Description:
|
|
* Main entry point for the application
|
|
***********************************************************/
|
|
int WINAPI WinMain(
|
|
__in HINSTANCE hInst,
|
|
__in_opt HINSTANCE hPrevInst,
|
|
__in_opt LPSTR lpCmdLine,
|
|
__in int nCmdShow
|
|
)
|
|
{
|
|
// Initialize COM.
|
|
if (FAILED(::CoInitialize( NULL )))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Create an instance of our main call-handling class
|
|
COperator *pOperator = new COperator( hInst );
|
|
if ( !pOperator )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Does initialization of SAPI and TAPI objects
|
|
HRESULT hr = pOperator->Initialize();
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// everything is initialized, so
|
|
// start the main dialog box
|
|
::DialogBoxParam( hInst,
|
|
MAKEINTRESOURCE(IDD_MAINDLG),
|
|
NULL,
|
|
MainDialogProc,
|
|
(LPARAM) pOperator
|
|
);
|
|
|
|
// This does the cleanup of SAPI and TAPI objects
|
|
delete pOperator;
|
|
|
|
::CoUninitialize();
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**********************************************************
|
|
* COperator::~COperator *
|
|
*-----------------------*
|
|
* Description:
|
|
* Destructor for the COperator class.
|
|
* Cleans up SAPI and TAPI objects.
|
|
***********************************************************/
|
|
COperator::~COperator()
|
|
{
|
|
ShutdownSapi();
|
|
ShutdownTapi();
|
|
} /* COperator::~COperator */
|
|
|
|
/**********************************************************
|
|
* COperator::Initialize *
|
|
*-----------------------*
|
|
* Description:
|
|
* Initializes SAPI and TAPI
|
|
* Return:
|
|
* S_OK if both SAPI and TAPI initialized successfully
|
|
***********************************************************/
|
|
HRESULT COperator::Initialize()
|
|
{
|
|
HRESULT hr = InitializeSapi();
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = InitializeTapi();
|
|
}
|
|
return hr;
|
|
} /* COperator::Initialize */
|
|
|
|
/**********************************************************
|
|
* COperator::InitializeSapi *
|
|
*---------------------------*
|
|
* Description:
|
|
* Various SAPI initializations.
|
|
* Return:
|
|
* S_OK if SAPI initialized successfully
|
|
* Return values of failed SAPI initialization
|
|
* functions
|
|
***********************************************************/
|
|
HRESULT COperator::InitializeSapi()
|
|
{
|
|
// Create a voice for speaking on this machine
|
|
HRESULT hr = m_cpLocalVoice.CoCreateInstance( CLSID_SpVoice );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
DoMessage( L"Could not create a TTS voice on the local machine" );
|
|
return hr;
|
|
}
|
|
|
|
// Create a reco engine for recognizing speech over the phone
|
|
// This is an inproc recognizer since it will likely be
|
|
// using a format other than the default
|
|
hr = m_cpIncomingRecognizer.CoCreateInstance( CLSID_SpInprocRecognizer );
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"CoCreateInstance on inproc reco engine failed");
|
|
return hr;
|
|
}
|
|
|
|
// Create a reco context for this engine
|
|
hr = m_cpIncomingRecognizer->CreateRecoContext( &m_cpIncomingRecoCtxt );
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"Could not create recognition context");
|
|
return hr;
|
|
}
|
|
|
|
// Set interest only in PHRASE_START, RECOGNITION, FALSE_RECOGNITION
|
|
const ULONGLONG ullInterest = SPFEI(SPEI_PHRASE_START) | SPFEI(SPEI_RECOGNITION) |
|
|
SPFEI(SPEI_FALSE_RECOGNITION);
|
|
hr = m_cpIncomingRecoCtxt->SetInterest( ullInterest, ullInterest );
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"Could not set interest in SAPI events");
|
|
return hr;
|
|
}
|
|
|
|
// Retain recognized audio
|
|
hr = m_cpIncomingRecoCtxt->SetAudioOptions( SPAO_RETAIN_AUDIO, NULL, NULL );
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"Could not set audio options to retain recognized audio");
|
|
return hr;
|
|
}
|
|
|
|
// Create a dictation grammar and load it
|
|
hr = m_cpIncomingRecoCtxt->CreateGrammar( 0, &m_cpDictGrammar );
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"Could not create dictation grammar");
|
|
return hr;
|
|
}
|
|
|
|
hr = m_cpDictGrammar->LoadDictation( NULL, SPLO_STATIC );
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"Could not load dictation");
|
|
return hr;
|
|
}
|
|
|
|
// Create a voice for talking on the phone.
|
|
hr = m_cpIncomingRecoCtxt->GetVoice( &m_cpOutgoingVoice );
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"Could not create a TTS voice for speaking over the phone");
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
} /* COperator::InitializeSapi */
|
|
|
|
|
|
/**********************************************************
|
|
* COperator::InitializeTapi *
|
|
*---------------------------*
|
|
* Description:
|
|
* Various TAPI initializations
|
|
* Return:
|
|
* S_OK iff TAPI initialized successfully
|
|
* Return values of failed TAPI initialization
|
|
* functions
|
|
***********************************************************/
|
|
HRESULT COperator::InitializeTapi()
|
|
{
|
|
// Cocreate the TAPI object
|
|
HRESULT hr = CoCreateInstance(
|
|
CLSID_TAPI,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_ITTAPI,
|
|
(LPVOID *)&m_pTapi
|
|
);
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"CoCreateInstance on TAPI failed");
|
|
return hr;
|
|
}
|
|
|
|
// call ITTAPI::Initialize(). this must be called before
|
|
// any other tapi functions are called.
|
|
hr = m_pTapi->Initialize();
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"TAPI failed to initialize");
|
|
m_pTapi->Release();
|
|
m_pTapi = NULL;
|
|
return hr;
|
|
}
|
|
|
|
// Create our own event notification object and register it
|
|
// See callnot.h and callnot.cpp
|
|
m_pTAPIEventNotification = new CTAPIEventNotification;
|
|
if ( NULL == m_pTAPIEventNotification )
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
hr = m_pTAPIEventNotification->Initialize();
|
|
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = RegisterTapiEventInterface();
|
|
}
|
|
|
|
// Set the Event filter to only give us only the events we process
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_pTapi->put_EventFilter(TE_CALLNOTIFICATION | TE_CALLSTATE);
|
|
}
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
DoMessage( L"Could not set up TAPI event notifications" );
|
|
return hr;
|
|
}
|
|
|
|
|
|
// Find all address objects that we will use to listen for calls on
|
|
hr = ListenOnAddresses();
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
DoMessage(L"Could not find any addresses to listen on");
|
|
|
|
m_pTapi->Release();
|
|
m_pTapi = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
} /* COperator::InitializeTapi */
|
|
|
|
/**********************************************************
|
|
* COperator::ShutdownSapi *
|
|
*-------------------------*
|
|
* Description:
|
|
* Releases all SAPI objects
|
|
***********************************************************/
|
|
void COperator::ShutdownSapi()
|
|
{
|
|
m_cpMMSysAudioIn.Release();
|
|
m_cpMMSysAudioOut.Release();
|
|
m_cpDictGrammar.Release();
|
|
m_cpIncomingRecoCtxt.Release();
|
|
m_cpIncomingRecognizer.Release();
|
|
m_cpLocalVoice.Release();
|
|
m_cpOutgoingVoice.Release();
|
|
} /* COperator::ShutdownSapi */
|
|
|
|
/**********************************************************
|
|
* COperator::ShutdownTapi *
|
|
*-------------------------*
|
|
* Description:
|
|
* Releases all TAPI objects
|
|
***********************************************************/
|
|
void COperator::ShutdownTapi()
|
|
{
|
|
// if there is still a call, release it
|
|
if (NULL != m_pCall)
|
|
{
|
|
m_pCall->Release();
|
|
m_pCall = NULL;
|
|
}
|
|
|
|
// release main object.
|
|
if (NULL != m_pTapi)
|
|
{
|
|
m_pTapi->Shutdown();
|
|
m_pTapi->Release();
|
|
}
|
|
} /* COperator::ShutdownTapi */
|
|
|
|
|
|
/**********************************************************
|
|
* MainDialogProc *
|
|
*----------------*
|
|
* Description:
|
|
* Main dialog proc for this sample
|
|
***********************************************************/
|
|
INT_PTR WINAPI MainDialogProc(
|
|
HWND hDlg,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
// This is set to be the window long in WM_INITDIALOG
|
|
COperator *pThis = (COperator *)(LONG_PTR) ::GetWindowLongPtr( hDlg, GWLP_USERDATA );
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
// Get the pointer to the COperator object from the lParam.
|
|
// Store it in the window long as user data
|
|
pThis = (COperator *) lParam;
|
|
::SetWindowLongPtr( hDlg, GWLP_USERDATA, (LONG_PTR) pThis );
|
|
|
|
// Hand the window handle to the event notification object
|
|
// so that this window will get window messages about incoming calls
|
|
pThis->m_pTAPIEventNotification->SetHWND( hDlg );
|
|
|
|
// Get the window handle
|
|
pThis->m_hDlg = hDlg;
|
|
|
|
// Wait for a call
|
|
::EnableWindow( ::GetDlgItem( hDlg, IDC_ANSWER ), FALSE );
|
|
pThis->SetStatusMessage( L"Waiting for a call..." );
|
|
|
|
return 0;
|
|
}
|
|
|
|
case WM_PRIVATETAPIEVENT:
|
|
{
|
|
// This message is received whenever a TAPI event occurs
|
|
pThis->OnTapiEvent( (TAPI_EVENT) wParam,
|
|
(IDispatch *) lParam );
|
|
|
|
return 0;
|
|
}
|
|
|
|
case WM_COMMAND:
|
|
{
|
|
if ( LOWORD(wParam) == IDCANCEL )
|
|
{
|
|
// Quit
|
|
EndDialog( hDlg, 0 );
|
|
|
|
return 1;
|
|
}
|
|
|
|
switch ( LOWORD(wParam) )
|
|
{
|
|
case IDC_AUTOANSWER:
|
|
{
|
|
// Auto answer check box state was changed
|
|
pThis->m_fAutoAnswer = !pThis->m_fAutoAnswer;
|
|
return 1;
|
|
}
|
|
|
|
case IDC_ANSWER:
|
|
{
|
|
// Answer the call
|
|
|
|
pThis->SetStatusMessage(L"Answering...");
|
|
|
|
if ( S_OK == pThis->AnswerTheCall() )
|
|
{
|
|
pThis->SetStatusMessage(L"Connected");
|
|
|
|
::EnableWindow( ::GetDlgItem( hDlg, IDC_ANSWER ), FALSE );
|
|
|
|
// Connected: Talk to the caller
|
|
|
|
// PLEASE NOTE: This is a single-threaded app, so if the caller
|
|
// hangs up after the call-handling sequence has started, the
|
|
// app will not be notified until after the entire call sequence
|
|
// has finished.
|
|
// If you want to be able to cut the call-handling short because
|
|
// the caller hung up, you need to have a separate thread listening
|
|
// for TAPI's CS_DISCONNECT notification.
|
|
HRESULT hrHandleCall = pThis->HandleCall();
|
|
if ( FAILED( hrHandleCall ) )
|
|
{
|
|
if ( TAPI_E_DROPPED == hrHandleCall )
|
|
{
|
|
pThis->SetStatusMessage( L"Caller hung up prematurely" );
|
|
}
|
|
else
|
|
{
|
|
pThis->DoMessage( L"Error encountered handling the call" );
|
|
}
|
|
}
|
|
|
|
// Hang up if we still need to
|
|
if ( NULL != pThis->m_pCall )
|
|
{
|
|
// The caller is still around; hang up on him
|
|
pThis->SetStatusMessage(L"Disconnecting...");
|
|
if (S_OK != pThis->DisconnectTheCall())
|
|
{
|
|
pThis->DoMessage(L"Disconnect failed");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
::EnableWindow( ::GetDlgItem( hDlg, IDC_ANSWER ), FALSE );
|
|
pThis->DoMessage(L"Answer failed");
|
|
}
|
|
|
|
// Waiting for the next call...
|
|
pThis->SetStatusMessage(L"Waiting for a call...");
|
|
|
|
return 1;
|
|
}
|
|
|
|
case IDC_DISCONNECTED:
|
|
{
|
|
// This message is sent from OnTapiEvent()
|
|
// Disconnected notification -- release the call
|
|
pThis->ReleaseTheCall();
|
|
|
|
::EnableWindow( ::GetDlgItem( hDlg, IDC_ANSWER ), FALSE );
|
|
|
|
pThis->SetStatusMessage(L"Waiting for a call...");
|
|
|
|
return 1;
|
|
}
|
|
default:
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
default:
|
|
|
|
return 0;
|
|
}
|
|
} /* MainDialogProc */
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// TAPI actions
|
|
///////////////////////////////////////////////////////////
|
|
|
|
/**********************************************************
|
|
* COperator::RegisterTapiEventInterface *
|
|
*---------------------------------------*
|
|
* Description:
|
|
* Get a unique identifier (m_ulAdvice) for
|
|
* this connection point.
|
|
* Return:
|
|
* S_OK
|
|
* Failed HRESULTs from QI(),
|
|
* IConnectionPointContainer::FindConnectionPoint(),
|
|
* or IConnectionPoint::Advise()
|
|
***********************************************************/
|
|
HRESULT COperator::RegisterTapiEventInterface()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IConnectionPointContainer * pCPC;
|
|
IConnectionPoint * pCP;
|
|
|
|
|
|
hr = m_pTapi->QueryInterface( IID_IConnectionPointContainer,
|
|
(void **)&pCPC );
|
|
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = pCPC->FindConnectionPoint( IID_ITTAPIEventNotification,
|
|
&pCP );
|
|
pCPC->Release();
|
|
}
|
|
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = pCP->Advise( m_pTAPIEventNotification,
|
|
&m_ulAdvise );
|
|
|
|
pCP->Release();
|
|
}
|
|
return hr;
|
|
} /* COperator::RegisterTapiEventInterface */
|
|
|
|
/**********************************************************
|
|
* COperator::ListenOnAddresses *
|
|
*------------------------------*
|
|
* Description:
|
|
* Find all addresses that support audio in and audio
|
|
* out. Call ListenOnThisAddress to start listening
|
|
* Return:
|
|
* S_OK
|
|
* S_FALSE if there are no addresses to listen on
|
|
* Failed HRESULT ITTAPI::EnumberateAddresses()
|
|
* or IEnumAddress::Next()
|
|
************************************************************/
|
|
HRESULT COperator::ListenOnAddresses()
|
|
{
|
|
// enumerate the addresses
|
|
IEnumAddress * pEnumAddress;
|
|
HRESULT hr = m_pTapi->EnumerateAddresses( &pEnumAddress );
|
|
|
|
if ( FAILED(hr) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
ITAddress * pAddress;
|
|
bool fAddressExists = false;
|
|
while ( true )
|
|
{
|
|
// get the next address
|
|
hr = pEnumAddress->Next( 1, &pAddress, NULL );
|
|
if (S_OK != hr)
|
|
{
|
|
// Done dealing with all the addresses
|
|
break;
|
|
}
|
|
|
|
// Does the address support audio?
|
|
if ( AddressSupportsMediaType(pAddress, TAPIMEDIATYPE_AUDIO) )
|
|
{
|
|
// If it does then we'll listen.
|
|
HRESULT hrListen = ListenOnThisAddress( pAddress );
|
|
|
|
if ( S_OK == hrListen )
|
|
{
|
|
fAddressExists = true;
|
|
}
|
|
}
|
|
pAddress->Release();
|
|
|
|
}
|
|
pEnumAddress->Release();
|
|
|
|
if ( !fAddressExists )
|
|
{
|
|
DoMessage( L"Could not find any addresses to listen on" );
|
|
}
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
else
|
|
{
|
|
return fAddressExists ? S_OK : S_FALSE;
|
|
}
|
|
} /* COperator::ListenOnAddress */
|
|
|
|
/**********************************************************
|
|
* COperator::ListenOnThisAddress *
|
|
*--------------------------------*
|
|
* Description:
|
|
* Call RegisterCallNotifications() to inform TAPI
|
|
* that we want notifications of calls on this
|
|
* address. We already registered our notification
|
|
* interface with TAPI, so now we are just telling
|
|
* TAPI that we want calls from this address to
|
|
* trigger our existing notification interface.
|
|
* Return:
|
|
* Return value of ITTAPI::RegisterCallNotifications()
|
|
************************************************************/
|
|
HRESULT COperator::ListenOnThisAddress( ITAddress * pAddress )
|
|
{
|
|
// RegisterCallNotifications takes a media type set of flags indicating
|
|
// the set of media types we are interested in. We know the
|
|
// address supports audio (see COperator::ListenOnAddress())
|
|
long lMediaTypes = TAPIMEDIATYPE_AUDIO;
|
|
|
|
long lRegister;
|
|
return m_pTapi->RegisterCallNotifications(
|
|
pAddress,
|
|
VARIANT_TRUE,
|
|
VARIANT_TRUE,
|
|
lMediaTypes,
|
|
m_ulAdvise,
|
|
&lRegister );
|
|
} /* COperator::ListenOnAddress */
|
|
|
|
|
|
/**********************************************************
|
|
* COperator::AnswerTheCall *
|
|
*--------------------------*
|
|
* Description:
|
|
* Called whenever notification is received of an
|
|
* incoming call and the app wants to answer it.
|
|
* Answers and handles the call.
|
|
* Return:
|
|
* S_OK
|
|
* Failed retval of QI(), ITCallInfo::get_Address(),
|
|
* COperator::SetAudioOutForCall(),
|
|
************************************************************/
|
|
HRESULT COperator::AnswerTheCall()
|
|
{
|
|
|
|
if (NULL == m_pCall)
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// Get the LegacyCallMediaControl interface so that we can
|
|
// get a device ID to reroute the audio
|
|
ITLegacyCallMediaControl *pLegacyCallMediaControl;
|
|
HRESULT hr = m_pCall->QueryInterface( IID_ITLegacyCallMediaControl,
|
|
(void**)&pLegacyCallMediaControl );
|
|
|
|
|
|
// Set the audio for the SAPI objects so that the call
|
|
// can be handled
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = SetAudioOutForCall( pLegacyCallMediaControl );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
DoMessage( L"Could not set up audio out for text-to-speech" );
|
|
}
|
|
}
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = SetAudioInForCall( pLegacyCallMediaControl );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
DoMessage( L"Could not set up audio in for speech recognition" );
|
|
}
|
|
}
|
|
pLegacyCallMediaControl->Release();
|
|
if ( FAILED(hr) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
// Now we can actually answer the call
|
|
hr = m_pCall->Answer();
|
|
|
|
return hr;
|
|
} /* COperator::AnswerTheCall */
|
|
|
|
/**********************************************************
|
|
* COperator::DisconnectTheCall *
|
|
*------------------------------*
|
|
* Description:
|
|
* Disconnects the call
|
|
* Return:
|
|
* S_OK
|
|
* S_FALSE if there was no call
|
|
* Failed return value of ITCall::Disconnect()
|
|
************************************************************/
|
|
HRESULT COperator::DisconnectTheCall()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
if (NULL != m_pCall)
|
|
{
|
|
// Hang up
|
|
return m_pCall->Disconnect( DC_NORMAL );
|
|
|
|
// Do not release the call yet, as that would prevent
|
|
// us from receiving the disconnected notification.
|
|
}
|
|
else
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
} /* COperator::DisconnectTheCall */
|
|
|
|
/**********************************************************
|
|
* COperator::ReleaseTheCall *
|
|
*---------------------------*
|
|
* Description:
|
|
* Releases the call
|
|
************************************************************/
|
|
void COperator::ReleaseTheCall()
|
|
{
|
|
if (NULL != m_pCall)
|
|
{
|
|
m_pCall->Release();
|
|
m_pCall = NULL;
|
|
}
|
|
|
|
// Let go of the SAPI audio in and audio out objects
|
|
if ( m_cpMMSysAudioOut )
|
|
{
|
|
m_cpMMSysAudioOut->SetState( SPAS_STOP, 0 );
|
|
}
|
|
if ( m_cpMMSysAudioIn )
|
|
{
|
|
m_cpMMSysAudioIn->SetState( SPAS_STOP, 0 );
|
|
}
|
|
|
|
m_cpMMSysAudioOut.Release();
|
|
m_cpMMSysAudioIn.Release();
|
|
} /* COperator::ReleaseTheCall */
|
|
|
|
/**********************************************************
|
|
* COperator::OnTapiEvent *
|
|
*------------------------*
|
|
* Description:
|
|
* This is the real TAPI event handler, called on our
|
|
* UI thread upon receipt of the WM_PRIVATETAPIEVENT
|
|
* message.
|
|
* Return:
|
|
* S_OK
|
|
************************************************************/
|
|
HRESULT COperator::OnTapiEvent( TAPI_EVENT TapiEvent,
|
|
IDispatch * pEvent )
|
|
{
|
|
HRESULT hr;
|
|
switch ( TapiEvent )
|
|
{
|
|
case TE_CALLNOTIFICATION:
|
|
{
|
|
// TE_CALLNOTIFICATION means that the application is being notified
|
|
// of a new call.
|
|
//
|
|
// Note that we don't answer to call at this point. The application
|
|
// should wait for a CS_OFFERING CallState message before answering
|
|
// the call.
|
|
//
|
|
ITCallNotificationEvent * pNotify;
|
|
hr = pEvent->QueryInterface( IID_ITCallNotificationEvent, (void **)&pNotify );
|
|
if (S_OK != hr)
|
|
{
|
|
DoMessage( L"Incoming call, but failed to get the interface");
|
|
}
|
|
else
|
|
{
|
|
CALL_PRIVILEGE cp;
|
|
ITCallInfo * pCall;
|
|
|
|
// Get the call
|
|
hr = pNotify->get_Call( &pCall );
|
|
pNotify->Release();
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// Check to see if we own the call
|
|
hr = pCall->get_Privilege( &cp );
|
|
if ( FAILED(hr) || (CP_OWNER != cp) )
|
|
{
|
|
// Just ignore it if we don't own it
|
|
pCall->Release();
|
|
pEvent->Release(); // We addrefed it CTAPIEventNotification::Event()
|
|
return S_OK;
|
|
}
|
|
|
|
// Get the ITBasicCallControl interface and save it in our
|
|
// member variable.
|
|
hr = pCall->QueryInterface( IID_ITBasicCallControl,
|
|
(void**)&m_pCall );
|
|
pCall->Release();
|
|
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// Update UI
|
|
::EnableWindow( ::GetDlgItem( m_hDlg, IDC_ANSWER ), TRUE );
|
|
SetStatusMessage(L"Incoming Owner Call");
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case TE_CALLSTATE:
|
|
{
|
|
// TE_CALLSTATE is a call state event. pEvent is
|
|
// an ITCallStateEvent
|
|
|
|
// Get the interface
|
|
ITCallStateEvent * pCallStateEvent;
|
|
hr = pEvent->QueryInterface( IID_ITCallStateEvent, (void **)&pCallStateEvent );
|
|
if ( FAILED(hr) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Get the CallState that we are being notified of.
|
|
CALL_STATE cs;
|
|
hr = pCallStateEvent->get_State( &cs );
|
|
pCallStateEvent->Release();
|
|
if ( FAILED(hr) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// If it's offering to be answered, update our UI
|
|
if (CS_OFFERING == cs)
|
|
{
|
|
if (m_fAutoAnswer)
|
|
{
|
|
::PostMessage(m_hDlg, WM_COMMAND, IDC_ANSWER, 0);
|
|
}
|
|
else
|
|
{
|
|
SetStatusMessage(L"Click the Answer button");
|
|
}
|
|
}
|
|
else if (CS_DISCONNECTED == cs)
|
|
{
|
|
::PostMessage(m_hDlg, WM_COMMAND, IDC_DISCONNECTED, 0);
|
|
}
|
|
else if (CS_CONNECTED == cs)
|
|
{
|
|
// Nothing to do -- we handle connection synchronously
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
pEvent->Release(); // We addrefed it CTAPIEventNotification::Event()
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// SAPI actions
|
|
///////////////////////////////////////////////////////////
|
|
|
|
/**********************************************************
|
|
* COperator::SetAudioOutForCall *
|
|
*-------------------------------*
|
|
* Description:
|
|
* Uses the legacy call media control in TAPI to
|
|
* get the device IDs for audio out.
|
|
* Uses these device IDs to set up the audio
|
|
* for text-to-speech
|
|
* Return:
|
|
* S_OK
|
|
* E_INVALIDARG if the pLegacyCallMediaControl is NULL
|
|
* E_OUTOFMEMORY
|
|
* SPERR_DEVICE_NOT_SUPPORTED if no supported
|
|
* formats could be found
|
|
* Failed return value of QI(),
|
|
* ITLegacyCallMediaControl::GetID(),
|
|
* CoCreateInstance(),
|
|
* ISpMMSysAudio::SetDeviceID(),
|
|
* ISpMMSysAudio::SetFormat(),
|
|
* ISpVoice::SetOutput()
|
|
************************************************************/
|
|
HRESULT COperator::SetAudioOutForCall( ITLegacyCallMediaControl *pLegacyCallMediaControl )
|
|
{
|
|
if (NULL == m_pCall)
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
if ( NULL == pLegacyCallMediaControl )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Get the device ID from ITLegacyCallMediaControl::GetID()
|
|
UINT *puDeviceID;
|
|
BSTR bstrWavOut = ::SysAllocString( L"wave/out" );
|
|
if ( !bstrWavOut )
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
DWORD dwSize = sizeof( puDeviceID );
|
|
HRESULT hr = pLegacyCallMediaControl->GetID( bstrWavOut, &dwSize, (BYTE**) &puDeviceID );
|
|
::SysFreeString( bstrWavOut );
|
|
|
|
// Find out what, if any, formats are supported
|
|
GUID guidWave = SPDFID_WaveFormatEx;
|
|
WAVEFORMATEX *pWaveFormatEx = NULL;
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// Loop through all of the SAPI audio formats and query the wave/out device
|
|
// about whether it supports each one.
|
|
// We will take the first one that we find
|
|
SPSTREAMFORMAT enumFmtId;
|
|
MMRESULT mmr = MMSYSERR_ALLOCATED;
|
|
for ( DWORD dw = 0; (MMSYSERR_NOERROR != mmr) && (dw < SPSF_NUM_FORMATS); dw++ )
|
|
{
|
|
if ( pWaveFormatEx && ( MMSYSERR_NOERROR != mmr ) )
|
|
{
|
|
// No dice: The audio device does not support this format
|
|
|
|
// Free up the WAVEFORMATEX pointer
|
|
::CoTaskMemFree( pWaveFormatEx );
|
|
pWaveFormatEx = NULL;
|
|
}
|
|
|
|
// Get the next format from SAPI and convert it into a WAVEFORMATEX
|
|
enumFmtId = (SPSTREAMFORMAT) (SPSF_8kHz8BitMono + dw);
|
|
HRESULT hrConvert = SpConvertStreamFormatEnum(
|
|
enumFmtId, &guidWave, &pWaveFormatEx );
|
|
|
|
if ( SUCCEEDED( hrConvert ) )
|
|
{
|
|
if ( puDeviceID != NULL )
|
|
{
|
|
// This call to waveOutOpen() does not actually open the device;
|
|
// it just queries the device whether it supports the given format
|
|
mmr = ::waveOutOpen( NULL, *puDeviceID, pWaveFormatEx, 0, 0, WAVE_FORMAT_QUERY );
|
|
}
|
|
else
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we made it all the way through the loop without breaking, that
|
|
// means we found no supported formats
|
|
if ( enumFmtId == SPSF_NUM_FORMATS )
|
|
{
|
|
return SPERR_DEVICE_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
// Cocreate a SAPI audio out object
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpMMSysAudioOut.CoCreateInstance( CLSID_SpMMAudioOut );
|
|
}
|
|
|
|
// Give the audio out object the device ID
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
hr = m_cpMMSysAudioOut->SetDeviceId( *puDeviceID );
|
|
}
|
|
|
|
// Use the format that we found works
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
_ASSERTE( pWaveFormatEx );
|
|
hr = m_cpMMSysAudioOut->SetFormat( guidWave, pWaveFormatEx );
|
|
}
|
|
|
|
// We are now done with the wave format pointer
|
|
if ( pWaveFormatEx )
|
|
{
|
|
::CoTaskMemFree( pWaveFormatEx );
|
|
}
|
|
|
|
// Set the appropriate output to the outgoing voice
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
_ASSERTE( m_cpOutgoingVoice );
|
|
hr = m_cpOutgoingVoice->SetOutput( m_cpMMSysAudioOut, FALSE );
|
|
}
|
|
|
|
return hr;
|
|
} /* COperator::SetAudioOutForCall */
|
|
|
|
/**********************************************************
|
|
* COperator::SetAudioInForCall *
|
|
*------------------------------*
|
|
* Description:
|
|
* Uses the legacy call media control in TAPI to
|
|
* get the device IDs for audio input.
|
|
* Uses these device IDs to set up the audio
|
|
* for speech recognition
|
|
* Return:
|
|
* S_OK
|
|
* E_INVALIDARG if pLegacyCallMediaControl is NULL
|
|
* E_OUTOFMEMORY
|
|
* SPERR_DEVICE_NOT_SUPPORTED if no supported
|
|
* formats could be found
|
|
* Failed return value of QI(),
|
|
* ITLegacyCallMediaControl::GetID(),
|
|
* CoCreateInstance(),
|
|
* ISpMMSysAudio::SetDeviceID(),
|
|
* ISpMMSysAudio::SetFormat()
|
|
************************************************************/
|
|
HRESULT COperator::SetAudioInForCall( ITLegacyCallMediaControl *pLegacyCallMediaControl )
|
|
{
|
|
if ( NULL == m_pCall )
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
if ( NULL == pLegacyCallMediaControl )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Get the device ID
|
|
UINT *puDeviceID;
|
|
BSTR bstrWavIn = ::SysAllocString( L"wave/in" );
|
|
if ( !bstrWavIn )
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
DWORD dwSize = sizeof( puDeviceID );
|
|
HRESULT hr = pLegacyCallMediaControl->GetID( bstrWavIn, &dwSize, (BYTE**) &puDeviceID );
|
|
::SysFreeString( bstrWavIn );
|
|
|
|
// Find out what, if any, formats are supported
|
|
GUID guidWave = SPDFID_WaveFormatEx;
|
|
WAVEFORMATEX *pWaveFormatEx = NULL;
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
// Loop through all of the SAPI audio formats and query the wave/out device
|
|
// about each one.
|
|
// We will take the first one that we find
|
|
SPSTREAMFORMAT enumFmtId;
|
|
MMRESULT mmr = MMSYSERR_ALLOCATED;
|
|
DWORD dw;
|
|
for ( dw = 0; (MMSYSERR_NOERROR != mmr) && (dw < SPSF_NUM_FORMATS); dw++ )
|
|
{
|
|
if ( pWaveFormatEx && ( MMSYSERR_NOERROR != mmr ) )
|
|
{
|
|
// Free up the WAVEFORMATEX pointer
|
|
::CoTaskMemFree( pWaveFormatEx );
|
|
pWaveFormatEx = NULL;
|
|
}
|
|
|
|
// Get the next format from SAPI and convert it into a WAVEFORMATEX
|
|
enumFmtId = (SPSTREAMFORMAT) (SPSF_8kHz8BitMono + dw);
|
|
HRESULT hrConvert = SpConvertStreamFormatEnum(
|
|
enumFmtId, &guidWave, &pWaveFormatEx );
|
|
if ( SUCCEEDED( hrConvert ) )
|
|
{
|
|
if ( puDeviceID != NULL )
|
|
{
|
|
// This call to waveOutOpen() does not actually open the device;
|
|
// it just queries the device whether it supports the given format
|
|
mmr = ::waveInOpen( NULL, *puDeviceID, pWaveFormatEx, 0, 0, WAVE_FORMAT_QUERY );
|
|
}
|
|
else
|
|
{
|
|
return E_UNEXPECTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we made it all the way through the loop without breaking, that
|
|
// means we found no supported formats
|
|
if ( SPSF_NUM_FORMATS == dw )
|
|
{
|
|
return SPERR_DEVICE_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
// Cocreate a SAPI audio in object
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpMMSysAudioIn.CoCreateInstance( CLSID_SpMMAudioIn );
|
|
}
|
|
|
|
// Give the audio in object the device ID
|
|
if ( SUCCEEDED(hr) )
|
|
{
|
|
hr = m_cpMMSysAudioIn->SetDeviceId( *puDeviceID );
|
|
}
|
|
|
|
// Use the format that we found works
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
_ASSERTE( pWaveFormatEx );
|
|
hr = m_cpMMSysAudioIn->SetFormat( guidWave, pWaveFormatEx );
|
|
}
|
|
|
|
// We are now done with the wave format pointer
|
|
if ( pWaveFormatEx )
|
|
{
|
|
::CoTaskMemFree( pWaveFormatEx );
|
|
}
|
|
|
|
// Set this as input to the reco context
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpIncomingRecognizer->SetInput( m_cpMMSysAudioIn, FALSE );
|
|
}
|
|
|
|
return hr;
|
|
} /* COperator::SetAudioInForCall */
|
|
|
|
|
|
/**********************************************************
|
|
* COperator::HandleCall *
|
|
*-----------------------*
|
|
* Description:
|
|
* Deals with the call
|
|
* Return:
|
|
* S_OK
|
|
* Failed return value of ISpMMSysAudio::SetState(),
|
|
* ISpVoice::Speak()
|
|
************************************************************/
|
|
HRESULT COperator::HandleCall()
|
|
{
|
|
// PLEASE NOTE: This is a single-threaded app, so if the caller
|
|
// hangs up after the call-handling sequence has started, the
|
|
// app will not be notified until after the entire call sequence
|
|
// has finished.
|
|
// If you want to be able to cut the call-handling short because
|
|
// the caller hung up, you need to have a separate thread listening
|
|
// for TAPI's CS_DISCONNECT notification.
|
|
|
|
_ASSERTE( m_cpMMSysAudioOut );
|
|
HRESULT hr = S_OK;
|
|
|
|
// Now that the call is connected, we can start up the audio output
|
|
hr = m_cpOutgoingVoice->Speak( L"Hello, please say something to me", 0, NULL );
|
|
|
|
// Start listening
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
m_cpDictGrammar->SetDictationState( SPRS_ACTIVE );
|
|
}
|
|
|
|
// We are expecting a PHRASESTART followed by either a RECOGNITION or a
|
|
// FALSERECOGNITION
|
|
|
|
// Wait for the PHRASE_START
|
|
CSpEvent event;
|
|
WORD eLastEventID = SPEI_FALSE_RECOGNITION;
|
|
hr = m_cpIncomingRecoCtxt->WaitForNotifyEvent(CALLER_TIMEOUT);
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = event.GetFrom( m_cpIncomingRecoCtxt );
|
|
}
|
|
|
|
// Enter this block only if we have not timed out (the user started speaking)
|
|
if ( ( S_OK == hr ) && ( SPEI_PHRASE_START == event.eEventId ) )
|
|
{
|
|
// Caller has started to speak, block "forever" until the
|
|
// result (or lack thereof) comes back.
|
|
// This is all right, since every PHRASE_START is guaranteed
|
|
// to be followed up by a RECOGNITION or FALSE_RECOGNITION
|
|
hr = m_cpIncomingRecoCtxt->WaitForNotifyEvent(INFINITE);
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
// Get the RECOGNITION or FALSE_RECOGNITION
|
|
hr = event.GetFrom( m_cpIncomingRecoCtxt );
|
|
eLastEventID = event.eEventId;
|
|
|
|
// This had better be either a RECOGNITION or FALSERECOGNITION!
|
|
_ASSERTE( (SPEI_RECOGNITION == eLastEventID) ||
|
|
(SPEI_FALSE_RECOGNITION == eLastEventID) );
|
|
}
|
|
}
|
|
|
|
|
|
// Make sure a recognition result was actually received (as opposed to a false recognition
|
|
// or timeout on the caller)
|
|
WCHAR *pwszCoMemText = NULL;
|
|
ISpRecoResult *pResult = NULL;
|
|
if ( SUCCEEDED( hr ) && ( SPEI_RECOGNITION == event.eEventId ) )
|
|
{
|
|
// Get the text of the result
|
|
pResult = event.RecoResult();
|
|
|
|
BYTE bDisplayAttr;
|
|
hr = pResult->GetText( SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, FALSE, &pwszCoMemText, &bDisplayAttr );
|
|
}
|
|
if ( SUCCEEDED( hr ) && pResult )
|
|
{
|
|
// Speak the result back locally
|
|
m_cpLocalVoice->Speak( L"I think the person on the phone said", SPF_ASYNC, 0 );
|
|
m_cpLocalVoice->Speak( pwszCoMemText, SPF_ASYNC, 0 );
|
|
m_cpLocalVoice->Speak( L"when he said", SPF_ASYNC, 0 );
|
|
|
|
// Get the audio so that the local voice can speak it back
|
|
CComPtr<ISpStreamFormat> cpStreamFormat;
|
|
HRESULT hrAudio = pResult->GetAudio( 0, 0, &cpStreamFormat );
|
|
if ( SUCCEEDED( hrAudio ) )
|
|
{
|
|
m_cpLocalVoice->SpeakStream( cpStreamFormat, SPF_ASYNC, 0 );
|
|
}
|
|
else
|
|
{
|
|
m_cpLocalVoice->Speak( L"no audio was available", SPF_ASYNC, 0 );
|
|
}
|
|
}
|
|
|
|
// Stop listening
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpDictGrammar->SetDictationState( SPRS_INACTIVE );
|
|
}
|
|
|
|
// Close the audio input so that we can open the audio output
|
|
// (half-duplex device)
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpMMSysAudioIn->SetState( SPAS_CLOSED, 0 );
|
|
}
|
|
|
|
// The caller may have hung up on us, in which case we don't want to do
|
|
// the following
|
|
if ( m_pCall )
|
|
{
|
|
if ( pResult )
|
|
{
|
|
// There's a result to playback
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpOutgoingVoice->Speak( L"I think I heard you say", 0, 0 );
|
|
}
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpOutgoingVoice->Speak( pwszCoMemText, 0, 0 );
|
|
}
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpOutgoingVoice->Speak( L"when you said", 0, 0 );
|
|
}
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = pResult->SpeakAudio( NULL, 0, NULL, NULL );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Caller didn't say anything
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpOutgoingVoice->Speak( L"I don't believe you said anything!", 0, 0 );
|
|
}
|
|
}
|
|
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpOutgoingVoice->Speak( L"OK bye now", 0, 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_cpLocalVoice->Speak( L"Prematurely terminated call", 0, 0 );
|
|
}
|
|
|
|
if ( pwszCoMemText )
|
|
{
|
|
::CoTaskMemFree( pwszCoMemText );
|
|
}
|
|
|
|
return m_pCall ? hr : TAPI_E_DROPPED;
|
|
} /* COperator::HandleCall */
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// Miscellany
|
|
///////////////////////////////////////////////////////////
|
|
|
|
/**********************************************************
|
|
* COperator::DoMessage *
|
|
*----------------------*
|
|
* Description:
|
|
* Displays a MessageBox
|
|
************************************************************/
|
|
void COperator::DoMessage( LPCWSTR pszMessage )
|
|
{
|
|
::MessageBox( m_hDlg, CW2CT(pszMessage), CW2T((WCHAR *) gszTelephonySample), MB_OK );
|
|
} /* COperator::DoMessage */
|
|
|
|
|
|
/**********************************************************
|
|
* COperator::SetStatusMessage *
|
|
*-----------------------------*
|
|
* Description:
|
|
* Displays a status message
|
|
************************************************************/
|
|
void COperator::SetStatusMessage( LPCWSTR pszMessage )
|
|
{
|
|
::SetDlgItemText( m_hDlg, IDC_STATUS, CW2CT(pszMessage) );
|
|
} /* COperator::SetStatusMessage */
|
|
|
|
/*************************************************************
|
|
* AddressSupportsMediaType *
|
|
*--------------------------*
|
|
* Return:
|
|
* TRUE iff the given address supports the given media
|
|
**************************************************************/
|
|
BOOL AddressSupportsMediaType( ITAddress * pAddress,
|
|
long lMediaType )
|
|
{
|
|
VARIANT_BOOL bSupport = VARIANT_FALSE;
|
|
ITMediaSupport * pMediaSupport;
|
|
|
|
if ( SUCCEEDED( pAddress->QueryInterface( IID_ITMediaSupport,
|
|
(void **)&pMediaSupport ) ) )
|
|
{
|
|
// does it support this media type?
|
|
pMediaSupport->QueryMediaType( lMediaType,
|
|
&bSupport );
|
|
|
|
pMediaSupport->Release();
|
|
}
|
|
return (bSupport == VARIANT_TRUE);
|
|
} /* AddressSupportsMediaType */
|