推荐一本好书:〈Dialogic从入门到系统工程师〉,想要的进来瞧瞧,有节选的(1分)

快乐

Unregistered / Unconfirmed
GUEST, unregistred user!
书名:〈Dialogic从入门到系统工程师〉
页码:700页
定价:270元
内容简介:
从CTI(Computer Telephony Integration 计算机语音集成)概念和产品进入中国以来,已经是好几年的时间了,在此期间,技术发展日新月异,在CTI领域出现了许许多多的简单的、复杂的、各种各样的应用系统,比如交互式语音应答(IVR)、呼叫中心(Call Center)、统一消息(Unify Message)、语音邮件(Voice Mail)等等。随着目标市场的明确和市场的细化,CTI领域的产品也在逐渐的进行功能细分。按照目前市场的应用来看,可以归类的CTI应用就是好几百种之多。可是非常遗憾的是,这些CTI产品的核心技术绝大多数来自于国外厂商,比如Dialogic、NMS等等。而且更遗憾的是,虽然CTI行业如此的发展迅猛,在国内居然看不到一本好的基于实际工程应用的书籍,来专门针对这一技术进行由浅入深的介绍和教学。目前,越来越多的公司和CTI爱好者介入到这一行业,但是面对一眼望不尽的英文资料,又有几个能象使用自己的母语一样来轻松的阅读这些英文资料呢?况且,由于CTI行业是一个新兴的行业,它基于电信技术和计算机技术,并在两者的基础上进行高层次的融合,因此难免会有越来越多的自己的专有名词,很多初学者面对这些全是专有名词的英文资料时,根本就不知道作何解释,又谈何能够快速的理解和应用呢?
Dialogic公司是一个极富盛名的Intel公司的子公司,谈到CTI就几乎想起了Dialogic,好象Dialogic就是CTI的代名词,确实不假,他们是CTI行业的倡导者和领导者,最早为该行业提供全面解决方案的设备供应商(EP),Dialogic的产品系列几乎覆盖了CTI的所有可能的应用并在不断快速的发展之中,他们提供的系列CTI技术核心产品,比如各种模拟卡、数字卡、DM3产品、IP技术、7号信令、传真等等软件和硬件系统,越来越紧密的结合各种CTI应用并为独立软件开发商(ISV)和原始设备供应商(OEMs)等开发和集成应用系统提供了创新的能力和无限的应用开发空间。因此,从Dialogic的模拟产品入手,逐步的我们将很快地从一个非通信专业学员成为一个专业CTI技术工程师。
作者使用Dialogic的产品已经是将近5年的时间了,回想当初才开始学习的时候,拿起Dialogic英文帮助手册,第一个感觉就是:不知其然也不知其所以然!不知从什么开始入手,不知道如何开始编写一个程序,不知道应用程序的框架应该是什么样子的。面对如此分类繁多的Dialogic英文帮助手册上的概念和专业术语,多么希望有一本浅显的、指导性的中文书籍,能够针对非通信专业的编程人员,全面的、渐进而系统的讲解这一复杂系统的程序设计和相关知识。
基于以上的原因,作者酝酿了很久,非常希望能够以通俗易懂的语言和浅显的文字,以实际工程经验为依据,由浅入深的对这一新兴的技术进行系统的描述,将它全面地展示给大家,展示给越来越多的CTI爱好者和CTI公司技术人员。
这本书正是为了实现这样的目标而编写的,作者在编写此书的过程中,历时许久,为了能同时照顾到入门读者和有一定基础的读者,每个章节的内容安排和难度阶梯都经过了仔细的设计和无数次的推翻修改,编写过程诸多困难,并参阅了大量通信技术资料和Dialogic产品技术资料,力图以最贴切的语言从电信通信行业的概念、术语以及Dialogic模拟卡产品编程开始,一步一步实现“24小时精通Dialogic”成为CTI专业通讯技术人才的目标!
通过本书及其他“24小时精通Dialogic”系列书籍,应该能够很快在Dialogic系列卡和软件架构上做随心所欲的系统设计和代码开发。且不敢说用这些书可以推动CTI在国内的发展和普及,也不敢说这些书能够让大家从纯技术的角度看到国外厂商所设计系统的优越性和灵活性;我只希望这些书籍能够给CTI行业的同仁们以及即将进入CTI行业的公司和个人爱好者提供一些必须的帮助。但是这样的意愿能否实现,还需要读者来评判!
同时,由于作者水平有限,难免有疏漏之处,希望大家多提意见,以便在修订时加以改正!

