TAPIEventProcess.cpp
上传用户:fast168168
上传日期:2010-01-05
资源大小:337k
文件大小:35k
源码类别:

TAPI编程

开发平台:

Visual C++

  1. #include "StdAfx.h"
  2. #include <tapi3.h>
  3. #include "tapieventprocess.h"
  4. const int Ver_Win2000 = 1;
  5. const int Ver_WinXP = 2;
  6. const int Timer_ID = 1;
  7. const int Max_Rec_Time = 60000;
  8. const DWORD ADDRESSLENGTH   = 128;
  9. ///////////////////////////////////////////////////////////////////
  10. // CallEventNotification::Event
  11. //
  12. // 1、这是ITCallEventNotification接口中的唯一的方法,当有事件被触发时,
  13. // TAPI 3.0 就会调用这个函数。
  14. // 2、这个函数里仅仅向用户界面线程(在本程序中可以理解为主对话框)发送一个消息,
  15. // 这样做的目的是在 TAPI 的 回调(callback)线程中包含尽量少的处理过程
  16. //
  17. ///////////////////////////////////////////////////////////////////
  18. HRESULT
  19. STDMETHODCALLTYPE
  20. CTAPIEventNotification::Event(
  21.                               TAPI_EVENT TapiEvent,
  22.                               IDispatch * pEvent
  23.                              )
  24. {
  25.     //
  26.     // 调用AddRef,计数器加1.
  27.     //
  28.     pEvent->AddRef();
  29.     //
  30.     // 向 UI 线程发送消息
  31.     //
  32.     PostMessage(
  33.                 m_hWnd,
  34.                 WM_PRIVATETAPIEVENT,
  35.                 (WPARAM) TapiEvent,
  36.                 (LPARAM) pEvent
  37.                );
  38.     return S_OK;
  39. }
  40. /*
  41. void CTAPIEventNotification::gg()
  42. {
  43.     PostMessage(
  44.                 m_hWnd,
  45.                 WM_PRIVATETAPIEVENT,
  46.                 0,
  47.                 0
  48.                );
  49. }
  50. */
  51. //////////////////////////////////////////////////////////////////////////////////////////////////////
  52. //
  53. // calss CTapi 实现代码
  54. //
  55. // 说明:
  56. // 1. 本类基于 TAPI3.1, 对 TAPI 的功能进行定制,满足特定需要
  57. // 功能:
  58. // 1. 对给定的电话号码呼叫
  59. // 2. 当有电话呼入时,给对方播放本地事先指定的声音文件
  60. // 3. 接受电话另一方的按键,分别对不同按键作出响应
  61. // 4. 具有来录音功能
  62. // 注意:
  63. // 1. 对 CTapi 类初始化时需要传入当前 UI(用户界面)的句柄,通过这个句柄向 UI 线程发送消息,在 UI
  64. //    线程中对 TAPI EVENT 进行响应。
  65. // 2. 需要 class CTAPIEventNotification 辅助,完成对事件的注册
  66. //////////////////////////////////////////////////////////////////////////////////////////////////////
  67. CTapi::CTapi()
  68. {
  69. m_pTapi = NULL;
  70. m_pAddress = NULL;
  71. m_pCall = NULL;
  72. m_AutoAnswer = false;
  73. m_pPlayFileTerm = NULL;
  74. m_pRecordFileTerm = NULL;
  75. m_Advise = 0;
  76. m_hWnd = 0;
  77. m_dwMessages = 0;
  78. m_Version = 0;
  79. m_WelcomeFileName = "Welcome.wav";
  80. m_RecFileName = "Message";
  81. m_RecFileExt = "avi";
  82. }
  83. CTapi::~CTapi()
  84. {
  85. }
  86. //////////////////////////////////////////////////////////////
  87. // InitializeTapi
  88. //
  89. // TAPI 初始化
  90. ///////////////////////////////////////////////////////////////
  91. HRESULT CTapi::InitializeTapi()
  92. {
  93.     HRESULT         hr;
  94.     //
  95.     // 初始化 TAPI object
  96.     //
  97.     hr = CoCreateInstance(
  98.                           CLSID_TAPI,
  99.                           NULL,
  100.                           CLSCTX_INPROC_SERVER,
  101.                           IID_ITTAPI,
  102.                           (LPVOID *)&m_pTapi
  103.                          );
  104.     if ( FAILED(hr) )
  105.     {
  106.         AfxMessageBox("初始化 TAPI 失败!");
  107.         return hr;
  108.     }
  109.     //
  110.     // 调用 initialize.  
  111. // 本函数必须在其它所有 TAPI 函数被调用之前调用
  112.     //
  113.     hr = m_pTapi->Initialize();
  114.     if ( FAILED(hr) )
  115.     {
  116.         AfxMessageBox("初始化 TAPI 失败!");
  117.         m_pTapi->Release();
  118.         m_pTapi = NULL;
  119.         return hr;
  120.     }
  121.     //
  122.     // 创建我们自己的事件处理实例并注册之,参见 CTAPIEventNodtification 的定义
  123.     //
  124.     CTAPIEventNotification *pTAPIEventNotification = new CTAPIEventNotification;
  125. //
  126. //将 CTapi中的窗口句柄传给 CTAPIEventNotification,由 CTAPIEventNotification 向UI进程发送消息
  127. //
  128. pTAPIEventNotification->m_hWnd = m_hWnd;
  129.     hr = RegisterTapiEventInterface(pTAPIEventNotification);
  130.     //
  131. // 这里已经不再需要 CALLBACK 对象,因为 TAPI 本身会在其内部创建并保留这个对象
  132.     //
  133.     pTAPIEventNotification->Release();
  134. OSVERSIONINFOEX osvi;
  135. BOOL bOsVersionInfoEx;
  136. //
  137. // 因为 TE_FILETERMINAL 只有在 Windows XP 下才支持,因此这里需要先判断操作系统的版本
  138. // 如果在 Windows XP 以下注册 TE_FILETERMINAL 消息时,这是 TAPI 将无法接受到任何消息!
  139. //
  140. // 获取操作系统版本,使用到结构体 OSVERSIONINFOEX 
  141. // 如果调用失败,再尝试用 OSVERSIONINFO 
  142. //
  143. ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
  144. osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
  145. if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
  146. {
  147. // 如果使用 OSVERSIONINFOEX 失败, 尝试 OSVERSIONINFO.
  148. osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
  149. if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) 
  150. return FALSE;
  151. }
  152. if(osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
  153. {
  154.       // 获得操作系统版本,Win2000以下忽略
  155.          if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 )
  156.  {
  157.  // Windows 2000
  158.  m_Version = Ver_Win2000;
  159.  }
  160.          if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 )
  161.  {
  162.  // Windows XP
  163.  m_Version = Ver_WinXP;
  164.  }
  165. }
  166. else
  167. {
  168. AfxMessageBox("您的操作系统版本太低,本系统至少要求在 Win2000 环境下");
  169. return E_FAIL;
  170. }
  171. //
  172. // 对我们所希望处理的事件设置事件进行事件过滤设置
  173. //
  174.     if(m_Version == Ver_Win2000)
  175. {
  176. hr = m_pTapi->put_EventFilter(TE_CALLNOTIFICATION | 
  177. TE_CALLSTATE  | 
  178. TE_CALLMEDIA  | 
  179. TE_DIGITEVENT );
  180. }
  181. else if(m_Version == Ver_WinXP)
  182. {
  183. hr = m_pTapi->put_EventFilter(TE_CALLNOTIFICATION | 
  184. TE_CALLSTATE  | 
  185. TE_CALLMEDIA  | 
  186. TE_DIGITEVENT |
  187. TE_FILETERMINAL); // TE_FILETERMINAL 只有在 WinXP 下才支持
  188. }
  189. if( FAILED(hr) )
  190. {
  191. AfxMessageBox("注册消息失败,系统无法接收到任何消息,但可以对其它号码呼叫");
  192. }
  193.     //
  194.     // 查找我们需的的 Address (本程序中为 Modem)并在其上开始侦听
  195.     //
  196.     hr = ListenOnAddresses();
  197.     if ( FAILED(hr) )
  198.     {
  199. //
  200. //如果失败,释放所之前所审请的资源
  201. //在 ListenOnAddresses 中已有错误信息输出,此处不需要再次输出 Errmsg
  202. //
  203.         m_pTapi->Release();
  204.         m_pTapi = NULL;
  205.         return hr;
  206.     }
  207.     return S_OK;
  208. }
  209. ///////////////////////////////////////////////////////////////
  210. // ShutdownTapi
  211. //
  212. // 功能:关闭 Tapi
  213. ///////////////////////////////////////////////////////////////
  214. void CTapi::ShutdownTapi()
  215. {
  216.     //
  217.     // 如果仍有一个 Call 存在的话,则挂断并释放之
  218.     //
  219.     DisconnectTheCall();
  220.     ReleaseTheCall();
  221.     //
  222. // 调用 Sleep 目的是为了留出一段时间让 TAPI 来挂断电话。
  223. // 因为对话框虽然会立即消失,但 TAPI 进程仍有可能未结束
  224. // 
  225.     Sleep(5000);
  226. //
  227.     // 释放主要的对象
  228.     //
  229.     if (NULL != m_pTapi)
  230.     {
  231.         m_pTapi->Shutdown();
  232.         m_pTapi->Release();
  233.     }
  234. }
  235. ///////////////////////////////////////////////////////////////////////////
  236. // RegisterTapiEventInterface
  237. //
  238. // 功能:注册 Tapi 事件接口
  239. ///////////////////////////////////////////////////////////////////////////
  240. HRESULT CTapi::RegisterTapiEventInterface(CTAPIEventNotification *pTAPIEventNotification)
  241. {
  242.     HRESULT                       hr = S_OK;
  243.     IConnectionPointContainer   * pCPC;
  244.     IConnectionPoint            * pCP;
  245.     //
  246. // 获得连接点容器
  247. //
  248.     hr = m_pTapi->QueryInterface(
  249.                                 IID_IConnectionPointContainer,
  250.                                 (void **)&pCPC
  251.                                );
  252.     if ( FAILED(hr) )
  253.     {
  254.         return hr;
  255.     }
  256.     //
  257. // 获得连接点
  258. //
  259. hr = pCPC->FindConnectionPoint(
  260.                                    IID_ITTAPIEventNotification,
  261.                                    &pCP
  262.                                   );
  263.     pCPC->Release();
  264.     if ( FAILED(hr) )
  265.     {
  266.         return hr;
  267.     }
  268. // m_Advise 用于取消注册之用
  269.     hr = pCP->Advise(
  270.                       pTAPIEventNotification,
  271.                       &m_Advise
  272.                      );
  273.     pCP->Release();
  274.     return hr;
  275. }
  276. //////////////////////////////////////////////////////////////
  277. // AddressSupportsMediaType
  278. //
  279. // 功能:验证给定的 Address 是否支持 给定的 MediaType
  280. // 返回:TRUE——支持;FALSE——不支持
  281. //////////////////////////////////////////////////////////////
  282. BOOL CTapi::AddressSupportsMediaType(
  283. ITAddress * pAddress,
  284. long        lMediaType
  285. )
  286. {
  287.     VARIANT_BOOL     bSupport = VARIANT_FALSE;
  288.     ITMediaSupport * pMediaSupport;
  289.   
  290.     if ( SUCCEEDED( pAddress->QueryInterface( IID_ITMediaSupport,
  291.                                               (void **)&pMediaSupport ) ) )
  292.     {
  293.         //
  294.         // 通过 pMediaSupport 验证 pAddress 是否支持 lMediaType
  295.         //
  296.         pMediaSupport->QueryMediaType(
  297.                                       lMediaType,
  298.                                       &bSupport
  299.                                      );
  300.         pMediaSupport->Release();
  301.     }
  302.     return (bSupport == VARIANT_TRUE);
  303. }
  304. ////////////////////////////////////////////////////////////////////////
  305. // ListenOnAddresses
  306. //
  307. // 功能:找到 Modem 所在的 Address,判断是否支持 AudioIn and AudioOut,
  308. // 如果都支持,则调用 ListenOnThisAddress 开始在此 Address 上侦听
  309. ////////////////////////////////////////////////////////////////////////
  310. HRESULT CTapi::ListenOnAddresses()
  311. {
  312.     HRESULT             hr = S_OK;
  313.     IEnumAddress *      pEnumAddress;
  314.     ITAddress *         pAddress;
  315.     //
  316.     // 列举所有的 Address
  317.     //
  318.     hr = m_pTapi->EnumerateAddresses( &pEnumAddress );
  319.     if ( FAILED(hr) )
  320.     {
  321.         return hr;
  322.     }
  323. BOOL bModem; //当前 Address 是否为 Modem 所有
  324. BOOL bState; //当前 Address 状态是否启用
  325. BOOL bListen; //当前 Address 上侦听是否成功
  326.     while ( TRUE )
  327.     {
  328.         //
  329.         // 获得下一个 Address
  330.         //
  331.         bModem = bState = bListen = FALSE;
  332. hr = pEnumAddress->Next( 1, &pAddress, NULL );
  333.         if (S_OK != hr)
  334.         {
  335.             break;
  336.         }
  337. BSTR bstrName; //当前 Address 名称
  338. ADDRESS_STATE state; //当前 Address 状态
  339. CString strName; //当前 Address 名称的 ASSIC 表示
  340. //
  341. // 获得当前 Address 名称,并判断是否为 Modem 所有,如果不是,结束本次循环,遍历一下 Address
  342. //
  343. pAddress->get_AddressName(&bstrName);
  344. strName = bstrName;
  345. SysFreeString(bstrName);
  346. strName.MakeLower();
  347. if( strName.Find("modem") == -1 )
  348. continue;
  349. //
  350. //找到了modem
  351. //
  352. bModem = TRUE;
  353. pAddress->get_State( &state);
  354. if( state != AS_INSERVICE)
  355. continue;
  356. //
  357. //上面找到的Modem目前处于启用状态
  358. //
  359. bState = TRUE;
  360.         //
  361. // 当前 Address 是否支持声音
  362. if ( AddressSupportsMediaType(pAddress, TAPIMEDIATYPE_AUDIO) )
  363.         {
  364.             //
  365.             // 如果支持声音,开始侦听
  366.             //
  367.             hr = ListenOnThisAddress( pAddress );
  368. if( SUCCEEDED(hr) )
  369. {
  370. //
  371. // 找到一个 Modem 设备,并且监听成功,退出函数
  372. // 检测当前 Address 能力,是否支持监视所有种类形式的按键
  373. //
  374.         ITAddressCapabilities *pAddressCaps = NULL;
  375.     long                    lType = 0;
  376. hr = pAddress->QueryInterface(IID_ITAddressCapabilities, (void**)&pAddressCaps);
  377. if ( SUCCEEDED(hr) )
  378. {
  379. hr = pAddressCaps->get_AddressCapability( AC_MONITORDIGITSUPPORT, &lType );
  380. if( SUCCEEDED(hr) )
  381. {
  382. if( !(lType & LINEDIGITMODE_DTMF) ) //LINEDIGITMODE_DTMFEND
  383. AfxMessageBox("您的 Modem 不支持音频拨号检测功能,无法获得音频拨号的电话机的按键");
  384. else if( !(lType & LINEDIGITMODE_PULSE) )
  385. AfxMessageBox("您的 Modem 不支持脉冲拨号检测功能,无法获得脉冲拨号的电话机的按键");
  386. }
  387. pAddressCaps->Release();
  388. m_pAddress = pAddress;
  389. pAddress->Release();
  390. pEnumAddress->Release();
  391. return S_OK;
  392. }
  393. }
  394.         }
  395.         pAddress->Release();
  396.     }
  397.     pEnumAddress->Release();
  398. //
  399. // 以下为对应的错误信息提示
  400. //
  401. if( !bModem )
  402. {
  403. AfxMessageBox("没有找到Modem,请检查您计算机那是否装有Modem");
  404. return E_FAIL;
  405. }
  406. if( !bState )
  407. {
  408. AfxMessageBox("系统监测到您机器内有Modem,但当前没又启用,请手工启用Modem之后在运行本程序");
  409. return E_FAIL;
  410. }
  411. if( !bListen )
  412. {
  413. AfxMessageBox("使用Modem监听失败,可能是您的 Modem 不支持语音,详情请于服务商联系");
  414. return E_FAIL;
  415. }
  416.     return S_OK;
  417. }
  418. ///////////////////////////////////////////////////////////////////
  419. // ListenOnThisAddress
  420. //
  421. // 功能:在调用这个函数之前,我们已经对所希望处理的事件进行了过滤设置,
  422. // 这里通过调用 RegisterCallNotifications 告诉 TAPI 我们将要在这个
  423. // Address 上触发那些已经设置的事件
  424. //
  425. ///////////////////////////////////////////////////////////////////
  426. HRESULT CTapi::ListenOnThisAddress(
  427. ITAddress * pAddress
  428. )
  429. {
  430.     long lMediaTypes = TAPIMEDIATYPE_AUDIO;
  431.     if ( AddressSupportsMediaType(pAddress, TAPIMEDIATYPE_VIDEO) )
  432.     {
  433.         lMediaTypes |= TAPIMEDIATYPE_VIDEO;
  434.     }
  435.     HRESULT  hr;
  436.     long     lRegister;
  437.     hr = m_pTapi->RegisterCallNotifications(
  438.                                            pAddress,
  439.                                            VARIANT_TRUE,
  440.                                            VARIANT_TRUE,
  441.                                            lMediaTypes,
  442.                                            0,
  443.                                            &lRegister
  444.                                           );
  445.     return hr;
  446. }
  447. /////////////////////////////////////////////////////////////////
  448. // MakeTheCall
  449. //
  450. // 功能:设置并呼叫号码
  451. /////////////////////////////////////////////////////////////////
  452. HRESULT
  453. CTapi::MakeTheCall(
  454.             IN DWORD dwAddressType,
  455.             IN PWCHAR szAddressToCall
  456.            )
  457. {
  458.     HRESULT                 hr = S_OK;
  459.     BSTR                    bstrAddressToCall;
  460. //
  461. // 检测当前地址是否有效
  462. //
  463. if( m_pAddress == NULL)
  464. return E_FAIL;
  465.     //
  466. // 这里我们只要求支持声音即可
  467. //
  468. long lMediaTypes = TAPIMEDIATYPE_AUDIO;
  469.     //
  470.     // 因为我们在对线路侦听时,已经确定 m_Address 至少支持声音,
  471. // 已经满足我们要求,不用另做检测
  472.     //
  473. //
  474.     // 建立呼叫
  475.     //
  476.     bstrAddressToCall = SysAllocString( szAddressToCall );
  477.     hr = m_pAddress->CreateCall( bstrAddressToCall,
  478.                                 dwAddressType,
  479.                                 lMediaTypes,
  480.                                 &m_pCall);
  481.     SysFreeString ( bstrAddressToCall );
  482.     if ( FAILED(hr) )
  483.     {
  484.         AfxMessageBox("建立呼叫失败!");
  485.         return hr;
  486.     }
  487.     //
  488.     // 为当前呼叫选择终端
  489.     //
  490.     hr = SelectTerminalsOnCall(m_pAddress, m_pCall );
  491. //
  492. // 正式发出呼叫
  493. //
  494.     hr = m_pCall->Connect( VARIANT_TRUE );
  495.     if ( FAILED(hr) )
  496.     {
  497.         m_pCall->Release();
  498.         m_pCall = NULL;
  499.         AfxMessageBox("无法连接!");
  500.         return hr;
  501.     }
  502.     return S_OK;
  503. }
  504. /////////////////////////////////////////////////////////
  505. // GetTerminal
  506. //
  507. // 为输入 stream 创建默认的 terminal,并通过 ppTerminal 返回
  508. //
  509. /////////////////////////////////////////////////////////
  510. HRESULT CTapi::GetTerminal(
  511.             ITAddress   * pAddress,
  512.             ITStream    * pStream,
  513.             ITTerminal ** ppTerminal
  514.            )
  515. {
  516.     //
  517.     // 确定当前传入 stream 的 media type 和 direction
  518.     //
  519.     HRESULT            hr;
  520.     long               lMediaType;
  521.     TERMINAL_DIRECTION dir;
  522.     hr = pStream->get_MediaType( &lMediaType );
  523.     if ( FAILED(hr) ) return hr;
  524.     hr = pStream->get_Direction( &dir );
  525.     if ( FAILED(hr) ) return hr;
  526.     //
  527. // 这里我们人处理 Audio 流,对于 Video 流,我们不令做处理
  528.     // 因此只调用 GetDefaultStaticTerminal.
  529. // 首先,获得 ITTerminalSupport 接口 
  530.     //
  531.     ITTerminalSupport * pTerminalSupport;
  532.     hr = pAddress->QueryInterface( IID_ITTerminalSupport,
  533.                                    (void **)&pTerminalSupport);
  534.     if ( SUCCEEDED(hr) )
  535.     {
  536.         //
  537. // 为这种 MediaType 和 Direction 获得默认的 Terminal
  538.         //
  539.         hr = pTerminalSupport->GetDefaultStaticTerminal(lMediaType,
  540.                                                         dir,
  541.                                                         ppTerminal);
  542.         pTerminalSupport->Release();
  543.     }
  544.     return hr;
  545. }
  546. /////////////////////////////////////////////////////////////////
  547. // SelectTerminalOnCall
  548. //
  549. // 在给定的 call 上中的 stream 中选择一个适当的 terminal
  550. /////////////////////////////////////////////////////////////////
  551. HRESULT CTapi::SelectTerminalsOnCall(
  552.                      ITAddress * pAddress,
  553.                      ITBasicCallControl * pCall
  554.                      )
  555. {
  556.     HRESULT hr;
  557.     //
  558.     // 为当前 call 获得 ITStreamControl 接口
  559.     //
  560.     ITStreamControl * pStreamControl;
  561.     hr = pCall->QueryInterface(IID_ITStreamControl,
  562.                                (void **) &pStreamControl);
  563.     if ( SUCCEEDED(hr) )
  564.     {
  565.         //
  566.         // 列举所有的 stream
  567.         //
  568.         IEnumStream * pEnumStreams;
  569.         hr = pStreamControl->EnumerateStreams(&pEnumStreams);
  570.         pStreamControl->Release();
  571.         if ( SUCCEEDED(hr) )
  572.         {
  573.             //
  574.             // 遍历所有的 stream
  575.             //
  576.             ITStream * pStream;
  577.             while ( S_OK == pEnumStreams->Next(1, &pStream, NULL) )
  578.             {
  579.                 ITTerminal * pTerminal;
  580.                 //
  581. // 找出当前 stream 的 media type 和 direction,
  582. // 并且为这种 media type 和 direction 创建默认的 terminal 
  583.                 //
  584.                 hr = GetTerminal(pAddress,
  585.                                  pStream,
  586.                                  &pTerminal);
  587.                 if ( SUCCEEDED(hr) )
  588.                 {
  589.                     //
  590. // 对当前的 Stream 选择上面获得的 Terminal
  591.                     //
  592.                     hr = pStream->SelectTerminal(pTerminal);
  593.                     pTerminal->Release();
  594.                 }
  595.                 pStream->Release();
  596.             }
  597.             pEnumStreams->Release();
  598.         }
  599.     }
  600.     return hr;
  601. }
  602. /////////////////////////////////////////////////////////////////
  603. // PreparePlay
  604. //
  605. // 功能:播放给定的声音文件
  606. /////////////////////////////////////////////////////////////////
  607. HRESULT 
  608. CTapi::PreparePlay(CString strFileName)
  609. {
  610. //
  611. // 检测当前的 call object 是否有效
  612. //
  613.     if (NULL == m_pCall)
  614.     {
  615.         AfxMessageBox("AnswerTheCall: Call object 无效");
  616.         return E_UNEXPECTED;
  617.     }
  618. //
  619. // 如果 m_pPlayFileTerm 不是NULL,先释放之
  620. //
  621.     if(NULL != m_pPlayFileTerm)
  622. {
  623. m_pPlayFileTerm->Release();
  624. m_pPlayFileTerm = NULL;
  625. }
  626.     //
  627. // 为请求终端作准备 - 需要 ITBasicCallControl2 接口,只有在 TAPI3.1 中才支持
  628. //
  629.     ITBasicCallControl2*    pITBCC2 = NULL;
  630.     HRESULT hr = m_pCall->QueryInterface( IID_ITBasicCallControl2, (void**)&pITBCC2 );
  631. if(FAILED(hr))
  632. {
  633.         AfxMessageBox("AnswerTheCall: QI ITBasicCallControl2 failed");
  634.         m_pCall->Disconnect(DC_NORMAL);
  635. return hr;
  636. }
  637.     //
  638. // 为请求终端作准备 - need CLSID BSTR 
  639. //
  640. BSTR bstrCLSID = NULL;
  641. hr = StringFromCLSID(CLSID_FilePlaybackTerminal, &bstrCLSID);
  642. if(FAILED(hr))
  643. {
  644. pITBCC2->Release();
  645.         AfxMessageBox("AnswerTheCall: StringFromCLSID failed");
  646.         m_pCall->Disconnect(DC_NORMAL);
  647. return hr;
  648. }
  649. //
  650. //正式请求终端,需要媒体流类型和媒体流方向
  651. //
  652. hr = pITBCC2->RequestTerminal(bstrCLSID, 
  653. TAPIMEDIATYPE_AUDIO, 
  654. TD_CAPTURE, 
  655. &m_pPlayFileTerm);
  656. //
  657. //释放内存
  658. //
  659. ::CoTaskMemFree(bstrCLSID);
  660. if(FAILED(hr))
  661. {
  662. pITBCC2->Release();
  663.         AfxMessageBox("AnswerTheCall: RequestTerminal failed");
  664.         m_pCall->Disconnect(DC_NORMAL);
  665. return hr;
  666. }
  667. //
  668. // 为设置文件名作准备
  669. //
  670. BSTR bstrFileName = strFileName.AllocSysString();
  671. if(NULL == bstrFileName)
  672. {
  673. m_pPlayFileTerm->Release();
  674. m_pPlayFileTerm = NULL;
  675. pITBCC2->Release();
  676.         AfxMessageBox("AnswerTheCall: SysAllocString for play list failed");
  677.         m_pCall->Disconnect(DC_NORMAL);
  678. return E_OUTOFMEMORY;
  679. }
  680. //
  681. // 为 m_pPlayFileTerm 设置文件名
  682. //
  683. hr = PutPlayList(m_pPlayFileTerm, bstrFileName);
  684. //
  685. // 释放内存
  686. //
  687. ::SysFreeString(bstrFileName);
  688. if(FAILED(hr))
  689. {
  690. m_pPlayFileTerm->Release();
  691. m_pPlayFileTerm = NULL;
  692. pITBCC2->Release();
  693. AfxMessageBox("AnswerTheCall: PutPlayList failed");
  694. m_pCall->Disconnect(DC_NORMAL);
  695. return hr;
  696. }
  697. //
  698. // 用 ITBasicCallControl2 选择终端
  699. //
  700. hr = pITBCC2->SelectTerminalOnCall(m_pPlayFileTerm);
  701. if(FAILED(hr))
  702. {
  703. m_pPlayFileTerm->Release();
  704. m_pPlayFileTerm = NULL;
  705. pITBCC2->Release();
  706.         AfxMessageBox("AnswerTheCall: SelectTerminalOnCall failed");
  707.         m_pCall->Disconnect(DC_NORMAL);
  708. return hr;
  709. }
  710. pITBCC2->Release();
  711. return S_OK;
  712. }
  713. /////////////////////////////////////////////////////////////////////
  714. // Answers the call
  715. //
  716. // 功能:应答电话,创建并选择播放的终端
  717. /////////////////////////////////////////////////////////////////////
  718. HRESULT
  719. CTapi::AnswerTheCall()
  720. {
  721. HRESULT hr;
  722. hr = PreparePlay(m_WelcomeFileName);
  723. if( FAILED(hr) )
  724. {
  725. AfxMessageBox("无法找到" + m_WelcomeFileName + "文件或该文件不是合法的语音文件!");
  726. return E_FAIL;
  727. }
  728. //
  729. // 现在可以正式应答电话了
  730. //
  731.     hr = m_pCall->Answer();
  732. if(FAILED(hr))
  733. {
  734. m_pPlayFileTerm->Release();
  735. m_pPlayFileTerm = NULL;
  736.         AfxMessageBox("AnswerTheCall: Answer failed");
  737.         m_pCall->Disconnect(DC_NORMAL);
  738. return hr;
  739. }
  740. //
  741. // 到此正常结束
  742. //
  743. // pITBCC2->Release();
  744.     return S_OK;
  745. }
  746. //////////////////////////////////////////////////////////////////////
  747. // DisconnectTheCall
  748. //
  749. // 功能:挂断电话
  750. //////////////////////////////////////////////////////////////////////
  751. HRESULT CTapi::DisconnectTheCall()
  752. {
  753.     HRESULT         hr = S_OK;
  754.     if (NULL != m_pCall)
  755.     {
  756.         hr = m_pCall->Disconnect( DC_NORMAL );
  757. //
  758.         // 暂时不要释放 m_pCall,因为这样做的话我们无法得到 Disconnected 消息
  759. //
  760. return hr;
  761.     }
  762.     return S_FALSE;
  763. }
  764. //////////////////////////////////////////////////////////////////////
  765. // ReleaseTheCall
  766. //
  767. // 释放通话过程中所用到的 object
  768. //
  769. //////////////////////////////////////////////////////////////////////
  770. void
  771. CTapi::ReleaseTheCall()
  772. {
  773. // KillTimer(m_hWnd, TIMER_ID);
  774. //
  775. // 释放终端
  776. //
  777. if(NULL != m_pPlayFileTerm)
  778. {
  779. m_pPlayFileTerm->Release();
  780. m_pPlayFileTerm = NULL;
  781. }
  782. if(NULL != m_pRecordFileTerm)
  783. {
  784. m_pRecordFileTerm->Release();
  785. m_pRecordFileTerm = NULL;
  786. }
  787. //
  788. // 释放 call object - 这样会同时释放所有的被选择过得终端
  789. //
  790.     if (NULL != m_pCall)
  791.     {
  792.         m_pCall->Release();
  793.         m_pCall = NULL;
  794.     }
  795. }
  796. //////////////////////////////////////////////////////////////////
  797. // PutPlayList 
  798. //
  799. // 功能:将要播放的声音文件放入播放列表。调用了 put_PlayList 
  800. //////////////////////////////////////////////////////////////////
  801. HRESULT CTapi::PutPlayList(
  802. IN ITTerminal *pITTerminal, 
  803. IN BSTR bstrFileName)
  804. {
  805. //
  806. // 检测输入的终端是否有效
  807. //
  808. if(NULL == pITTerminal)
  809. {
  810.         AfxMessageBox("PutPlayList: playback terminal NULL");
  811. return E_UNEXPECTED;
  812. }
  813. //
  814.     // 获得 ITMediaPlayback 接口 
  815. //
  816.     ITMediaPlayback*    pMediaPlayback = NULL;
  817.     HRESULT hr = pITTerminal->QueryInterface(
  818. IID_ITMediaPlayback,
  819. (void**)&pMediaPlayback);
  820. if(FAILED(hr))
  821. {
  822.         AfxMessageBox("PutPlayList: QI ITMediaPlayback failed");
  823. return hr;
  824. }
  825. //
  826. // put_PlayList 函数需要用到的 VARIANT 变量
  827. //
  828. VARIANT varPlaylist;
  829. VariantInit(&varPlaylist);
  830. //
  831. // 设置 SAFEARRAYBOUND 
  832. //
  833.     SAFEARRAYBOUND DimensionBounds;
  834.     DimensionBounds.lLbound = 1;
  835. //
  836. // 设置 playlist 中的文件数
  837. //
  838.     DimensionBounds.cElements = 1;
  839.     //
  840. // 把文件名置于 playlist 中的 index = 1处
  841. //
  842.     long lArrayPos = 1;
  843. //
  844. // 用于存放文件名 - 用于把文件名加入到 SafeArray
  845. //
  846.     VARIANT* pvarArrayEntry = new VARIANT;
  847.     if( pvarArrayEntry == NULL)
  848.     {
  849.         AfxMessageBox("PutPlayList: new VARIANT failed");
  850.         return E_OUTOFMEMORY;
  851.     }
  852. VariantInit(pvarArrayEntry);
  853.     //
  854. // 创建 SAFEARRAY
  855. //
  856.     SAFEARRAY *pPlayListArray = NULL;
  857.     pPlayListArray = SafeArrayCreate( VT_VARIANT, 1, &DimensionBounds);
  858.     if( pPlayListArray == NULL)
  859.     {
  860.         AfxMessageBox("PutPlayList: SafeArrayCreate failed");
  861. delete pvarArrayEntry;
  862.         return E_OUTOFMEMORY;
  863.     }
  864. //
  865. // 正式把存储在 pvarArrayEntry 中的文件名加入到 SafeArray 中
  866. // 当我们需要加入多个元素到 SafeArray 中时,只需让 lArrayPos 加 1 即可
  867. //
  868.     pvarArrayEntry->vt = VT_BSTR;
  869.     pvarArrayEntry->bstrVal = ::SysAllocString(bstrFileName);
  870.     SafeArrayPutElement( pPlayListArray, &lArrayPos, pvarArrayEntry);
  871. VariantClear(pvarArrayEntry);
  872. //
  873. // 准备varPlaylist, 为调用 put_PlayList 作准备
  874. //
  875.     V_VT(&varPlaylist) = VT_ARRAY | VT_VARIANT;
  876.     V_ARRAY(&varPlaylist) = pPlayListArray;
  877. //
  878. // 最后,现在可以调用 put_PlayList 把 varPlaylist 放入到播放列表里了
  879. //
  880.     hr = pMediaPlayback->put_PlayList(varPlaylist);
  881.     if(FAILED(hr))
  882.     {
  883.         AfxMessageBox("PutPlayList: put_PlayList failed");
  884. delete pvarArrayEntry;
  885.         return E_OUTOFMEMORY;
  886.     }
  887. //
  888. //释放内存
  889. //
  890. delete pvarArrayEntry;
  891.     pMediaPlayback->Release();
  892. VariantClear(&varPlaylist);
  893.     return hr;
  894. }
  895. ///////////////////////////////////////////////////////////////////
  896. // StartRecord
  897. //
  898. //
  899. // 功能:录音
  900. // 说明:只有 WinXP 下才支持
  901. ///////////////////////////////////////////////////////////////////
  902. HRESULT CTapi::StartRecord()
  903. {
  904. HRESULT hr;
  905. hr = CreateAndSelectFileRecordTerminal();
  906. if(FAILED(hr))
  907. {
  908. //
  909. // 致命错误 - 无法继续下去 - 切断此次通话
  910. //
  911. m_pCall->Disconnect(DC_NORMAL);
  912. AfxMessageBox("CreateAndSelectFileRecordTerminal failed");
  913. return hr;
  914. }
  915. //
  916. // 获得 ITMediaControl 接口 - we need to call Start
  917. //
  918. ITMediaControl* pITMC = NULL;
  919. hr = m_pRecordFileTerm->QueryInterface(IID_ITMediaControl, (void**)&pITMC);
  920. if(FAILED(hr))
  921. {
  922. //
  923. // 致命错误 - 无法继续下去 - 切断此次通话
  924. //
  925.     AfxMessageBox("ITFileTerminalEvent, but failed to QI ITMediaControl");
  926. m_pCall->Disconnect(DC_NORMAL);
  927. return hr;
  928. }
  929. hr = pITMC->Start();
  930. pITMC->Release();
  931. if(FAILED(hr))
  932. {
  933. //
  934. // 致命错误 - 无法继续下去 - 切断此次通话
  935. //
  936.     AfxMessageBox("ITFileTerminalEvent, but ITMediaControl::Start");
  937. m_pCall->Disconnect(DC_NORMAL);
  938. return hr;
  939. }
  940. //
  941. // 录音一分钟后自动停止
  942. //
  943. SetTimer(m_hWnd,Timer_ID, Max_Rec_Time, NULL);
  944. m_dwMessages++;
  945. KillTimer(m_hWnd,Timer_ID); 
  946. return S_OK;
  947. }
  948. ///////////////////////////////////////////////////////////////////
  949. // CheckStreamMT
  950. //
  951. // 检测输入的 pITStream 是否支持给定的媒体类型
  952. //
  953. ///////////////////////////////////////////////////////////////////
  954. HRESULT CTapi::CheckStreamMT(
  955. IN ITStream* pITStream,
  956. IN long mt)
  957. {
  958. long mtStream = TAPIMEDIATYPE_AUDIO;
  959. HRESULT hr = E_FAIL;
  960. if(FAILED(hr=pITStream->get_MediaType(&mtStream)))
  961. {
  962. return hr;
  963. }
  964. if(!(mt&mtStream))
  965. {
  966. return S_FALSE;
  967. }
  968. else
  969. {
  970. return S_OK;
  971. }
  972. }
  973. ///////////////////////////////////////////////////////////////////
  974. // CheckStreamDir
  975. //
  976. // 检测输入的 pITStream 是否有给定的媒体流方向
  977. //
  978. ///////////////////////////////////////////////////////////////////
  979. HRESULT CTapi::CheckStreamDir(
  980. IN ITStream* pITStream,
  981. IN TERMINAL_DIRECTION td)
  982. {
  983. TERMINAL_DIRECTION tdStream = TD_CAPTURE ;
  984. HRESULT hr =E_FAIL;
  985. if(FAILED(hr=pITStream->get_Direction(&tdStream)))
  986. {
  987. return hr;
  988. }
  989. if(td!=tdStream)
  990. {
  991. return S_FALSE;
  992. }
  993. else
  994. {
  995. return S_OK;
  996. }
  997. }
  998. ///////////////////////////////////////////////////////////////////
  999. // CreateAndSelectFileRecordTerminal
  1000. //
  1001. // 功能:
  1002. // 1. 创建录音文件终端,
  1003. // 2. 并对其指定录音文件名, 
  1004. // 3. 对当前输入媒体流选择音轨终端
  1005. ///////////////////////////////////////////////////////////////////
  1006. HRESULT CTapi::CreateAndSelectFileRecordTerminal()
  1007. {
  1008. //
  1009.     // 检测当前 m_pCall 是否有效
  1010.     //
  1011.     if (NULL == m_pCall) 
  1012.     {
  1013.         AfxMessageBox("CreateAndSelectFileRecordTerminal: m_pCall 无效,可能当前没有通话");
  1014.         return E_UNEXPECTED;
  1015.     }
  1016.     //
  1017.     // 获得 ITStreamControl 接口
  1018.     //
  1019. ITStreamControl* pStreamControl = NULL;
  1020.     HRESULT hr = m_pCall->QueryInterface( IID_ITStreamControl, (void**)&pStreamControl );
  1021.     if (FAILED(hr))
  1022.     {
  1023.         AfxMessageBox("CreateAndSelectFileRecordTerminal: QI for ITStreamControl failed");
  1024.         return E_FAIL;
  1025.     }
  1026. //
  1027. // 确保录音终端在这之前已经释放
  1028. //
  1029. if(NULL != m_pRecordFileTerm)
  1030. {
  1031. m_pRecordFileTerm->Release();
  1032. m_pRecordFileTerm = NULL;
  1033. }
  1034.     //
  1035. // QI for ITBasicCallControl2 - 准备请求终端
  1036. //
  1037. ITBasicCallControl2* pITBCC2 = NULL;
  1038.     hr = m_pCall->QueryInterface( IID_ITBasicCallControl2, (void**)&pITBCC2 );
  1039. if(FAILED(hr))
  1040. {
  1041. pStreamControl->Release();
  1042. AfxMessageBox("CreateAndSelectFileRecordTerminal: QI for ITBasicCallControl2 failed");
  1043. return hr;
  1044. }
  1045. //
  1046. // 为 ReqestTerminal 获得 CLSID
  1047. //
  1048. BSTR bstrCLSID = NULL;
  1049. hr = StringFromCLSID(CLSID_FileRecordingTerminal, &bstrCLSID);
  1050. if(FAILED(hr))
  1051. {
  1052. pITBCC2->Release();
  1053. pStreamControl->Release();
  1054. AfxMessageBox("CreateAndSelectFileRecordTerminal: StringFromCLSID failed");
  1055. return hr;
  1056. }
  1057. //
  1058. // 请求录音终端,需要输入 媒体类型 和 媒体流方向
  1059. //
  1060. hr = pITBCC2->RequestTerminal(bstrCLSID, 
  1061. TAPIMEDIATYPE_AUDIO, 
  1062. TD_RENDER, 
  1063. &m_pRecordFileTerm);
  1064. //
  1065. // 释放内存
  1066. //
  1067. ::CoTaskMemFree(bstrCLSID);
  1068. pITBCC2->Release();
  1069. if(FAILED(hr))
  1070. {
  1071. pStreamControl->Release();
  1072. AfxMessageBox("CreateAndSelectFileRecordTerminal: RequestTerminal failed");
  1073. return hr;
  1074. }
  1075.     //
  1076. // 获得 ITMediaRecord 接口,然后可以调用 put_FileName 来指定要录音的文件名
  1077.     //
  1078. ITMediaRecord* pITMediaRec = NULL;
  1079.     hr = m_pRecordFileTerm->QueryInterface( IID_ITMediaRecord, (void**)&pITMediaRec );
  1080. if(FAILED(hr))
  1081. {
  1082. pStreamControl->Release();
  1083. AfxMessageBox("CreateAndSelectFileRecordTerminal: QI ITMediaRecord failed");
  1084. return hr;
  1085. }
  1086. //
  1087. // 为受到的每一个消息建立不同的文件,一文件名后面的数字来区分
  1088. //
  1089. CString RecFileName;
  1090. CString tmp;
  1091. tmp.Format("%d.", m_dwMessages);
  1092. RecFileName = m_RecFileName + tmp + m_RecFileExt;
  1093. BSTR bstrFileName = RecFileName.AllocSysString();
  1094. if(NULL == bstrFileName)
  1095. {
  1096. pStreamControl->Release();
  1097. AfxMessageBox("CreateAndSelectFileRecordTerminal: SysAllocString failed");
  1098. return E_OUTOFMEMORY;
  1099. }
  1100. //
  1101. // 设置文件名
  1102. //
  1103. hr = pITMediaRec->put_FileName(bstrFileName);
  1104. //
  1105. // 释放内存
  1106. //
  1107. ::SysFreeString(bstrFileName);
  1108. pITMediaRec->Release();
  1109. if(FAILED(hr))
  1110. {
  1111. pStreamControl->Release();
  1112. AfxMessageBox("CreateAndSelectFileRecordTerminal: put_FileName failed");
  1113. return hr;
  1114. }
  1115.     //
  1116.     // 获得 ITMultiTrackTerminal 接口
  1117.     //
  1118. ITMultiTrackTerminal* pMTRecTerminal = NULL;
  1119.     hr = m_pRecordFileTerm->QueryInterface( IID_ITMultiTrackTerminal, (void**)&pMTRecTerminal );
  1120. if(FAILED(hr))
  1121. {
  1122. pStreamControl->Release();
  1123. AfxMessageBox("CreateAndSelectFileRecordTerminal: QI IID_ITMultiTrackTerminal failed");
  1124. return hr;
  1125. }
  1126.     //
  1127.     // 列举所有的媒体流
  1128.     //
  1129.     IEnumStream* pEnumStreams = NULL;
  1130.     hr = pStreamControl->EnumerateStreams(&pEnumStreams);
  1131.     pStreamControl->Release();
  1132.     if (FAILED(hr))
  1133.     {
  1134. pMTRecTerminal->Release();
  1135. AfxMessageBox("CreateAndSelectFileRecordTerminal: EnumerateStreams failed");
  1136. return hr;
  1137.     }
  1138. //
  1139. // 只处理音频媒体类型
  1140. //
  1141. long lMediaTypes = TAPIMEDIATYPE_AUDIO;
  1142.     //
  1143.     // 遍历媒体流,创建我们需要的
  1144.     //
  1145. ITStream* pStream = NULL;
  1146.     while (S_OK == pEnumStreams->Next(1, &pStream, NULL))
  1147.     {
  1148.         
  1149.         if ( (S_OK==CheckStreamMT(pStream,lMediaTypes))
  1150. && (S_OK==CheckStreamDir(pStream,TD_RENDER))) 
  1151.         {
  1152.             //
  1153. // 我们已经有了录音文件终端,下面要创建音轨终端
  1154.             //
  1155.             ITTerminal* pRecordingTrack = NULL;
  1156. //
  1157. // 检测媒体类型
  1158. //
  1159. long lStreamMediaType=0;
  1160. hr = pStream->get_MediaType(&lStreamMediaType);
  1161.             if (FAILED(hr))
  1162.             {
  1163. pStream->Release();
  1164. pStream=NULL;
  1165.                 continue;
  1166.             }
  1167. //
  1168. // 创建音轨终端
  1169. //
  1170.             hr = pMTRecTerminal->CreateTrackTerminal(lStreamMediaType, TD_RENDER, &pRecordingTrack);
  1171. if (FAILED(hr))
  1172.             {
  1173. pStream->Release();
  1174. pStream=NULL;
  1175.                 break;
  1176.             }
  1177.             //
  1178. // 让当前媒体流选择音轨终端
  1179. //
  1180. hr = pStream->SelectTerminal(pRecordingTrack);
  1181. //
  1182. // 释放内存
  1183. //
  1184. pStream->Release();
  1185. pStream=NULL;
  1186.             pRecordingTrack->Release();
  1187. pRecordingTrack=NULL;
  1188.             if (FAILED(hr))
  1189.             {
  1190.                 break;
  1191.             }
  1192. lMediaTypes^=lStreamMediaType;
  1193. if(lMediaTypes==0)
  1194. {
  1195.             // 
  1196. // 我们已经为所有媒体类型选择了终端,任务完成!
  1197. //
  1198. break;
  1199. }
  1200.         } 
  1201. //
  1202. // 释放内存
  1203. //
  1204. if(pStream!=NULL)
  1205. {
  1206. pStream->Release();
  1207. pStream = NULL;
  1208. }
  1209.     } // while (enumerating streams on the call)
  1210. //
  1211. //释放内存
  1212. //
  1213.     pMTRecTerminal->Release();
  1214. pEnumStreams->Release();
  1215.     
  1216. //
  1217. // 检测我们是否已经为所有媒体选择了终端
  1218. //
  1219.     if (lMediaTypes==0)
  1220.     {
  1221.         return S_OK;
  1222.     }
  1223.     else
  1224.     {
  1225. AfxMessageBox("CreateAndSelectFileRecordTerminal: no streams available");
  1226.         return E_FAIL;
  1227.     }
  1228. }
  1229. ///////////////////////////////////////////////////////////////////
  1230. // SameCall
  1231. //
  1232. // 检测是否与 m_pCall 是同一个
  1233. //
  1234. ///////////////////////////////////////////////////////////////////
  1235. bool CTapi::SameCall(ITCallStateEvent* pCallStateEvent)
  1236. {
  1237. if(NULL == pCallStateEvent)
  1238. {
  1239. return false;
  1240. }
  1241. //
  1242. // 获得 Call object
  1243. //
  1244. ITCallInfo* pCallInfo = NULL;
  1245. HRESULT hr = pCallStateEvent->get_Call(&pCallInfo);
  1246. if(NULL != pCallInfo &&  ( SUCCEEDED(hr) && NULL != m_pCall) )
  1247. {
  1248. bool bIsEqual = true;
  1249. //
  1250. // 比较两个 Call object 的 IUnknown 接口
  1251. //
  1252. IUnknown* pIUnkCallInfo = NULL;
  1253. IUnknown* pIUnkCallControl = NULL;
  1254. pCallInfo->QueryInterface(IID_IUnknown, (void**)&pIUnkCallInfo);
  1255. m_pCall->QueryInterface(IID_IUnknown, (void**)&pIUnkCallControl);
  1256. //
  1257. //比较
  1258. //
  1259. if(pIUnkCallInfo != pIUnkCallControl)
  1260. {
  1261. bIsEqual = false;
  1262. }
  1263. //
  1264. //释放内存
  1265. //
  1266. pCallInfo->Release();
  1267. if(NULL != pIUnkCallInfo)
  1268. {
  1269. pIUnkCallInfo->Release();
  1270. }
  1271. if(NULL != pIUnkCallControl)
  1272. {
  1273. pIUnkCallControl->Release();
  1274. }
  1275. return bIsEqual;
  1276. }
  1277. else
  1278. {
  1279. return false;
  1280. }
  1281. }
  1282. //////////////////////////////////////////////////////////////////
  1283. //
  1284. // GetTerminalFromStreamEvent 
  1285. //
  1286. // 功能:从 MediaEvent 事件中获得媒体流,然后,再由媒体流获得终端
  1287. // 如果对应的终端存在,则返回 NULL
  1288. //////////////////////////////////////////////////////////////////
  1289. HRESULT CTapi::GetTerminalFromStreamEvent( 
  1290. IN ITCallMediaEvent * pCallMediaEvent,
  1291.             OUT ITTerminal ** ppTerminal )
  1292. {
  1293.     HRESULT hr;
  1294. *ppTerminal = NULL;
  1295.     //
  1296.     // 获得这一事件当中的媒体流
  1297.     //
  1298.     ITStream * pStream;
  1299.     hr = pCallMediaEvent->get_Stream( &pStream );
  1300.     if ( FAILED(hr) || (pStream == NULL) ) return E_FAIL;
  1301.     //
  1302.     // 列举支持这一种媒体流的终端
  1303.     //
  1304.     IEnumTerminal * pEnumTerminal;
  1305.     hr = pStream->EnumerateTerminals( &pEnumTerminal );
  1306.     pStream->Release();
  1307.     if ( FAILED(hr) ) return hr;
  1308.     //
  1309. // 获得第一个终端,如果没有找到,返回 E_FAIL 跳出这一事件的处理
  1310.     //
  1311.     hr = pEnumTerminal->Next(1, ppTerminal, NULL);
  1312.     if ( hr != S_OK )
  1313.     {
  1314.         pEnumTerminal->Release();
  1315.         return E_FAIL;
  1316.     }
  1317.     //
  1318. // 进一步检测,注意这里不可能有大于一个终端,因为我们对每一各媒体流只选择
  1319. // 过一个终端
  1320.     //
  1321.     ITTerminal * pExtraTerminal;
  1322.     hr = pEnumTerminal->Next(1, &pExtraTerminal, NULL);
  1323.     pEnumTerminal->Release();
  1324.     if ( hr == S_OK ) // 大于一个终端,不可能出现的情况
  1325.     {
  1326.         pExtraTerminal->Release();
  1327.         (*ppTerminal)->Release();
  1328.         *ppTerminal = NULL;
  1329.         return E_UNEXPECTED;
  1330.     }
  1331.     return S_OK;
  1332. }