VOIP123声明:此文版权归 余洪涌 tel:13606060253
VOIP123说明 :本博文中含图片部分有所删节
感谢CCAV,感谢GOOGLE,感谢FreeSwitch群,感谢freeswitch.org,感谢freeswitch.org.cn
感谢 ~!
作者声明:
首先需要声明,本文档不是严谨的学术书籍,是针对实际研发和部署使用过程中的一些问题的总结,很多的问题答案只是一种解决办法,不一定是最佳答案。由于环境的不同,问题的答案甚至在各位读者的测试环境中都无法重现。甚至是错误的答案。:)因此,本书尽量描述清楚问题的出现环境。本书的测试环境是基于FreeSwitch 的windows版的1.2.1的源码上编译的。
VOIP 基础部分
0.
本文档读者对象是哪些?
毫无疑问FreeSwitch百问的读者肯定是技术人员,他们可能是:
A.对voip有兴趣没有基础的伙计
B.对FreeSwitch有兴趣站在门口的伙计
C.计划把FreeSwitch从实验所或者研发中心部署应用到实网系统中的伙计
D.准备使用FreeSwitch做IPPBX的伙计
E.准备开发IP呼叫中心的伙计
F.对FreeSwitch进行运营维护的伙计
1.
研究VOIP/FreeSwitch之前需要哪些基础知识?
假如你不开发应用,只是使用FreeSwitch,通常熟悉SIP就够。
假如要你开发应用,熟悉C/C++是必须的。
虽然FreeSwitch支持很多语言的开发接口,但是实际上基本都是在C/C++的接口上通过SWIG进行扩展的。通过swig 包装支持多种脚本语本语言控制呼叫流程,如perl php lua python ruby java tcl等。
事件套接字使用
Event Socket 可以使用任何其它语言通过Socket 方式控制呼叫流程、扩展FreeSWITCH 功能。通俗讲FreeSwitch是也是一个VOIP,目前主流的VOIP,甚至包括运营商的IMS毫无例外都是基于SIP协议的。因此要搞定FreeSwitch,SIP协议是必须的。
2.
VOIP基础设施有哪些?
以太网络,这个是废话,没有这咋实现IP通信呢。
IP话机,相当于普通电话机,不过不是普通电话机的电话线
RJ11的接口而是网线
RJ45接口.
软电话,安装在电脑上的(通常是windows下)模拟ip话机的软件。当然安卓和linux下也有软电话。
SIP Server/IPPBX,完成VOIP交换的设备或者安装在pc上的软件。
IP网关,通常是完成VOIP网络到PSTN E1数字接口或者模拟电话线接口转换的设备。
3.
SIP 常见问题哪些?
SIP协议广义上是包括SIP协议,RTP协议和SDP协议。他们的关系可以这样描述:SIP是信令控制,相当于电信里面的7号信令,或者相当于发号施令的领导角色,RTP 是媒体传输,相当于干体力活的民工角色。SDP 是媒体描述,相当于描述使用哪些或者哪种语音和视频编码进行协商。
举例说明:a 用户使用电话打给b手机,SIP是信令控制相当于a 举起电话机然后b的号码和通话之后挂机的动作。RTP是媒体传输相当于电话通了a 和b 语音通话的动作。SDP 是媒体描述相当于a 和b 都使用普通话进行对话。假如a 和b 没有一个共同的语言会导致协商不成功。就无法通话。比如a是中国人,b是英国人,a问:会说汉语吗。b听不懂,b 问:can you speak English?a 也不懂。最终只能放弃通话。
SIP是控制命令,是具有文本的,通过网络抓包是可以直接看出内容,这样调试排查问题就比较方便。SIP协议默认使用UDP协议。但是也支持TCP协议,有些变态的系统或者软电话只能支持TCP,比如微软的LYNC。SIP默认是使用5060端口。但是通常也可以指定其他端口。比如5061之类。
有些软件,比如tom 版本的skype 启动之后也会使用5060端口,这样导致了其他软电话或者FreeSwitch启动失败。通常系统有很多线路可以并发使用,但是他们都是使用一个端口(默认是5060)进行信令控制的。
SIP使用模式有两种:一种是注册模式,大家每个人分配一个分机号码和密码,然后都注册到一个SIP SERVER /IPPBX 上。呼叫的时候只要呼叫分机号码就可以。
另外一种是点对点模式:通过呼叫软电话的或者IP话机的IP+端口(默认5060)的模式。
标准的sip 地址格式是:sip:1234@IP:PORT。
其中1234是号码。
4.
RTP 常见问题有哪些?
RTP的默认端口是没有指定的,通常系统有很多线路可以并发使用,每一路通常都会占用两个端口一个是RTP 一个是RTCP,RTP 媒体传输的机器的IP 和SIP 的信令控制的IP可以不是同一个。虽然很多情况下他们是一个,表示信令控制和媒体传输都是一台机器上发起的。但是大的系统里面往往他们的ip 不是同一个。小系统:一个部门就是一个人,这个人既是领导(SIP)也是干苦力活的(RTP)。因为一个机器的cpu 毕竟计算能力有限。而一个大的系统相当于一个部门很多人,领导(SIP)通常一个就够,干苦力活的(RTP)需要很多人。SIP的流量通常很少,但是RTP 的流量通常是巨大的,因为要传输语音流个视频流。所谓:领导一句话,手下干活累死人。RTP的语音流是一份一份(从时间上看是离散的)进行传输的。一份就是一个UDP包,通常情况下一个包包括了
20ms或30毫秒的语音。每个包的负载大小根据语音编码器不同,在几十
Byte 到几百Byte之间。这样的话,假如是20ms 一个包,1秒的语音要发
50次,可见流量是很大的。
5.
SDP 常见问题有哪些?
SDP 是对媒体的描述,描述了RTP 的IP 和端口PORT。说明自己这边有哪些编码器(相当于一个人会说哪些语言),也描述了是否支持视频(说白了就是有视频编码器)。也描述了是否支持按键。很多情况下呼叫建立不成功都是因为SDP 协商不成功导致。这样需要通过抓包才能看出。
总结一下:
SDP 的核心有几个:
A.说明媒体的来源IP地址
B.说明媒体的来源的端口port
C.说明自己有哪些媒体能力(能说几个语言)
D.包括对dtmf 按键的是否支持是说明。一旦两个软电话要对话,就要对比交换上面这些信息。
6.
常用的支持语音的软电话有哪些?
Windows 下常见的软电话有:Xlite/ eyebeam,kapanga,linphone,jitsi,PJSIP等等
Linux下有Linphone,PJSIP等
安卓系统下常用的软电话有:sipdroid,CSipSimple,jPhoneLite,IMSDROID。
PDF 文件使用 “pdfFactory Pro” 试用版本创建
www.fineprint.cn
7.
常用的支持视频的软电话有哪些?
Windows 和Linux 下的软电话大多也支持视频。
Windows 下常见的软电话有:
Xlite/ eyebeam,kapanga,linphone,jitsi等等
Linux下有Linphone等
本人使用kapanga和Linphone 测试过FreeSwitch的视频通话。
8.
常见语音编码器有哪些?
常用的语音编码器有G.711 Ulaw 和Alaw。 这个是绝大多数的VOIP都支持的。G.711要是都不支持,就不能称为VOIP,所以刚刚开始测试使用某个系统或者软电话,使用G.711准没有错。G.729 这个是公网上使用最多的,但是由于有授权,有些软电话不支持,或者需要采购才能使用。
iLBC 大多数软电话都支持,而且没有授权限制。
SPEEX大多数软电话都支持,网络上也有源码,siri 和科大的语音识别的的语音传输就是使用这个编码,flash player 也支持这个编码。
GSM
9.常见视频编码器有哪些?
H.263 最基本的视频,大多数的支持视频的软电话都支持。其中H.263 又细分为
H.263 , H.263+/ H.263-1998等。
MP4
H.264 (MPEG-4 part 10)效果最好
10. PSTN和VOIP区别有哪些?
PSTN是电路交换,是时分,话音质量是可靠的可控的,无论闲死还是忙死,话音质量都是一样的。VOIP 是数据包交换,话音质量有用以太网的天生属性,质量是不可靠的。通常网络情况下话音效果不错,但是一旦网络繁忙,话音质量就立刻下降。因此VOIP 的运营最大的难点是在于系统的稳定性。
11. PSTN常用信令有哪些?
常用的PSTN信令有ISDN PRI 和SS7。其中区别在于ISDN PRI 是一个E1(30路话路)使用其中的16时隙作为信令控制。SS7 是7号信令,是很多E1,最大可以到2^14 (4096)话路共用某个E1的某个时隙(通常是16时隙,作为信令LINK)作为信令控制,为了提高可靠性,通常使用两个E1线路上的时隙(即两个LINK)作为信令控制。
12. VOIP的系统开发和测试有哪些常用工具?
最常用的通常是网络抓包程序Ethereal + WinPcap或者wireshark + WinPcap
有用sip协议是文本字节流模式,抓包之后,直接使用工具里面的查看窗口直接查看。
13.
如何使用Ethereal对指定机器进行抓包分析?
在Capture 菜单中选择Option子菜单。然后在出现的对话框中选择网卡(Interface)。在Capture Filter 里面输入udp and host 192.168.11.105。Udp表示只是抓udp 的包,因为sip默认是基于udp,rtp 也是基于udp。Host 192.168.11.105 表示只抓
ip 是192.168.11.105的数据包(从本机器发给192.168.11.105,或者从192.168.11.105发给本机)。其中192.168.11.105也可以是本机地址。然后点start 开始抓包。
14.
如何使用Ethereal对指定端口进行抓包分析?
类似上面问题,在Capture Filter 里面输入udp and port 5060。Udp表示只是抓udp 的包。Port 5060 表示只是抓5060 端口上的包。因为sip默认是基于udp,默认是5060 端口。以上的命令就是只是抓sip信令控制的包。
15. Ethereal能对本机内的通信进行抓包吗?
本机的内地通信实际上没有经过网卡,因此Ethereal无法实现对本机内的通信进行抓包,比如本机的软电话呼叫本机上的FreeSwitch,就无法抓包。
16. Ethereal能对其他机器之间的通信进行抓包吗?
一般情况下,假如A机器和B机器进行通信,而你要在C机器上对A机和B机的通信进行抓包,是抓不到包的。比如a机的软电话呼叫B本机上的软电话,你要在C机器上对他们的通信进行抓包,是抓不到包。
17. Windows下使用啥命令工具可看哪个port被谁占用?
首先:在windows 命令行下:输入
netstat -oan >c:\tmp.txt
然后打开c:\tmp.txt:
可以看到UDP 5060端口的占用的。PID 是6032。
从图中我们看到6032 还占用了5080等端口。
打开任务管理器,点查看菜单,然后点选择列。。。在出现的对话框中勾选
PID,如下图:
然后在进程标签页总找出
PID 是6032的进程名称。如下图:
从图中可以看出是FreeSwitchConsole.exe。我们知道这个是
FreeSwitch的启动进程。
18.
如何根据使用的编码器计算VOIP需要的带宽?
VOIP的数据包包括SIP 数据包和RTP 数据包,SIP只是传输控制命令,跟RTP的语音媒体数据包比较起来简直就是九牛一毛,完全可以忽略不计。下面仅仅计算RTP的数据包
RTP的数据包=以太网地址+IP类型+IP包头+UDP包头+RTP包头+语音编码之后的负载。以G.711 alaw 编码器为例,以太网地址12字节,IP类型2字节。IP数据包头:20字节。UDP协议的报头: 8个字节。RTP包头是12字节。语音负载:160字节:一个包20ms,8K的采样频率,每个采样点使用alaw 编码之后是1个字节。因此20ms的语音在编码之后的负载是160 字节。总共是12+2+20+8+12+160=214字节。1秒的语音就是
214*(1000/20)=214*50=10700字节,按照流量计算就是10700*8/1024=84Kpbs 左右。
以G.729 编码器为例:
假如是G.729. 一个包20ms其他不变,只是语音编码之后变成了8字节,因此语音负
载是8字节,可以算出来:总共是
12+2+20+8+12+8=62字节
语音就是62*(1000/20)=214*50=10700字节,按照流量计算就是10700*8/1024=24Kpbs
左右。一个10Mbps 带宽,有效的带宽能达到4-5M左右,以G.711 alaw为例子:一线需要的带宽是84Kbps.因此可以支持的线路数是:5*1000/84=60线左右。但是考虑到业务通常还有带宽需求,因此,30线是比较保险的。以G.729为例子:一线需要的带宽是24Kbps.因此可以支持的线路数是:5*1000/24=200线左右。但是考虑到业务通常还有带宽需求,比如CRM的弹屏通常包括了用户360度的信息和历史记录,数据量也是不小。因此,10M的带宽,100线是比较保险的。
另外要注意的是ADSL 的带宽上行和下行是不对称的,而VOIP的语音通话的开销是要求
上下行一样的带宽。因此假如并发使用的线路多了,而且碰到单边效果不好的情况,很可能的带宽不对称导致的。
19.
如何测试你的系统的WAN的进出口带宽?
有很多网站都可以提供带宽测试 http://www.speedtest.net/是比较好用的一个。上面有很多。host可以选择进行测试。根据本人测试的结果还是比较符合实际情况。
FreeSwitch基础和配置部分
20. FreeSwitch是什么?
FreeSWITCH 是一个开源的电话交换平它具有很强的可伸缩性,从一个简单的软电话客户端到运营商级的软交换设备。他可以是一个SIP SERVER,也可以通过他实现很多协议转换。也可以实现VOIP 的IVR 或者呼叫中心。
21. FreeSwitch是谁发起开发的?
Anthony Minessale 在2005年的时候认为Asterisk 存在许多问题而修复这些问题需要很多时间。于是他想从头创建一个Asterisk 2.0后面就变成了FreeSWITCH,因此从某种意义上说FreeSWITCH是Asterisk 2.0。一开始没有人认真考虑他的问题,因此他就自己开发,牛人通常就是这样。呵呵
22. FreeSwitch历史是什么?
2005开始怀孕,2007年发表。
1.0 2010年发布
1.0.6 2012年发布
1.2.X 和1.3.X
23. FreeSwitch能做啥?
FreeSWITCH几乎无所不能可以用作,一个简单的交换引擎、一个PBX,一个媒体网关或媒体支持IVR 的服务器等。它支持SIP、H323、Skype、Google Talk,RTMP 等协议,支持板卡E1接口,这样就可以实现通过运营商打电话到手机或者固定电话之上。
24. FreeSwitch如何与其他系统集成?
FreeSwitch 跟其他系统集成基本上是通过他的支持的协议接口进行集成的。最主要的集成方式就是通过SIP协议。可以作为一个分机注册到其他系统上,也可以其他系统作为分机注册到FreeSwitch上以实现互通,甚至可以不通过注册直接使用点对点方式进行通讯。实际使用过程中:集成过以下第3方的系统或者设备,
FreeSwitch==SIP==毅航公司ISX1000/4000系列多媒体可编程交换机,ISX1000/4000系列的新驱动支持iLBC编码器,可以和FreeSwitch公网集成,从而实现FreeSwitch 的落地出口。
FreeSwitch==SIP==东进公司,Keygoe 系列多媒体可编程交换机
FreeSwitch==SIP==Dialogic公司,HMP多媒体可编程软交换系列
25. FreeSwitch最新版稳定本号(2012-11-21)是什么?
在http://files.freeswitch.org/上最新的稳定版本是1.2.4:
http://wiki.freeswitch.org/wiki/Main_Page上最新的稳定版本是1.2.5
The current stable release of FreeSWITCH is 1.2.5, released on 21-November-2012.
通过git 上去取,最新的版本据说是1.3.3。
26. FreeSwitch支持哪些操作系统?
主流的LINUX 和WINDOWS操作系统都支持。都有现成的安装包。这个对初学者比较有利。很多初学者不熟悉LINUX系统,使用WINDOWS可以迅速入门,安装配置到运行起来呼叫通第一个软化电话前后不超过10分钟。会比较有成就感;-) !
27.去哪里下载FreeSwitch安装包和源码?
在http://files.freeswitch.org/下载比较方便,初学者可以现在已经编译好的二进制安装包。使用GIT 去下载源码很麻烦。因此不建议。
28. FreeSwitch在windows下如何安装?
以1.2.3版本为例,下载freeswitch1.2.3.msi然后双击运行,然后点next,然后在END-User License 界面勾选I Accept。。。,然后点next,然后选择Complete 完全安装(反正没有多大),然后点Install。就开始安装拷贝。拷贝完成点ok 完成安装。假如机器上有360之类的还要点允许访问和记住选择。FreeSwitch 会安装到C:\Program Files\FreeSWITCH 目录下假如机器上之前安装过去其他版本,请先卸载,然后完全删除C:\Program Files\FreeSWITCH 目录以及目录下的所有东西,然后再安装,假如C:\Program Files\FreeSWITCH 目录以及目录下的有文件东西将可能导致安装不成功。
29. FreeSwitch在LINUX下如何编译和安装?
参考:http://www.91asterisk.com/thread-491-1-1.html
先到http://files.freeswitch.org/下载freeswitch-1.2.1.tar.gz
# tar zxvf freeswitch-1.2.1.tar.gz
编译源代码(Compiling the Source Code ):
编译源代码(Compiling the Source Code ):
认FreeSwitch安装在/usr/local/freeswitch/,然而如果不想使用默认安装路径,可以添加“–prefix=”参数改变。FreeSwitch的安装路径。
# ./configure –prefix=/usr/freeswitch
配置好环境后,使用
make命令来编译:
# make
选择哪些模块编译到FreeSwitch核心(core)。
# vi modules.conf
loggers
mod_console -Logs messages to the console
mod_syslog -Logs messages to your syslog repository
applications
mod_bridgecall -Allows you to bridge calls between two different endpoints
mod_commands -A mass plethora of API interface commands
mod_conference -Conference room module
mod_dptools – Dialplan Tools: Sets channel variables and sleeps
mod_echo -Echo testing module
mod_ivrtest -Testing ground for functions in IVR.
mod_park -Park calls
mod_playback -Plays back sound files
mod_rss -Obtains RSS feeds
mod_skel – Dummy module
asr_tts
mod_cepstral -Links into Cepstral for dynamic sound output
codecs
mod_amr
mod_g711 -G.711u/G.711a codec (ulaw/alaw)
mod_g722
mod_g723_1
mod_g726
mod_g729
mod_gsm -GSM codec
mod_ilbc -ILBC codec
mod_l16 -L.16 codec
mod_lpc10 lpc 10 codec
mod_speex -Speex codec
dialplans
dialplans
directories below)
mod_dialplan_xml -Allows you to program dialplans in XML format
directories
mod_ldap -LDAP module made to obtain dialplans, user accounts, etc.
endpoints
mod_dingaling – Jabber/G! Talk integration module
mod_iax -IAX2 module
mod_portaudio -Voice through a local soundcard
mod_sofia -SIP module
mod_wanpipe -Sangoma Card module
mod_woomera -H.323/Woomera module
event_handlers
mod_cdr -mod_event_multicast -Takes events received and multicasts them
mod_event_socket
mod_event_test
mod_xmpp_event
mod_zeroconf -Zeroconf broadcast module
formats
mod_native_file
mod_sndfile -Interface to libsndfile for file formats
languages
mod_perl -Allows dialplans to be written in Perl
mod_spidermonkey -Allows dialplans to be written in Javascript
mod_spidermonkey_teletone
mod_spidermonkey_core_db
mod_spidermonkey_odbc
timers
mod_softtimer -Timing module for FreeSWITCH. No hardware needed
xml_int
mod_xml_rpc -Allows to remotely control the API
安装源代码:
你要妥当选择你需要的模块,因为你现在构造的可能在这些模块之上安装:
# make installall
==================Rebuilding==================
After doing a “svn update” or changing some source files, you may want to clean out your
build area. To do that, do “make megaclean”. Note that this leaves the “configure” output files。
such as “config.status”. If you want to undo that, then do “make distclean”, and run “./configure”
again.
such as “config.status”. If you want to undo that, then do “make distclean”, and run “./configure”
again.
Then do a “make installall”.
To undo an install do “make modwipe uninstall”. This affects only files under $(PREFIX). Note
that some make targets are reliant on binaries under $(PREFIX) installed by earlier targets (such
as apr-1-config). So you can’t just remove the entire $(PREFIX) directory and do another make,
without cleaning out everything (in the manner just described).
Note that doing a “make installall” will not overwrite any existing files in $(PREFIX)/conf if
there already exists a $(PREFIX)/conf/freeswitch.xml file.
==============================================
配置
Freeswitch:
在你的freesitch启动之前,你将必须配置位于freeswitch的/usr/freeswitch/conf/($(PREFIX)/conf/)目录的xml配置文件。运行FreeSwitch:
# cd /usr/freeswitch
./freeswitch
30. FreeSwitch在windows下如何编译?
1.2.1或者之后的版本需要使用VS2010进行编译。
本人使用VS2010旗舰版(安装VS2010的时候C#也要安装,否则有些FreeSwitch的工程无法打开)。VS2010 运行之后,打开源码包里面的Freeswitch.2010.sln。打开之后可能会出现点确定不管他。点确定不管他。注意:有许多模块,比如rtmp_mod 模块默认是没有编译产生的,需要手工自己在模块对应的工程右键点编译。
31. FreeSwitch在windows下如何安装到C盘之外?
目录下,而且变态的安装包不能修改安装目录。只能安装到C:\ProgramFiles\FreeSWITCH这个目录。要搞到其他目录运行可以在安装之后,拷贝整个FreeSWITCH 目录到其他盘比如d:。然后运行d:\ FreeSWITCH目录下的FreeSwitchConsole.exe。
32. FreeSwitch在windows下如何启动?
双击FreeSwitchConsole.exe 就可以。可以把这个文件的快捷方式放到启动里面(在程序里面右键点启动,然后点打开)。然后把windows 设置成自动登录(需要修改注册表)。这样万一机器重启,机器就自动登录了,然后FreeSwitchConsole.exe会自动启动。
33. FreeSwitch在实际使用部署的时候如何启动比较安全?
带上-nonat 参数启动比较安全。FreeSwitchConsole.exe -nonat。
34. FS_CLI跟FreeSwitch是啥关系?
FS_Cli 是FreeSwitch 一个客户端控制界面,可以在FS_Cli上对FreeSwitch进行管理,比如日志级别设置,查看日志,执行呼叫等操作。FS_Cli 是通过ESL 接口对FreeSwitch进行管理。FS_Cli也可以执行APP模拟进行发起呼叫,播放语音等功能。FS_Cli 有快捷按键,F1-F12,功能对应如下:
<key name=”1″ value=”help”/>
<key name=”2″ value=”status”/>
<key name=”3″ value=”show channels”/>
<key name=”4″ value=”show calls”/>
<key name=”5″ value=”sofia status”/>
<key name=”6″ value=”reloadxml”/>
<key name=”7″ value=”console loglevel 0″/>
<key name=”8″ value=”console loglevel 7″/>
<key name=”9″ value=”sofia status profile internal”/>
<key name=”10″ value=”sofia profile internal siptrace on”/>
<key name=”11″ value=”sofia profile internal siptrace off”/>
<key name=”12″ value=”version”/>
FS_Cli默认是连接到本机的机器上,也可以连接到其他机器上的FreeSwitch进行管理。
35.在FS_CLI上如何拨打测试分机?
FS> originate sofia/profile/internal/1000 &echo (拨打1000并执行echo程序)
FS> originate user/1000 &echo (同上)
FS> originate sofia/profile/internal/1000 9999 (相当于在软电话1000上拨打9999)
FS> originate sofia/profile/internal/1000 9999 XML default (同上)
36. FreeSwitch能跟哪些外部协议对接?
它支持SIP、H323、Skype、Google Talk,RTMP 等协议。实际本人测试过sip 和RTMP。
37. FreeSwitch如何跟PSTN 对接,实现落地?
有两个办法:
办法1.
FreeSwitch 通过SIP接到第三方的VOIP网关上,VOIP网关通过E1 接口接到
PSTN上,通常VOIP网关可以支持ISDN PRI 和SS7 信令。比如上面提到的通过ISX1000/4000进行落地。
办法2.
使用支持FreeSwitch的E1接口卡,在机器上插这种卡,安装卡驱动。然后安装FreeSwitch ,再进行协议配置才能使用。
38.已经有哪些硬件板卡支持FreeSwitch 跟运营商的E1电路对接?
目前已知的sangoma板卡可以ISDN PRI和SS7信令。
如何在LINUX 的freeswitch 平台上安装sangoma 请参考:
http://www.voip88.com/article-1202-1.html
如何安装在Windows 的freeswitch 平台上安装sangoma 请参考:
http://wiki.sangoma.com/fs-windows-freeswitch-compile-isdn
SS7信令的支持软件是商业版本,是要额外收费的,根据了解,1个E1端口差不多是1K的授权费用,根据http://wiki.sangoma.com/wanpipe-freeswitch#sangoma-freetdm-ss7-library-libsngss7 上面的介绍,ss7不支持tup,仅仅支持isup:
Sangoma FreeTDM SS7 Library (libsng_ss7)
Features
Sangoma’s SS7 Library uses Continuous Computing’s (Trillium) MTP2/3 and ISUP stacks
to provide a commercial grade SS7 interface to FreeSWITCH, via the FreeTDM channel
driver.
List of supported variants
ISUP (ITU/ANSI)
MTP3 (ITU/ANSI)
MTP2 (ITU/ANSI)
SCCP API
39. FreeSwitch默认配置启动之后占用哪些端口?
FreeSwitch启动之后,占用以下这些端口:
SIP 5060 5080
RTP:16384-32768
TCP:1935 假如启动
mod_rtmp模块
8021 等
具体如下:
TCP 0.0.0.0:1935
TCP TCP
TCP 192.168.11.105:5060
TCP 192.168.11.105:5080
UDP 127.0.0.1:50621
UDP 127.0.0.1:50622
UDP 127.0.0.1:50623
UDP 127.0.0.1:50624
UDP 192.168.11.105:5060
UDP 192.168.11.105:5080
假如这些端口已经被占用,将可能导致启动错误。
40. FreeSwitch在多个IP机器上如何指定运行在某个IP上?
修改\conf\sip_profiles\ internal.xml和external.xml
<param name=”sip-ip” value=”$${local_ip_v4}”/>
<param name=”sip-port” value=”$${internal_sip_port}”/>
<param name=”rtp-ip” value=”$${local_ip_v4}”/>
对应于internal.xml和external.xml 的sip 的ip 和rtp 的ip。
41. FreeSwitch常用目录有哪些?
主要目录:
mod 可加载模块sounds 声音文件,使用playback() 时默认的寻找路径
log 日志,CDR 等。recordings 录音,使用record() 时默认的存放路径
conf 配置文件目录。
42. FreeSwitch基本配置文件有哪些?
在/Conf目录下:
vars.xml文件:一些常用变量默认分机密码=1234,codec,sip ,ip,port等
/sip_profiles
/autoload_configs
/dialplan
/directory
/dialplan/default.xml缺省的拨号计划
/directory/default/*.xml SIP用户,每用户一个文件
/sip_profiles/internal.xml一个SIP profile,或称作一个SIP-UA,监听在本地IP及端口5060,一般供内网用户使用
/sip_profiles/externa.xml另一个SIP-UA,用作外部连接,端口5080
/autoload_configs/modules.conf.xml配置当FreeSWITCH启动时自动装载哪些模块。
43. FreeSwitch如何设置日志级别?
在FS_CLI管理界面上:
FS>console loglevel 级别
FS>console loglevel 级别
从0-7,比如6设置成INFO 级别,基本越高日志越大比如设置成7,DEBUG级别。几乎每个操作都很多日志。
输入之后,会返回当前的级别提示如下:
FS> console loglevel 0
+OK log level 0 [0]
+OK console log level set to CONSOLE
FS> console loglevel 7
+OK log level 7 [7]
+OK console log level set to DEBUG
FS> console loglevel 6
+OK log level 6 [6]
+OK console log level set to INFO
假如要看sip 的详细日志,使用以下命令:
sofia profile internal siptrace on打开sip 日志
sofia profile internal siptrace off关闭sip 日志
44. FreeSwitch如何看有多少用户注册上来?
在FS_CLI管理界面上:
FS>sofia status profile internal
(显示多少用户已注册)
假如刚刚启动,没有人注册上来:
提示如下:
。。。。。
ZRTP-PASSTHRU false
AGGRESSIVENAT false
STUN-ENABLED true
STUN-AUTO-DISABLE false
CALLS-IN 0
FAILED-CALLS-IN 0
CALLS-OUT 0
FAILED-CALLS-OUT 0
REGISTRATIONS 0
我使用eyebeam测试,实际上开了两个软电话,注册两个上来。控制台看到是2注册上来。这里面还有sip 的ip,拨入和拨出系统的编码器等等很多有用信息。
45. FreeSwitch如何看有哪些用户注册上来?
在FS_CLI管理界面上:
FS> sofia status profile internal reg 显示哪些用户已注册
刚刚启动FreeSwitch,FS> sofia status profile internal reg提示如下:
+OK log level [7]
freeswitch@internal> sofia status profile internal reg
Registrations:
==============================
==============================
Total items returned: 0
==============================
==============================
Total items returned: 0,
FS> sofia status profile internal reg提示如下:
这个可以看到注册上来的机器的ip 地址分机号码等详细信息。
我使用eyebeam测试,实际上注册两个上来。控制台看到是2个注注册上来。
\db\ sofia_reg_internal.db 里面保存的是注册的信息。
假如碰到意外情况,可能存在有软电话注册上来之后,一直在里面的情况。看这个行:
Status: Registered(UDP)(unknown) EXP(2012-11-22 20:45:46) EXPSECS(3655)
最后的秒数目就是注册有效期。
46. FreeSwitch默认配置启动之后有哪些默认注册用户和密码是多少?
FreeSwitch默认配置启动默认有1000-1019 20个帐号。他们的默认密码是1234.
使用软电话可以注册上去进行呼叫。
47. FreeSwitch默认配置启动之后有哪些分机比较有用?
FreeSwitch默认配置启动之后。下面的这些分机会经常用到
9196 echo,回音测试
9195 echo,回音测试,延迟5秒
5000 示例IVR
30xx电话会议比如
3000,3001.拨入之后假如是第一个人会听音乐
48.如何手工添加FreeSwitch分机?
首先在conf/directory/default目录下增加一个用户配置文件,配置文件可以参考已经有的配置文件。然后修改拨号计划(Dialplan)使其它用户可以呼叫到它。最后重启FreeSwitch。
49. FreeSwitch拨号计划的正则表达式有哪些是最常用的模式?
拨号计划使用perl的正则表达式。
常用的匹配模式如下:
^ 表示开始匹配,^123 表示匹配123开头
$表示结束匹配
456$表示匹配456结束
| 表示或者,匹配任何一个
[] 表示匹配其中的任意一个字符
[0-9] 等于匹配[0123456789]
\d等于匹配[0-9]
\d+ 等于匹配1 个或多个数字
\d* 等于匹配0 个或多个前面的字符
50. FreeSwitch默认配置如何修改拨号计划设置没有注册上来不走留言信箱?
修改/conf/dialplan 目录下的default.xml文件。在<context name=”default”> 之后加入:
<extension name=”Local_Extension2″>
<condition field=”destination_number” expression=”^(1[01][0-9][0-9])$”>
<action application=”export” data=”dialed_extension=$1″/>
<action application=”set” data=”call_timeout=10″/>
<action application=”set” data=”hangup_after_bridge=true”/>
<action application=”set” data=”continue_on_fail=false”/>
<action application=”bridge” data=”user/${dialed_extension}@${domain_name}”/>
</condition>
</extension>
其中的destination_number 是分机号码
这个例子表示分机号码是1000-1199 共200个
51. FreeSwitch默认配置加载哪些编码器?
使用show codec 命令可以看到系统加载的编码器。
FS> show codec
type,name,ikey
codec,ADPCM (IMA),mod_spandsp
codec,AMR,mod_amr
codec,G.711 alaw,CORE_PCM_MODULE
codec,G.711 ulaw,CORE_PCM_MODULE
codec,G.722,mod_spandsp
codec,G.723.1 6.3k,mod_g723_1
codec,G.726 16k,mod_spandsp
codec,G.726 16k (AAL2),mod_spandsp
codec,G.726 24k,mod_spandsp
codec,G.726 24k (AAL2),mod_spandsp
codec,G.726 32k,mod_spandsp
codec,G.726 32k (AAL2),mod_spandsp
codec,G.726 40k,mod_spandsp
codec,G.726 40k (AAL2),mod_spandsp
codec,G.729,mod_g729
codec,GSM,mod_spandsp
codec,H.261 Video (passthru),mod_h26x
codec,H.263 Video (passthru),mod_h26x
codec,H.263+ Video (passthru),mod_h26x
codec,H.263++ Video (passthru),mod_h26x
codec,H.264 Video (passthru),mod_h26x
codec,LPC-10,mod_spandsp
codec,PROXY PASS-THROUGH,CORE_PCM_MODULE
codec,PROXY VIDEO PASS-THROUGH,CORE_PCM_MODULE
codec,RAW Signed Linear (16 bit),CORE_PCM_MODULE
codec,Speex,mod_speex
codec,G.711 alaw,CORE_PCM_MODULE
codec,G.711 ulaw,CORE_PCM_MODULE
codec,G.722,mod_spandsp
codec,G.723.1 6.3k,mod_g723_1
codec,G.726 16k,mod_spandsp
codec,G.726 16k (AAL2),mod_spandsp
codec,G.726 24k,mod_spandsp
codec,G.726 24k (AAL2),mod_spandsp
codec,G.726 32k,mod_spandsp
codec,G.726 32k (AAL2),mod_spandsp
codec,G.726 40k,mod_spandsp
codec,G.726 40k (AAL2),mod_spandsp
codec,G.729,mod_g729
codec,GSM,mod_spandsp
codec,H.261 Video (passthru),mod_h26x
codec,H.263 Video (passthru),mod_h26x
codec,H.263+ Video (passthru),mod_h26x
codec,H.263++ Video (passthru),mod_h26x
codec,H.264 Video (passthru),mod_h26x
codec,LPC-10,mod_spandsp
codec,PROXY PASS-THROUGH,CORE_PCM_MODULE
codec,PROXY VIDEO PASS-THROUGH,CORE_PCM_MODULE
codec,RAW Signed Linear (16 bit),CORE_PCM_MODULE
codec,Speex,mod_speex
但是要注意,有加载不一定就能使用。具体能否使用还要看vars.xml里面的配置
52. FreeSwitch默认配置哪些编码器能使用?
上面说到系统加载了很多编码器,但是不一定能使用,具体能使用哪些编码器,要看conf/ vars.xml 配置文件里面的下面的参数:
<X-PRE-PROCESS cmd=”set” data=”global_codec_prefs=G722,PCMU,PCMA,GSM”/>
<X-PRE-PROCESS cmd=”set” data=”outbound_codec_prefs=PCMU,PCMA,GSM”/>
global_codec_prefs是全局能使用的编码器。
outbound_codec_prefs 是FreeSwitch拨出的时候使用的编码器。
53. FreeSwitch如何设置修改默认配置添加支持G.729 ,iLBC等编码器?
首先查看:conf\autoload_configs 目录下的modules.conf.xml 配置文件
看看是否有打开注释加载到系统,注释了就是没有加载的。
比如mod_ilbc 默认是没有加载的。而mod_g729 默认是加载的。然后在conf/ vars.xml 配置文件里面的看下面的参数:
<X-PRE-PROCESS cmd=”set” data=”global_codec_prefs=G722,PCMU,PCMA,GSM”/>
<X-PRE-PROCESS cmd=”set” data=”outbound_codec_prefs=PCMU,PCMA,GSM”/>
假如没有就加上:
<X-PRE-PROCESS cmd=”set” data=”global_codec_prefs=G722,PCMU,PCMA,GSM,G729,iLBC”/>
<X-PRE-PROCESS cmd=”set” data=”outbound_codec_prefs=PCMU,PCMA,GSM,G729,iLBC”/>
然后保存。重启FreeSwitch就可以。
然后有人说我加了G729,但是还是不行,那是因为FreeSwitch 的G729只能支持透传的方式。不能转码,导致呼叫到IVR,或者两个软电话一个是G729一个不是G729 也不能话。
要想使用G729通话,只能两个软电话都是G729的情况才行。
54.如何看FreeSwitch当前支持哪些语音和视频编码器?
结合使用show codec 命令可以看到系统加载的编码器和
sofia status profile internal
或者假如是5080端口
sofia status profile external
55.软电话上如何指定编码器?
大多数软电话上都能指定使用编码器。我们强烈建议在内网测试,只要指定G.711 alaw语音编码器就可以。简单明了,有问题也好查,因为SDP 的协商过程太过复杂,初学者很晕。以eyebeam为例子,在eyebeam上点右键,点setting然后点media,audio,advance。(通常情况下视频是不需要的,可以都去掉,只有当需要视频功能的时候才加上h.263)。
56.如何实现用FreeSwitch进行转码?
FreeSwitch的默认配置是不进行转码的,比如软电话a 呼叫软电话b,a 只有G.711alaw,b只有G.711 ulaw。他们都注册到FreeSwitch上,注册是成功的,但是无论a 呼叫b 还是b 呼叫a 都是不行的。但是a 呼叫a 自己是可以的,b呼叫b自己也是可以的。
要让FreeSwitch支持分机间能转码通话。需要修改:
conf\sip_profiles\ 目录下的
internal.xml或external.xml (5080端口上的)下面两个参数都设置为false:
<param name=”inbound-late-negotiation” value=”false”/>
<param name=”inbound-zrtp-passthru” value=”false”/>
57. FreeSwitch哪些编码器不支持转码?
在FreeSwitch里面的G.729 和G.723.1 AMR等都是只能透传的。无法实现转码也无法实现IVR。
58.如何解决FreeSwitch的G.729的转码?
要实现G.729的转码需要自己开发编码器,即在FreeSwitch的源码上进行修改。有人已经做成功这个事情。有编译好的二进制包可以使用。在群里面有做好的二进制安装包,我放到网络上:url下载地址:http://42.121.7.65/FreeSWITCH1.2.0-G729.rar。或者自己增加这个编码器。或者网络上购买别人做好的这个编码器。成都的一家公司有出售这个源码。
59. FreeSwitch如何编程修改源码,手工添加编码器?
根据http://www.linuxidc.com/Linux/2012-08/68045.htm上面的介绍:
以模块方式加载的编码在目录src\mod\codecs下面,所以如果我们想添加自己的编码,在此目录下参考其他实现即可,freeswitch对新编码的添加接口比较简单,自己增加编解码器步骤如下:
注册几个四个回调:
init
encode
decode
destroy
然后通过switch_core_codec_add_implementation把这几个回调的实现注册进去。
60. FreeSwitch如何设置修改默认配置才能支持视频通话?
在conf/ vars.xml 配置文件里面修改下面的参数,增加H263或者H264编码:
<X-PRE-PROCESS cmd=”set” data=”global_codec_prefs=G722,PCMU,PCMA,GSM”/>
<X-PRE-PROCESS cmd=”set” data=”outbound_codec_prefs=PCMU,PCMA,GSM”/>
<X-PRE-PROCESS cmd=”set” data=”global_codec_prefs=G722,PCMU,PCMA,GSM”/>
<X-PRE-PROCESS cmd=”set” data=”outbound_codec_prefs=PCMU,PCMA,GSM”/>
<X-PRE-PROCESS cmd=”set” data=”global_codec_prefs=PCMU,PCMA,GSM,H263″/>
<X-PRE-PROCESS cmd=”set” data=”outbound_codec_prefs=PCMU,PCMA,GSM,H263″/>
然后保存。重启FreeSwitch就可以。要测试支持视频通话,软电话要支持。先要有视频的编码器,然后在eyebeam 里面还要设置。
另外:FreeSwitch 的视频通话是不支持转码的,因此使用的时候需要所有的软电话都设置为一样的视频编码器,比如都设置为H263编码器。
61. FreeSwitch如何修改默认配置实现在两个网卡上不同的网段上,与运行两个网段上的软电话互通.?
在两个网卡上不同的网段上,与运行两个网段上的软电话互通.这是一个实际应用环境中会经常碰到的问题。
比如:
192.168.1.X 5060 internal profile 1000 1002 register on 192.168.1.X 5060
1000==>1002 ok 1002==>1000 ok
192.168.11.X 5060 external profile 1000 1002 register on 192.168.11.X
5060 1000==>1002 fail 1002==>1000 ok
1000 register on internal 192.168.1.X 5060 1002 register on external
1000 register on internal 192.168.1.X 5060 1002 register on external
X
5060
external 1002 ==>internal 1000 ok
internal 1000==>external 1002 ok
结论只能呼叫在internal上注册的分机。
修改文件internal.xml和external.xml
位置:/usr/local/freeswitch/conf/sip_profiles
external.xml 修改内容:
<param name=”rtp-ip” value=”192.168.1.236″/>
<param name=”sip-ip” value=”192.168.1.236″/>
<param name=”sip-port” value=”5060″/>
internal.xml
<param name=”rtp-ip” value=”192.168.11.101″/>
<param name=”sip-ip” value=”192.168.11.101″/>
修改sip的external与internal配置文件
打开sip_profile/external.xml文件,反注释下面的行:
<param name=”force-register-domain” value=”$${domain}”/>
<param name=”force-register-db-domain” value=”$${domain}”/>
<param name=”dbname” value=”share_presence”/>
<param name=”presence-hosts” value=”$${domain}”/>
打开sip_profile/internal.xml文件,反注释相同的行:
<param name=”force-register-domain” value=”$${domain}”/>
<param name=”force-register-db-domain” value=”$${domain}”/>
<param name=”dbname” value=”share_presence”/>
<param name=”presence-hosts” value=”$${domain}”/>
FreeSwitch FlashPhone部分
62.什么是flash phone/SIP?
Flash phone 就是通过网页上的flash插件来实现语音媒体通话的功能。作为搞voip sip 的
研发人员,了解这个有一定的必要性。我们认为支持rtmp协议能进行语音通话的系统都可以叫做flash phone。其代表是开源的red5phone ,它使用java 基于red5 媒体服务器上开发的实现flash到sip转换网关。网址是:http://code.google.com/p/red5phone/
63.为啥需要flash phone/SIP?
Flash sip 或者flash phone 的优势是在浏览器里面使用flash player 插件不需要新安装插件。这对用户是非常方便的。
64.啥情况下下需要flash phone?
出现的用途有几种场景:
A.访问系统的用户是非特定的用户或者游客。他们通过网页上对系统进行访
问。使用activeX 插件的形式,用户会不信任,也不会安装。因此可以通过Flash sip 或者flash phone来对系统进行语音访问。因为绝大多数的机器浏览器都安装了Flash player插件。通俗一点描述:用户浏览某个网站,对里面的某个产品有兴趣,可以点产品边上的电话按键马上和客服或者销售人员进行语音沟通。以前类似的业务是通过
web 800 或者web call 让用户输入手机号码然后系统呼叫用户的手机跟坐席进行通话来实现的。许多用户不想泄漏手机,不会输入手机号码。
B.B/S结构的呼叫中心,系统的客服或者坐席是经常变换机器的,他们今天可能在公司上班,明天可能在家里上班。
C.有些地方的运营商不允许使用SIP 进行VOIP通讯。比如山东聊城联通的宽带就是禁止使用SIP 进行VOIP通讯。这个时候通过Flash phone作为媒体通讯是合适的。
65.使用flash phone使用什么协议?
flash phone使用adobe 公司的flash player插件,跟系统使用RTMP进行通信。RTMP 是基于TCP 协议开发的一种媒体协议。客户端就是flash player 或者基于AIR的应用程序,服务器可以说FMS,RED5, 和FreeSwitch 的MOD_RTMP
66.使用flash phone需要注意哪些问题?
由于RTMP 是基于TCP 协议开发的一种媒体协议,TCP的协议网络影响特别大。因此使用flash phone 的时候需要注意:
A.系统的服务端和客户端最好能在一个运营商内使用。跨运营商带宽是不能保证的,延时也会明显加大。在使用tcp 进行媒体传输通讯的时候影响会显著放大。
B.假如要公众服务的系统,最好是架设在BGP的四线接入的机房内部,才能有效
保证接入的带宽和延时。
C.RTMP 默认是使用1935 端口。机器上要保证这个端口是没有进程其他占用的。
D.flash phone 测试呼叫9195延迟5秒的回音测试,是不行的,有BUG。
67. FreeSwitch如何增加RTMP 接口协议模块以实现对flash phone的支持?
FreeSwitch默认是不提供mod_rtmp模块需要自己编译。编译好mod_rtmp模块
之后通过修改 \conf\autoload_configs 目录下的modules.conf.xml 配置文件:
<!–<load module=”mod_rtmp”/> –>
改为:
<load module=”mod_rtmp”/>
然后保存。重启FreeSwitch 就可以使用。
68. FreeSwitch 的flash 配置文件是哪个,如何配置RTMP的端口?
\conf\autoload_configs目录下的rtmp.conf.xml 就是这模块的配置文件。
配置文件里面的:
<param name=”bind-address” value=”0.0.0.0:1935″ />
其中的1935 就是rtmp 模块发默认端口。假如1935端口被占用,就要修改这个参数。
69. FreeSwitch的flash 配置文件如何配置不需要login就可以使用?
\conf\autoload_configs目录下的rtmp.conf.xml配置文件。
配置文件里面的:
<param name=”auth-calls” value=”true” />
参数改为:
<param name=”auth-calls” value=”false” />
然后不用flash phone 上login 就可以呼叫。但是这样是有风险的,我们建议除非是内网使用,否则不要设置为false。
70. FreeSwitch的flash phone 使用啥工具进行修改开发?
提供的支持rtmp的代码在\clients\flex 目录下。flash phone是基于FLEX 开发的目前支持Adobe Flash Builder 4.6开发。
71. FreeSwitch的flash phone 代码哪些是最有用的?
FreeSwitch 默认提供的代码很庞大,功能很多,尤其是JS部分,给初学者带来很大的困惑。实际上基本的通话功能只要很少的code,简化之后的code 只有300多行。整个demo 可以到http://42.121.7.65/fs_flex.rar上去下载。
72. FreeSwitch的flash phone 如何呼叫分机?
flash phone 连接connect到系统之后,可以呼叫已经注册到FreeSwitch上的分机。呼叫的方法是直接呼叫:sip:分机@FreeSwitch的IP。
73. FreeSwitch的啥情况下iLBC编码不能使用?
本人测试的1.2.1的版本由于FreeSwitch 的某个bug,导致某些情况下(比如flash phone客户端)通过iLBC编码呼叫其他系统,或者其他软电话分机的时候,INVITE 里面的SDP 是错误的。iLBC编码的RTP MAP 应该是98.在这里flash phone 默认的SPEEX的RTP MAP变成了98.而iLBC编码的RTP MAP变成了99.从而导致无法跟其他系统(比如毅航的ISX系列)集成使用,比如会造成单通。这个时候需要通过修改源码,重新编译mod_sofia 模块替换之后才能使用。
具体修改的地方是sofia_glue.c文件里面的sofia_glue_set_local_sdp函数里面的下面部分:
if (!tech_pvt->payload_space) {
int i;
tech_pvt->payload_space = 98;
//yhy2012-11-16 98是iLBC的RTM MAP,应该改为其他比如97
Ver 1.2.3:
mod_rtmp.c
//yhy2012-12-23 带宽或者采样rate 由16000改为8000.
#define SPEEX_BAND_YHY 8000
switch_core_timer_init(&tech_pvt->timer, “soft”, 20, (SPEEX_BAND_YHY / (1000 / 20)),
switch_core_session_get_pool(session));
/* Initialize read & write codecs */
if (switch_core_codec_init(&tech_pvt->read_codec, /* name */ “SPEEX”,
/* fmtp */ NULL, /* rate */ SPEEX_BAND_YHY, /* ms */ 20, /* channels */ 1,
/* flags */ SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
/* codec settings */ NULL, switch_core_session_get_pool(session)) !=
SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, “Can’t initialize read codec\n”);
return SWITCH_STATUS_FALSE;
}
if (switch_core_codec_init(&tech_pvt->write_codec, /* name */ “SPEEX”,
/* fmtp */ NULL, /* rate */ SPEEX_BAND_YHY, /* ms */ 20, /* channels */ 1,
/* flags */ SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
/* codec settings */ NULL, switch_core_session_get_pool(session)) !=
/* fmtp */ NULL, /* rate */ SPEEX_BAND_YHY, /* ms */ 20, /* channels */ 1,
/* flags */ SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
/* codec settings */ NULL, switch_core_session_get_pool(session)) !=
return SWITCH_STATUS_FALSE;
}
在修改代码之后测试iLBC和speex@8000h@20i 不能同时设置到编码器里面,否则:eyebeam(软电话的)呼叫会导致ilbc 的rtpmat=97 ,speex 的rtpmap=98。flashphone的呼叫会导致speex 的rtpmap=97,ilbc 的rtpmat=98 导致不一致的错误。只能使用iLBC 或者SPEEX里面的一个。
在修改代码之后测试。假如没有设置speex@8000h@20i ,flash呼叫内部分机的时候SDP不会出现:speex 的rtpmap=97。但是假如是flash 呼叫外部的没有注册上来的
sip,那么会出现speex 的rtpmap=97的sdp 。呼叫sip:1000@192.168.1.253:8286
74.软电话分机如何实现对FreeSwitch 的flash phone的呼叫?
flash phone 连接到FreeSwitch的MOD_RTMP之后。需要login 才能被呼叫。
软电话分机要呼叫,需要修改拨号计划:在conf\dialplan目录下的default.xml 文件里面修改:
在 <!– http://wiki.freeswitch.org/wiki/Dialplan_XML –>
<include>
<context name=”default”>
<extension name=”Local_Extension2″>
<condition field=”destination_number” expression=”^(10[01][0-9])$”>
<action application=”export” data=”dialed_extension=$1″/>
<action application=”set” data=”call_timeout=10″/>
<action application=”set” data=”hangup_after_bridge=true”/>
<action application=”set” data=”continue_on_fail=true”/>
<action application=”bridge”
data=”${rtmp_contact(default/${dialed_extension}@$${domain})}”/>
<action application=”bridge”
data=”${rtmp_contact(default/${dialed_extension}@$${domain})}”/>
</condition>
</extension>
注意两条:
bridge 命令,因为测试发现nod_rtmp有bug。导致呼叫到flash 客户端有时候不成功。continue_on_fail=true的情况下,万一bridge 不行,我们就再来一次bridge!不行甚至可以考虑再来一次。
75.如何实现同时支持呼叫分机和flash client 分机?
类似上面:
<include>
<context name=”default”>
<extension name=”Local_Extension2″>
<condition field=”destination_number” expression=”^(10[01][0-9])$”>
<action application=”export” data=”dialed_extension=$1″/>
<action application=”set” data=”call_timeout=10″/>
<action application=”set” data=”hangup_after_bridge=true”/>
<action application=”set” data=”continue_on_fail=true”/>
<action application=”bridge” data=”user/${dialed_extension}@${domain_name}”/>
<action application=”bridge”
data=”${rtmp_contact(default/${dialed_extension}@$${domain})}”/>
<action application=”bridge”
data=”${rtmp_contact(default/${dialed_extension}@$${domain})}”/>
</condition>
</extension>
注意有3条bridge 命令,第一次是呼叫软电话的分机。要是没有软电话分机注册上,就呼叫flash client 的分机。
FreeSwitch 高级配置部分
76.没有注册的实现对FreeSwitch 的分机的呼叫?
修改:acl.conf.xml。比如允许192.168.11.X网段拨入到软电话或者flash client,acl.conf.xml修改如下,然后没有注册的呼叫注册的可以直接呼到默认的配置5080 端口就行。
<configuration name=”acl.conf” description=”Network Lists”>
<network-lists>
<!-These
ACL’s are automatically created on startup.
rfc1918.auto -RFC1918 Space
nat.auto -RFC1918 Excluding your local lan.
localnet.auto -ACL for your local lan.
loopback.auto -ACL for your local lan.
–>
<list name=”lan” default=”allow”>
<node type=”deny” cidr=”192.168.42.0/24″/>
<node type=”allow” cidr=”192.168.42.42/32″/>
</list>
<!–
This will traverse the directory adding all users
with the cidr= tag to this ACL, when this ACL matches
the users variables and params apply as if they
digest authenticated.
–>
<list name=”domains” default=”deny”>
<!–domain= is special it scans the domain from the directory to build the ACL –>
<node type=”allow” domain=”$${domain}”/>
<node type=”allow” host=”192.168.11.0″ mask=”255.255.255.0″/>
<!–use cidr= if you wish to allow ip ranges to this domains acl. –>
<!–<node type=”allow” cidr=”192.168.0.0/24″/> –>
</list>
</network-lists>
</configuration>
注意上面的黑体部分。
77. FreeSwitch 为啥会没有发挂机信号给A leg?
有两种情况会导致这个问题:
A.
<action application=”set” data=”hangup_after_bridge=true”/> 设置成了false。
B.
Invite 里面的from 1005@fs ip 地址发起的会导致被叫挂机,fs 不会转发挂机信号给主叫。因此跟第三方集成的时候,from 的地址要保证是对的。
78. FreeSwitch如何修改默认配置才能拨打外部的SIP电话或者SIP网关?
在conf\sip_profiles\external 目录下建立:
gw1.xml 内容如下:
<gateway name=”gw1″>
<param name=”realm” value=”192.168.11.103:5060″/>
<param name=”username” value=”5678″/>
<param name=”password” value=”1234″/>
<param name=”register” value=”false” />
</gateway>
其中ip 地址和端口是外拨网关的ip 和sip 的端口。
然后在conf\dialplan\default 建立一个call_out.xml。
内容如下:
<include>
<extension name=”call out”>
<condition field=”destination_number” expression=”^9(\d+)$”>
<action application=”bridge” data=”sofia/gateway/gw1/$1″/>
</condition>
</extension>
</include>
表示拨打 9开头的号码都走外拨网关gw1的路由。
79.外部的SIP网关如何拨打到某个分机?
在conf/dialplan/public目录下建立
my_did.xml 文件内容如下:
<include>
<extension name=”public_did”>
<condition field=”destination_number” expression=”^(你的接入号码)$”>
<action application=”transfer” data=”1000 XML default”/>
</condition>
</extension>
</include>
之后拨打你的接入号码就会到分机上。
80. FreeSwitch公网运营如何设计?
FreeSwitch运行在公网,意味着:
A.假如是对公众提供服务,软电话分机分布在全国各地,各个运营商都可能存在,因此各个运营商的接入带宽延时是要考虑的。我们建议部署在BGP 4线机房
B.意味这软电话在公司内网,需要NAT 穿透才能使用,因此需要去掉–nonat启动
C.FreeSwitch公网运行的一种建议方案如下图:
WAN E1/PCM
G729/iLBC
WAN WAN WAN WAN
模拟线
空中信道
BGP机房
FreeSwitch
业务系统
联通宽带:
软电话、IP话机等
电信宽带:
软电话、IP话机等
长宽宽带:
软电话、IP话机等
其他宽带:
软电话、IP话机等
公司落地
VOIP 网关
PSTN/MSC
运营商
固定电话移动电话
81. FreeSwitch公网运营有哪些需要特别考虑的?
除了考虑实网部署的问题之外,公网运营考虑的问题还有带宽核算和安全性,
带宽的问题主要是编码器的使用
G729 默认是没有转码的
因此考虑iLBC编码是合适的。
82. FreeSwitch公网运营环境下哪些情况下测试过?
联通WCDMA 3G数据通道。
电信CDMA 2000 3G数据通道。
电信宽带用户。
联通宽带用户。
长宽宽带用户。
移动(铁通)宽带用户。
客户软电话在公司内部通过WIFI上网的环境测试。
83. FreeSwitch如何禁止IP地址发生改变后, 自动重启sofia模块?
修改文件sofia.conf.xml
位置:/usr/local/freeswitch/conf/autoload_configs/sofia.conf.xml
修改内容:
<param name=”auto-restart” value=”false”/>
该属性设置的目的是防止FS在检测到IP地址发生改变后,自动重启sofia模块。
84. FreeSwitch公网运营部署如何配置用户帐号密码?
默认用户的认证密码和语音邮箱登陆密码都异常简单(默认为1234)。可以参考以下
建议:
1、删除默认的静态XML配置,通过mod_xml_curl模块使用后台数据库中动态的数据。
2、手动修改配置文件中的用户名和密码。
3、通过运行scripts/perl/randomize-passwords.pl修改。
85. FreeSwitch的SDP有啥缺陷?
Alaw 和ulaw 没有m=rtpmap 0/8000 之类的详细描述,导致呼叫到kapanga 上,kapanga接通就挂机。没有rtpmat 描述导致呼叫到red5 phone 等不行。
86. FreeSwitch 的SDP有啥特殊的?
跟其他的软电话之类的对比多了rtpmap=13静音的说明。
87. FreeSwitch对内存cpu需求如何?
基本的机器就可以,目前主流的双四核的cpu,8G内存配置的pc Server机器,可以支持300先线并发带转码。在P950 CPU 2G内存配置的笔记本上测试可以支持30路的转码(G.711–SPEEX)。假如内存不够,会导致FreeSwitch崩溃。
FreeSwitch ESL编程部分
88. FreeSwitch
如何设置支持SOCKET EVENT API外联模式编程?
在\conf\dialplan\default.xml 配置文件里面加上:
<extension name=”socket”>
<condition field=”destination_number” expression=”^12396$”>
<action application=”socket” data=”127.0.0.1:8084 async full”/>
</condition>
</extension>
其中的12396表示接入号码。
之后,拨打12396 的号码就会到本地的8084端口的服务流程(用户自己控制的ivr)上。
89. FreeSwitch ESL外联模式同步和异步模式有啥区别?
A.设置区别:
在\conf\dialplan\default.xml 配置文件里的<extension name=”socket”> 章节里面:
异步模式参数:
<action application=”socket” data=”127.0.0.1:8084 async full”/>
同步模式参数:
<action application=”socket” data=”127.0.0.1:8084 sync full”/>
B.使用区别:主要区别在于执行app 或者api 的时候异步模式是马上返回的。而同步模式是阻塞的。一直到条件满足函数才会返回的。
C.例子:以播音举例
esl_execute(handle, “play_and_get_digits”, “1 12 1 15000 # intelno.wav” , NULL);
其中的”1 12 1 10000 # intelno.wav” 分别对应下面参数:
<min> <max> <tries> <timeout ms> <terminators> <file>
表示最小收1个按键最大收12个按键重复1次等15000毫秒,按#结束。提示语音是intelno.wav。
异步模式下esl_execute执行之后马上返回,ivr可以进入处理收按键环节。
用户开始听见intelno.wav的语音内容。在异步模式下,用户的按键处理条件可以是程序自己来处理。按键的结束条件也不仅仅只有上面几个,比如可以设置两个按键的间隔时间等参数。这样大大增加了灵活性。
同步模式下,函数esl_execute执行就没有返回,除非以下几个条件之一达到:
用户按键达到12个,用户按了#键,语音播放结束超过15秒时间。函数才会返回。表面上看同步模式比较简单,开发流程比较容易。但是一旦是多线并发,业务复杂的流程,比如使用esl 开发呼叫中心的流程,或者其他的复杂的流程。同步模式处理起来就吃力。简单就意味着不够灵活。为了系统将来的业务扩展支持,我们建议使用异步模式。
90. FreeSwitch默认播音和录音目录在哪里?
播放文件的默认目录:
\sounds\en\us\callie
录音文件的默认目录:
\sounds\en\us\callie
91. FreeSwitch如何指定录音文件和目录?
可以在record 的时候写上绝对路径。
比如 d:/record/1.wav 。
92. FreeSwitch如何指定alaw播音文件的格式?
FreeSwitch的播音除wav文件之外,其他是根据文件扩展名来区分格式的。
国内通常的ivr系统都是使用单声道的8000Hz alaw编码的语音格式。
FreeSwitch 能支持这种格式的语音有两种办法:
A.文件名称的扩展名叫做.alaw。
B.使用工具给这些文件加上wav 头,然后扩展名叫做.wav。
假如是其他格式的语音类似参考修改。
具体可以在fs_cli下使用
show files 命令查看。然后使用合适的语音文件扩展名。
93. FreeSwitch如何指定alaw录音文件的格式?
国内通常的ivr系统都是使用单声道的8000Hz alaw编码的语音格式。要做到能录制到这种格式的语音,需要以下几个步骤:
先在拨号计划里面加上:
<action application=”set” data=”record_sample_rate=8000″/>
<action application=”export” data=”RECORD_STEREO=false”/>
来指定是8000HZ和单声道。默认是立体声的。
比如:
<extension name=”socket”>
<condition field=”destination_number” expression=”^12396$”>
<action application=”set” data=”record_sample_rate=8000″/>
<action application=”export” data=”RECORD_STEREO=false”/>
<action application=”socket” data=”127.0.0.1:8084 async full”/>
</condition>
</extension>
然后:在uuid_record 或者record的时候,录音的时候指定是.alaw的扩展名,这样就指定是alaw编码的语音。假如是.wav扩展名。则是线性16bit没有编码的语音。
94. FreeSwitch双方通话声音都正常,但是录音文件,回放时发现声音很小,如何解决?
录音生成的WAV文件,和实际录音对比声音只是小一点点。使用cooledit 播放声音是几乎正常的。回放录音文件时发现声音很小,可以通过设置增益来实现:
uuid_audio <uuid> [start [read|write] [mute|level <level>]|stop]
level参数从 -4 to 4, 0 是默认声音大小. 4是最大
95. FreeSwitch 如何实现支持视频的录制和播放?
FreeSwtich通过模块fsv支持视频的录制和播放,此模块提供两个app,record_fsv和
play_fsv,一个录像,一个播放,其实现在目录:src\mod\applications\mod_fsv/mod_fsv.c
使用方法:
录像:
dialplan 中调用app record_fsv
<action application=”record_fsv” data=”file.fsv”/>
参数为录音文件名。
播放视频:
dialplan中调用app play_fsv
<action application=”play_fsv” data=”file.fsv”/>
参数同样为文件名。
96. FreeSwitch ESL LIB例子有哪些?
API开发
demo
source :libs/esl 目录下
testserver.c外联模式例子
testclient.c内联模式例子
fs_cli.c fs client的code
97. FreeSwitch ESL LIB例子windows下如何方便建立VS工程?
以testserver.c为例子建立
VS工程
Windows 的ESL LIB例子demo ,有一个已经存在的工程:
\libs\esl 目录下
fs_cli.2010.vcxproj
我们可以在这个工程的基础上很快建立例子的工程
使用Vs2010打开这个过程
然后把testserver.c文件里面的内容拷贝覆盖fs_cli.c里面的内容然后编译,保存就可以了。
98. FreeSwitch ESL LIB例子windows下使用有哪些要注意?
Windows下使用Socket的程序在使用Socket之前必须调用WSAStartup函数。
以testserver.c 例子为例:在main()最开始的地方的初始化前要增加下面这些代码:
WSADATA Data;
if( WSAStartup(MAKEWORD(2, 1), &Data) != 0)
{
return false;
}
否则testserver.c启动会马上退出。
如下面:
int main(void)
{
WSADATA Data;
if( WSAStartup(MAKEWORD(2, 1), &Data) != 0)
{
return 0;
}
esl_global_set_default_logger(7);
esl_listen_threaded(“localhost”, 8084, mycallback, 100000);
return 0;
}
Callback 的函数,每个呼叫会多一个线程。
因此callback 里面是多线程的。
99. FreeSwitch ESL开发如何理解IVR的播音取按键条件?
Ivr系统最常用的是播音,然后取按键的动作。典型是情况是播语音菜单,或者播提示输入一串号码的语音提示。该动作的结束条件很多,初学者往往稀里糊涂的。
讨论如下:
A.ivr系统先播音,过程可以按键打断,进入收按键后续动作这个FreeSwitch本身就是支持的。
B.播音正常结束。等待用户输入按键
C.进入收用户输入按键动作之后:有很多条件是并发的。
这个些条件通常可以分为:
最大允许输入几个按键
按某个或者某些特殊按键,比如*或者#结束输入
全部按键的最大允许时间,两个按键之间的允许最大间隔时间
典型的情况是一旦上面的4个条件任何一个达到条件,就要完成整个播音取按键动作。上面的这4种情况能满足大多数的ivr业务需要。。。。当然还可以自己设计条件,比如包括播音的整个动作的允许时间,但是很少的情况会使用到这些额外的。
100. FreeSwitch ESL开发如何支持播音取按键?
以异步模式为例子:主要由3个函数组成,说明如下:
//事件体检查内容函数
//返回: <0 表示对方挂机,=3表示按键结束条件,比如”#” 满足,=2表示最大按键个数结束条件满足,=1表示FreeSwitch 的app或者api执行完成
//参数:handle:会话handle
//endchar:按键结束条件,比如”#”表示按#号结束输入,”"表示没有结束按键条件
//支持最大3个结束按键比如
EndDtmf=”*0#” 表示按 0,* 或者#都可以结束
//maxdtmf:最大按键个数结束条件,0表示没有按键个数结束条件
//TwoDtmfTimer:两个按键间的间隔时间结束条件,单位秒,0表示没有两个按键间的间隔时间结束条件
//dtmfbuf:收到的用户的按键(输出参数),包括结束按键的条件,比如”#”
int check_event_body(const char*eventbody,char*dtmfbuf,int maxdtmf,char*endchar)
//播音之后的取按键检查函数
//返回: <0 表示对方挂机,=100 表示按键最大时间结束条件满足=101 表示两个按键间的间隔时间结束条件条件满足,=3表示按键结束条件,比如”#” 满足,=2表示最大按键个数结束条件满足
//参数:handle:会话handle
//filename:语音文件名称,多个文件以分号”;”隔开,文件名称可以带.wav,扩展名,
假如没有,默认是.pcm扩展名.可指定路径
,假如没有默认是语音程序的./data/system目录下
//enddtmf:按键结束条件,比如”#”表示按#号结束输入,”"表示没有结束按键条件
//支持最大3个结束按键比如EndDtmf=”*0#” 表示按 0,* 或者#都可以结束
//MaxDtmf:最大按键个数结束条件,0表示没有按键个数结束条件
//MaxTimer:按键最大时间结束条件,单位秒,0表示没有最大时间结束条件
//TwoDtmfTimer:两个按键间的间隔时间结束条件,单位秒,0表示没有两个按键间的间隔时间结束条件
//outdtmf:收到的用户的按键(输出参数),包括结束按键的条件,比如”#”
//其他说明:假如只有播音,不收取按键设置:MaxDtmf=0
int check_play_dtmf_event(esl_handle_t *handle,char*enddtmf,int MaxDtmf,int
MaxTimer,int TwoDtmfTimer,char*outdtmf)
//播音取按键函数
//返回: <0 表示对方挂机,=100 表示按键最大时间结束条件满足=101 表示两个按键间的间隔时间结束条件条件满足,=3表示按键结束条件,比如”#” 满足,=2表示最大按键个数结束条件满足
//参数:handle:会话handle
//filename:语音文件名称,多个文件以分号”;”隔开,文件名称可以带.wav,扩展名,
假如没有默认是.pcm扩展名.可指定路径
,假如没有默认是语音程序的./data/system目录下
//EndDtmf:按键结束条件,比如”#”表示按#号结束输入,”"表示没有结束按键条件
//支持最大3个结束按键比如EndDtmf=”*0#” 表示按 0,* 或者#都可以结束
//MaxDtmf:最大按键个数结束条件,0表示没有按键个数结束条件
//MaxTimer:按键最大时间结束条件,单位秒,0表示没有最大时间结束条件
//TwoDtmfTimer:两个按键间的间隔时间结束条件,单位秒,0表示没有两个按键间的间隔时间结束条件
//dtmf:收到的用户的按键(输出参数),包括结束按键的条件,比如”#”
//其他说明:假如只有播音,不收取按键设置:MaxDtmf=0
int play_get_dtmf(esl_handle_t *handle,char*filename,char*EndDtmf,int
MaxDtmf,int MaxTimer,int TwoDtmfTimer,char*outdtmf)
函数体如下:
int check_event_body(const char*eventbody,char*dtmfbuf,int maxdtmf,char*
endchar)
{
char tmp[128];
unsigned int i,len;
if (eventbody==NULL) return 0;
len=strlen(eventbody);
if(len>64) len=64;
strncpy(tmp,eventbody+strlen(“Event-Name: “),len);
tmp[len]=0;
for(i=0;i<strlen(tmp);i++)if(tmp[i]==’\n’ || tmp[i]==’\r’) {tmp[i]=0;break;}
for(i=0;i<strlen(tmp);i++)if(tmp[i]==’\n’ || tmp[i]==’\r’) {tmp[i]=0;break;}
if(strnicmp(eventbody,”Event-Name: DTMF”,strlen(“Event-Name:
DTMF”))==0)
{
char*p;
//esl_log(ESL_LOG_INFO, “event_body:%s\n”, eventbody);
if(p=strstr(eventbody,”DTMF-Digit: “))
{
strcpy(tmp,p+strlen(“DTMF-Digit: “));
for(i=0;i<strlen(tmp);i++)if(tmp[i]==’\n’ || tmp[i]==’\r’)
{tmp[i]=0;break;}
if(strcmp(tmp,”%23″)==0) strcpy(tmp,”#”);
esl_log(ESL_LOG_INFO, “dtmf:[%s]\n”, tmp);
if(dtmfbuf && strlen(dtmfbuf)<64) strcat(dtmfbuf,tmp);
if(strlen(dtmfbuf)>=maxdtmf) return 2;
len=strlen(endchar);
//支持最大3个结束按键
if(len>0) if(tmp[0]==endchar[0]) return 3;
if(len>1) if(tmp[0]==endchar[1]) return 3;
if(len>2) if(tmp[0]==endchar[2]) return 3;
}
}
return 0;
}
int check_play_dtmf_event(esl_handle_t *handle,char*enddtmf,int MaxDtmf,int
MaxTimer,int TwoDtmfTimer,char*outdtmf)
{
int done = 0,c=0,twodtmfc=0;
esl_status_t status;
time_t exp = 0;
char dtmf[128];
int res=0,press_dtmf;
press_dtmf=0;
dtmf[0]=0;
while((status = esl_recv_timed(handle, 1000)) != ESL_FAIL)
{
if(press_dtmf)
{
twodtmfc++;
twodtmfc++;
alldtmf %d seconds timout.\n”,c); res=100;break;}
if(TwoDtmfTimer>0 && twodtmfc>0 &&
twodtmfc>=TwoDtmfTimer){esl_log(ESL_LOG_INFO, “Waiting twodtmf %d seconds
timout.\n”,c); res=101;break;}
if (status == ESL_SUCCESS)
{
const char *type = esl_event_get_header(handle->last_event,
“content-type”);
if (type)
{
if(strcasecmp(type, “text/disconnect-notice”)==0)
{
const char *dispo =
esl_event_get_header(handle->last_event, “content-disposition”);
esl_log(ESL_LOG_INFO, “Got a disconnection notice
dispostion: [%s]\n”, dispo ? dispo : “”);
if (dispo && strcmp(dispo, “linger”)==0)
{
res=-99;
break;
}
}
if(strcasecmp(type, “text/event-plain”)==0)
{
const char
*eventbody=esl_event_get_body(handle->last_event);
int oldlen=strlen(outdtmf);
if((res=check_event_body(eventbody,outdtmf,MaxDtmf,enddtmf)))
{
esl_log(ESL_LOG_INFO, “check_event_body
res=%d.\n”,res);
if(res==1)//play 结束开始等按键
{
press_dtmf=1;
if(MaxDtmf==0) break;
}
else
break;
} }
if(strlen(outdtmf)>oldlen) twodtmfc=0;
}
}
}
}
return res;
}
int play_get_dtmf(esl_handle_t *handle,char*filename,char*EndDtmf,int
MaxDtmf,int MaxTimer,int TwoDtmfTimer,char*outdtmf)
{
int res=0;
char cmd_tmp[1024],enddtmf[128],outdtmfbuff[128];
if(EndDtmf==NULL || EndDtmf[0]==0)
strcpy(enddtmf,”q”);
else
strcpy(enddtmf,EndDtmf);
if(outdtmf) outdtmf[0]=0;
memset(outdtmfbuff,0,sizeof(outdtmfbuff));
//<min> <max> <tries> <timeout ms> <terminators> <file>
sprintf(cmd_tmp,”1 1 1 10 a %s”,filename);//设置让
play_get_dtmf 函数里面的按键处理条件无效,ivr check_dtmf_event 函数自己处理按键判断
printf(“play_get_dtmf %s\n”,filename);
esl_execute(handle, “play_and_get_digits”,cmd_tmp, NULL);
res=check_play_dtmf_event(handle,enddtmf,MaxDtmf,MaxTimer,TwoDtmfTimer,outdtmfbuff);
if(outdtmf) strcpy(outdtmf,outdtmfbuff);
printf(“play_get_dtmf %s end,dtmf=[%s]\n”,filename,outdtmfbuff);
return res;
}
使用例子如下:
//播音,语音播放结束不等按键,
play_get_dtmf(&handle,”hello.alaw”,”#”,0,0,0,dtmf);
//播音同时取按键,语音播放结束等用户按键,
play_get_dtmf(&handle,”menu.alaw”,”*0#”,5,20,0,dtmf);
101. FreeSwitch ESL开发如何支持录音取按键?
主要有3个函数组成,说明如下:
//事件体检查内容函数,这个上面已经介绍过
int check_event_body(const char*eventbody,char*dtmfbuf,int maxdtmf,char* endchar)
//录音之后的取按键检查函数
//返回: <0 表示对方挂机,=100 表示按键最大时间结束条件满足=101 表示两个按键间的间隔时间结束条件条件满足,=3表示按键结束条件,比如”#” 满足,=2表示最大按键个数结束条件满足
//参数:handle:会话handle
//filename:语音文件名称,多个文件以分号”;”隔开,文件名称可以带.wav,扩展名,假如没有,默认是.pcm扩展名.可指定路径,假如没有,默认是语音程序的./data/system目录下
//enddtmf:按键结束条件,比如”#”表示按#号结束输入,”"表示没有结束按键条件
//支持最大3个结束按键比如EndDtmf=”*0#” 表示按 0,* 或者#都可以结束
//MaxDtmf:最大按键个数结束条件,0表示没有按键个数结束条件
//MaxTimer:按键最大时间结束条件,单位秒,0表示没有最大时间结束条件
//TwoDtmfTimer:两个按键间的间隔时间结束条件,单位秒,0表示没有两个按键间的间隔时间结束条件
//outdtmf:收到的用户的按键(输出参数),包括结束按键的条件,比如”#”
//其他说明:假如只有播音,不收取按键设置:MaxDtmf=0
int check_record_dtmf_event(esl_handle_t *handle,char*enddtmf,int MaxDtmf,int
MaxTimer,int TwoDtmfTimer,char*outdtmf)
//录音取按键函数
//返回: <0 表示对方挂机,=100 表示按键最大时间结束条件满足=101 表示两个按键间的间隔时间结束条件条件满足,=3表示按键结束条件,比如”#” 满足,=2表示最大按键个数结束条件满足
//参数:handle:会话handle
//uuid:会话的id
//filename:语音文件名称,多个文件以分号”;”隔开,文件名称可以带.wav,扩展名,假如没有,默认是.pcm扩展名.可指定路径,假如没有,默认是语音程序的./data/system目录下
//EndDtmf:按键结束条件,比如”#”表示按
#号结束输入,”"表示没有结束按键条件
//支持最大3个结束按键比如EndDtmf=”*0#” 表示按 0,* 或者#都可以结束
//MaxDtmf:最大按键个数结束条件,0表示没有按键个数结束条件
//MaxTimer:按键最大时间结束条件,单位秒,0表示没有最大时间结束条件
//TwoDtmfTimer:两个按键间的间隔时间结束条件,单位秒,0表示没有两个按键间的间隔时间结束条件
//outdtmf:收到的用户的按键(输出参数),包括结束按键的条件,比如”#”
int record_get_dtmf(esl_handle_t *handle,char*uuid,char*filename,char*EndDtmf,int
MaxDtmf,int MaxTimer,int TwoDtmfTimer,char*outdtmf)
函数体如下:
int check_record_dtmf_event(esl_handle_t *handle,char*enddtmf,int MaxDtmf,int
MaxTimer,int TwoDtmfTimer,char*outdtmf)
{
int done = 0,c=0,twodtmfc=0;
esl_status_t status;
time_t exp = 0;
char dtmf[128];
int res=0,press_dtmf;
press_dtmf=1;
dtmf[0]=0;
while((status = esl_recv_timed(handle, 1000)) != ESL_FAIL)
{
if(press_dtmf)
{
twodtmfc++;
c++;
}
if(MaxTimer>0 && c>=MaxTimer){esl_log(ESL_LOG_INFO, “Waiting
alldtmf %d seconds timout.\n”,c); res=100;break;}
if(TwoDtmfTimer>0 && twodtmfc>0 &&
twodtmfc>=TwoDtmfTimer){esl_log(ESL_LOG_INFO, “Waiting twodtmf %d seconds
timout.\n”,c); res=101;break;}
if (status == ESL_SUCCESS)
{
const char *type = esl_event_get_header(handle->last_event,
“content-type”);
if (type)
{
if(strcasecmp(type, “text/disconnect-notice”)==0)
{
const char *dispo = esl_event_get_header(handle->last_event,
“content-disposition”);
esl_log(ESL_LOG_INFO, “Got a disconnection notice dispostion:
[%s]\n”, dispo ? dispo : “”);
if (dispo && strcmp(dispo, “linger”)==0)
{
res=-99;
break;
}
}
if(strcasecmp(type, “text/event-plain”)==0)
{
const char
*eventbody=esl_event_get_body(handle->last_event);
int oldlen=strlen(outdtmf);
if((res=check_event_body(eventbody,outdtmf,MaxDtmf,enddtmf)))
{ {
esl_log(ESL_LOG_INFO, “check_event_body
res=%d.\n”,res);
break;
}
if(oldlen==1) press_dtmf=1;
if(strlen(outdtmf)>oldlen) twodtmfc=0;
}
}
}
}
return res;
}
int record_get_dtmf(esl_handle_t *handle,char*uuid,char*filename,char*EndDtmf,int
MaxDtmf,int MaxTimer,int TwoDtmfTimer,char*outdtmf)
{
int res;
char cmd_tmp[1024],enddtmf[128],outdtmfbuff[128];
if(EndDtmf==NULL || EndDtmf[0]==0)
strcpy(enddtmf,”q”);
else
strcpy(enddtmf,EndDtmf);
if(outdtmf) outdtmf[0]=0;
printf(“record_get_dtmf:%s\n”,filename);
memset(outdtmfbuff,0,sizeof(outdtmfbuff));
sprintf(cmd_tmp,”api uuid_record %s
start %s %d\n”,uuid,filename,MaxTimer*2);//MaxTimer*2的条件是为了保证
uuid_record的条件无效。而是通过自己的ivr check_record_dtmf_event函数里面进行条件录音结束判断
esl_send_recv_timed(handle, cmd_tmp,1000);
res=check_record_dtmf_event(handle,enddtmf,MaxDtmf,MaxTimer,TwoDtmfTimer,outdtmfbuff);
if(outdtmf) strcpy(outdtmf,outdtmfbuff);
printf(“record_get_dtmf:%s,end,dtmf=[%s]\n”,filename,outdtmfbuff);
sprintf(cmd_tmp,”api uuid_record %s stop all\n”,uuid);
printf(“record_get_dtmf:%s stop\n”,filename);
esl_send_recv_timed(handle, cmd_tmp,1000);
printf(“record_get_dtmf:%s stop end\n”,filename);
return res;
}
使用例子如下:
// 录音到z:/目录下.alaw 扩展名表示录制为8K16bit 的alaw 格式语音,.wav扩展名录制为8K16bit 的线性pcm格式语音
sprintf(recordfilename,”z:/%s.alaw”,uuid);//录制到z:/目录下,以uuid作为文件名次
record_get_dtmf(&handle,uuid,recordfilename,”#”,1,30,0,dtmf);
102. FreeSwitch ESL开发如何支持电话转接?
主要有2个函数组成,说明如下:
//事件体检查内容函数,这个上面已经介绍过
int check_event_body(const char*eventbody,char*dtmfbuf,int maxdtmf,char*
endchar)
//事件检查函数
//返回: <0 表示对方挂机
//参数:handle:会话handle
//timer:最大等等时间,单位秒,0表示没有没有限制,一直等到用户挂机
int check_event(esl_handle_t *handle,int timer)
使用例子如下:
case ’0′://人工服务
{
esl_execute(&handle, “bridge”, “user/1000@${domain_name}”, NULL);
if(check_event(&handle,0)<0) goto END;
break;
}
103. FreeSwitch ESL开发如何支持电话会议?
主要有2个函数组成,说明如下:
//事件体检查内容函数,这个上面已经介绍过
int check_event_body(const char*eventbody,char*dtmfbuf,int maxdtmf,char*
endchar)
//事件检查函数, 这个上面已经介绍过
int check_event(esl_handle_t *handle,int timer)
使用例子如下:
case ‘#’://参加会议
{
esl_execute(&handle, “conference”, “3000@default”, NULL);
if(check_event(&handle,0)<0) goto END;
break;
}
104.如何实现FreeSwitch ESL开发的完整IVR演示流程?
系统提供的demo 比较简单,没有处理基本的ivr的工作单元:
播音取按键,录音取按键,电话转接,参加会议。
下面的这个文件能实现上面的这些基本功能:
先修改拨号计划:增加下面的内容:
<extension name=”socket”>
<condition field=”destination_number” expression=”^12396$”>
<action application=”set” data=”call_timeout=30″/>
<action application=”set” data=”record_sample_rate=8000″/>
<action application=”export” data=”RECORD_STEREO=false”/>
<action application=”set” data=”hangup_after_bridge=true”/>
<action application=”set” data=”continue_on_fail=true”/>
<action application=”socket” data=”127.0.0.1:8084 async full”/>
</condition>
</extension>
然后把下面的code 拷贝到fs_cli.c 然后编译fs_cli 工程就可以使用。
使用的时候软电话注册上去然后拨打12396
fs_cli.c:
#include <stdio.h>
#include <stdlib.h>
#include <esl.h>
int check_event_body(const char*eventbody,char*dtmfbuf,int maxdtmf,char* endchar)
{
char tmp[128];
unsigned int i,len;
if (eventbody==NULL) return 0;
len=strlen(eventbody);
if(len>64) len=64;
strncpy(tmp,eventbody+strlen(“Event-Name: “),len);
tmp[len]=0;
for(i=0;i<strlen(tmp);i++)if(tmp[i]==’\n’ || tmp[i]==’\r’) {tmp[i]=0;break;}
esl_log(ESL_LOG_INFO, “Event-Name:[%s]\n”, tmp);
if(strcmp(tmp,”CHANNEL_EXECUTE_COMPLETE”)==0) return 1;
if(strcmp(tmp,”CHANNEL_HANGUP”)==0) return -98;
if(strnicmp(eventbody,”Event-Name: DTMF”,strlen(“Event-Name: DTMF”))==0)
{
char*p;
//esl_log(ESL_LOG_INFO, “event_body:%s\n”, eventbody);
if(p=strstr(eventbody,”DTMF-Digit: “))
{
strcpy(tmp,p+strlen(“DTMF-Digit: “));
for(i=0;i<strlen(tmp);i++)if(tmp[i]==’\n’ || tmp[i]==’\r’) {tmp[i]=0;break;}
if(strcmp(tmp,”%23″)==0) strcpy(tmp,”#”);
esl_log(ESL_LOG_INFO, “dtmf:[%s]\n”, tmp);
if(dtmfbuf && strlen(dtmfbuf)<64) strcat(dtmfbuf,tmp);
if(strlen(dtmfbuf)>=maxdtmf) return 2;
len=strlen(endchar);
//支持最大3个结束按键
if(len>0) if(tmp[0]==endchar[0]) return 3;
if(len>1) if(tmp[0]==endchar[1]) return 3;
if(len>2) if(tmp[0]==endchar[2]) return 3;
}
}
return 0;
}
int check_event(esl_handle_t *handle,int timer)
{
int done = 0,c=0;
esl_status_t status;
time_t exp = 0;
char dtmf[128];
int res=0;
dtmf[0]=0;
while((status = esl_recv_timed(handle, 1000)) != ESL_FAIL)
{
c++;
esl_log(ESL_LOG_INFO, “Waiting 1 seconds events.\n”);
if(timer>0 && c>=timer){res=100;break;}
if (status == ESL_SUCCESS)
{
const char *type = esl_event_get_header(handle->last_event, “content-type”);
if (type)
{
if(strcasecmp(type, “text/disconnect-notice”)==0)
{
const char *dispo = esl_event_get_header(handle->last_event,
“content-disposition”);
esl_log(ESL_LOG_INFO, “Got a disconnection notice dispostion: [%s]\n”,
dispo ? dispo : “”);
if (dispo && strcmp(dispo, “linger”)==0)
{
res=-99;
break;
break;
const char *eventbody=esl_event_get_body(handle->last_event);
if((res=check_event_body(eventbody,dtmf,1,”#”)))
{
esl_log(ESL_LOG_INFO, “check_event_body res=%d.\n”,res);
if(res<0) break;
}
}
}
}
}
return res;
}
int check_last_event(esl_handle_t *handle,int timer)
{
int done = 0,c=0;
esl_status_t status;
time_t exp = 0;
char dtmf[128];
int res=0;
dtmf[0]=0;
while((status = esl_recv_timed(handle, 1000)) != ESL_FAIL)
{
c++;
// esl_log(ESL_LOG_INFO, “Waiting 1 seconds events.\n”);
if(timer>0 && c>=timer){res=99;break;}
if (done)
{
if (time(NULL) >= exp) {res=-99; break; }
continue;
}
if (status == ESL_SUCCESS)
{
const char *type = esl_event_get_header(handle->last_event, “content-type”);
if (type)
{
if(strcasecmp(type, “text/disconnect-notice”)==0)
{
const char *dispo = esl_event_get_header(handle->last_event,
“content-disposition”);
esl_log(ESL_LOG_INFO, “Got a disconnection notice dispostion: [%s]\n”,
if(strcasecmp(type, “text/disconnect-notice”)==0)
{
const char *dispo = esl_event_get_header(handle->last_event,
“content-disposition”);
esl_log(ESL_LOG_INFO, “Got a disconnection notice dispostion: [%s]\n”,
done = 1;
exp = time(NULL) + 5;
}
}
if(strcasecmp(type, “text/event-plain”)==0)
{
const char *eventbody=esl_event_get_body(handle->last_event);
if((res=check_event_body(eventbody,dtmf,1,”#”)))
{
esl_log(ESL_LOG_INFO, “check_last_event_body res=%d.\n”,res);
}
}
}
}
}
esl_log(ESL_LOG_INFO, “check_last_event res=%d.\n”,res);
return res;
}
//播音之后的取按键检查函数
//返回: <0 表示对方挂机,=100 表示按键最大时间结束条件满足
=101 表示两个按键间的间隔时间结束条件条件满足,=3表示按键结束条件,比如”#” 满足,=2表示最大按键个数结束条件满足
//参数:handle:会话
handle
//filename:语音文件名称,多个文件以分号”;”隔开,文件名称可以带.wav,扩展名,假如有默认是.pcm扩展名.可指定路径,假如没有,默认是语音程序的./data/system目录下
//enddtmf:按键结束条件,比如”#”表示按#号结束输入,”"表示没有结束按键条件//支持最
大3个结束按键比如EndDtmf=”*0#” 表示按 0,* 或者#都可以结束
//MaxDtmf:最大按键个数结束条件,0表示没有按键个数结束条件
//MaxTimer:按键最大时间结束条件,单位秒,0表示没有最大时间结束条件
//TwoDtmfTimer:两个按键间的间隔时间结束条件,单位秒,0表示没有两个按键间的间隔
时间结束条件
//outdtmf:收到的用户的按键(输出参数),包括结束按键的条件,比如”#”
//其他说明:假如只有播音,不收取按键设置:MaxDtmf=0
int check_play_dtmf_event(esl_handle_t *handle,char*enddtmf,int MaxDtmf,int
MaxTimer,int TwoDtmfTimer,char*outdtmf)
{
int check_play_dtmf_event(esl_handle_t *handle,char*enddtmf,int MaxDtmf,int
MaxTimer,int TwoDtmfTimer,char*outdtmf)
{
esl_status_t status;
time_t exp = 0;
char dtmf[128];
int res=0,press_dtmf;
press_dtmf=0;
dtmf[0]=0;
while((status = esl_recv_timed(handle, 1000)) != ESL_FAIL)
{
if(press_dtmf)
{
twodtmfc++;
c++;
}
//esl_log(ESL_LOG_INFO, “Waiting 1 seconds events.\n”);
if(MaxTimer>0 && c>=MaxTimer){esl_log(ESL_LOG_INFO, “Waiting alldtmf %d seconds timout.\n”,c); res=100;break;}
if(TwoDtmfTimer>0 && twodtmfc>0 &&
twodtmfc>=TwoDtmfTimer){esl_log(ESL_LOG_INFO, “Waiting twodtmf %d seconds
timout.\n”,c); res=101;break;}
if (status == ESL_SUCCESS)
{
const char *type = esl_event_get_header(handle->last_event, “content-type”);
if (type)
{
if(strcasecmp(type, “text/disconnect-notice”)==0)
{
const char *dispo = esl_event_get_header(handle->last_event,
“content-disposition”);
esl_log(ESL_LOG_INFO, “Got a disconnection notice dispostion: [%s]\n”,
dispo ? dispo : “”);
if (dispo && strcmp(dispo, “linger”)==0)
{
res=-99;
break;
}
}
if(strcasecmp(type, “text/event-plain”)==0)
{
const char *eventbody=esl_event_get_body(handle->last_event);
int oldlen=strlen(outdtmf);
if((res=check_event_body(eventbody,outdtmf,MaxDtmf,enddtmf)))
{
int oldlen=strlen(outdtmf);
if((res=check_event_body(eventbody,outdtmf,MaxDtmf,enddtmf)))
{
if(res==1)//play 结束开始等按键
{
press_dtmf=1;
if(MaxDtmf==0) break;
}
else
break;
}
if(oldlen==1) press_dtmf=1;
if(strlen(outdtmf)>oldlen) twodtmfc=0;
}
}
}
}
return res;
}
//播音取按键
//返回: <0 表示对方挂机,=100 表示按键最大时间结束条件满足
=101 表示两个按键间的间隔时间结束条件条件满足,=3表示按键结束条件,比如”#” 满足,=2表示最大按键个数结束条件满足
//参数:handle:会话handle
//filename:语音文件名称,多个文件以分号”;”隔开,文件名称可以带.wav,扩展名,假如有默认是.pcm扩展名.可指定路径,假如没有,默认是语音程序的./data/system目录下
//EndDtmf:按键结束条件,比如”#”表示按#号结束输入,”"表示没有结束按键条件//支持最大3个结束按键比如EndDtmf=”*0#” 表示按 0,* 或者#都可以结束
//MaxDtmf:最大按键个数结束条件,0表示没有按键个数结束条件
//MaxTimer:按键最大时间结束条件,单位秒,0表示没有最大时间结束条件
//TwoDtmfTimer:两个按键间的间隔时间结束条件,单位秒,0表示没有两个按键间的间隔
时间结束条件
//dtmf:收到的用户的按键(输出参数),包括结束按键的条件,比如”#”
//说明:假如只有播音,不收取按键设置:MaxDtmf=0
int play_get_dtmf(esl_handle_t *handle,char*filename,char*EndDtmf,int MaxDtmf,int
MaxTimer,int TwoDtmfTimer,char*outdtmf)
{
int res=0;
char cmd_tmp[1024],enddtmf[128],outdtmfbuff[128];
if(EndDtmf==NULL || EndDtmf[0]==0)
strcpy(enddtmf,”q”);
else
strcpy(enddtmf,EndDtmf);
if(outdtmf) outdtmf[0]=0;
memset(outdtmfbuff,0,sizeof(outdtmfbuff));
//<min> <max> <tries> <timeout ms> <terminators> <file>
sprintf(cmd_tmp,”1 1 1 10 a %s”,filename);//设置让play_get_dtmf 函数里面的按键处理条件无效,ivr check_dtmf_event 函数自己处理按键判断
printf(“play_get_dtmf %s\n”,filename);
esl_execute(handle, “play_and_get_digits”,cmd_tmp, NULL);
res=check_play_dtmf_event(handle,enddtmf,MaxDtmf,MaxTimer,TwoDtmfTimer,outdtmfbuff);
if(outdtmf) strcpy(outdtmf,outdtmfbuff);
printf(“play_get_dtmf %s end,dtmf=[%s]\n”,filename,outdtmfbuff);
return res;
}
//录音之后的取按键检查函数
//返回: <0 表示对方挂机,=100 表示按键最大时间结束条件满足=101 表示两个按键间的间隔时间结束条件条件满足,=3表示按键结束条件,比如”#” 满足,=2表示最大按键个数结束条件满足
//参数:handle:会话handle
//filename:语音文件名称,多个文件以分号”;”隔开,文件名称可以带.wav,扩展名,假如没有
,
默认是.pcm扩展名.可指定路径,假如没有,默认是语音程序的./data/system目录下
//enddtmf:按键结束条件,比如”#”表示按#号结束输入,”"表示没有结束按键条件//支持最大3个结束按键比如EndDtmf=”*0#” 表示按 0,* 或者#都可以结束
//MaxDtmf:最大按键个数结束条件,0表示没有按键个数结束条件
//MaxTimer:按键最大时间结束条件,单位秒,0表示没有最大时间结束条件
//TwoDtmfTimer:两个按键间的间隔时间结束条件,单位秒,0表示没有两个按键间的间隔
时间结束条件
//outdtmf:收到的用户的按键(输出参数),包括结束按键的条件,比如”#”
//其他说明:假如只有播音,不收取按键设置:MaxDtmf=0
int check_record_dtmf_event(esl_handle_t *handle,char*enddtmf,int MaxDtmf,int
MaxTimer,int TwoDtmfTimer,char*outdtmf)
{
int done = 0,c=0,twodtmfc=0;
esl_status_t status;
time_t exp = 0;
char dtmf[128];
int res=0,press_dtmf;
press_dtmf=1;
dtmf[0]=0;
while((status = esl_recv_timed(handle, 1000)) != ESL_FAIL)
{
if(press_dtmf)
{
{
;
c++;
}
if(MaxTimer>0 && c>=MaxTimer){esl_log(ESL_LOG_INFO, “Waiting alldtmf %d seconds
timout.\n”,c); res=100;break;}
if(TwoDtmfTimer>0 && twodtmfc>0 &&
twodtmfc>=TwoDtmfTimer){esl_log(ESL_LOG_INFO, “Waiting twodtmf %d seconds
timout.\n”,c); res=101;break;}
if (status == ESL_SUCCESS)
{
const char *type = esl_event_get_header(handle->last_event, “content-type”);
if (type)
{
if(strcasecmp(type, “text/disconnect-notice”)==0)
{
const char *dispo = esl_event_get_header(handle->last_event,
“content-disposition”);
esl_log(ESL_LOG_INFO, “Got a disconnection notice dispostion: [%s]\n”,
dispo ? dispo : “”);
if (dispo && strcmp(dispo, “linger”)==0)
{
res=-99;
break;
}
}
if(strcasecmp(type, “text/event-plain”)==0)
{
const char *eventbody=esl_event_get_body(handle->last_event);
int oldlen=strlen(outdtmf);
if((res=check_event_body(eventbody,outdtmf,MaxDtmf,enddtmf)))
{
// esl_log(ESL_LOG_INFO, “check_event_body res=%d.\n”,res);
break;
}
if(oldlen==1) press_dtmf=1;
if(strlen(outdtmf)>oldlen) twodtmfc=0;
}
}
}
}
return res;
}
//录音取按键函数
//返回: <0 表示对方挂机,=100 表示按键最大时间结束条件满足=101 表示两个按键间的间隔时间结束条件条件满足,=3表示按键结束条件,比如”#” 满足,=2表示最大按键个数结束条件满足
//参数:handle:会话handle
//uuid:会话的id
//filename:语音文件名称,多个文件以分号”;”隔开,文件名称可以带.wav,扩展名,假如没有默认是.pcm扩展名.可指定路径,假如没有,默认是语音程序的./data/system目录下
//EndDtmf:按键结束条件,比如”#”表示按#号结束输入,”"表示没有结束按键条件//支持最大3个结束按键比如EndDtmf=”*0#” 表示按 0,* 或者#都可以结束
//MaxDtmf:最大按键个数结束条件,0表示没有按键个数结束条件
//MaxTimer:按键最大时间结束条件,单位秒,0表示没有最大时间结束条件
//TwoDtmfTimer:两个按键间的间隔时间结束条件,单位秒,0表示没有两个按键间的间隔
时间结束条件
//outdtmf:收到的用户的按键(输出参数),包括结束按键的条件,比如”#”
int record_get_dtmf(esl_handle_t *handle,char*uuid,char*filename,char*EndDtmf,int
MaxDtmf,int MaxTimer,int TwoDtmfTimer,char*outdtmf)
{
int res;
char cmd_tmp[1024],enddtmf[128],outdtmfbuff[128];
if(EndDtmf==NULL || EndDtmf[0]==0)
strcpy(enddtmf,”q”);
else
strcpy(enddtmf,EndDtmf);
if(outdtmf) outdtmf[0]=0;
printf(“record_get_dtmf:%s\n”,filename);
memset(outdtmfbuff,0,sizeof(outdtmfbuff));
sprintf(cmd_tmp,”api uuid_record %s
start %s %d\n”,uuid,filename,MaxTimer*2);//MaxTimer*2的条件是为了保证
uuid_record的条件无效。而是通过自己的ivr check_record_dtmf_event函数里面进行条件录音结束判断
esl_send_recv_timed(handle, cmd_tmp,1000);
res=check_record_dtmf_event(handle,enddtmf,MaxDtmf,MaxTimer,TwoDtmfTimer,outdtmfbuff);
if(outdtmf) strcpy(outdtmf,outdtmfbuff);
printf(“record_get_dtmf:%s,end,dtmf=[%s]\n”,filename,outdtmfbuff);
sprintf(cmd_tmp,”api uuid_record %s stop all\n”,uuid);
printf(“record_get_dtmf:%s stop\n”,filename);
esl_send_recv_timed(handle, cmd_tmp,1000);
printf(“record_get_dtmf:%s stop end\n”,filename);
return res;
}
static void mycallback(esl_socket_t server_sock, esl_socket_t client_sock, struct sockaddr_in
*addr)
{
static void mycallback(esl_socket_t server_sock, esl_socket_t client_sock, struct sockaddr_in *addr)
{
esl_handle_t handle = {{0}};
esl_attach_handle(&handle, client_sock, addr);
esl_log(ESL_LOG_INFO, “Connected! %d\n”, handle.sock);
esl_filter(&handle, “unique-id”, esl_event_get_header(handle.info_event,”caller-unique-id”));
strcpy(uuid,esl_event_get_header(handle.info_event, “caller-unique-id”));
printf(“handle.sock=%d,UUID=%s\n”,handle.sock,uuid);
esl_events(&handle, ESL_EVENT_TYPE_PLAIN, “SESSION_HEARTBEAT CHANNEL_ANSWER
CHANNEL_ORIGINATE CHANNEL_PROGRESS CHANNEL_HANGUP ”
“CHANNEL_BRIDGE CHANNEL_UNBRIDGE CHANNEL_OUTGOING
CHANNEL_EXECUTE CHANNEL_EXECUTE_COMPLETE DTMF CUSTOM
conference::maintenance”);
//This will send a command and place its response event on handle->last_sr_event and
handle->last_sr_reply
esl_send_recv(&handle, “linger”);//Tells FreeSWITCH not to close the socket connect when a
channel hangs up. Instead, it keeps the socket connection open until the last event related to
the channel has been received by the socket client.
esl_execute(&handle, “answer”, NULL, NULL);
if(check_event(&handle,2)<0) goto END;
if(play_get_dtmf(&handle,”hello.alaw”,”#”,0,0,0,dtmf)<0) goto END; //hello.alaw:”欢迎致电我公司,我们公司是转移提供400服务公司”
while(1)
{
if(play_get_dtmf(&handle,”menu.alaw”,”*0#”,5,20,0,dtmf)<0) goto END;//menu.alaw:”请输入分机号码,按#号确认,留言请按星号,人工服务请按0,参加会议请按#号”
if(dtmf[0]!=0)
{
break;
break;
{
if(play_get_dtmf(&handle,”startrecord.alaw”,”#”,0,0,0,dtmf)<0) goto END;
//startrecord.alaw:请在听到滴的一声之后开始录音,按任意键结束录音,滴…..
sprintf(recordfilename,”z:/%s.alaw”,uuid);//录制到z:/目录下.alaw 扩展名录制为8K16bit 的alaw 格式语音,.wav扩展名录制为8K16bit 的线性pcm格式语音
if(record_get_dtmf(&handle,uuid,recordfilename,”#”,1,30,0,dtmf)<0) goto END;
if(play_get_dtmf(&handle,recordfilename,”#”,0,0,0,dtmf)<0) goto END;
if(play_get_dtmf(&handle,”bye.alaw”,”#”,0,0,0,dtmf)<0) goto END;
//bye.alaw:”谢谢使用,再见”
break;
}
case ’0′://人工服务
{
esl_execute(&handle, “bridge”, “user/1000@${domain_name}”, NULL);
if(check_event(&handle,0)<0) goto END;
break;
}
case ‘#’://参加会议
{
esl_execute(&handle, “conference”, “3000@default”, NULL);
if(check_event(&handle,0)<0) goto END;
break;
}
default:
{
strtok(dtmf,”#”);//去掉#号键
sprintf(cmd_tmp,”user/%s@${domain_name}”,dtmf);////${domain_name}=192.168.1.236
esl_execute(&handle, “bridge”,cmd_tmp , NULL);
if(check_event(&handle,0)<0) goto END;
break;
}
}
printf(“ivr hangup…..\n”);
sprintf(cmd_tmp,”api uuid_kill %s\n”,uuid);
esl_send_recv_timed(&handle, cmd_tmp,1000);
esl_send_recv_timed(&handle, cmd_tmp,1000);
}
int main(void)
{
WSADATA Data;
if( WSAStartup(MAKEWORD(2, 1), &Data) != 0)
{
return 0;
}
esl_global_set_default_logger(6);
printf(“System Start ok.start mycallback and wait callin….\n”);
esl_listen_threaded(“localhost”, 8084, mycallback, 100000);
return 0;
}
105.实网环境中如何跟踪和解决崩溃?
非开发环境的实网环境下的debug
a.使用微软的工具
可以使用微软的安装包,也可以使用安装之后的,免安装版本:
b.需要提供开发环境编译产生的所有工程的pdb文件
以test.ext为例:非开发环境下的
debug步骤:
.0 准备提供test.pdb 在test.exe,Pdb文件名次要和编译产生的exe名次一样,不能修改。Releae版本也可参数pdb文件。
.1免安装版本:Windows_debug.rar解压都某个目录
.2 命令行下执行:
“Windows Kits\8.0\Debuggers\x86\adplus_old.vbs”
-crash -pn
test.exe -o “c:\debug”
Debug 目录是预先建立好的输出debug日志的目录
.3、一旦程序崩溃会参数
dump和log文件
比如下面的code
int check_time()
{
static char *p;
static int c=1;
printf(“time=%d\n”,time(NULL));
if((c++)%10==0)
{
printf(“time=%d,%s\n”,time(NULL),time(NULL));
*p=1;
p++;
}
return 0;
}
崩溃之后,打开PID-4324__TEST.EXE__Date_11-14-2012__Time_10-11-3737.log文件可以找到是哪个函数里面崩溃
这样对崩溃的跟踪就比较准确。
FreeSwitch 已知bug
参考资料:
fs1.2.1使用flash 的rtmp_mod功能会崩溃。
fs1.2.3启动崩溃,怀疑是软电话eyebeam 没有关闭,自动的注册,注册的时候fs正在初始化某些东西导致崩溃。fs1.2.5.3启动崩溃,怀疑是软电话eyebeam 没有关闭,自动的注册,注册的时候fs正在初始化某些东西导致崩溃。
附录:
《FreeSwitch VOIP实战》 杜金房著
http://wiki.freeswitch.org/wiki/Main_Page
http://www.freeswitch.org.cn/
相关推荐
这个名为“百问FreeSwitch(第三版).rar”的压缩包文件包含了2017年2月更新的关于FreeSwitch的详细资料,特别是《百问FreeSwitch(第三版).pdf》这本书,是学习和理解FreeSwitch技术的重要资源。 FreeSwitch的...
总结而言,文档《百问FreeSwitch-TB完整版-226页》为技术人员提供了深入浅出的FreeSwitch和VOIP知识,内容涵盖基础理论、开发工具、配置方法、NAT穿透技术以及作者的个人联系信息,是一个适合初学者和中级研发运营...
### 百问FreeSwitch(第二版)2.0 关键知识点概述 #### 一、文档背景及发展历程 《百问FreeSwitch(第二版)2.0》由余洪涌编著,出版时间为2014年9月。该文档自2012年首次创建以来,经过多次修订和完善,最终形成了...
标题《百问FreeSwitch(完整版)》意味着本文档是一份针对FreeSwitch的综合问答指南。FreeSwitch是一个开源的通信软件,它支持多种通信协议,包括VoIP。本书面向的读者群体广泛,既包括对VoIP感兴趣的新手,也包括计划...
余洪涌对freeswitch中经常性的问题的一个整理与回答。本文档比较适合与搞软交换或者freeswitch初级和中级研发运营工程人员
### Freeswitch Q&A 文档知识点总结 #### VOIP基础部分 **1. VOIP/FreeSwitch学习前的基础知识** - **网络基础知识**:包括TCP/IP协议、路由选择及网络拓扑结构。 - **SIP协议**:了解会话初始化协议(SIP)的...
"百问FreeSwitch"是围绕这个主题的一份资料集合,包含了FreeSwitch的使用和开发过程中可能遇到的各种问题和解决方案。 在FreeSwitch的使用中,了解SIP(Session Initiation Protocol)是非常重要的,因为它是...
《百问FreeSwitch》第二版是由余洪涌编著的一本关于FreeSwitch开源通信平台的指导书籍,详细介绍了在2014年版本的FreeSwitch环境下的使用方法和技巧。本书内容涵盖VOIP基础知识、SIP协议、RTP和SDP协议、以及如何...
"百问FreeSwitch(第三版)"是配套的问答集,提供了100个常见问题及解答,覆盖了FreeSWITCH的安装、配置、调试、扩展等多个方面。书签目录的设置使得读者能够快速定位到自己关心的问题,极大地提升了学习效率。 在...
《Freeswitch权威指南》是一本深度探讨Freeswitch开源通信平台的专业书籍,结合C和C++源码,为读者提供了全面且深入的学习资源。Freeswitch是一个强大的、开源的、多线程的VoIP(Voice over Internet Protocol)...
此外,"百问FreeSwitch(第三版).pdf"这样的资料可以帮助读者更深入地了解和学习FreeSwitch的相关知识。 总之,FreeSwitch是一个功能强大且高度可定制的通信平台,适合各种规模的企业和开发者构建高效、安全的VoIP...