目 录:
序言 16
写作本书的原因 16
目标读者 17
作者编写的其他书籍 17
第一章 CTI通信基础知识 18
1.1 概述 18
1.2 电信技术概念和术语 19
1.2.1 模拟信号和数字信号 19
1.1.2 模拟线路和数字线路 22
1.1.3信令的基本概念 22
1.1.4 信令的分类 24
1.1.4 脉冲拨号和DTMF拨号 25
1.2 计算机语音技术相关标准 26
第二章 常用名词解释 28
2.1 CTI 28
2.2 呼叫处理 28
2.3呼叫处理的功能 29
2.3.1语音的存储转发(Store &
Forward) 29
2.3.2识别键盘上的数字 29
2.3.3文语转换(Text-to-Speech) 29
2.3.4语音识别(Voice Recognition) 30
2.3.5电话呼出 30
2.3.6传真的存储与转发(Fax Store and Forward) 30
2.3.7传真合成 31
2.4呼叫处理的主要应用 31
2.4.1自动总机(Automated attendant) 31
2.4.2语音信箱(Voice mail) 31
2.4.3消息传递(Message delivery) 32
2.4.4可闻文本(Audiotex) 32
2.4.5按呼叫次数付费(pay-per-call) 32
2.4.6自动订购系统(Automated order entry) 33
2.4.7呼叫中心(Call center) 33
2.5通信基础术语 33
2.5.1电话线 33
2.5.2拨号音 34
2.5.3电话呼入 34
2.5.4拨号 34
2.5.5呼叫接续 35
2.5.6拍叉簧 35
2.5.7拆线 35
2.6语音卡工作原理 36
2.6.1计算机总线连接 36
2.6.2电话线接口 36
2.6.3语音总线 37
2.6.4操作系统支持 37
2.6.5电脑语音功能 38
2.6.6语音处理功能 39
2.6.7数字化方法 39
2.6.8采样速率 39
2.6.9每样值编码比特数 40
2.6.10自动增益控制(AGC) 40
2.6.11静音压缩SCR 40
2.6.12"特殊效果"处理 40
2.6.13信号音检测 40
2.6.14 DTMF/MF/脉冲拨号是什么 40
2.6.15 Fax集成 43
2.7硬件原理类概念 43
2.7.1处理器 43
2.7.2语音卡与PC机的接口 44
2.7.3 语音卡与电话网的接口 45
2.7.4资源总线 45
2.7.5固件原理 45
2.8 CTI应用程序接口API 46
2.8.1当前流行的API 46
2.8.2 TAPI 47
2.8.3 TSAPI 48
2.8.4 JTAPI 48
2.8.5 S.100 49
2.8.6 各种API分析比较 49
2.9 其它术语 50
2.9.1 PCM 50
2.9.2 ADPCM 50
2.9.3 AGC 51
2.9.4 CLASS 51
2.9.5 CO 51
2.9.6 DSP 51
2.9.7 NGN 52
2.9.8 ACD 52
第三章 数字通信技术基础 52
3.1 数字通信概述 52
3.2 什么是数字中继 54
3.3 E1 帧结构 56
3.4 中国1号数字线路信令 57
3.4.1 E1线路数据的编码格式 57
3.4.2 中国1号信令线路编码含义 58
3.4.3 局间信令 62
3.5 局间多频记发器信号方式 69
3.5.1记发器信号的功能、特点与要求 69
3.5.2 MFP和MFC 69
3.5.3 R2MF信令 71
3.5.4 记发器信号的传送方式 73
3.5.5多频记发器信号的种类及基本含义 75
3.5.6局间记发器信号的发送顺序 83
3.6 SS7信令系统简介 90
3.6.1 SS7信令协议栈 90
3.6.2 ISUP呼叫建立示例 93
3.6.3 800业务的TCAP消息路由示例 97
3.6.4各种信令点用到的SS7协议层 98
第四章 Dialogic基础知识 99
4.1 Dialogic公司介绍 99
4.2 Dialogic产品术语解释 100
4.3 Dialogic产品体系结构简介 100
4.4 Dialogic产品分类 101
4.4.1 模拟产品 103
4.5 Dialogic模拟卡编程相关概念和术语 103
4.6 安装Dialogic系统的软件和硬件 105
4.6.1 硬件需求 105
4.6.2 软件需求 105
4.6.3 安装Dialogic模拟卡 106
4.6.4 配置DCM(Dialogic配置管理) 106
4.7 什么是主叫识别 111
4.7.1主叫识别的功能和业务基本要求 111
4.7.2 主叫识别信息数据传送协议 111
4.7.3 数据的定义 112
4.8 Dialogic SDK开发和运行环境 113
第五章 Dialogic系列语音卡简介 115
5.1 低密度模拟语音卡系列 115
5.1.1 PROLINE/2V 2线多功能模拟语音卡 115
5.1.2 Dialogic/4和D/4PCI 半长型4线模拟语音卡 116
5.1.3 D/41H 4线模拟语音卡 118
5.1.4 D/41ESC和D/41EPCI 4线SC总线模拟语音卡 119
5.1.5 VFX/40ESC和VFX/PCI 4线模拟传真语音卡 121
5.1.6 D/160SC-LS 16线SC总线模拟语音卡 123
5.2 高密度数字语音卡系列 126
5.2.1 D/300SC-E1和D/300PCI-E1 30线数字中继语音卡 126
5.2.2 D/600SC-2E1 60线数字中继语音卡 128
5.3 DM3高密度平台系列 131
5.3.1 DM3 IPLink DM3高密度标准化IP语音平台 131
5.3.2 DM3高密度平台PCI cPCI 134
5.3.3 DM/F200及DM/F300传真资源卡 135
5.3.4 PCI QuadSpan系列 136
5.4 座席卡系列 138
5.4.1 MSI/SC-G座席卡 138
5.5 会议卡系列 140
5.5.1 DCB/SC基于DSP的会议卡 140
5.6 高密度ISDN BRI接口卡 142
5.6.1 BRI/80SC和BRI/160SC高密度ISDN BRI接口卡 142
5.7 过机卡 144
5.7.1 SCx过机卡 144
5.7.2 ATM服务器适配卡 145
5.8 GammaLink传真系列 146
5.8.1 GammaFax传真系列卡 146
5.9 企业传真卡系列 148
5.9.1 Cpi/3000CT-E1 30端口高密度传真卡 148
5.10 PBX集成平台 150
5.10.1 D/82JCT-U PBX集成平台(支持语音识别) 150
5.11 资源卡系列 152
5.11.1 SingleSpan-JCT/DualSpan-JCT JCT语音处理卡 152
5.11.2 D/41JCT-LS 4端口PCI模拟语音卡(支持语音识别) 155
5.11.3 D/120JCT-LS 12端口PCI模拟语音卡(支持语音识别) 158
5.12 Apollo调制高密度解调器 161
5.13 SS7信令解决方案 162
5.13.1 Dialogic 7号信令方案 162
5.13.2 PCCS6-2E1 7号信令卡 164
5.13.3 DSC131单信令7号信令网关 165
5.13.4 DSC231 备份信令7号信令网关 166
5.14 软件产品 168
5.14.1 CT-Connect 呼叫控制软件 168
5.14.2 Dialogic CT Media开放平台 169
5.14.3 CSTA Switch Simulator 170
5.14.4 BoardWatch 171
5.14.5 Dialogic SDK 172
第六章 Dialogic模拟卡程序设计 174
6.1 实现一个最简单的模拟卡程序 174
6.1.1 准备开发环境 174
6.1.2 打开Dialogic设备 175
6.1.3 等待电话呼叫 178
6.1.4 摘机应答 178
6.1.5 播放语音文件 179
6.1.6 挂机 180
6.1.7 关闭Dialogic设备 180
6.1.8 退出程序 180
6.1.9 编译和运行 180
6.1.10 提出问题 182
6.2 使用多线程 183
6.2.1 建立一个工作者线程(Worker Thread) 183
6.2.2 修改主函数 184
6.2.3 编译并运行 185
6.2.4 提出问题 186
6.3 使用异步模式 188
6.3.1 使用Dialogic事件通知机制 188
6.3.2 实现状态机 194
6.3.3 编译并运行 203
6.3.4 提出问题 213
6.4 总结 220
6.4.1 Dialogic软件系统架构及开发接口 220
第七章 了解Dialogic SRL 223
7.1 SRL术语表 223
7.2 SRL里有什么 223
7.3 静态连接 225
7.4 运行时链接 225
7.5 SRL事件管理 227
7.6 SRL用户数据管理 228
7.7 标准属性函数 232
7.8 I/O函数终止条件 233
第八章 SRL基本概念及基本编程模式 237
8.1 Dialogic SRL设备概念及基本编程模式 237
8.2 同步编程模式设计及实现 238
8.2.1 呼入应用系统编程框架 238
8.2.2 呼出应用程序编程框架 243
8.3 异步编程模式设计及实现 244
8.3.1 设计一个健壮的有限状态机及驱动函数 245
8.3.2 呼入应用系统的异步编程模型 251
8.3.3 呼出应用系统的异步编程模型 252
8.4 使用SRL扩展异步编程模式 253
8.5 基本SRL编程模式总结及辨析 256
8.5.1 SRL三种基本编程模式的特征 256
8.5.2 SRL三种基本编程模式的优点和弊端 256
8.5.3 SRL三种基本编程模式的选择标准 258
第九章 SRL高级概念及高级编程模式 260
9.1 SRL事件句柄定义及调用机制 260
9.1.1 回调函数定义 260
9.1.2 SRL回调机制 262
9.1.3 使用SRL级句柄服务线程 263
9.1.4 使用应用程序级句柄服务线程 264
9.2 SRL回调同步模式 264
9.2.1 什么是SRL回调同步模式 264
9.2.2 SRL回调同步代码例子 265
9.3 使用SRL回调异步模式 275
9.3.1 什么是SRL回调异步模式 275
9.3.2 SRL回调异步程序例子 276
9.4 使用Windows回调异步模式 281
9.4.1 什么是Windows回调异步模式 281
9.4.2 Windows回调异步程序例子 282
9.5 使用Win32同步机制回调异步模式 292
9.5.1什么是Win32同步机制异步模式 292
9.5.2 Win32同步异步模式例子 293
9.6 SRL高级编程模式总结及辨析 299
9.6.1 各种SRL高级编程模式的优点和弊端 299
9.6.2 高级SRL编程模式选择标准 301
第十章 Dialogic SRL API详解 303
10.1 SRL事件管理函数 303
10.1.1 事件处理函数 303
10.1.2 事件数据取回函数 304
10.1.3 SRL参数函数 304
10.2 标准属性函数 305
10.3 SRL事件管理函数及标准属性函数参考 305
sr_dishdlr() 禁止事件处理句柄 305
sr_enbhdlr() 启用事件句柄函数 308
sr_getboardcnt() 取回指定类型的卡数目 312
sr_GetDllVersion() 取回SRL DLL的版本号 314
sr_getevtdatap() 取回事件关联数据的内存地址 316
sr_getevtdev() 取回发生事件的设备 318
sr_getevtlen() 取回事件关联数据的字节长度 320
sr_getevttype() 取得当前事件的类型 322
sr_getparm() 取回SRL参数 324
sr_libinit() 初始化SRL DLL 326
sr_NotifyEvt() 将事件通知发送到Windows窗口 328
sr_putevt() 将一个事件送到SRL事件队列 332
sr_setparm() 设置SRL参数 334
sr_waitevt() 等待事件发生 340
sr_waitevtEx() 在某些设备上等待事件 343
第十一章 Dialogic语音功能概述 347
11.1 语音资源功能分类 348
11.2 PBXpert功能 349
11.3.2全局信号音产生GTG 350
11.3.3韵律信号音生成CTG 350
11.3.4全局拨号脉冲检测GDPD 352
11.3.5 R2MF信令 353
11.4 放音和录音功能 353
11.4.1 改变录音提示音长度 353
11.4.2 速度和音量控制 353
11.4.3 事务录音 354
11.4.4 WAVE文件偏移量放音 359
11.4.5 压缩式录音 359
11.4.6 回声消除资源 359
11.4.7 GSM和G.726语音编码 360
11.5 来电显示Caller ID 360
11.6 SC总线路由 360
11.7 Syntellect自动总机 360
11.8 模拟显示服务接口ADSI 361
第十二章 Dialogic数字卡编程 363
12.1 概述 363
12.2 D/300SC-E1卡的资源 364
12.2.1 资源分类及管理函数 364
12.2.2 资源的命名 364
12.3 编写一个处理呼入的D/300SC-E1程序 368
12.3.1 准备编程环境 369
12.3.2 打开语音处理资源设备 370
12.3.3 打开数字时隙设备 370
12.3.4执行SC总线连接 373
12.3.5 建立前向信令 375
12.3.6 设置事件掩码 378
12.3.7 在数字通道执行挂机操作 382
12.3.8 等待数字线路电话呼叫 383
12.3.9 MFC接续 384
12.3.9 摘机应答 394
12.3.10 捕获数字挂机事件 394
12.3.11播放语音文件说“你好,World!” 396
12.3.12 挂机 397
12.3.13 断开语音处理资源和数字时隙资源的连接 398
12.3.14 关闭语音处理设备 398
12.3.15 关闭数字时隙设备 399
12.3.16退出系统 399
12.4 同步模式D/300SC-E1卡呼入程序 399
12.4.1源代码 399
12.4.2 编译并运行 416
12.5 异步模式D/300SC-E1卡呼入程序 416
12.5.1 源代码 416
12.6 编写D/300SC-E1卡呼出程序 436
12.6 .1概述 436
12.6.2 建立前向R2MF信号音模板 437
12.6.3 初始化R2MF播放终止条件表 438
12.6.4 打开语音资源和数字时隙资源 440
12.6.5 执行SC总线连接 440
12.6.6 使通道可以检测到后向R2MF 440
12.6.7 使通道可以检测到挂机事件 441
12.6.8 初始化数字时隙挂机 442
12.6.9 建立前向信令播放函数 442
12.6.10 外拨呼叫 443
12.6.11 播放提示语 447
12.6.12 挂机 447
11.6.13 退出系统 448
12.7 同步模式D/300SC-E1呼出程序 448
12.7.1 源代码 448
12.7.2 编译并运行 467
12.8 异步模式D/300SC-E1卡呼出程序 467
12.8.1 源代码 467
12.8 总结 494
第十三章 Dialogic传真卡编程 494
13.1 概述 494
13.2 Dialogic支持传真的硬件 494
13.3 传真通信原理 495
13.3.1 传真技术术语 495
13.3.2 传真过程状态 496
13.3.3 传真传输模式 497
13.3.4 文件存储格式 498
13.3.5 传真数据编码方式 499
13.3.6 错误恢复模式(ECM) 500
13.3.7 图象放缩 501
13.3.7 图象分辨率 501
13.3.8 子址传真路由 501
13.4 如何发送传真 502
13.4.1 传真发送步骤 502
13.4.2 打开传真资源 502
13.4.3 设置传真卡初始化状态 503
13.4.4 配置传真设备通道 503
13.4.5建立DF_IOTT或DF_ASCIIDATA结构 504
13.4.6 发送或接收传真 504
13.5 DF_IOTT结构解析 504
13.4 传真发送步骤 504
13.5 传真接收步骤 505
13.6 使用VFX/40SC来处理传真 505
13.6.1 示范程序 505
13.7使用D/300SC-E1 + VFX/40SC来处理传真 514
13.7.1 示范程序 514
第十四章 Dialogic坐席卡编程 530
14.1 概述 530
14.2 如何为坐席卡编程 532
14.2.1需要包含的头文件 532
14.2.2 初始化 532
14.2.3 设置硬件配置 532
14.2.4 设置MSI卡事件掩码 533
14.2.5 初始化MSI通道 533
14.2.5 MSI和SRL 533
14.2.6 修改默认MSI卡设置 534
第十五章 SC总线 536
15.1 SC总线概述 536
第十六章 Dialogic呼叫分析 536
16.1 呼叫分析 536
16.1.1 Dialogic呼叫分析实现原理 537
16.1.2 编程实现PerfectCall呼叫分析 540
16.1.3 改变PerfectCall默认音频定义 543
16.2 DX_CAP结构详解 549
16.2.1 DX_CAP控制呼叫分析的原理 552
16.3 Caller ID 587
16.3 全局拨号脉冲检测(GDPD) 591
第十七章 SRL&Voice数据结构 591
第十八章 使用TAPI和TSAPI 591
18.1 了解TAPI和TSAPI 591
18.1 使用TAPI编程 595
18.2 使用TSAPI编程 595
第十九章 开发过程FAQs 595
19.1 如何向座席发送按键数字? 595
19.2 为什么dx_playtone()播放不停止? 595
19.3 为什么I/O函数检测不到DM_LCOFF? 596
19.3 如何检测对方挂机 596
19.4 为什么D/41H和Dialogic/4不能使用GlobalCall? 596
19.5 为什么SCO UNIX 5.0.5上使用SC总线函数失败? 597
19.5 在WIN9X 下,为何D/41EPCI不能使用? 597
19.6 用D/160SC-LS卡开发时,如何检测有无拨号音? 597
19.7 在WINNT4.0+SP5+DNA3.3环境下,发现ISDN线路不能外拨? 598
19.8在使用D/41EPCI For Unix(5.0.5)时,启动不了为何? 598
19.9一台机器上装了Win98,但在启动Dialogic/4时,发现总是不能启动? 599
19.10在一台机器中,如欲安装VFX/40ESC+与Dialogic/4,该如何配置? 599
19.11 不定时的ctcTimeOut错误是什么原因? 599
19.11 座席卡的会议资源有多少? 600
19.11 座席卡如何外拨? 600
19.12在GFAX中,如何接收多页传真只生成一个文件? 600
19.13在SR5.0中,使用GDK-DEMO/GAMMA_XT,编译时总是出错? 601
19.14在一台机器中如何用BRI/2VFD、BRI/80SC进行对拨? 601
19.15交换机(华为08系列)上用DSS1协议,D/300SC-E1板卡上用什么协议? 601
19.16如何配置DM/IP3031A-E1-NIC+D/41ESC卡,并如何运行IPTGATE程序? 602
19.17 Dialogic中有哪些卡支持Barge_in功能 602
19.18CP6/SC为何不能接收传真? 602
19.19 CT Media 2.0 的最新动态? 603
19.20 IPLink卡目前的4种用法? 603
19.21 Unix下VFX/40ESC+卡速率是否可调? 604
19.23在ISDN中,如何得到来电? 604
19.24一片D/41ESC卡与DM3卡一起用过,但在该卡单独使用时,不能启动? 604
19.25在Dialogic卡中有没有发送FSK格式的卡? 604
19.26在NT4.0中文机器上安装Dialogic/4,发现所有中断、地址都不能使该卡启动? 605
19.27在一台机器上,装有一片D/41ESC,但在启动时报错? 605
19.28在中文NT4.0,Service Pack5,VFX40/PCI只能发,不能收? 605
19.29如何在Antares卡侦测DTMF和挂机? 605
19.31利用座席卡实现电话会议功能时,如何让旁听用户可以向主持人发出请求? 606
19.32如何在CT-MEDIA使用D/41ESC? 606
19.33 D/41ESC与Antares卡共用时,运行demo,出错 606
19.34 D/41ESC卡在编程中,接通后,接收声音如声音很高,如何解决? 607
19.35如何在Unix上安装1.05e? 607
19.36 如何在SCO5.0上安装Release 2? 607
19.37使用传真卡,编写相应的fax_send功能时,编译已通过;但link出现问题,再发一行与多行都是一页。 608
19.38使用D/160SC-LS卡编程时,如欲将第一(口)线进来的电话,让其转拨第二(口)线上,在实现的时候发现没有声音。 608
19.39用汇编发出DTMF数据,使用Dialogic/4中的dx_getdig()时,有时会有丢码现象。 609
19.40在D/41ESC中,先打开两条通道,一条用于接收、一条用于外拨,两条线路通了后连接、放音、接收按钮数字,并将其数字作为电话号码拨出,第一次能行,第二次就不行,显示状态为忙线。 609
19.41在使用Dialogic/4进行程序开发时,对方如果是忙(音)线,可用何函数进行测试,检测线路状态的函数是哪些。 610
19.42 D/41ESC能否检测摘挂机? 613
19.43 Dialogic/4的安装有问题? 613
19.44Dialogic卡在UNIX下的安装? 613
19.45在PⅡ333工控机中,当我们装五块Dialogic/4卡时,第一块卡总不能启动。 614
19.46语音卡录制声音文件后原来是使用该语音卡进行放音,现在如欲通过声卡放音,应用哪种工具完成以上任务。 614
19.46两块D/41D在OS:Dos,driver:dos-e.2下,如何安装? 614
19.47两块DTI/301SC,在安装时发现driver不能注册。 615
19.48 PROLINE/2V卡,如何通过line in端口,把声音录入。 615
第二十章 呼叫中心简介 616
20.1呼叫中心定义 616
20.2呼叫中心的作用 616
20.3 CTI与呼叫中心 617
20.4 呼叫中心的分类 618
20.5 呼叫中心应用的主要技术 619
20.5.1 交换机和排队机 619
20.5.2 CTI服务器 620
20.5.3 交互式语音应答IVR 621
20.6 呼叫中心的解决方案 621
第二十一章 Dialogic SS7信令方案简介 622
21.1 SS7简介 622
21.1.2 MTP: 消息传递部分 623
21.1.3 TUP:电话用户部分 623
21.1.4 ISUP: ISDN用户部分 623
21.1.5 SCCP:信令连接控制部分 623
21.1.6 TCAP: 事务处理能力应用部分 623
21.2 Dialogic七号信令产品 624
21.2.1概述 624
21.2.2七号信令网关产品 624
21.2.3.配置 625
21.2.4软件开发包 626
21.2.5应用举例 626
简单的呼叫连接 627
智能网应用 627
短消息业务(SMS) 628
第二十二章Dialogic SIU参考 629
22.1概述 629
22.1.1 硬件 629
22.1.2 信令 630
22.1.3 应用软件 630
22.2系统结构 631
22.3指标 632
22.3.1 电源供电 632
22.3.2 信令接口 633
22.3.3 串口 633
22.3.4 告警输出 634
22.3.5 可视化显示 634
22.4 物理指标 634
22.5 信令能力 634
22.6 操作 635
22.6.1 概述 635
22.6.2 信令分配 635
22.6.3 通道连接 637
22.6.4 软件环境 639
22.6.5 SIU与主机应用之间的通信 639
22.7电话应用 640
22.7.1 多HOST的应用 640
22.7.2 单SIU操作 640
22.7.3 双SIU操作 641
22.7.4 呼叫控制接口 641
22.7.5 电路监测接口 642
22.8 TCAP应用 642
22.8.1 双机操作 643
22.8.2 TCAP应用接口 643
22.9协议配置 644
22.10 HOST软件 644
22.10.1 概述 645
22.10.2 初始化HOST 646
22.10.3 管理到SIU的TCP/IP连接 646
22.10.4 生成一个应用 646
22.10.5 运行一个应用程序 647
22.10.6 下载应用host 647
22.10.7 SIU管理接口 647
22.11 软件编程 647
22.11.1 概述 647
22.12基本概念 648
22.12.1 Modules 648
22.12.2 Module ID 649
22.12.3 Messages 649
22.12.4 Message Queues 650
22.12.5 库函数 650
22.13 支持的操作系统 651
22.14 用户部分模块 651
22.14.1 TUP模块主要功能 651
22.14.2 ISUP模块主要功能 652
22.14.3 TCAP模块主要功能 652
22.14.4 SCCP模块主要功能 653
22.14.5 MAP模块主要功能 654
22.15 配置文件例子 654
22.15.1 config.txt 654
22.15.2 system.txt 656
22.16技术规格 658
22.16.1 DSC131/DSC231 658
22.16.2 PCCS6 661
后记 663

