WebRTC,视频和音频的实时通信,已经在超过十亿个浏览器上工作。很快移动应用程序就会成倍增长。WebRTC有望成为物联网(IOT)所采用的多媒体通信层。
FreeSwitch是一个完整的WebRTC解决方案,使现有的和遗留的应用程序能够通过新的渠道为用户服务。此外,Freeswitch是一个超级可编程应用服务器,是您创建杀手级服务的基础,它定义了“快速致富”的新概念!
WebRTC是一组P2P多媒体流的技术和标准。WebRTC需要一个额外的信令和会话管理层才能真正有用:它包括用户目录、用户定位、故障管理等等。
FreeSWITCH既支持WebRTC上的SIP(会话初始化协议),也支持VERTO协议。VERTO是一种新的开源协议,它的设计旨在为JavaScript开发者提供方便。
我们将看到如何安装、配置和管理FreeSWITCH服务,把它作为一个集成的WebRTC平台。我们还将看到Verto Communicator (VC)的惊人先进性,和简单的浏览器客户端视频会议和远程呈现应用。
最后,我们将以两种难以区分的方式实现同一个应用:WebRTC房间,一种基于SIP.js,另一种基于verto.js。
这一章,我们将讨论以下主题:
- WebRTC 概念
- WebRTC 里的加密
- FreeSWITCH的WebRTC
- SIP和Verto协议的基本原理
- 安装并配置一个完整的FreeSWITCH WebRTC平台
- Verto通信的惊人特性
- 怎样利用Verto和SIP.js编写测试应用
WebRTC概念
WebRTC是一组支持P2P音频、视频和数据采集、流媒体和交换的标准和技术。它的首款“杀手级”应用是视频电话和视频会议。它首先在web浏览器上实现(已有超过10亿个兼容WebRTC的浏览器),这项技术已经在许多智能手机应用上推广,据预测,它可能成为物联网多媒体通信的基础(将有数十亿的物联网设备启用WebRTC)。
在最直接和流行的实现中,浏览器会访问一个页面,并加载页面所包含的Javascript。脚本的函数使用WebRTC API(后面介绍)与计算机多媒体硬件交互(麦克风、摄像头),并且和对端交换音视频媒体流。
这里的重点是P2P。WebRTC不规定或支持任何超出抓取、发送、接收和播放流之外的操作。没有信令,没有用户目录或定位服务,没有协商通信会话建立和销毁的协议,等等。
WebRTC既不是协议,也不是服务。仅使用WebRTC技术,你必须对两个端点的应用地址硬编码,嗅探它们的监听,而应用程序只对这些端点有效。
除了最简单演示之外,WebRTC还需要某种信令和会话管理协议。对于你的WebRTC服务来说,你可以选择现成的协议,或者自己发明一套私有协议。
最成熟的会话管理信令协议显然是SIP,它支持着整个世界的通信网络,从PSTN运营商到移动运营商,再到PBX呼叫服务,SIP的身影无处不在。另一种广泛应用的协议是XMPP,它主要用于管理即时消息和状态服务。有许多专有封闭的协议,其中以Skype背后的协议最为突出。还有面向Web开发人员和JavaScript编程人员的开源替代方案,如Freeswitch的Verto。你必须选择一种,或自己写。
WebRTC技术体现为软件开发人员构建程序时用到的三组应用编程接口(API):GetUserMedia (GUM)、RTCPeerConnection (RTCPC 或PC)和RTCDataChannels (RTCDC 或 DC)。
- GetUserMedia 用于枚举机器上的多媒体硬件,请求用户权限来控制麦克风和摄像头,获取媒体流,并通过屏幕和扬声器播放流。
- RTCPeerConnection 用于在定义的端点间传输音频和视频流。
- RTCDataChannels 用于在定义的端点间传输任意数据(即使是结构化数据)。
WebRTC RTCPeerConnection 必须使用ICE,STUN和TURN来穿透防火墙和NAT,最终抵达另一端。
ICE是WebRTC自身推广的一种技术,并获得了巨大的吸引力。它是解决所有网络问题的真正的灵丹妙药,并且工作完成得相当完美,是多年VoIP工程服务的精华。
ICE将通过一个STUN服务器告诉应用程序它的公网地址是什么(从NAT和防火墙的另一侧看到的公网地址),并使用一个TURN服务器作为中继,防止 P2P通信失败。因此,一个功能完整的WebRTC应用需要:
- 使用 GetUserMedia API 控制麦克风和摄像头
- 使用一种会话协议和可能的外部辅助服务器定位对方端点并建立会话
- 使用ICE (和 STUN 和 TURN) 确定网络路径
- 使用 RTCPeerConnection 传输音/视频媒体流
加密无处不在
对媒体流和数据流加密是强制性的。 如果不对交换的媒体流加密,就没有WebRTC通信。包括所有的媒体流(音频和视频)和可能存在的数据流都需要加密。媒体流被加密为SRTP,通过DTLS交换密钥。这一切都是由RTCPeerConnection API替你完成的。
但是,当你操作时,对会话协议一并加密才更具意义。这种信令选择的传输层通常选择安全的WebSocket。在定义WebRTC信令服务器和终端端时,WSS:// 这样的URI前缀(WebSocket Secure)到处可见。WSS信令交换是TLS加密的(就像web的HTTPS)。未加密的交换方式,前缀是WS,但除了测试之外,很少使用。
FreeSWITCH的WebRTC
好了,抽象的内容说得够多了,是时候拿出点实质性的东西了:FreeSWITCH是怎样实现WebRTC的?
FreeSWITCH是一个完整的WebRTC平台,既可以作为WebRTC网关,也可以作为WebRTC应用服务器。例如,FreeSWITCH能够把WebRTC客户端连接到外部“正常”的SIP和PSTN服务(或者PBX),从而使现有或遗留的服务能够被超过10亿的浏览器在线访问。与此同时(这是真实的,作为网关同时提供某些附加的服务),FreeSWITCH能够为WebRTC客户端(浏览器或App)提供与其它“正常”SIP和PSTN客户端相同的服务,比如PBX、IVR、呼叫中心、会议系统、电话卡,等等。
对FreeSWITCH来说,WebRTC只不过是另一种类型的通信通道,和SIP、TDM、MGCP、H323、Skype、Jingle是一样的。FreeSWITCH的所有特性天然支持WebRTC。
FS 的WebRTC信令: SIP和Verto
从这一章的前面部分内容中,我们已经知道WebRTC需要一种会话协议来补充它自己的媒体流功能。这个协议将用于定位端点位置、建立与拆除会话,管理状态和消息。 FreeSWITCH支持两种这类型的协议:SIP和Verto。
这两种不同的协议只影响客户端的编写方式(客户端是加载Javascript的网页,可以访问FreeSWITCH)和客户端与FreeSWITCH的“交谈”方式。两种协议都可以完全访问FreeSWITCH的所有功能和服务。此外,不同客户端可以同时使用两种协议接入同一服务,甚至直接互动。例如:我们可以在FreeSWITCH上发起一个视频会议,让一个基于SIP的FreeSWITCH客户端,一个基于Verto的客户端,还有其它TDM客户端,普通(非WebRTC)SIP客户端同时接入会议室:
为什么选择SIP?又为什么选择Verto?
记住:这里的选择影响的只是访问FreeSWITCH的页面应用的Javascript编写方式。在本章的后续内容中,我们将介绍一个具体实例,分别用两种协议实现一个相同的应用程序。从终端用户的角度看,感觉不到其中的差异。
尽管SIP很复杂,但有电信背景的程序员很容易接受,他们对这些概念很熟悉:会话、对话、事务、REGISTER/ INVITE/ BYE消息,等等。它是在复杂环境中集成WebRTC客户端的完美选择,在扩展、监视和部署方面拥有20年的经验积累。
Verto是一种完全不同的体验:非常简单,它使用Web程序员已经熟悉的编程结构,如JSON数据结构。此外,Verto还提供了与FreeSWITCH内部服务器交互的API和双向同步的数据结构。例如,在FreeSWITCH中使用Verto时,你可以有一个数据结构(假设是视频会议的成员列表),它对所有Verto客户端透明地实时同步信息,这样,如果有新的成员加入会议(即使是从PSTN加入),他的状态会立刻呈现在所有Verto客户端的浏览器上,不需要任何客户端侧的动作(比如拉取状态)。
你可以在基于SIP的JavaScript上复制这种行为(聪明的程序员可以完成任何他想做的事情,所有的一切不都是字节吗?),使用数据流,特定的消息,推送技术等等,但在Verto中,它是原生的,仅需要几行代码。
FS 的WebRTC媒体流: SRTP
与会话信令选择(SIP或Verto可选)不同,WebRTC客户端的媒体流处理总是遵循WebRTC规范,强制加密。它没有选择,只能用SRTP,并通过DTLS交换密钥。事实上,在FreeSWITCH内部,对两种不同信令的客户端,媒体流加密使用的是同一个模块和同一套代码。如前所术,WebRTC定义了媒体流和加密方式,把会话协议留给应用实现者自由处理。
在FS中安装并配置WebRTC
为了快速测试,Tristan Mahe贡献了一个脚本,它自动安装WebRTC需要的所有内容,从FreeSWITCH本身到WEB服务器证书,再到Verto通信器,用于新部署的Debian 8最小安装服务器。在硬件上安装基础的操作系统,或安装一台虚拟机,ssh连接这台机器,执行完脚本,你就已经准备好一台新的WebRTC会议服务器。从这里下载脚本:
https://freeswitch.org/confluence/display/FREESWITCH/Debian+8+Jessie#Debian8J essie–QuickStartFreeSWITCHDemoWithVertoCommunicator
在接下来的内容中,我们不会使用自动安装脚本。而是从安装开始,逐步构建一个完整的WebRTC FreeSWITCH平台。
事实上,在缺省部署完成后,支持WebRTC不需要附加安装任何模块。所有需要的模块都已经安装好了。FreeSwitch是一款可以开箱即用的WebRTC应用服务器。
我们需要编辑几个配置文件,这样,Freeswitch就可以找到强制的SSL证书、知道要监听的端口以及许多其他细节。
证书
这是许多FreeSWITCH/WebRTC新手的一块绊脚石。我们需要许多与证书相关的文件,以及它们的不同组合,以帮助加密FreeSwitch流量的不同部分:“传统的”基于TLS的SIP(即”sips”,详情请参考RFC3261);基于WSS的SIP;基于WSS的Verto;和SRTP媒体。
HTTPS服务器也需要证书文件,它将为我们的WebRTC客户端加载页面和JavaScript提供服务。我们需要证书颁发机构的根文件和链文件。我们也需要私钥。此外,原始证书文件需要以不同的方式连接,以形成我们需要的不同的即用证书。内容有点多,难怪很多人觉得这很困惑。
我采用一种简单的解决方案:把所有证书相关的文件放在一个目录中,让FreeSWITCH的模块和WEB服务器(Apache 或 Nginx)到这个目录获取。我还写了个脚本以保证它们正确连接。如果你的证书供应商和我的一样,你可以直接使用它;如果不一样也不要紧,你可以修改它,让它适配你的证书提供商。
首先,忘掉自签证书和类似的增强解决方案。自签名证书“可能”适用于测试目的,但是要正确协调所有的活动部件,这样做是不值得的,而且肯定会浪费大量的时间和挫折。简单地说:不要使用自签证书。对真实有效的域名,使用真实有效的证书。
如果你的服务只响应一两个域名的请求,那么你是幸运的。可以从letsencrypt.org免费获取证书。如果你想让你的服务对一个域名下的所有子域名都响应(比如:www.mydomain.com、 sip.mydomain.com、 support.mydomain.com、 sip.mycustomername.mydomain.com、 www.mysecondcustomername.mydomain.com ),那么你需要购买通配证书。当前没有免费的通配证书可以选择,但将来也许会有。
如使用www.letsencrypt.org提供的免费(且完全有效)证书(请查看其网站上的说明):
-
-
-
cp /etc/letsencrypt/live/my.fqdn.com/* /usr/local/freeswitch/certs/ cd /usr/local/freeswitch/certs/
-
-
cat fullchain.pem privkey.pem > wss.pem cat cert.pem privkey.pem > agent.pem cat chain.pem > cafile.pem
如果是由Comodo颁发的PositiveSSL wilcard证书(谷歌搜索最低价的),请注意:你必须找到并下载addtrustexternalcaroot.crt文件:
-
-
-
cd /usr/local/freeswitch/certs/ cp myserver.key privkey.pem
-
-
cp STAR_mydomain_com.crt cert.pem
-
-
cp STAR_mydomain_com.ca-bundle chain.pem
-
-
cat cert.pem chain.pem addtrustexternalcaroot.crt > fullchain.pem cat cert.pem privkey.pem fullchain.pem > wss.pem
-
-
cat fullchain.pem privkey.pem > agent.pem cat chain.pem > cafile.pem
安装并配置Apache或NginX,提供HTTPS服务
我们需要为WebRTC客户端准备支持TLS/SSL访问的WEB服务器。任意一款支持HTTPS的WEB服务器都行。目前最流行的两款WEB服务器是Apache和NginX。接下来我们看看怎么在Debian 8 (Jessie)上安装它们,并启用HTTPS/SSL。
然后,我们将准备好的用于提供服务的页面文件放在下面目录下:
/var/www/html/
这一点Apache和NginX一样
你只需要选用Apache或NginX的其中一个,没必要两个都安装
Apache
第一步是更新安装包和数据库,然后安装apache2和ca-certificates包。后一个包将安装证书机构颁发的证书,这在处理SSL时可能很有用。
接下来启用SSL Apache模块,以及通过HTTPS提供服务的缺省SSL站点。
接下来的两行代码(为了方便阅读,排版时分为三行),使用PERL替换SSL/TLS证书和私钥的文件名和路径。最后一行代码重启Apache服务:
-
apt-get update
-
-
apt-get install apache2 ca-certificates a2enmod ssl
-
-
a2ensite default-ssl.conf perl -i -pe \ ‘s|/etc/ssl/certs/ssl-cert-
-
-
snakeoil.pem|/usr/local/freeswitch/certs/cert.pem|g’ \ /etc/apache2/sites- enabled/default-ssl.conf
-
-
perl -i -pe \ ‘s|/etc/ssl/private/ssl-cert-
-
-
snakeoil.key|/usr/local/freeswitch/certs/privkey.pem|g’ \
-
-
/etc/apache2/sites-enabled/default-ssl.conf service apache2 restart
NginX
和Apache安装一样,我们先更新安装包一数据库。然后安装nginx和ca-certificates包。后一个包将安装证书机构颁发的证书,这在处理SSL时可能很有用。
接下来,启用443端口(SSL)监听,并为通过HTTPS提供服务的缺省站点包含snippet。接下来的两行代码(为了方便阅读,排版时分为三行),使用PERL替换SSL/TLS证书和私钥的文件名和路径。最后一行代码重启NginX。
-
apt-get update
-
-
apt-get install nginx ca-certificates
-
-
perl -i -pe ‘s/# listen/listen/g’ /etc/nginx/sites-enabled/default perl -i -pe \
-
-
‘s/# include snippets\/snakeoil.conf/include snippets\/snakeoil.conf/g’ \
-
-
/etc/nginx/sites-enabled/default perl -i -pe \ ‘s|/etc/ssl/certs/ssl-cert-
-
-
snakeoil.pem|/usr/local/freeswitch/certs/cert.pem|g’ \
-
-
/etc/nginx/snippets/snakeoil.conf perl -i -pe \ ‘s|/etc/ssl/private/ssl-cert-
-
-
snakeoil.key|/usr/local/freeswitch/certs/privkey.pem|g’ \
-
-
/etc/nginx/snippets/snakeoil.conf service nginx restart
配置 VERTO
在FreeSWITCH缺省安装环境下,mod_verto模块的配置文件是/usr/local/freeswitch/conf/autoload_configs/verto.conf.xml。我们将编辑”default- v4″这个配置,IPV4是目前网络连接通常使用的地址类型。您要检查TCP端口,Verto将在该端口上侦听WSS传输上的信令。注意,secure=”true”表示WSS(而不是简单的WS)。
-
<param name=“bind-local” value=“$${local_ip_v4}:8082” secure=“true”/>
-
-
<param name=“ext-rtp-ip” value=“93.58.44.181”/>
-
-
<param name=“debug” value=“0”/>
参数ext-rtp-ip的值配置媒体的外部IP地址,VERTO会通知WebRTC客户端连接这个地址以进行媒体交换。因此,对于从因特网访问的客户端来说,ext-rtp-ip必须设置为NAT后的公网IP(通常和WEB服务的IP地址相同)。
你可能需要临时把”debug”的值设为10,这样VERTO的所有交换信息都会呈现在FreeSWITCH控制台和fs_cli界面上(红色的!)。把mod_verto的debug输出与客户端浏览器上的javascript控制台日志结合起来,就能得到信令交换的完整跟踪信息。
配置基于SIP的WebRTC (WSS)
在FreeSWITCH缺省安装环境下,你只需要修改”internal” SIP profile。打开/usr/local/freeswitch/conf/sip_profiles/internal.xml文件,检查”wss-binding”配置,它指定了SIP模块(mod_sofia)监听WSS信令的TCP端口。
-
<param name=“wss-binding” value=“:7443″/>
-
-
<param name=“ext-rtp-ip” value=“93.58.44.181”/>
-
-
<param name=“ext-sip-ip” value=“93.58.44.181”/>
参数ext-rtp-ip和ext-sip-ip设置媒体和信令的交换地址,WebRTC客户端将通过它们交换信令和媒体。它必须设置为WebRTC客户端看到的FreeSWITCH IP地址。因此,如果客户端从因特网访问,ext-rtp-ip必须设置为NAT转换后的公司IP,换句话说,路由可达的IP(通常和WEB服务器IP相同)。
编写 WebRTC 客户端
WebRTC 客户端最主流的实现就是一个加载JavaScript的HTML页面。HTML和JavaScript分别定义WebRTC的GUI和行为。
通常,由HTML加载的javascript部分会用到一个或多个javascript库。这些库实现选定的信令协议(SIP或VERTO)和WebRTC API的交互。我们已经了解WebRTC API提供了访问本地多媒体硬件(麦克风和摄像头)的能力;还能管理音视频媒体流;还有双向的数据通道。
会话信令协议将会利用和补充WebRTC P2P功能,因此它们的作用远远超过连接一个已知的地址和端口。
SIP 和 JavaScript
WebRTC的SIP在jssip JavaScript开源库中得到了显著的实现。JsSIP由José Luis Millán, Iñaki Baz Castillo, 和 Saúl Ibarra Corretgé编写,他们是RFC 7118的联合作者,这份规范定义了SIP的WS和WSS用法。
后来,在OnSIP工作期间,Will Mitchell和他的团队分裂JsSIP并进一步开发。他们的开发成果以开源SIP.js库的的形式发布。如今,SIP.js已经被广泛采用,它是FreeSWITCH社区中最常用的Javascript WebRTC SIP库。
另一个值得注意且独立的JavaScript实现的开源SIP库是sipml5(事实上它是HTML5的第一个SIP客户端),它由Doubango电信开发。
这些库都很容易从网上找到,包含完整的文档,实例客户端。支持和开发最积极的似乎是SIP.js。在本章的后面内容中,我们会看到SIP.js的实际应用,实现一个WebRTC聊天室。
Verto 和 JavaScript
VERTO协议是一个典型的JSON协议,JavaScript程序员通过引用其中的一个“特色”库,就可以使用它。
第一个“特色”库是verto-min.js,它是一个迷你版的基础库,随FreeSWITCH源码一起发布 ,一个普通的“原始”javascript库,依赖于jquery和and jquery.json。另一个“特色”库是一个JQuery的”verto”插件,可以从”npm” Node管理器获取。
VERTO库是由Anthony Minessale II开发的,他是FreeSWITCH的主要开发者和作者,在FreeSWITCH 1.5开发期间,VERTO的进展与mod_verto模块并行。
Verto JQuery插件和它的文档由Italo Rossi及其团队在evolution .net.br进一步开发(他们还是Verto Communicator的开发者),并由开源软件wiz Chad Phillips (hunmonk on IRC)提供。从http://evoluxbr.github.io/verto–docs/,你可以找到完整的文档,还有手把手的编程指南和客户端示例。
在我们的WebRTC聊天室中,为了简单起见,将选择verto-min.js库。
Verto Communicator, 一个高级的WebRTC客户端
Verto Communicator是由Italo Rossi及其团队在evolution .net.br编写的,它是一个完整的高级开源WebRTC VERTO客户端示例,非常酷,具有视频会议的附加功能。
如果你在FreeSWITCH中配置了一个具有完整选项的视频会议,像演示配置里的3500那样,那么就能体验Verto Communicator (VC)的所有特性。
它具有屏幕共享、音频/视频静音或解除、聊天、会员名单、实时显示发言者、自动可视化的头像、全屏、拨号板、麦克风和摄像头的选择、带宽等功能。
下图是Seven Du Jin Fang(杜金房)提供的一张VC使用截屏,杜先生是FreeSWITCH视频代码的作者,还是蓬勃发展的FreeSWITCH中国社区的领导人。图中他在主屏幕区,其他与会者正陆续加入,并自动围绕在他周围。
当你以”moderator”(设置”|moderator”标识)主持权限接入会议时,它将为你提供大量附加功能:你可以踢除与会者;开始和停止音视频录制;播放音/视频文件;截图;在每个参与者视频流的底部放置带有任意文本(例如,名称和位置)的横幅;选择与会者的“位置”,等。等。你实际上成为了会议的“电影导演”:让谁显示在屏幕上,决定屏幕上同时显示多少人,你可以让演讲者的共享屏幕显示为全屏,并在其中嵌入一个演讲者的小画面,等等。现场合成画面。
要在Debian 8 Jessie上安装Verto Communicator,git克隆FreeSWITCH源码,并执行:
-
cd /usr/src
-
-
git config –global url.“https://”.insteadOf git://
-
-
git clone https://freeswitch.org/stash/scm/fs/freeswitch.git -bv1.6 freeswitch
-
-
cd /usr/src/freeswitch/html5/verto/verto_communicator/
-
-
./debian8-install.sh
-
-
ln -s /usr/src/freeswitch/html5/verto/verto_communicator/dist
-
-
/var/www/html/vc
然后你需要编辑/var/www/html/vc/config.json,根据你的值修改(把密码改为/usr/local/freeswitch/conf/vars.xml里配置的值):
-
{
-
-
“login”: “1008”,
-
-
“password”: “mydefaultpasswordfromvars.xml”, “wsURL”: “wss://lab.opentelecomsolutions.com:8082”
-
-
}
在/usr/src/freeswitch/html5/verto/verto_communicator/src/config.json.sample,有一个更完整的定制配置示例,它由FreeSWITCH内核组开发成员Ken Rice精心制作。
我们会在第13章,会议与WebRTC视频会议中再次介绍VC。
WebRTC 聊天室,同时支持SIP和Verto客户端
让我们来看一个简单的项目,希望您能够将它用于测试、生产,并作为进一步开发的基础。
顺便说一句,Len Graham已经改编了一个早期的WebRTC聊天室版本(来自Packt 2016年出版的”Mastering FreeSWITCH” 一书),并把它作成FusionPBX的VERTO客户端插件(FusionPBX官网www.fusionpbx.com,Mark Crane是它的主要开发人员。FusionPBX是一个完整的Web界面,用于配置和管理基于FreeSWITCH的工业级PBX。)。
WebRTC聊天室是一个基础的WEB客户端,它能够发起和接收视频呼叫、在通话中发送DTMF,并与FreeSWITCH会议功能的聊天系统对接。它有两个版本,区别在于JavaScript实现会话信令协议的部分。其中一个版本使用SIP,另一个使用VERTO。有趣的是,两个版本的HTML、用户界面、行为、用户体验完全相同。实际上,如果不在浏览器中启动JavaScript控制台,它们是不可区分的。
下面是sip.js风格的截图(您可以在javascript控制台中看到sip消息),稍后您将看到Verto风格:
在WebRTC会议室中聊天
通常,会议聊天功能只在VERTO WebRTC客户端可用。通过VERTO协议的保持数据结构交同步分发给所有连接客户端的特性,聊天功能几乎唾手可得。Verto客户端只需要实现接收聊天事件的回调函数就可以。
SIP协议没有定义数据同步和事件分发的内容,它完全属于另一个世界。SIP WebRTC聊天室的实现只交换“传统”的SIP简单消息,它不使用WebRTC或JavaScript技术获取聊天事件,而只使用SIP技术。实事上,它使用与“传统”SIP硬/软电话相同的SIP即时消息技术(我们将会看到,在WebRTC聊天室里,会议聊天消息可以通过“传统”的SIP硬/软电话接收和发送)。
作为与SIP会议参与者交换聊天信息的“桥梁”,我们将使用FreeSWITCH内部的CHAT API,以及mod_sms及其聊天方案(chatplan)。
正如我们稍后将看到的,mod_sms能够截获和路由(根据其chatplan)FreeSWITCH的内部CHAT API相关的所有消息。FreeSWITCH的会议模块(mod_conference)使用FreeSWITCH CHAT API向与会者发送聊天消息。同时,SIP客户端发送的简单SIP消息可以被mod_sms接收,并根据chatplan路由(这和FreeSWITCH接收呼叫并通过拨号方案路由的方式本质上是一样的)。
因此,考虑接收会议室信息的话,用会议室的CHAT API注册SIP客户端就够了。此外,设置一个chatplan,可以指示mod_sms模块SIP客户端发送过来的简单SIP消息路由给会议的CHAT API。
顺便说一下,这一机制同样适用于其它实现FreeSWITCH CHAT API的模块,比如说通过mod_dingaling或mod_gsmopen接入的与会者。
FreeSWITCH会议
FreeSWITCH的会议有许多特性。演示配置实例中,有一个会议”profile”,它封装了所有内容。这个”profile”名叫”video-mcu-stereo”,它是在/usr/local/freeswitch/conf/autoload_configs/conference.conf.xml中定义的。使用演示配置,可以拨打3500-3599接入会议,可以享受“CD音质的会议”。
它提供的最重要特性包括:对视频流混频;屏幕共享;视频特效,如字幕、色度键、画中画、视频播放等等。此外,它还提供了一个聊天和实时显示与会者状态的系统。
我们将主要用3500进行测试,但如果你更关注资源开销和节省CPU,可以用3000-3099(缺省会议配置,定义了名为”nb_conferences”的extension)。
mod_sms 和 chatplan
Chatplan就是聊天相关的dialplan。Chatplan是一种新的黑科技!首先,你必须编辑/usr/local/freeswitch/conf/autoload_configs/modules.conf.xml文件,去掉mod_sms那一行的注释符,这样才能在FS启动时加载mod_sms模块:
<load module="mod_sms"/>
接下来,你可以重启FreeSWITCH,或通过fs_cli命令手动加载mod_sms模块:
load mod_sms
现在,我们必须编辑chatplan,打开/usr/local/freeswitch/conf/chatplan/default.xml:
-
-
-
<include>
-
-
<context name=“public”>
-
-
<extension name=“demo”>
-
-
<condition field=“to” expression=“^(3\d{3})@.*$”>
-
-
<action application=“info”/>
-
-
<action application=“set” data=“to=${to_user}-${to_host}”/>
-
-
<action application=“set” data=“dest_proto=conf”/>
-
-
<action application=“set” data=“chat_proto=conf”/>
-
-
<action application=“set” data=“skip_global_process=true”/>
-
-
<action application=“set” data=“from=${from_user}”/>
-
-
<action application=“set” data=“final_delivery=true”/>
-
-
<action application=“set” data=“from_full=<sip:${from_user}@${from_host}>”/>
-
-
<action application=“info”/>
-
-
<action application=“send”/>
-
-
<action application=“stop”/>
-
-
<action application=“info”/>
-
-
</condition>
-
-
</extension>
-
-
</context>
-
-
</include>
这个chatplan会拦截发送给会议的SIP即时消息(正则表达式以3打头,紧跟3位数字,然后@加上一些内容,对应拨号方案里的会议号码),交把它路由给会议聊天室(to”字段模拟会议聊天事件的内部FreeSWITCH路由)。然后我们修改”from”字段,让它展现在会议聊天室时可读性更好些。
在转换的前后,我们都所有的变量信息输出到FreeSWITCH控制台(和拨号方案里的”info” APP一样)。 这个chatplan将在SIP即时消息和会议聊天事件间建立桥梁。
别忘记在fs_cli发命令重载FreeSWITCH配置:
reloadxml
拨号方案的 “chat_proto” extension
在mod_conference内部(conference_event.c 文件),有一种机制可以将会议中的所有聊天信息广播给所有的参会者(避免二次发送)。
消息将发给与会者客户端的”presence_id”,但前提是这个客户端通道的”chat_proto”变量值是有效的(例如:mod_conference需要知道对特定的presence_id”向CHAT FS API提供哪个参数)。许多终端模块有它们自己的聊天协议,你甚至可以在fs_cli中使用它:
freeswitch@lxc111> show api chat name,description,syntax,ikey
chat,chat,<proto>|<from>|<to>|<message>|[<content-type>],mod_dptools
无论如何,mod_conference广播消息会用到”chat_proto”变量,但只有mod_verto通道会自动设置这个变量值。如果没有chat_proto,使用SIP的与会者将收不到任何聊天消息,我们需要纠正这种歧视。让我们编辑/usr/local/freeswitch/conf/dialplan/default.xml里的拨号方案,在文件的开头处插入以下内容:
-
<extension name=“chat1” continue=“true”>
-
-
<condition field=“${source}” expression=“^mod_sofia$”>
-
-
<action application=“set” data=“chat_proto=sip”/>
-
-
</condition>
-
-
</extension>
-
这个extension将会为所有来自mod_sofia的呼叫添加”chat_proto”变量,把它设置为”sip”,它对所有SIP呼叫都有效,包括使用SIP的WebRTC呼叫。我们要感谢”continue=true”的参数设置,拨号计划会继续执行,寻找匹配其它extension。
HTML
哈,现在我们进入应用程序的内部圣地。实际上,SIP.js版和VERTO版的HTML (index.html)是相同的。唯一需要更改的是加载哪个JavaScript文件。
下面VERTO版的代码:
-
-
-
<html lang=“en”>
-
-
<head>
-
-
<meta charset=“utf-8”>
-
-
<meta http-equiv=“X-UA-Compatible” content=“IE=edge”>
-
-
<meta name=“viewport” content=“width=device-width, initial-scale=1”>
-
-
<!– The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags –>
-
-
<meta name=“description” content=“A WebRTC client for Verto FreeSWITCH module”>
-
-
<meta name=“author” content=“Giovanni Maruzzelli”>
-
-
<link rel=“icon” href=“favicon.ico”>
-
-
<title>WebRTC Rooms</title>
-
-
<!– Bootstrap core CSS –>
-
-
<link href=“css/bootstrap.min.css” rel=“stylesheet”>
-
-
<!– Custom styles for this template –>
-
-
<link href=“high.css” rel=“stylesheet”>
-
-
</head>
-
-
<body>
-
-
<div id=“conference”>
-
-
<input type=“hidden” id=“passwd” value=“mysecretpassword_LOL” />
-
-
<input type=“hidden” id=“cidnumber” value=“WebRTC” />
-
-
<div class=“form-signin”>
-
-
<h2 class=“form-signin-heading”>WebRTC Rooms</h2>
-
-
<div id=“content” class=“form-signin-content”>
-
-
<input type=number id=“login” min=1000 max=1019 step=1 class=“form-control” placeholder=“Login with a Number between 1000 and 1019” required autofocus>
-
-
<button class=“btn btn-lg btn-primary btn-success” data- inline=“true” id=“loginbtn”>Login</button>
-
-
<input id=“ext” class=“form-control” placeholder=“Insert the Extension to Call or Wait to be Called” required autofocus>
-
-
<button class=“btn btn-lg btn-primary btn-success” data- inline=“true” id=“callbtn”>Call</button>
-
-
<button class=“btn btn-lg btn-primary btn-danger” data- inline=“true” id=“backbtn”>Back</button>
-
-
</div>
-
-
<div id=“video1” align=“center” class=“embed-responsive embed- responsive-4by3”>
-
-
<video id=“webcam” autoplay=“autoplay” class=“embed- responsive-item”> </video>
-
-
</div>
-
-
<button class=“btn btn-lg btn-primary btn-danger” data- inline=“true” id=“hupbtn”>Hangup</button>
-
-
<br id=“br” />
-
-
<textarea id=“chatwin” class=“form-control” rows=“5” readonly></textarea>
-
-
<br id=“br” />
-
-
<input id=“chatmsg” class=“form-control” rows=“1” placeholder=“type here your chat msg” autofocus></input>
-
-
<button class=“btn btn-primary btn-success” data-inline=“true” id=“chatsend”>Send Msg</button>
-
-
</div>
-
-
<div align=“center” class=“inner”>
-
-
<p>2017<br/>Giovanni Maruzzelli – OpenTelecom.IT</p>
-
-
</div>
-
-
</div>
-
-
<script type=“text/javascript” src=“js/jquery.min.js”></script>
-
-
<script type=“text/javascript” src=“js/jquery.json-2.4.min.js”></script>
-
-
<script type=“text/javascript” src=“js/verto-min.js”></script>
-
-
<script type=“text/javascript” src=“js/md5.js”></script>
-
-
<script type=“text/javascript” src=“high2.js”></script>
-
-
</body>
-
-
</html>
这个文件使用CSS的引导框架,因此它可以在移动设备上完美工作,并适应各种不同的显示格式和尺寸。前几行代码就是为这个目的而写,设置一个极简但快速的GUI界面。在接下来的HTML主体中,有许多DIV、BUTTON、 INPUT、TEXTAREA,每个都有它自己的ID。
我们将会在Javascript中用这些ID来引用每个独立的HTML元素,可以控制它们的显示和隐藏(比如说,在注册成功后隐藏登陆按钮),或者读取输入内容,或者输出某些内容(我们需要读取呼叫的号码,需要在文本区显示聊天的内容)。
我们有一个特殊的”video”元素,bootstrap.css以一种漂亮的方式在各种设备上展示它。它的”id”将被VERTO和SIP.js的初始化脚本引用(例如,他们需要知道在哪里显示视频流)。
最 后几行说明要加载的脚本。我们认得verto-min.js,它来自/usr/src/freeswitch/html5/verto/video_demo/js/verto-min.js,是VERTO JavaScript库的一种风格,随FreeSWITCH源码发布。在我们的脚本里,与HTML元素的ID交互的是high2.js。
修改index.html的最后几行,加载SIP.js相关的几个脚本:
-
<script type=“text/javascript” src=“js/jquery.min.js”></script>
-
-
<script type=“text/javascript” src=“js/jquery.json-2.4.min.js”></script>
-
-
<script type=“text/javascript” src=“js/sip.js”></script>
-
-
<script type=“text/javascript” src=“js/md5.js”></script>
-
-
<script type=“text/javascript” src=“highsipjs2.js”></script>
-
-
</body>
-
-
</html>
我们之前描述过sip.js库,可以从http://sipjs.com/download/ 下载最新的版本。最后的highsipjs2.js是我们写的脚本。
下面是VERTO 版的一个截图,我们打开Javascript 控制台,可以看到Verto 对象:
JavaScript
无论是VERTO版还是SIP.js版,WebRTC聊天室的脚本是非常相似的。事实上,首先实现的是VERTO版,它是2016年出版的《”Mastering FreeSWITCH”》一书里的实例。在这本书中,你可以看到逐行的解说。新版中解决了一些问题,并增加了一些特性(自动应答、更友好的DTMF发送方式、可以呼叫非数字的分机,以及各种优化)。
-
$(window).load(function() { cur_call = null; chatting_with = false;
-
-
$(“#conference”).show();
-
-
$(“#ext”).hide();
-
-
$(“#backbtn”).hide();
-
-
$(“#cidname”).hide();
-
-
$(“#callbtn”).hide();
-
-
$(“#hupbtn”).hide();
-
-
$(“#chatwin”).hide();
-
-
$(“#chatmsg”).hide();
-
-
$(“#chatsend”).hide();
-
-
$(“#webcam”).hide();
-
-
$(“#video1”).hide();
-
-
$(“#login”).keyup(function(event) {
-
-
if (event.keyCode == 13 && !event.shiftKey) {
-
-
$(“#loginbtn”).trigger(“click”);
-
-
}
-
-
});
-
-
$(“#ext”).keyup(function(event) {
-
-
if (event.keyCode == 13 && !event.shiftKey) {
-
-
$(“#callbtn”).trigger(“click”);
-
-
});
-
-
}
-
-
});
启动时,两种风格的脚本都执行”load”函数,它设置HTML的可见元素,并定义了用户在登陆框或输入分机时按“回车”键的响应方式。
用户填入登陆信息并按回车或点击登陆按钮时,会执行init()函数。在init()函数内部,创建主要的JavaScript对象—-我们的WebRTC UA客户端。让我们看下SIP.js风格的代码,VERTO风格的很相似:
-
function init() { cur_call = null;
-
-
chatting_with = false;
-
-
var nameHost;
-
-
var which_server;
-
-
nameHost = window.location.hostname;
-
-
which_server = “wss://” + nameHost + “:” + “3384”; console.error(“which_server=”, which_server);
-
-
ua = new SIP.UA({
-
-
wsServers: which_server,
-
-
uri: $(“#login”).val() + “@” + nameHost,
-
-
password: $(“#passwd”).val(), userAgentString: ‘SIP.js/0.7.7 Sara’, traceSip: true,
-
-
});
-
-
-
-
$(“#cidname”).keyup(function(event) {
-
-
if (event.keyCode == 13 && !event.shiftKey) {
-
-
$(“#callbtn”).trigger(“click”);
-
-
}
-
-
});
-
-
setupChat();
-
-
$(document).keypress(function(event) {
-
-
var key = String.fromCharCode(event.keyCode || event.charCode); var i = parseInt(key);
-
-
var tag = event.target.tagName.toLowerCase();
-
-
-
-
if (tag != ‘input’) {
-
-
if (key === “#” || key === “*” || key === “0” || (i > 0 && i <=9)) {
-
-
cur_call.dtmf(key);
-
-
}
-
-
}
-
-
});
-
-
}
我们首先确保没有呼叫和聊天数据,清空数据结构。然后确定要连接的服务器(在基于域的分区进行大规模扩展时很有用),然后使用从HTML获取的值创建主要对象,即”ua”对象。
我们准备在输入被叫号码时响应回车按键。
然后,我们调用setupChat()函数,这个函数对两种风格的实现来说有些细微的差别。这是因为:在VERTO风格的实现中,我们需要填充”from”字段,而在SIP.js风格的实现中,这些字段由主对象自动设置。下面是VERTO风格的代码:
-
function setupChat() {
-
-
$(“#chatwin”).html(“”);
-
-
-
-
$(“#chatsend”).click(function() {
-
-
if (!cur_call && chatting_with) { return;
-
-
}
-
-
cur_call.message({
-
-
to: chatting_with,
-
-
body: $(“#chatmsg”).val(),
-
-
from_msg_name: cur_call.params.caller_id_name, from_msg_number: cur_call.params.caller_id_number
-
-
});
-
-
$(“#chatmsg”).val(“”);
-
-
});
-
-
-
-
$(“#chatmsg”).keyup(function(event) {
-
-
if (event.keyCode == 13 && !event.shiftKey) {
-
-
$(“#chatsend”).trigger(“click”);
-
-
}
-
-
});
-
-
}
然后,我们可以管理许多按钮和场景:挂机、回退、呼叫,诸如此类。
“呼叫”按钮执行docall()函数,两种风格的代码还是很相似:在SIP.js风格中调用INVITE方法,而在VERTO风格中调用newCall方法。它们得到或多或少相似的参数表:在VERTO风格代码中,你在主对象初始化时设置事件的回调函数;而SIP.js风格代码中,现在设置回调。
让我们看看VERTO风格的代码:
-
function docall() { if (cur_call) {
-
-
return;
-
-
}
-
-
cur_call = verto.newCall({ destination_number: $(“#ext”).val(), caller_id_name: $(“#login”).val(), caller_id_number: $(“#login”).val(), useVideo: true,
-
-
useStereo: true, useCamera: ‘any’, useSpeak: ‘any’, useMic: ‘any’
-
-
});
-
-
}
脚本的其余部分都是在设置回调,以响应聊天消息、挂机、以及会话的建立(比如等待对端应答)等等。
下面是FreeSWITCH控制台的一幅截图,显示了chatplan信息,SIP的即时消息,还有Verto消息。
让我们看看来电应答处理的区别(两种风格的代码都会自动替你应答)。
SIP.js注册handleInvite函数作为来电事件的回调函数,下面是实现代码:
-
function handleInvite(s) { cur_call = s;
-
-
s.accept({
-
-
media: {
-
-
constraints: { audio: true,
-
-
video: true
-
-
},
-
-
render: {
-
-
remote: document.getElementById(‘webcam’)
-
-
}
-
-
}
-
-
});
-
-
$(“#ext”).val(s.remoteIdentity.uri.toString()); cur_call.on(‘accepted’, onAccepted.bind(cur_call)); cur_call.once(‘bye’, onTerminated.bind(cur_call)); cur_call.once(‘failed’, onTerminated.bind(cur_call)); cur_call.once(‘cancel’, onTerminated.bind(cur_call));
-
-
$(“#chatwin”).html(“”);
-
-
}
它接受呼叫,为媒体流给出自己的约束,设置一些呼叫事件(成功、远程挂机等)的回调,然后设置几个HTML字段的内容。
VERTO风格的脚本同样注册了来电事件和状态变化(类似GUI消息驱动的编程风格)的回调函数:
-
onDialogState: function(d) { if (!cur_call) {
-
-
cur_call = d;
-
-
}
-
-
switch (d.state) {
-
-
case $.verto.enum.state.ringing: console.error(“RINGING”);
-
-
-
-
$(“#webcam”).show();
-
-
$(“#video1”).show(); cur_call.answer({
-
-
callee_id_name: “ciao”, callee_id_number: “1234567”, useVideo: true,
-
-
useStereo: true, useCamera: true, useMic: true
-
-
});
-
-
break;
待办事项
内容很多!比如,用VERTO实现直接聊天(会议之外,普通呼叫的即时消息)。
在SIP.js版本中,在聊天文本区打印发送的消息(接收消息的显示已经实现了)。
交互与视觉的优化。
添加按钮、菜单和命令来管理会议布局,版主职责,桌面共享等。
对呼叫失败进行更健壮的管理,在出现问题时进行健壮的清理。
总结
这一章,我们讨论了以下内容:
- 什么是 WebRTC?它有什么意义?
- WebRTC中的强制安全
- FreeSWITCH的WebRTC 实现
- WebRTC中,信令协议 (SIP 和 Verto) 所扮演的角色
- 完成 FreeSWITCH 和支持WebRTC 的组件安装和配置(包括证书、Web服务器)
- 介绍一个超级先进和功能齐全的浏览器客户端,可实现WebRTC 视频会议和状态呈现:Verto Communicator
- 怎样用VERTO 或SIP.js 实现一个Web客户端
这一章,我们研究了理论和细节,并学习了如何为我们的用户群建立真实的WebRTC服务。
下一章,我们将深入XML拨号方案—FreeSWITCH内核的呼叫路由引擎。我们将研究如何实现最多样化的服务,同时利用缺省演示配置中的丰富示例。