应答机(TAPI 2.1)






4.95/5 (60投票s)
关于 TAPI 的一些描述 + 一个不完整的应答机示例。
引言
本文试图描述和简化应答机的开发,使用 Microsoft Telephony Application Programming Interface (TAPI) 和 MFC。它只描述了 TAPI 2.x,与 COM 无关!
示例并非完整的应答机(因为它不记录任何语音)。事实上,它更像是 IVR。它接听电话,捕获来电显示,并响应用户按下的数字。
此外,本文试图解决一些常见问题和困难,例如来电显示、DTMF 和语音!在 TAPI 论坛上可以看到相关的许多问题。最后,我将介绍一个简单的架构和一些有用的在线资源。
致谢与免责声明
由于链接数量很多,我已将它们放在文章中的需要处,以及文章底部。非常感谢所有作者的贡献和善意。背景
TAPI
TAPI 是 Telephony Application Programming Interface 的缩写,由 Intel 和 Microsoft 联合设计的一套 API,旨在简化电话通信设备(如调制解调器)的应用程序开发。有了它们的帮助,我们就不需要了解大量的硬件及其相关协议细节(如调制解调器的 AT 命令)。
使用这套 API,您将能够编写能够发送和接收数据、语音、传真的应用程序。现在可以编写各种系统,如 VOIP、PBX、呼叫控制、IVR、视频会议等。有了如此广泛的应用,无需说明这套 API 是很难使用的!(在我看到它们之前,我以为在 Windows 上开发非常容易!)
版本
这套 API 有很多版本(在撰写本文时,对于 C++:1.3、1.4、2.0、2.1、2.2、3.0、3.1)。但从 2.2 版到 3.0 版有一个重大变化:在 3.x 版之前,它们是基于 C 的 API,使用一组结构进行数据输入或输出。通过 3.x 版(在 Windows XP 中完成),Microsoft 做出了重大改变,以便 TAPI 开发可以用于所有编程语言(不仅仅是 C++),并且它使用了 COM(Component Object Model)。因此,TAPI 3.x 是基于 COM 的,MFC 可能不是编写此类(基于 COM 的)应用程序的最佳平台。我不熟悉 COM。我只读了 Michel Dun 的精彩文章。也许如果我学好了,我会写一篇关于 TAPI 3.x 的文章,但本文讨论的是 TAPI 2.x,特别是 TAPI 2.1。
使用 TAPI 的一个初始化工作是与 TSP 协商支持的版本。请参阅 MSDN 中的 `lineNegotiateAPIVersion`。
既然我提到了 Michael Dunn,我应该说有一个以他的名字命名的 TAPI 网站:http://www.rainyjay.com/tapi/tapi.htm 谁知道呢,也许这个网站属于 Code Project 中著名的 Michael Dunn!
TSP
TAPI 内部有一个机制,使得不同的硬件制造商能够为开发者提供一个统一的接口。我们的应用程序使用这套 API,而另一端是调制解调器驱动程序。调制解调器制造商应编写一个能够执行主要 TAPI 功能的驱动程序。请看下图。因此,有时我们不应该为这个或那个事情责怪 TAPI,这取决于 TSP 和驱动程序是否符合 TAPI。
线路和电话,麦克风/扬声器插孔
仔细看看您的调制解调器,您会看到它底部有两个大插座(类似 RJ45),一个标有 Line(线路),另一个标有 Phone(电话)。许多人每天都在使用它们。您可能已经将您的电话连接到它的电话插座,并将线路连接到您的本地电话系统,该系统将连接到 PSTN。请记住这些术语。您在处理 TAPI 时会经常听到它们。
此外,有些调制解调器上还有两个标有 Mic(麦克风)和 Speaker(扬声器)的插孔。您可以使用它们将线路连接到耳机,如果调制解调器支持的话。在我的示例应用程序中,您将看到我使用的所有 API 都有两个版本,一些以 line 开头(如 lineOpen),一些以 phone 开头(如 phoneOpen)。
lineOpen 尝试通过调制解调器连接到线路,而 phoneOpen 尝试打开您选择的电话设备,如耳机或电话听筒。
TAPI 调用顺序
要编写 TAPI 应用程序,我们应该从了解现实世界中发生的事情开始。当我们拨打电话、电话响铃、用户按下按钮、来电显示到来时,会发生什么……。掌握了这些事件,我们就可以制定逻辑来编写我们的应用程序。除了这一系列事件,我们还应考虑启动事件驱动应用程序所需的 TAPI 函数调用顺序。
应答机的简单顺序如下:
- 初始化线路/电话。(现在您知道什么是线路/电话了!)
- 打开线路/电话。
- 创建事件接收机制。(例如,运行一个线程来接收事件)
- 进行电话交互。
- 关闭和清理。
每个顺序都有其细节(子序列)和对 TAPI 函数的调用。这里列出了主要调用:
初始化:
- `lineInitializeEx`
- `lineNegotiateAPIVersion`
- `lineGetDevCaps`
打开:
- `lineOpen`
- `lineSetNumRings`
- `lineSetStatusMessages`
创建事件线程
不需要更多的 TAPI 函数调用
关闭与清理
- `lineDrop`
- `lineClose`
- `lineDeallocateCall`
- `lineShutdown`
看起来很简单?嗯,没那么简单!每个调用都需要正确的输入,此外,所有输出都必须被检查。然而,一个正确的输出并不表明工作做得好(异步调用),但一个错误的输出表明操作不受支持。还有其他更重要的事情,比如处理内存分配和释放,以及处理事件并给出正确的响应,管理声音播放等等。
我在 CP 上提供的东西使这项工作变得更容易,但它非常有限,而且如果您不想学习任何东西,它就帮不了您!如果您正在寻找一个能完成所有工作的库,那么本文和示例不适合您。
TAPI 事件
正如我之前提到的,一项重要的工作是创建一个事件线程来处理 TAPI 事件。但哪些事件呢?TAPI 事件很多。我创建了一个事件线程并对应用程序进行了呼叫,这些是我收到的并需要处理的事件:事件 | wParam(1/2) | 如何处理 |
LINE_APPNEWCALL | 可用的呼叫句柄 | |
LINE_LINEDEVSTATE | LINEDEVSTATE_RINGING | 正在响铃,请接电话! |
LINE_CALLINFO | LINECALLINFOSTATE_CALLERID | 来电显示 |
LINE_CALLSTATE | LINECALLSTATE_CONNECTED | 已建立连接 |
LINE_MONITORDIGITS | DTMF 类型 | 对方按下了数字键 |
LINE_REPLY | 不!可能发生了错误。 | |
LINE_CALLSTATE | LINECALLSTATE_DISCONNECTED | 对方已挂断电话 |
声音播放
注意:我不会解释或实现录音!声音播放是您需要非常小心使用的一个方面。这里可能会发生一些严重错误。有很多考虑因素,如声音格式、质量、缓冲和及时且正确的解缓冲,等等。事实上,编写一个出色的声音类或库将是您可能完成的第二项(甚至第一项)耗时的工作。
有一些考虑因素:
选择音质取决于您的调制解调器功能。如今,几乎所有类型的语音调制解调器都支持“Wave PCM 8.000 KHz; 16 位; 单声道”,这是最好的格式!因为这是实际电话线、电缆和通信工具(至少在我所在的国家/地区)能够转换的最佳质量(8 KHz 语音)。但这种质量几乎很差!音量很低,所以我们需要将我们的声音录得稍大一点,以便在另一端能够被接受。自己尝试一下。也许休息一下!
然而,Microsoft 表示正确的格式是 8.000KHZ 8 位单声道 CCITT u-law(Microsoft 知识库文章 ID:Q142745)。
缓冲是另一个问题。阅读下一部分关于延迟的内容,它很有趣。
解决延迟问题
延迟是大多数使用 unimodem 的 TAPI 应用程序的一个问题!当您需要处理 DTMF 时就会发生。当用户在电话上按下一个数字时,应用程序必须停止声音播放,并可能播放另一个声音或执行其他操作。
为了停止当前正在播放的声音,我们必须调用 `waveOutReset` API。MSDN 说:“waveOutReset 函数停止在给定的波形音频输出设备上播放,并将当前位置重置为零。所有待处理的播放缓冲区将被标记为完成并返回给应用程序。” 这在谈论声卡时是正确的。但我对调制解调器(主要是内部软调制解调器)的小经验告诉我,当调用此 API 时,它会执行以下操作:“等待给定缓冲区完全播放后返回!”但效果非常差。我的文件不是很大,至少有 100 KB,所以不需要双缓冲,但结果显然非常糟糕!这是一个很大的问题,用户按下一个数字,等待响应,但最后一个声音仍在播放,她按了更多数字,等等。所以我必须找到一个解决方案,而且我几乎做到了!我使用了一种新的双缓冲方案。方式如下:我将所有声音数据导入内存缓冲区!然后将其逻辑上分成一些非常小的部分。然后我使用一个变量来存储基地址,并将地址和我的逻辑小缓冲区大小传递给 `waveOutWrite` API,在小块完成的消息后,我传递 API“基地址 + 小尺寸”。这样我就不需要从文件重新加载块,并且可以最小化块大小,甚至到 1K,而没有任何问题。现在如果用户按下数字键,`waveOutReset` 继续播放(非常)小尺寸的数据,这几乎微不足道。结果比我想象的要好。在声卡上不是很好,但在调制解调器上很好。问题几乎解决了:)几乎解决了,因为我一直使用 16 位文件。当我使用 8 位文件时,我遇到了一些问题。也许问题出在我的调制解调器上。我将尝试解决这个问题。
来电显示垃圾!
为什么我要为来电显示添加一个主题?嗯,仅仅是因为我大部分的头疼都与它有关!虽然许多调制解调器能够支持语音,但更多的调制解调器无法检测来电显示。这里的示例代码有能力检测,前提是一切正常。我应该在这里提到,大多数 V.92 调制解调器在查找来电显示方面表现良好。
我在这里要描述的不是 TAPI 软件部分,它很简单。但我将说明一些在检测来电显示时最常见的问题。如果您在调制解调器上看到标有“支持来电显示”的标签,但即使随调制解调器附带的光盘上的应用程序也不起作用,那么这部分就是您的。
来电显示是非常本地化的!这完全取决于您当地的电话公司选择一项技术并使用它。最常见的技术是 DTMF 和 FSK。
我第一次听到 DTMF 时,它让我很困惑。我想,“这不是对方按下的按键吗?”好吧,那没关系!
DTMF 是 Dual Tone Multi-Frequency 的缩写。“DTMF 为每个按键分配一个特定的声音频率或音调,以便监视微处理器可以轻松识别。然后将该频率转换为可用的模拟或数字信号。这通常被称为触摸音。”这就是我的字典说的。有些电信公司以这种格式向我们发送 CID 真是太搞笑了!
如果您在给朋友打电话时按下了电话的按键(例如 #1),您就向您的朋友发送了一个 DTMF 码!就这样,号码将在电话的第一次和第二次响铃之间发送给您。
FSK 是 Frequency Shift Keying 的缩写,我真的不明白它与 DTMF 工作方式的区别!我读了一些关于在频率变化前在域中发生变化的内容!好吧,这并不重要,因为我不是硬件工程师。
因此,您需要询问您当地的电话公司哪种系统正在运行,然后尝试找到支持您所在地区机制的调制解调器。我发现了一些支持 DTMF 的调制解调器,而大多数不支持!大多数调制解调器支持 FSK。
还有一些硬件可以将 DTMF 转换为 FSK 并发送给调制解调器!在这里:http://www.artech.com.tw/html/english/ex200/ex200.htm
我在网上找到(excele tools,链接可在文章底部找到)说:“北美使用 Bellcore FSK 来电显示标准,该标准在第一次和第二次响铃之间发送数据”,并且“在欧洲,人们已经成功使用 USR 型号 5630”。好吧,我不知道!如果您在英国,您可能会遇到一些麻烦!!看这里:www.ainslie.org.uk/callerid/dev_soft.htm
如果您对您所在地区有正确的信息,并且购买了合适的调制解调器,仍然有充分的理由无法收到该信息!
检查您是否没有太早接听电话,并且在收到来电显示之前!例如,在我住的地方,来电显示在第一次和第二次响铃之间到来。如果我在第一次响铃时将我的 TAPI 应用程序设置为接听电话,那么我就会丢失所有信息!我不确定为什么会发生这种情况,或者它是否发生在全世界,或者不。
您能信任您的调制解调器制造商或他们的驱动程序开发人员吗?!虽然许多调制解调器支持来电显示,但它们的 .inf 文件中存在错误,这可能导致我们通过 TAPI 检测失败。您可以测试一下:
在 Windows 2k 和 XP 中,有一个调制解调器日志记录器,它将极大地帮助我们。
在 XP 中,转到 **控制面板** 并找到“**电话和调制解调器选项**”。打开它,转到中间选项卡(**调制解调器**),然后按“**属性**”按钮。在“**诊断**”选项卡中,选中“**附加到日志**”复选框,按“**查看日志**”。它将在记事本窗口中打开日志文件,删除所有内容并保存,关闭记事本,然后确定对话框。
现在使用一个软件监听线路。如果您现在不使用 COM 端口,Hyperterminal 将是一个不错的选择。然后致电。连接到您的计算机的号码呼叫后,再次按“查看日志”以查看日志中是否有任何电话号码。
如果您看不到您的号码,请尝试下面的第二个测试:
在 Windows 中打开 Hyper terminal,为连接命名,并在下一个 HyperTerminal 对话框中选择一个 **com 端口** 而不是您看到的调制解调器名称。如果您的调制解调器位于第三个 COM 端口,例如,选择 **COM3**,然后按 OK。现在使用另一条线路(也许是您的手机)呼叫连接到您计算机的线路。您应该在每次响铃之间看到“**Ring**”"**Ring**”...如果任何数据通过该端口传输,您就会看到它。如果您什么都没看到,那么您的调制解调器可能不支持来电显示。如果可能,请致电制造商。
这些测试向我们展示了什么?
很多时候,您的调制解调器驱动程序附带的 **.inf** 文件中存在错误,您可以解决它!并阻止您收到真正的来电显示。如果您的调制解调器检测到来电显示,那么您应该能够在监听端口时或在调制解调器记录它时看到它。如果您在那里看到号码,但无法通过您的应用程序获取它,问题就在于 .**inf** 文件。如何?在 FSK 格式中,一个字符串出现在端口的数字之前。如果调制解调器检测到字符串,TSP 将通知 TAPI,您将收到真实消息;如果不是,您将丢失数据,但任何直接监听端口的应用程序都会收到它!(例如 Hyper Terminal)。字符串是“NMBR = ”,而在许多 .inf 文件中则写为“NMBR=”。一些空格现在非常有价值!您可以更正 .inf 文件并重新安装调制解调器驱动程序。感谢描述此问题的人。(不过这不是我的问题!)
如果所有测试都无法让您获得号码,请尽快致电制造商,或退回调制解调器,或更换它(我做了很多次!!但制造商没有回复,我损失了一些钱来测试调制解调器 )。
更多信息:
http://www.talkingcallerid.com/ModemDriver.htm
http://www.caller-id-answers.info/index.htm
http://www.ainslie.org.uk/callerid/cli_faq.htm
http://www.repairfaq.org/filipg/LINK/F_CallerID.html
http://www.exceletel.com/support/hardware/VoiceModems/
使用代码
终于到这里了。我提供什么?
我这里提供的是一个模板(不是 C++ 模板!)类似的示例。该示例执行以下操作:
- 查找合适的硬件(如果存在的话!)。- 初始化、打开并监听与应答机相关的消息。
- 检测来电显示和 DTMF 并通知您。
要使用此示例,您需要将以下类添加到您的项目中:
1 - 添加 TapiObj.cpp, TapiObj.h。这将把 CLine、CPhone 和 CTapiObj 类附加到您的项目中。
2 - 添加 HSound.cpp, HSound.h。这将添加声音类(HSound)。
3 - 添加辅助类:HErrLogger, HDevices, HPlayList, HSettings。您可以自己完成或更改这些类。这些类确实不完整,特别是 HErrLogger。您可以编写自己的版本或修改这些类,或从代码中删除它们!
4 - 最后添加并修改核心类:HCentralManager。您只需要编辑 CentralManager 并在主消息处理器函数 ProcessMessage(...) 中调用您自己的函数。
您可以看到,这就是为什么我说它像一个模板,您需要将您的代码放在里面。如前所述,我创建这个示例不是为了不理解就使用(那样的话,我可以创建一个 ActiveX 控件)。但我认为 CodeProject 不是为此类目的而设的。更重要的是,该示例使用了一种特殊的架构,我认为它使 TAPI 的工作变得更容易、更快捷,但(我认为)它不是一个好的面向对象方法。看看这张图片:
为什么为声音、线路和主处理器使用单独的线程?原因很清楚,我不想占用 LineEvent 的操作,因为可能有更重要的消息到来,比如 Disconnect,它甚至需要在播放完播放列表之前进行处理。所以,在接收到新消息时,我将消息发布到另一个线程,主 TAPI 消息循环会立即继续,准备处理新消息:)
组件如何汇集以形成应用程序:
当您在主对话框中按下开始按钮时,一个名为 `m_manager` 的 `HCentralManager` 类型的对象将通过将其父项设置为 `this` 来初始化。然后,我们要求它 `WakeUp`。这就是运行“老大哥”所需的一切!!
// 初始化并启动 CentralManager m_manager.SetParent(this); m_manager.WakeUp();现在 m_manager 中发生了什么:
`WakeUP` 成员函数启动了 3 个负责 `CHLine`、`CHSound` 和管理器 `CHCentralManager` 之间消息通信的线程。然后我们要求线路和电话 `Start`。还记得我之前描述的线路吗?考虑 Line Object,它是真实硬件线路的一个抽象。我所要做的就是初始化和打开一条真实的硬件线路。
就是这样!现在线路已打开并准备好供用户呼叫,然后核心类 `CHLine` 将检测线路的新状态并将适当的消息发送给我们。
/* 唤醒消息线程 */ m_pThread = AfxBeginThread( MessageThread, (LPVOID)this, THREAD_PRIORITY_NORMAL,0,NULL,0); ... /* 启动线路操作 */ m_line.Start(); /* 启动电话设备 */ m_phone.Start();让我们来看看中心管理器的主要函数,它是一个所有消息的站点:`ProcessMessage` 函数。
void CHCentralManager::ProcessMessage(MSG msg) { switch (msg.message) { case WM_DTMF: /* 用户按下的新 DTMF 码 */ if (m_pWParent) m_pWParent->PostMessage( WM_STATUS_CHNGD,msg.lParam,ST_DTMF); break; /* 播放列表完成,我们必须终止会话 */ case WM_SOUND_DONE: break; /* 线路状态已更改,执行适当的操作。 */ case WM_STATUS_CHNGD: switch (msg.lParam) { case ST_INIT: break; case ST_OPEN: break; case ST_RESTART: break; case ST_READY: break; case ST_BUSY: break; case ST_SHUTDN: break; case ST_RING: break; case ST_PICKED_UP: break; case ST_FAILED: break; } break; /* 如果收到了用户的来电显示 */ case WM_CALLERID: g_phoneNumber = m_line.GetCallerID(); /* 通知父窗口 */ m_pWParent->PostMessage(WM_STATUS_CHNGD,0,ST_CALLER_ID); break; default : break; } }这在实际代码中有一些细微的修改。在转到代码之前,请阅读以下部分。
已知问题
- 存在内存泄漏!(不多)。我认为它不经常发生。我不知道是因为我太忙、太懒还是太累而把它留下了!我可能在以后的上传中解决这个问题。- 我是初学者,所以这个示例有很多 bug 是很正常的。我需要 MVP 的反馈来找出我做错了什么!我希望有人来帮助我改进。
- 我仍然存在 100% 正确退出 1 的问题(我不确定我是否正确处理了所有消息)。我会尝试做得更好,但与专业人士一起会更快、更准确地完成。
- 多次缓冲在某些调制解调器上使用 8 位文件时存在问题。有一个简单的解决方案,请阅读下面的故障排除。
故障排除
如果您无法运行示例应用程序,您应该首先确保您的调制解调器是语音调制解调器,并且已安装了正确的驱动程序。您可以转到设备管理器,查看是否安装了 unimodem 声卡设备。
您可能需要查找错误和问题。大多数 TAPI 错误将(不完整的应用程序部分)记录到一个文本文件 'errors.log' 中,检查是否有任何内容记录在那里。
我使用 16 位 PCM 文件,请检查您的调制解调器是否支持它,否则尝试 8 位并将在 HSound 的开头行设置缓冲区大小大于您的最大文件。(我的缓冲在某些调制解调器上使用 8 位文件时存在一些问题)。
在上述之后,如果还有任何问题,您需要调试应用程序。抱歉,工作量很大,我太忙了,无法完成错误报告。
一些故事和建议!!
5 个月前,我收到一个项目的请求,我以前从未做过!一个应用程序需要接听商店客户的电话并向他们推销东西。该应用程序需要根据客户来电时收到的来电显示来识别客户。这家商店有一个很棒的数据库,其中包含在运行 MS SQL Server 2000 的服务器上的号码和地址。我需要通过调用存储过程来验证用户。我接受了这个项目(一次很棒的经历!),但遇到的问题让我把这部分放在这里:
对于那些以前没有写过此类应用程序的人:
- **尽可能多地申请时间**。不要认为这是一个小系统!我的项目,一个非常简单的电话应用程序,至少有 9000 行源代码(仅仅是一个简单的应答机),花了我 3 个月的时间学习和编码(加上通过我的大学期末考试!)。
- **检查您是否能找到好的硬件**。如果您住在一个无法去商店询问具有某些技术能力的调制解调器的地方,或者您居住的地方充斥着假硬件(就像我住的地方一样),永远永远不要考虑编写这样的应用程序!我推荐 V92 调制解调器,它们的问题较少。
-**如果您必须使用来电显示**,那么请阅读本文的来电显示部分,然后去当地的电话公司与他们的技术支持部门联系。请记住(据我所知)没有调制解调器能够找到所有类型的来电显示,因此您应该找到一个与您所在地区兼容的调制解调器(有时这真的很难)。
-**找到“蓝色渡轮”!**如果您身边有可以请教的人,请不要浪费时间!Microsoft 在 TAPI 和新闻组中有 MVP 可以帮助您。请参阅文章底部的链接。
- **在确定您需要什么之前,请勿购买调制解调器**。许多调制解调器支持语音和 DTMF,但您不能在一台机器上放置超过 1 个!因此,请检查您是否需要支持多线路、来电显示、DTMF、语音、电话选项(如果您需要切换听筒/耳机/扬声器和应用程序)。
-**有比调制解调器和 TAPI 更好的选择**,例如 Dialogic 板卡。尝试查找有关它们的信息,并确保这些硬件非常昂贵且编程要容易得多!制造商通常有自己的 API 集。如果您要构建一个多线路应用程序,我首先推荐它们!
我在网上发现,超过 70% 的电话系统在 Windows 平台上无法正常工作。我不确定这是否真的属实!我也不知道他们使用什么系统,也许是 Linux,或者……。
我的美好愿望
- 与一个团队合作,即使只有两个人,也是一个梦想!!我曾经有过这样的经历,但我的同事不得不搬到另一个城市:(您绝对无法相信我向上帝祈祷了多少次,希望再次有机会与一个团队合作。网络上的链接和帮助:
以下是我查找信息并撰写本文的 artículos、示例文件和应用程序列表。www.microsoft.com/msj/0498/tapi.aspx 一个使用 TAPI 2.1 的应答机。
https://codeproject.org.cn/internet/TAPISample.asp 对我来说一个很好的起点。非常感谢 'T.YogaRamanan'
还有一个 TAPI 3.x 版本的 MSJ 文章和示例:www.microsoft.com/msj/1198/tapi3/tapi3.aspx
http://www.i-b-a-m.de/Andreas_Marschall's_TAPI_and_TSPI_FAQ.htm 我认为这是最好的 FAQ!他还在新闻组 Microsoft.public.win32.programmer.tapi 中回答您的问题。
www.julmar.com/ 及其非常出色的示例和库。
www.allen-martin-inc.com/amtapitelephony.htm
www.rainyjay.com/tapi/tapi.htm
www.caller-id-answers.info/index.htm
http://project.uet.itgo.com/sapi.htm
www.exceletel.com/support/hardware/VoiceModems
还有很多其他的。非常感谢他们的所有帮助。