摘录出来的一段:
6.3 使用异步模式
异步模式的特点是函数被调用后立即返回,应用程序线程继续执行,接收到系统发出的事件后并按照发生的事件选择流程分支。这相当于一个有限状态机,它使用输入事件来驱动状态进行到下一个节点。本小节需要实现三个主题:
3.3.1 使用Dialogic事件通知机制
3.3.2 实现状态机
3.3.2 编译并运行

6.3.1 使用Dialogic事件通知机制
本节将修改3.2的程序,使用Dialogic事件通知机制实现。这里需要用到几个重要的函数:
? sr_setparm()
? dx_setevtmsk()
? sr_waitevt()
? sr_getevtdev()
? sr_getevttype()
? sr_getevtdatap()
? sr_getevtlen()

函数解释:
sr_setparm() – 设置一个运行时库的参数,该运行时库叫做SRL,它有很多可配置的参数用来控制SRL的运行行为,是Dialogic产品程序设计里一个非常重要的概念。
sr_waitevt() – 用来等待发生在任意设备上事件的函数,该函数原型如下:
long sr_waitevt(int timeout) ;

该函数在由timeout指定的时间段类等待设备上事件的发生,如果时间过去了还没有事件发生,则函数超时返回。将此参数指定为-1将使函数永远等待直到事件发生。如果等待事件成功或发生错误,该函数返回。使用下面的函数去取回和事件相关的数据。

sr_getevtdev() – 取回发生事件的设备的Dialogic设备句柄,该函数在sr_waitevt()返回后立即调用来取回发生事件的设备。原型如下:
long sr_getevtdev(ehandle) ;
参数ehandle有两种选择,如果事件是被sr_waitevt()函数等到的,那么直接将此参数设置为0就可以;另外一种选择在以后的章节里描述。返回值是发生事件的Dialogic设备句柄。

sr_getevttype() – 该函数用来取回事件的类型。Dialogic预定义了很多事件类型。这些事件以常量的方式提供,全部以TDX_开始。比如表示播放完成的事件TDX_PLAY和表示呼叫状态变化的事件TDX_CST等等。这些常量定义在srllib.h文件里。该函数的原型定义如下:
long sr_getevttype(ehandle) ;

参数ehandle和函数sr_getevtdev()含义相同,这里不再阐述。执行成功函数就返回事件的类型,失败就返回-1。

sr_getevtdatap() – 取回和事件相关的数据块的指针。因为有的Dialogic事件是带附加数据块的,当此事件发生的时候,Dialogic会为该事件准备恰当的数据块。比如TDX_CST事件就带有数据块,该数据块由DX_CST定义。现在解释一下CST的含义:CST指Call Status Transition.它指的是在呼叫过程中的状态变化事件,比如摘机、挂机、电话呼入、检测到特殊信号音等都是CST事件,因为这些事件导致呼叫状态的变化发生。

sr_getevtlen() – 该函数用来取回事件附加数据的字节长度。
long sr_getevtlen(ehandle) ;

参数ehandle和函数sr_getevtdev()含义相同,这里不再阐述。执行成功函数就返回事件的类型,失败就返回-1。

现在开始修改3.2的代码,在3.2里,流程只被执行一次就退出了程序。现在,我们为其增加一个功能:可以循环处理(等待电话->播放语音->挂机)这个过程,直到用户在计算机键盘上按下一个键之后程序才退出,并为程序代码增加错误处理的能力。Main.cpp文件和主函数main()修改如下:

/*************************************************************************
* ?Copyright 2002. All rights reserved.
* Dialogic 异步模式模拟卡示例程序
* 支持D/41ESC、VFX/40ESC、LSI/81SC、LSI/161SC 和D/160SC-LS卡
* 使用Microsoft Visual C++ 5.0以上版本编译
* OS :Windows NT 4.0(SP6) 或 Windows 2000(Prefessional/Server/Advance Server)
**************************************************************************/
/*Microsoft Visual C++ 头文件*/
#include <stdio.h>
#include <process.h>
#include <string.h>
#include <conio.h>
#include <windows.h>
#include <winbase.h>

/*Dialogic头文件*/
#include <srllib.h> /*SRL函数头文件*/
#include <dxxxlib.h> /*Voice函数头文件*/
#include <sctools.h> /*执行SCBus总线连接的函数nr_scroute()需要的头文件*/

#define MAX_DEV 4 /*定义所使用的最大设备数量*/
int kb_hit = 0;
/*全局变量,用来标识键盘是否有按键按下,控制循环结束*/
DWORD WINAPI kbhit_thread(LPVOID lpArgs);
/*键盘按键监视线程*/

/*定义一个结构来存放和通道相关的信息,比如通道的句柄,通道的状态等*/
typedef struct chinfo_tag {
int dev;
/*通道的句柄*/
} CHINFO;

CHINFO chinfo[MAX_DEV];
/*4个通道的通道信息结构数组*/
/*
* 主函数main()。初始化数据和建立相关的线程并等待Dialogic设备事件发生
*/
int main(int argc,char** argv)
{
/*定义4个通道的名字*/
char dev_namep[4][] = {“dxxxB1C1”,”dxxxB1C2”,”dxxxB1X3”,”dxxxB1C4”};

HANDLE hdlKbMonitor = NULL;
/*键盘按键监视线程句柄*/
DWORD dwThreadID = 0;/*键盘按键监视线程ID*/

/*将SRL设置为单线程异步模式SR_STASYNC*/
int srlmode = SR_STASYNC;

if(sr_setparm(SRL_DEVICE,SR_MODELTYPE,&srlmode) == -1)
{
printf(“失败:调用sr_setparm()函数 : 错误码 # %ld.n”,ATDV_LASTERR(SRL_DEVICE));

return(0);

}

/*建立键盘事件监视线程*/
hdlKbMonitor = (HANDLE)_begin
threadex(NULL,
0,
kbhit_thread,
NULL,
0,
&dwThreadID);

if (hdlKbMonitor == (HANDLE) -1 )
{
printf("错误的建立键盘输入监视线程 – 退出程序n");

return(1);

}

/*打开和初始化MAX_DEV个Dialogic设备通道*/
for(int cnt = 0 ;
cnt < MAX_DEV ;
cnt ++)
{
/*打开Dialogic设备通道*/
chinfo[cnt].dev = dx_open(dev_namep[cnt],0);

if(chinfo[cnt].dev == -1)
{
/*打开失败,错误来自于操作系统,使用dx_fileerrno()取回发生的错误码*/
printf(“失败:调用dx_open():错误码 #%ld.n”,dx_fileerrno());

return (0);
/*退出系统*/
}
if(dx_setevtmsk(chinfo[cnt].dev,DM_RINGS)== -1)/*使通道可以检测到震铃事件*/
{
printf(“失败:调用dx_setevtmsk():错误码 #%ld.n”,
ATDV_LASTERR(chinfo[cnt].dev));

return (0);

}

/*将该通道的语音通道资源和模拟接口资源通道之间建立全双工连接*/
if(nr_scroute(chinfo[cnt].dev,
SC_VOX,
chinfo[cnt].dev,
SC_LSI,
SC_FULLDUP) == -1)
{
printf(“失败:调用nr_scroute():错误码 #%ld.n”,
ATDV_LASTERR(chinfo[cnt].dev));

return (0);

}

/*将通道初始化为挂机状态*/
if(dx_sethook(chinfo[cnt].dev,DX_ONHOOK,EV_SYNC) == -1)
{
printf(“失败:dx_sethook():错误码 #%ld.n”,
ATDV_LASTERR(chinfo[cnt].dev));

return (0);

}
}

/*循环处理()直到键盘按键被按下*/
int evt_dev,evt_type;
/*存放发生的事件的设备和发生的事件类型*/
while(kb_hit != 1) /*如果键盘按下,该标志将被设置为1,否则为0*/
{
if(sr_waitevt(-1) == -1) /*一直等待直到事件发生*/
{
printf(“失败:sr_waitevt():错误码 #%ld.n”,
ATDV_LASTERR(SRL_DEVICE));

}
evt_dev = sr_getevtdev(0);

evt_type = sr_getevttype(0);


/*调用状态机处理函数,在3.3.2节描述状态机的设计*/
if(process(evt_dev,evt_type) == -1)
break;

}

/*循环已经退出,现在关闭所有已经打开的设备*/
for(cnt = 0 ;
cnt < MAX_DEV ;
cnt ++)
{
dx_close(chinfo[cnt].dev);

}

return (1);

}
/*************************************************************************
* 名字:kbhit_thread()
* 描述:监视计算机键盘的按键
***************************************************************/
DWORD WINAPI kbhit_thread(LPVOID lpArgs)
{
getch();/*阻塞等待键盘按键*/
printf(“keyboard pressed.n”);

kb_hit = 1;
/*将键盘按下标志设置为1,表示已经检测到按键*/
return(0);

}
上面就完成了主程序部分的修改,并增加了一个键盘事件监视线程,用来监视键盘的按键,一旦某一个键被按下,标志kb_hit将被设置为1,主函数main()里的循环while检测到kb_hit等于1时,将结束循环,接着关闭已经打开的设备并退出程序。
键盘事件监视线程只简单的调用 C函数getch()来等待键盘按键,一旦键盘有按键,则该getch()函数将返回,接着kb_hit标志将被设置为1,之后键盘监视线程完成任务退出。
在主函数的while循环可以看到,sr_waitevt()函数被循环调用来等待发生在Dialogic设备上的事件。为该函数指定了参数-1表示将不返回直到等待到事件发生或发生错误。
在函数sr_waitevt()成功返回后,函数sr_getevtdev()被用来查询是哪个设备发生的事件;而函数sr_getevttype()函数用来查询了在这个设备上发生了什么事件。
在得到发生事件的设备句柄和发生的事件的类型后,一个叫做process()的函数被调用来处理这个设备上发生的事件。函数根据设备句柄和事件类型,按照程序预设计的逻辑执行适当的操作,这个函数我们暂且把它叫做状态机函数。为什么叫做状态机函数?因为它根据目前的状态和发生的事件驱动流程向下一步执行(通过调用适当的函数来实现状态向下一步转移)。以上部分已经很清楚了,下面用一个小节来描述状态机函数process()。
6.3.2 实现状态机
按照上面的计划和设计,状态机是按照通道当前的状态和发生的事件来判断下一步应该执行什么。其实,一个Dialogic模拟卡程序的事务逻辑就是被状态机函数实现的。
由于4个通道都执行同样的操作:等待电话->播放语音->挂机,并一直循环下去直到键盘按键发生。因此,不管是哪一个通道,我们可以这样考虑:
? 如果输入的是震铃事件,那么我们就摘机应答
? 如果是摘机完成事件,那我们就开始放音
? 如果是放音完成事件,那我们就挂机
? 如果是挂机完成事件,那我们就等待下一次呼叫
以上是一个循环的过程,也是一个事件选择的过程,在C++里,实现选择最好的语句是Switch语句。为什么摘机应答后要等待摘机完成事件才开始放音呢?因为我们使用的是异步函数,函数被调用时立即返回,但是并不代表已经执行完成了要求的功能,必须等待一个完成事件才能确定函数的执行已经完成了,可以进行下一步动作。
Dialogic对一般会发生的事件和函数的结束通知消息定义了一些常量,而这些消息中,有的消息又可以带附加数据来表示该事件的具体属性。比如,TDX_CST事件是一批呼叫状态变化事件的集合,他包含了震铃事件DE_RINGS、摘机完成事件DX_OFFHOOK、挂机完成事件DX_ONHOOK等等,更详细的信息请看以后的章节。那么当我们接收到TDX_CST事件之后,我们需要从该事件的附加数据中抽取事件的真实属性,是DE_RINGS、还是DX_ONHOOK、DX_OFFHOOK或别的事件。Dialogic定义的以下事件是本程序中需要用到的:
? TDX_CST – 用来通知震铃、摘机挂机完成等
? TDX_PLAY – 用来通知语音文件播放完成
另外,以前在同步模式(3.2节)的时候,我们使用dx_playf()来播放语音文件,由于该函数只能在同步模式下执行,因此现在这个函数不能使用了,我们换一个同样是播放语音文件但是支持异步模式的函数dx_play(),比dx_playf()函数少了一个“f”。原型如下:

int dx_play(int chdev,DX_IOTT *iottp,DV_TPT *tptp,unsigned short mode);


其中:
chdev – Dialogic设备句柄,由dx_open()函数打开
iottp – 一个DX_IOTT结构的指针,该结构描述播放的语音的源位置和属性
tptp – 一个DV_TPT结构的指针,以前我们曾经遇到过,存放函数终止条件
mode – 函数执行的模式,该函数支持异步模式EV_ASYNC。

关于DX_IOTT结构和DV_TPT结构,在此先不去讨论它,只要感性的使用就可以,在稍后的章节里会有对这些结构的详细的解释。
由于TDX_CST事件必须通过附加的数据才能判断是TDX_CST事件集合内的哪一个事件发生了,TDX_CST事件的附加数据格式已经被Dialogic定义,它就是DX_CST数据结构。为了使用TDX_CST事件,必须在程序里定义一个DX_CST的结构变量,来存放TDX_CST事件的数据,于是,状态机函数可以设计如下:
/*******************************************************************
* 名字:int process(int evt_dev,int evt_type)
* 描述:状态机函数,按照输入的事件来选择不同的操作
* 输入:int evt_dev – 发生事件的设备句柄
* int evt_type – 发生的事件类型
* 输出:不等于-1 代表成功
* -1 代表操作过程失败
*
************************************************************/

/*原型申明*/
int OnOnHook(int evt_dev);/*用来处理挂机完成事件*/
int OnOffHook(int evt_dev);
/*用来处理摘机完成事件*/
int OnRings(int evt_dev);
/*用来处理震铃事件*/
int OnPlay(int evt_dev);
/*用来处理语音文件播放完成事件*/
int process(int evt_dev,int evt_type)
{
DX_CST *cstp;
/*存放TDX_CST事件的附加数据结构指针*/
int errcode = 0;
/*存放函数返回值*/

switch(evt_type)
{
case TDX_CST:
cstp = (DX_CST*)dx_getevtdatap(0);
/*取回TDX_CST事件的附加数据*/
switch(cstp->cst_event)
{
case DX_ONHOOK: /*代表挂机完成,我们不需要执行任何动作*/
break;

case DX_OFFHOOK:/*摘机完成,开始播放语音文件*/
OnOffHook(evt_dev);

break;

case DE_RINGS:/*震铃事件发生,执行摘机动作*/
OnRings(evt_dev);

Break;

}
break;

case TDX_PLAY:
OnPlay(dev_evt);

break;

default:
break;

}
}

下面来设计那些实现功能的函数:
/********************************************************************
* 名字 : int OnRings(int evt_dev)
* 描述 : 当收到震铃事件时,调用该函数来执行一定的功能。在我们前面的设计过程里,当系
* 统接收到震铃时,需要执行的操作是摘机。那么,在这个函数里我们实现摘机功能。
* 输入 : int evt_dev – 发生震铃事件的设备句柄
* 输出 :不等于-1 代表成功
* -1 代表失败
*********************************************************************/
int OnRings(int evt_dev)
{
if(dx_sethook(evt_dev,DX_OFFHOOK,EV_ASYNC) == -1)/*执行异步摘机动作*/
return –1;

return 0;

}
该函数里,dx_sethook()的第三个参数从以前的EV_SYNC变成了EV_ASYNC,它的意思是以异步模式执行dx_sethook()函数,意思是dx_sethook()被调用后将立即返回,摘机操作在底层执行,当摘机完成之后,系统会使用一个TDX_CST事件来通知摘机过程完成。Dialogic定义了代表同步和异步模式的常量:
EV_SYNC – 调用函数以同步方式
EV_ASYNC – 调用函数以异步方式
注意,并不是所有的Dialogic函数都可以用这两种方式来调用,比如函数dx_playf()就只能运行在同步模式,否则如果使用EV_ASYNC作为函数dx_playd()的参数mode的值来调用该函数,则该函数将返回错误。详细的Dialogic函数说明请看以后的章节。


/********************************************************************
* 名字 : int OnOffHook(int evt_dev)
* 描述 : 当收到摘机完成事件时,调用该函数来执行一定的功能。在我们前面的设计过程里,当
* 系统接收到摘机完成时,需要执行的操作是调用放音函数来播放语音文件。那么,在这
* 个函数里我们实现播放语音文件的功能。
* 输入 : int evt_dev – 发生摘机完成事件的设备句柄
* 输出 :不等于-1 代表成功
* -1 代表失败
*********************************************************************/
int OnOffHook(int evt_dev)
{
static DX_IOTT iott;
/*I/O传输参数表*/
static DV_TPT tpt;
/*I/O函数终止参数表*/
int voxhandle = 0;
/*语音文件的句柄*/

/*设置I/O函数dx_play()终止条件*/
dx_clrtpt(&tpt,1);
/*清空DV_TPT结构的内容*/
tpt.tp_type = IO_EOT;
/*本DV_TPT是最后一个DV_TPT结构*/
tpt.tp_termno = DX_MAXDTMF;/*接收到tp_length个电话按键后终止函数*/
tpt.tp_length = 1;
/*收到任意1个电话机按键就终止函数*/
tpt.tp_flags = TF_MAXDTMF;


/*打开Dialogic文件操作函数打开语音文件*/
voxhandle = dx_fileopen(“D:/Demo/Voice/HelloWorld.vox”,
O_RDONLY | O_BINARY);

if(voxhandle == -1) /*打开文件错误*/
{
printf(“打开语音文件错误:错误码 #%ld.n”,dx_fileerrno());

return (–1);

}

/*设置I/O函数dx_play()播放语音的来源*/
iott.io_type = IO_DEV|IO_EOT;
/*语音来源于文件 | 这是最后一个IOTT结构*/
iott.io_bufp = 0;
/*该参数需要从内存播放时才需要设置*/
iott.io_offset = 0;
/*从文件头开始播放*/
iott.io_length = -1;
/*播放直到文件尾*/
iott.io_nextp = 0;
/*由于没有下一个IOTT了,因此此处设置为0*/
iott.io_fhandle = voxhandle;
/*语音文件句柄*/

/*
* 以上打开了一个语音文件voxhandle,在接收到文件播放完成事件之前,我们不能
* 关闭此句柄,因为Dialogic的底层依然在使用它,必须等到接收到文件播放完成
* 事件之后才能完毕,怎么办呢?voxhandle是一个局部变量,退出该函数后,
* voxhandle将不存在了。Dialogic提供了一个方法来把用户的数据存储在通道
* 里,下面我们把文件句柄存储在通道里,等到播放完成事件被接收到时在把它取出来关
* 闭。
*/
/*将打开的文件句柄存储在当前的通道里*/
sr_setparm(evt_dev,SR_USERCONTEXT,(void*)voxhandle);


/*使用Dialogic函数异步播放语音文件*/
if(dx_play(evt_dev,&iott,&tpt,EV_ASYNC) == -1)
{
printf(“失败:dx_play():错误码 #%ld.n”,ATDV_LASTERR(evt_dev));

return (–1);

}
return 0;

}
在上面的程序里,我们又用到了一些新的Dialogic函数方法和数据结构,其中一个是DX_IOTT,另一个是sr_setparm()函数用到的SR_USERCONTEXT,,详细的请参考以后的相关章节,以下概要说明一下:
DX_IOTT – 在进行I/O操作的时候,比如放音、录音等。该结构用来说明源或目标的属性。如果使用在放音时,可以使用该结构来指定放音的数据是从文件、内存或其它设备,甚至把多个DX_IOTT结构连接起来使用。Dialogic提供了数组方式和链表方式,每个DX_IOTT结构指定一个语音数据来源,如果提供多个DX_IOTT连接起来,Dialogic在播放这样的数据时,将顺序搜索每个DX_IOTT标识的语音数据源并识别其格式进行连续播放,因此,这允许使用文件和内存混合、多个文件混合或多块内存混合等多种方式进行放音;如果使用在录音时,DX_IOTT结构提供录音目的地的信息,比如说录音到文件或内存等。
SR_USERCONTEXT – 该值是Dialogic定义的常量,用来指示sr_setparm()函数将用户数据存储到指定的通道,将来可以将这数据取回来使用。调用方式为:sr_setparm(通道句柄(int),SR_USERCONTEXT,需要设置的用户数据(void *)),在这里我们将通道正在播放的语音文件的句柄存放在通道中,等待接收到播放完成事件时再使用sr_getparm()函数将它取出来关闭。

另外,我们在函数的首部将DX_IOTT结构的变量iott和DV_TPT结构的变量定义为静态变量:
static DX_IOTT iott;
/*I/O传输参数表*/
static DV_TPT tpt;
/*I/O函数终止参数表*/
为什么要定义为静态变量而不是局部一般变量呢?这一点必须非常注意,否则函数在执行期间会出现莫名其妙的情况。Dialogic要求:在异步函数执行期间,异步函数使用到的指针所指向的内存必须保持有效直到函数执行完成(接收到完成事件),在上面的函数里,dx_play()函数使用到了&iott和&tpt,这两个指针指向的内存必须要保持有效直到接收到TDX_PLAY事件。可是由于函数dx_play()在以异步模式被调用后将立即返回,这导致函数OnOffHook()函数的立即返回,如果iott和tpt定义为一般局部变量,由于一般局部变量占用的内存通常分配在栈里,一旦被调用的函数返回,函数使用的栈空间将被系统收回,局部变量也就随之失效!如果此时别的函数申请局部变量,系统有可能就把这属于iott和tpt的内存分配给请求的函数,函数初始化局部变量的时候这些内存就被改写。如果此时dx_play()已播放完成或未播放完成但已不使用这些数据,那么系统不会出现什么意外;可如果此时dx_play()还没有播放完成,则当dx_play()再取这些数据的时候必然会取回无效的数据,这将导致dx_play()函数出现异常行为!因为dx_play()函数只使用原始的数据而不会使用一份拷贝的。所以在函数的执行期间,必须保持这些指针指向的内存有效(所谓有效包括内存可使用和数据合法)。由于函数的静态变量分配在堆里,他不会随函数的退出而失效,除非是主函数退出所有的变量都将失效。另外一个方法是把iott和tpt定义为全局变量也可以,主要视程序设计方便和可读性来确定。

最后,有一点必须注意的是,Dialogic建议操作文件最好是使用Dialogic提供的文件操作函数打开、读、写、关闭和定位等。虽然这些函数使用的参数和Win32定义的open()、read()、write()、close()及seek()函数的参数相同,但是为了兼容和稳定,要求使用dx_fileopen()、dx_fileread()、dx_filewrite()、dx_fileclose()及dx_fileseek()来代替。


/********************************************************************
* 名字 : int OnPlay(int evt_dev)
* 描述 : 当收到语音播放完成事件时,调用该函数来执行一定的功能。在我们前面的设计过程里,
* 当语音播放完成时,需要执行的操作是挂机。那么,在这个函数里我们实现挂机功能。
* 输入 : int evt_dev – 发生语音播放完成事件的设备句柄
* 输出 :不等于-1 代表成功
* -1 代表失败
*********************************************************************/
int OnPlay(int evt_dev)
{
/*取回存放在通道里的语音文件句柄*/
int voxhandle = 0;

sr_getparm(evt_dev,SR_USERCONTEXT,(void*)&voxhandle);


/*首先关闭播放已完成的语音文件*/
if(dx_fileclose(voxhandle) == -1)
{
/*如果关闭失败,处理错误:直接返回-1*/
printf(“错误:dx_fileclose(): 错误码 #%ld.n”,dx_fileerrno());

return (-1);

}

/*将通道挂机,为等待下一次呼叫做准备*/
if(dx_sethook(evt_dev,DX_ONHOOK,EV_ASYNC) == -1)/*执行异步摘机动作*/
return –1;

return 0;

}

在上面的函数中,首先将以前放置在通道里的文件句柄取出来关闭,然后调用dx_sethook()函数将通道挂机等待下一次呼叫。
由播放文件时设置的条件可以知道,文件播放结束可能由于两个原因:一是播放到文件尾;一个是用户在电话机上按了任意一个电话按键。其实在调用函数dx_sethook()将通道挂机之前,我们可以调用Dialogic 的一个用来检查异步函数结束原因的函数ATDX_TERMMSK()来检查引起dx_play()函数结束的原因。这可能是:
TM_EOD – 播放到了文件尾(End of Data)
TM_MAXDTMF – 用户按了电话按键(Maxinum DTMF Digits Received)
如果需要根据结束原因进行处理,就可以使用该函数来查询结束原因。其实,这样的需求是经常存在的。比如当播放语音要求用户输入数字编号的用户名,函数返回后就可以按照结束原因来判断用户做了什么样的动作:如果原因是TM_EOD,那说明可能用户并没有理会系统的提示(没有按下任意键就到了语音播放结束),这时要么重新提示用户输入,要么挂机;如果语音是TM_MAXDTMF,那么说明语音未播放完用户就已经开始输入了,那么下一步就得调用dx_getdig()来接收用户的按键。

/********************************************************************
* 名字 : int OnOnHook(int evt_dev)
* 描述 : 当收到挂机完成事件时,调用该函数来执行一定的功能。在我们前面的设计过
   * 程里,当系统接收到挂机完成事件时,不需要任何执行。只为了等待下一次电
   * 话呼叫那么,在这个函数里我们什么都不需要实现。
* 输入 : int evt_dev – 发生挂机完成事件的设备句柄
* 输出 :不等于-1 代表成功
* -1 代表失败
*********************************************************************/
int OnOnHook(int evt_dev)
{
return 0;

}

该函数什么都不做,占个位置而已。

6.3.3 编译并运行
把上面的代码整理一下,main.cpp文件的内容应该是这样的:
/*********************************************************************
* ?Copyright 2002. All rights reserved.
* Dialogic 异步模式模拟卡示例程序
* 支持D/41ESC、VFX/40ESC、LSI/81SC、LSI/161SC 和D/160SC-LS卡
* 使用Microsoft Visual C++ 5.0以上版本编译
* OS :Windows NT 4.0(SP6) 或
* Windows 2000(Prefessional/Server/Advance Server)
*********************************************************************/
/*Microsoft Visual C++ 头文件*/
#include <stdio.h>
#include <process.h>
#include <string.h>
#include <conio.h>
#include <windows.h>
#include <winbase.h>

/*Dialogic头文件*/
#include <srllib.h> /*SRL函数头文件*/
#include <dxxxlib.h> /*Voice函数头文件*/
#include <sctools.h> /*nr_scroute()函数头文件*/

/*定义*/
#define MAX_DEV 4 /*定义所使用的最大设备数量*/

/*全局变量和函数原型*/
int kb_hit = 0;
/*全局变量,用来标识键盘是否有按键按下,控制循环结束*/

DWORD WINAPI kbhit_thread(LPVOID lpArgs);
/*键盘按键监视线程*/
int process(int evt_dev,int evt_type);
/*事件处理状态机函数*/

/*定义一个结构来存放和通道相关的信息,比如通道的句柄,通道的状态等*/
typedef struct chinfo_tag {
int dev;
/*通道的句柄*/
} CHINFO;

CHINFO chinfo[MAX_DEV];
/*4个通道的通道信息结构数组*/

/*****************************************************************
* 名称:主函数 main()。
* 描述:初始化数据和建立相关的线程并等待Dialogic设备事件发生
* 输入/输出:对程序没有意义
*****************************************************************/
int main(int argc,char** argv)
{
/*
* 定义4个通道的名字,可扩展为更多的通道或使用sr_getboardcnt()查询卡数目,
* 使用ATDV_SUBDEVS()来取得卡的通道数。
*/
char dev_namep[4][] = {“dxxxB1C1”,”dxxxB1C2”,”dxxxB1C3”,”dxxxB1C4”};


HANDLE hdlKbMonitor = NULL;
/*键盘按键监视线程句柄*/
DWORD dwThreadID = 0;/*键盘按键监视线程ID*/

/*将SRL设置为单线程异步模式SR_STASYNC*/
int srlmode = SR_STASYNC;

if(sr_setparm(SRL_DEVICE,SR_MODELTYPE,&srlmode) == -1)
{
printf(“失败:sr_setparm()函数 : 错误码 # %ld.n”,
ATDV_LASTERR(SRL_DEVICE));

return(0);

}

/*建立键盘事件监视线程*/
hdlKbMonitor = (HANDLE)_begin
threadex(NULL,
0,
kbhit_thread,
NULL,
0,
&dwThreadID);

if (hdlKbMonitor == (HANDLE) -1 )
{
printf("错误的建立键盘输入监视线程 – 退出程序n");

return(0);

}

/*打开和初始化MAX_DEV个Dialogic设备通道*/
for(int cnt = 0 ;
cnt < MAX_DEV ;
cnt ++)
{
/*打开Dialogic设备通道*/
chinfo[cnt].dev = dx_open(dev_namep[cnt],0);

if(chinfo[cnt].dev == -1)
{
/*打开失败,错误来自于操作系统,使用dx_fileerrno()取回发生的错误码*/
printf(“失败:调用dx_open():错误码 #%ld.n”,dx_fileerrno());

return (0);
/*退出系统*/
}

/*使通道可以检测到震铃事件*/
if(dx_setevtmsk(chinfo[cnt].dev,DM_RINGS)== -1)
{
printf(“失败:调用dx_setevtmsk():错误码 #%ld.n”,
ATDV_LASTERR(chinfo[cnt].dev));

return (0);

}

/*将该通道的语音通道资源和模拟接口资源通道之间建立全双工连接*/
if(nr_scroute(chinfo[cnt].dev,
SC_VOX,
chinfo[cnt].dev,
SC_LSI,
SC_FULLDUP) == -1)
{
printf(“失败:nr_scroute():错误码 #%ld.n”,
ATDV_LASTERR(chinfo[cnt].dev));

return (0);

}

/*将通道初始化为挂机状态*/
if(dx_sethook(chinfo[cnt].dev,DX_ONHOOK,EV_ASYNC) == -1)
{
printf(“失败:dx_sethook():错误码 #%ld.n”,
ATDV_LASTERR(chinfo[cnt].dev));

return (0);

}
}

/*循环处理()直到键盘按键被按下*/
int evt_dev,evt_type;
/*分别存放发生事件的设备和发生事件的类型*/
while(kb_hit != 1) /*如果键盘按下,该标志将被设置为1,否则为0*/
{
if(sr_waitevt(-1) == -1) /*一直等待直到事件发生*/
{
printf(“失败:sr_waitevt():错误码 #%ld.n”,
dx_fileerrno());

}
evt_dev = sr_getevtdev();

evt_type = sr_getevttype();


/*调用状态机处理函数,在3.3.2节描述状态机的设计*/
if(process(evt_dev,evt_type) == -1) /*函数process()返回-1表示失败*/
break;

}

/*循环已经退出,现在关闭所有已经打开的设备*/
for(cnt = 0 ;
cnt < MAX_DEV ;
cnt ++)
{
dx_close(chinfo[cnt].dev);

}

return (1);

}
/*********************************************************************
* 名字:kbhit_thread()
* 描述:监视计算机键盘的按键
**********************************************************************/
DWORD WINAPI kbhit_thread(LPVOID lpArgs)
{
getch();/*阻塞等待键盘按键*/
printf(“keyboard pressed.n”);

kb_hit = 1;
/*将键盘按下标志设置为1,表示已经检测到按键*/
return(0);

}

/*******************************************************************
* 名字:int process(int evt_dev,int evt_type)
* 描述:状态机函数,按照输入的事件来选择不同的操作
* 输入:int evt_dev – 发生事件的设备句柄
* int evt_type – 发生的事件类型
* 输出:不等于-1 代表成功
* -1 代表操作过程失败
*
************************************************************/
/*原型申明*/
int OnOnHook();/*该函数什么都不做*/
int OnOffHook(int evt_dev);
/*用来处理摘机完成事件*/
int OnRings(int evt_dev);
/*用来处理震铃事件*/
int OnPlay(int evt_dev);
/*用来处理语音文件播放完成事件*/

int process(int evt_dev,int evt_type)
{
DX_CST *cstp;
/*存放TDX_CST事件的附加数据结构指针*/
int errcode = 0;
/*存放函数返回值*/

switch(evt_type)
{
case TDX_CST:
cstp = (DX_CST*)dx_getevtdatap();
/*取回TDX_CST事件的附加数据*/
switch(cstp->cst_event)
{
case DX_ONHOOK: /*代表挂机完成,我们不需要执行任何动作*/
          OnOnHook(evt_dev);

break;

case DX_OFFHOOK:/*摘机完成,开始播放语音文件*/
OnOffHook(evt_dev);

break;

case DE_RINGS:/*震铃事件发生,执行摘机动作*/
OnRings(evt_dev);

Break;

}
break;

case TDX_PLAY:
OnPlay(dev_evt);

break;

default:/*TDX_CST和TDX_PLAY之外的事件*/
printf(“未请求的事件%d发生在设备%d上.n”,evt_type,evt_dev);

break;

}
}

/**************************************************************
* 名字 : int OnRings(int evt_dev)
* 描述 : 当收到震铃事件时,调用该函数来执行一定的功能。在我们前面的设计
* 过程里,当系统接收到震铃时,需要执行的操作是摘机.那么,在这个
* 函数里我们实现摘机功能。
* 输入 : int evt_dev – 发生震铃事件的设备句柄
* 输出 :不等于-1 代表成功
* -1 代表失败
**************************************************************/
int OnRings(int evt_dev)
{
if(dx_sethook(evt_dev,DX_OFFHOOK,EV_ASYNC) == -1)/*执行异步摘机动作*/
return (–1);

return (0);

}

/***************************************************************
* 名字 : int OnOffHook(int evt_dev)
* 描述 : 当收到摘机完成事件时,调用该函数来执行一定的功能。在我们前面的设
* 计过程里,当系统接收到摘机完成时,需要执行的操作是调用放音函数来
* 播放语音文件。那么,在这个函数里我们实现播放语音文件的功能。
* 输入 : int evt_dev – 发生摘机完成事件的设备句柄
* 输出 :不等于-1 代表成功
* -1 代表失败
***************************************************************/
int OnOffHook(int evt_dev)
{
static DX_IOTT iott;
/*I/O传输参数表*/
static DV_TPT tpt;
/*I/O函数终止参数表*/
int voxhandle = 0;
/*语音文件的句柄*/

/*设置I/O函数dx_play()终止条件*/
dx_clrtpt(&tpt,1);
/*清空DV_TPT结构的内容*/
tpt.tp_type = IO_EOT;
/*本DV_TPT是最后一个DV_TPT结构*/
tpt.tp_termno = DX_MAXDTMF;/*接收到tp_length个电话按键后终止函数*/
tpt.tp_length = 1;
/*收到任意1个电话机按键就终止函数*/
tpt.tp_flags = TF_MAXDTMF;


/*打开Dialogic文件操作函数打开语音文件*/
voxhandle = dx_fileopen(“D:/Demo/Voice/HelloWorld.vox”,
O_RDONLY | O_BINARY);

if(voxhandle == -1) /*打开文件错误*/
{
printf(“打开语音文件错误:错误码 #%ld.n”,dx_fileerrno());

return (–1);

}

/*设置I/O函数dx_play()播放语音的来源*/
iott.io_type = IO_DEV|IO_EOT;
/*语音来源于文件 | 这是最后一个IOTT结构*/
iott.io_bufp = 0;
/*该参数需要从内存播放时才需要设置*/
iott.io_offset = 0;
/*从文件头开始播放*/
iott.io_length = -1;
/*播放直到文件尾*/
iott.io_nextp = 0;
/*由于没有下一个IOTT了,因此此处设置为0*/
iott.io_fhandle = voxhandle;
/*语音文件句柄*/

/*
* 以上打开了一个语音文件voxhandle,在接收到文件播放完成事件之前,我们不能
* 关闭此句柄,因为Dialogic的底层依然在使用它,必须等到接收到文件播放完成
* 事件之后才能完毕,怎么办呢?voxhandle是一个局部变量,退出该函数后,
* voxhandle将不存在了。Dialogic提供了一个方法来把用户的数据存储在通道
* 里,下面我们把文件句柄存储在通道里,等到播放完成事件被接收到时在把它取出来关
* 闭。
*/
/*将打开的文件句柄存储在当前的通道里*/
sr_setparm(evt_dev,SR_USERCONTEXT,(void*)voxhandle);


/*使用Dialogic函数异步播放语音文件*/
if(dx_play(evt_dev,&iott,&tpt,EV_ASYNC) == -1)
{
printf(“失败:dx_play():错误码 #%ld.n”,ATDV_LASTERR(evt_dev));

return (–1);

}
return 0;

}

/**************************************************************
* 名字 : int OnPlay(int evt_dev)
* 描述 : 当收到语音播放完成事件时,调用该函数来执行一定的功能。在我们前
* 面的设计过程里,当语音播放完成时,需要执行的操作是挂机.那么,在
* 这个函数里我们实现挂机功能。
* 输入 : int evt_dev – 发生语音播放完成事件的设备句柄
* 输出 :不等于-1 代表成功
* -1 代表失败
************************************************************/
int OnPlay(int evt_dev)
{
/*取回存放在通道里的语音文件句柄*/
int voxhandle = 0;

sr_getparm(evt_dev,SR_USERCONTEXT,(void*)&voxhandle);


/*首先关闭播放已完成的语音文件*/
if(dx_fileclose(voxhandle) == -1)
{
/*如果关闭失败,处理错误:直接返回-1*/
printf(“错误:dx_fileclose(): 错误码 #%ld.n”,dx_fileerrno());

return (-1);

}

/*将通道挂机,为等待下一次呼叫做准备*/
if(dx_sethook(evt_dev,DX_ONHOOK,EV_ASYNC) == -1)/*执行异步摘机动作*/
return –1;

return 0;

}

  /********************************************************************
* 名字 : int OnOnHook(int evt_dev)
* 描述 : 当收到挂机完成事件时,调用该函数来执行一定的功能。在我们前面的设计过
   * 程里,当系统接收到挂机完成事件时,不需要任何执行。只为了等待下一次电
   * 话呼叫那么,在这个函数里我们什么都不需要实现。
* 输入 : int evt_dev – 发生挂机完成事件的设备句柄
* 输出 :不等于-1 代表成功
* -1 代表失败
*********************************************************************/
int OnOnHook(int evt_dev)
{
return 0;

  }

上面的代码基本上是一个能够检查错误并使用Dialogic异步模式来完成的一个程序。原代码见光盘DemoHelloWorldAsyncHelloWorldASync.dsp.
编译程序并运行,观看其执行效果,发现和3.2的差不多,只是可以循环接多次电话并且看起来好象稳定多了。

6.3.4 提出问题
在3.3.3程序的执行过程中,当需要退出程序时,我们在计算机键盘上按任意键就可以。可是,如果程序正在等待电话或播放语音文件的时候,我们按下键键盘上的任意键可以看到屏幕上出现:
keyboard pressed.
的字样,可是程序并没有因此而退出,好象没有反应,可是键盘监视线程确实已经检测到按键发生了。问题出在哪里?对用户的操作不响应,不是一个好程序。回去再看看程序,发现问题出现在主函数的while循环上:
while(kb_hit != 1)
{
if(sr_waitevt(-1) == -1)
{
......
}
......
}
看上面的程序,问题找到了。While循环退出的条件是当kb_hit等于1的时候,可是sr_waitevt()是一个阻塞函数,当它在等待事件的时候,什么情况也不能使它返回(即使kb_hit被键盘监视线程从0置为1),因为此时线程被挂起在sr_waitevt()函数处,不能执行到while循环的判断语句,当然即使kb_hit变成了1也没有用。必须有一个事件使sr_waitevt()返回,sr_waitevt()以下的流程调用的函数都是异步函数,不会阻塞程序执行。因此,最主要的罪魁祸首就是sr_waitevt()函数。根据问题发生的原因,目前唯一的解决办法就是人为地制造一个事件,sr_waitevt()发现有事件之后就会立即返回,而下面的process()函数不管处理与否,只要不调用阻塞函数,程序的流程就会运行到while循环,此时如果kb_hit被设置为1,while循环将退出,这样程序就结束了,达到了按一键就退出程序的目的。
Dialogic的系统设计得很完美,它提供了让我们可以人为地制造一个事件的能力,这就是sr_putevt()函数,该函数人为地制造一个事件。其原型如下:
long sr_putevt(dev, evttype, evtlen, evtdatap, errcode);

dev – 被发送事件的设备句柄
evttype – 事件的类型
evtlen – 事件所带的附加数据的长度。如果没有附加数据,此处为0
evtdatap – 事件的附加数据,如果没有,此处为NULL
errcode – 当该事件发生时,通道被设置为错误状态,0为没有错误
我们就计划往第一个设备发送一个事件来引起sr_waitevt()返回,这个事件Dialogic允许用户自己定义。我们也可以找一个已经定义的事件。Dialogic定义了一个不大用到的事件DE_WINK,这是一个CST事件(在TDX_CST事件集合里的事件),我们就用它来扮演这个角色。
由于DE_WINK是一个CST事件,要使通道可以检测到CST事件,必须为该通道设置事件掩码,就象我们在打开通道时使用:
if(dx_setevtmsk(chinfo[cnt].dev,DM_RINGS)== -1)
来设置通道可以检测到CST事件DE_RINGS一样,我们要sr_waitevt()能检测到这个事件,也要为它设置掩码。和DE_WINK事件对应的掩码是DM_WINK。我们修改设置震铃掩码的语句使它还可以检测到DE_WINK事件,如下:
if(dx_setevtmsk(chinfo[cnt].dev,DM_RINGS | DM_WINK)== -1)
此时如果DE_WINK事件发生,sr_waitevt()函数就可以检测到并返回了。下一步是要选择在什么时候和什么地点来发送这个DE_WINK事件。当然,最好的时候是键盘监视线程在检测到计算机有按键的时候立刻发送,那么地点当然就在键盘监视线程里了。修改一下键盘监视线程的代码,我们使用下面的代码来在键盘按键发生的时候发送这个TDX_CST事件。如下:
/*********************************************************************
* 名字:kbhit_thread()
* 描述:监视计算机键盘的按键
**********************************************************************/
DWORD WINAPI kbhit_thread(LPVOID lpArgs)
{
/*建立DE_WINK这个CST事件需要的附加数据DX_CST*/
TDX_CST cst;

cst.cst_event = DE_WINK;
/*CST事件为DE_WINK*/
cst.cst_data = 0;/*DE_WINK不带数据*/

getch();/*阻塞等待键盘按键*/
printf(“keyboard pressed.n”);

kb_hit = 1;
/*将键盘按下标志设置为1,表示已经检测到按键*/

/*给第一个通道dxxxB1C1(它的句柄是chinfo[0].dev)发送一个TDX_CST事件*/
sr_putevt(chinfo[0].dev,TDX_CST,sizeof(DX_CST),&cst,0);


return(0);

}
当sr_waitevt()检测到这个TDX_CST事件后立刻就返回,此时TDX_CST被传入状态机函数process(),可是process()里并没有DE_WINK对应的处理分支,该消息被process()抛弃,于是流程进行到while循环处。While检测到kb_hit已经是1,循环就结束了。紧接着已打开的通道被while循环下for循环里的dx_close()函数挨个关闭,程序返回到Windows,实现了退出。

上面的问题是解决了,但是process()函数又出现了问题。现在我们设计的电话应答流程很简单,只有几个状态(等待电话、摘机、放音、挂机),虽然简单,process里的switch选择语句看起来已经不小了。如果是一个复杂的交互语音应答系统(IVR),有几百上千个状态。process()里的switch语句的分支会多到什么程度?会混乱和复杂到什么程度?也许会让开发者自己都读不懂和难以定位到要修改的地方。
那么是否别的办法,答案是显然的。研究一下process()函数里的程序结构会发现,其实整个流程的走向由以下几个因素控制:
1.当前通道的状态。等待电话、摘机、播放、挂机中的某一种状态。
2.当前发生的事件。震铃、摘机完成、播放完成、挂机完成中的某一种事件。
3.这样的状态下发生这样的事件,应该执行什么样的函数。OnRings()、OnOffHook()、OnPlay()、OnOnHook()中的某一个函数。
4.执行这样的函数,通道的下一个状态应该是什么。等待电话、摘机、播放、挂机中的某一种状态。
5.如果在执行函数的过程中出现错误怎么处理。我们需要定义一个针对具体状态具体执行函数的错误处理办法。

上面1-5的描述可以用一个结构来描述,定义如下:
typedef struct state_machine_tag {
int state;

int evt;

int (*func)(int evt_dev);

int next_state;

int (*err_handler)(void *err_desc)
} STATE_MACHINE;


上面就定义了一个状态机结构,它把一个状态机函数的执行逻辑抽象为一个数据结构。这样看起来要清楚多了。假设我们定义一个全局错误处理函数:
int global_err_handler(void *err_desc);

那么,状态机函数process()就可以用数据结构STATE_MACHINE来描述。我们使用一个数组来描述这个有限的状态集合,并假设当把数据结构STATE_MACHINE里的每个成员都置为0的结构就代表了这个数组的结束,那么,描述可以如下:

const int WaitRing = 0;
/*等待呼叫状态*/
const int OffHook = 1;
/*摘机状态*/
const int PlayMedia = 2;/*播放语音状态*/
const int OnHook = 3;/*挂机状态*/

STATE_MACHINE state_machine[] =
{
{WaitRing,DE_RINGS,OnRings,OffHook,global_err_handler},
{OffHook,DE_OFFHOOK,OnOffHook,PlayMedia,global_err_handler},
{PlayMedia,TDX_PLAY,OnPlay,OnHook,global_err_handler},
{OnHook,DE_ONHOOK,OnOnHook,WaitRing,global_err_handler}
{0,0,0,0,0}
};


但是,象TDX_CST这种事件,它需要附加数据来详细说明是什么具体的事件:DE_RINGS、DE_WINK、DX_ONHOOK、DX_OFFHOOK还是其它?所以我们需要一个翻译函数,将一个原始的事件翻译成需要的事件。要将一个事件翻译成最终有用的事件,需要该事件的所有信息来确定。比如说TDX_CST事件需要一个DX_CST数据结构来确定这个TDX_CST事件到底是什么事件,那么我们在研究翻译函数之前,首先定义一个信息结构,这个结构包含了翻译事件所需要的所有原始信息。按照Dialogic所能提供的事件相关函数,我们可以把这个事件信息结构定义如下:
typedef struct dx_msg_tg {
int dev;
/*发生事件的设备句柄*/
int evttype;
/*事件类型*/
void* evtdatap;
/*事件数据*/
int evtlen;
/*数据的长度*/
int dx_evttype;
/*被翻译后的事件类型*/
int errcode;
/*取得这些信息的时候发生的错误*/
int state;
/*取得这个信息时通道的状态*/
} DX_MSG;


该结构基本上可以描述Dialogic设备事件的信息了。那么,依照前面所进行的代码基础,现在开始来设计一个最简单的翻译函数:定义如下:
int DxTranslateMessage(DX_MSG *dx_msg)
{
switch(dx_msg->evttype)
{
case TDX_CST: /*TDX_CST事件时候进行转换*/
dx_msg->dx_evttype = ((DX_CST *)dx_msg->evttype)->cst_event;

break;

default: /*其他时候先不处理,就使用发生的事件作为翻译过的事件*/
dx_msg->dx_evttype = dx_msg->evttype;

break;

}
return (0);

}

这个函数实现了一个简单的翻译,它将TDX_CST事件转换成实际的事件,而其他的所有事件忽略不管,这在目前来说基本上就够了。
DX_MSG是一个自己定义的结构,Dialogic并没有这样的结构。所以该结构里的信息也得自己来填,Dialogic的sr_waitevt()、sr_getevtdev()、sr_getevttype()、sr_getevtlen()这些函数联合起来一起管理事件和事件的数据。其实这些都是和事件相关的,我们定义下面的函数来填写结构的信息:
int DxGetMessage(DX_MSG *dx_msg,int timeout)
{
if(sr_waitevt(timeout) == -1)
return 0;

if(dx_msg->dev = sr_getevtdev()== -1)
return 0;

if(dx_msg->evttype = sr_getevttype() == -1)
return 0;

dx_msg->evtdatap = sr_getevtdatap();

dx_msg->evtlen = sr_getevtlen();

dx_msg->dx_evttype = -1;

dx_msg->state = get_channel_state_from_handle(dev);/*一个没有定义的函数*/
return (1);

}

该函数一般情况下已经能够很好的取得Dialogic的设备事件信息了。
那么,下面我们就要结合上面的STATE_MACHINE结构描述的信息,让取得的事件可以驱动状态机被执行,我们定义这个函数来实现这个驱动过程:
int DxDispatchMessage(DX_MSG *dx_msg)
{
 int state_index = 0;


/*当没有到达事件状态结束*/
while(!IsStateValid (state_machine[state_index]))
{
if(state_machine[state_index].state == dx_msg->state) &&

state_machine[state_index].evt == dx_msg->dx_evttype )
{
if((*state_machine[state_index].func)(dx_msg->dev) == -1)
(*state_machine[state_index].err_handler)(dx_msg);

else

dx_msg->state = state_machine[state_index].next_state;

}
state_index ++;
/*到下一个状态*/
}
return(0);

}
其中,IsStateValid(STATE_MACHINE *sm)是一个用来检测当前状态是否有效的函数,可以这样定义:
int IsStateValid(STATE_MACHINE *sm)
{
if(sm->state == 0 &&
sm->evt == 0 &&
sm->func == 0 &&

sm->next_state == 0 &&
sm->err_handler == 0)
return 0;

return 1;

}

那么,3.3.3程序里的while循环:
while(kb_hit != 1)
{
......
}

可以被以下的代码替代:
DX_MSG dxmsg;

while(DxGetMessage(&dxmsg))
{
DxTranslateMessage(&dxmsg);

DxDispatchMessage(&dxmsg);

}
这个样子看起来怎么很象Windows的某段代码?对了,这就是维持Windows应用程序能基于消息来运行的核心部分代码-消息循环。
上面的列举的代码是不能被执行的,因为里面还存在一些没有解决的问题,比如通道的状态存放在什么地方的?又怎么取回来。我们将在作者编写的另一本叫做《IVR可视化集成开发环境实现》的书里更详细的来描述和展开这一过程,并使之成为一个稳健的可执行的系统。

6.4 总结
通过以上各个小节的内容以及程序设计阶梯性由简到繁的过程,我们应该基本上了解了Dialogic模拟卡编程的一般方式以及如何使用Microsoft Visual C++语言来实现。Dialogic模拟卡编程也可以在其他语言环境比如Borland C++ Builder下实现,但是编程时使用的库需要做一定的改变,在此不做详细的论述。
在以上的程序设计过程中,我们应该已经大量的看到了一个叫做SRL的东西,并有大量的函数是以”sr_”作为前缀的。那么,SRL和sr_是什么东西呢?这正式我们下面要解释的内容。

6.4.1 Dialogic软件系统架构及开发接口
Dialogic系统是一套设计得非常不错的硬件软件综合系统,按照功能分类,可以将Dialogic分为三个部分:
1. 驱动程序部分
2. 系统管理和维护部分
3. 应用开发接口部分

驱动程序部分负责硬件的驱动和提供给开发部分的控制硬件的接口。
系统管理和维护部分用来启动、停止Dialogic驱动以及设置系统的参数等等
开发部分提供给应用程序的编程接口。

Dialogic的产品按照架构的不同,可以简单的分为两类:
1. SpringWare架构
2. DM3结构
将硬件和软件按照一定的方法结合起来形成的模式,称为架构,软件包含了系统软件和应用程序编程接口。Dialogic把最早的过分强调各种不同网络特性的架构称为SpringWare,相对的,把目前最新的架构称为DM3 – Dialogic MediaStream?结构。最新的DM3架构将网络的很多特性抽象以及使用媒体流技术,为一种网络编写的应用(比如模拟网)可以方便的移植到另一种网络上(比如数字网),并支持多个厂家设备在DM3架构下分布式配置,这比以前的SpringWare架构有很多的优越性。关于DM3产品的程序设计,详细请参考作者编写的《Dialogic DM3程序设计从入门到精通》。

那么,功能是在系统架构之下实现的,也就是说,首先是架构,然后是功能。Dialogic的SpringWare架构下都提供有三种功能的软件,如上所描述:
1.驱动程序部分
2.管理和维护部分
3.应用开发接口部分

SpringWare架构下有其所支持的硬件集合及相应硬件的驱动程序、管理维护和应用程序接口(API)。比如使用dx_系列函数或dt_系列函数在D/41E或D/300SC-E1上做的开发就属于SpringWare架构的应用。
在最早的GlobalCall API和DM3 API没有出来之前,Dialogic有一套标准的后来被命名为SpringWare架构系统的应用程序开发接口,这些接口函数的集合后来被Dialogic叫做R4 API。
R4 API来源于Dialogic System Release 4 Application Programming Interface,也就是Dialogic在版本4之前使用的标准API,在此之后,又出现了GlobalCall API以及DM3架构下的MDI(Dialogic MediaStream Direct Interface)和DM3 AFC(Dialogic DM3 Application Foundation Code)。
R4 API过分的强调了通信网络的不同,比如在模拟卡上做的应用就不能简单的移植到数字卡上去使用;而在南非做的应用程序到中国来可能就不能正确的执行。考虑到这种情况,Dialogic在R4 API的基础上,包装Dialogic SRL,通过改变一些实现方法来屏蔽这种不同并提供了一套开发接口。称为GlobalCall API。其意思是Call在全球。该API接口屏蔽了网络的不同以及国家与国家之间的差异。
在此之后,Dialogic又为了支持分布式媒体流技术,又设计了一种叫做DM3的架构,上面使用的API接口有两种MDI和DM3 APC。
MDI 是一个最底层的接口,直接和硬件通信,所以称为MediaStream Direct Interface(缩写为MDI),为了编程方便易懂,Dialogic将此API包装了一层,给开发者提供一个高级点的接口叫做Dialogic DM3 Application Foundation Code(缩写为DM3 AFC)。
Dialogic提供的API如此繁多,可能有些非常熟悉R4 API,有的人非常熟悉GlobalCall API,而他们都不知道MDI和AFC,怎么做到熟悉和开发呢?于是Dialogic 又设计出一些兼容的函数接口库(Media Library),使得可以使用R4 API或GlobalCall API在DM3架构上做开发,这就是R4 On DM3和GlobalCall On DM3的由来。
Dialogic系统是事件驱动的系统,基于事件(比如sr_waitevt())和消息机制(比如DM3的系统就是基于部件与部件、部件与系统间的消息传送来实现功能的)。上面已经罗列了太多的概念,为了逐步熟悉这些概念和可以在Dialogic的不同架构和API上做开发,我们还是得一步一步的来。R4 API是Dialogic标准的API,一直沿用,我们以前的章节使用的API 就是Dialogic R4 API,在本书的以后章节也主要使用R4 API 来设计。关于别的API可以参考作者的其他书籍。
在以前的章节里,我们知道了Dialogic的事件通知机制和事件管理函数,这些事件驱动了系统状态机的运行。既然它是核心,那么在进行其他的高级编程和设计之前,我们必须首先把Dialogic的事件管理机制搞清楚,而Dialogic的所有编程模式(同步、异步或其他)全都是基于事件机制的,同步也使用了事件机制,这些事件在Dialogic设备上发生并被一个专门管理事件的部件管理和分发到应用程序。在Dialogic系统里,用来管理事件和提供事件机制的部件叫做Standard Runtime Library,简称SRL,它是一个Win32 DLL和一些API接口。下面的章节就使用R4 API来编写程序逐步的了解Dialogic SRL以及可以实现的Dialogic 模拟卡编程模式,同时这些模式也可以使用在数字卡和传真卡、坐席卡等的编程上。
 
各位如果有兴趣可以通过 uxcallme@163.com 联系具体事宜!
 
是你自己写的,还是你公司写的内部资料。
 
顶部