最近在调试基于web的sip开发库(有sip.js和jssip两家),原理都差不多,都是通过SIP RFC的websocket扩展(SIP over WebSocket RFC 7118,标准情况下是UDP、TCP或TLS传输方式),通过ws消息传递标准sip消息(目前主流VoIP平台,例如:Asterisk,Freeswitch,Kamillo等均有支持),然后通过浏览器webrtc进行音视频编解码和rtp通信。
需求的来源也很简单,目前类似PJSIP的C/C++的sip开发库虽然已经非常成熟,但native开发始终是个门槛,尤其是界面开发(之前曾经开发定制过一款单exe的mbphone sip软电话,可以说80%工作量都花在界面上了,还是在使用了duilib库的前提下),现在想找齐Windows、Linux、Mac三平台都简洁好用的SIP软电话都非常困难,更不用说开发集成了,尤其是现在WEB应用和移动应用普及的时代,开发全平台的软件成本太高。
webrtc的开源平台和客户端sdk开发解决方案似乎很多,github一拉都齐活,但仔细试下来,由于webrtc的开放性并没有定义应用层通信协议,所以许多开发者大多是另起炉灶采用私有应用层通信协议,甚至ws+http混用,与标准程控交换机IPPBX、呼叫中心、视频会议等现有设施并不兼容(一般是通过这些Webrtc平台额外提供的sip网关进行转接,商用的zoom、腾讯会议也是类似解决方案)。
如果需要开发标准的SIP软电话,就需要开发5个原生的客户端(例如采用PJSIP,支持windows、Android、iOS、Linux、MacOS全平台),对于中小公司和个人开发者几乎不现实,所以视线还是回到跨平台特别是web解决方案上来。
早几年,各家开源sip通信平台对于websocket的支持还不太成熟,例如freeswitch当时实现的webrtc软电话方案,是通过verto方案折中实现verto就是一个私有的应用层协议,它并不与sip协议兼容,而且文档也不完备,除了直接使用它提供的verto软电话,并不具备太多个性化定制能力。
好在freeswitch最新1.10版本上,sip的websocket支持已经可用了,而基于javscript的sip协议栈例如sip.js和Jssip也非常成熟了,所以通过WEB实现标准的sip通信成为可能。
- 开发服务器
使用docker快速搭建一个支持websocket的sip服务器:
1 | #docker pull safarov/freeswitch |
我们这里使用host网络,避免映射太多端口的麻烦以及docker网络性能的瓶颈,因为sip通信需要海量的udp端口段。
我们这里还映射了容器内配置文件目录到主机fscfg目录下,方便修改配置文件。
- ICE服务器(stun、turn)
1 | #docker run -d --name ice --network=host coturn/coturn |
由于webrtc强制ICE进行网络协商,在sip服务器上或另一台服务器上架设coturn服务器以支持ICE协商。
- 开发服务器基本配置
1 | #cd ~/fscfg/ |
- 启动服务器端调试
应用上述修改并打开调试开关(主要是打印sip消息方便调试)。
使用sip软电话或硬件sip电话机注册到服务器,例如账号1000,密码mbstudio,注册地址是172.21.2.210(端口5060)。
其中,transport可以手工指定sip通信协议使用tcp还是udp(默认)。
1 | #docker restart fs |
能看到上述200 OK消息表示服务器已经正常工作。
- sip开发库准备
下载最新的JSSIP库:https://jssip.net/download/releases/jssip-3.10.0.js
发布时可用: https://jssip.net/download/releases/jssip-3.10.0.min.js 减少体积。
这里暂时不选择sip.js是因为其最新版本已经完全采用ts语言开发了,需要编译、发布才能使用。虽然也提供了UMD的js文件,但其文档和demo完全只讲ts,对新手非常不友好。
私以为,现在web应用开发不论规模就上ts,各种包依赖、编译、打包发布,丢掉了web开发的天生的源码开放性、简洁性和可读性,题外话了。
- web sip软电话开发步骤
新建一个html页面,引入jssip库,配置sip服务器wss地址和用户名密码,并注册回调函数启动sip ua,关键代码如下:
- SIP账号注册在线
1 | index.html: |
在浏览器中直接打开index.html,如果一切正常的话,console中会不报错并提示connected, registered。
并且sip服务器端也应该可以看到正常的注册消息200 OK,表示1000在线了。
注意:
fs的默认wss监听地址是7443,同时,由于是内网IP时址,没有正确的ssl证书,第一次运行时会直接报错websocket无法连接,需要手工在浏览器里输入 https://172.21.2.210:7443 在弹出的警告页面里手工允许访问,后续demo就可以正常连接了。
- 发起呼叫
代码中先写死被叫号码,例如1002(提前在电脑或手机上使用其它软件或硬件注册在线)。
1 | index.html: |
刷新页面,会弹出权限麦克风和摄像头权限窗口,允许后自动呼叫1002。
由于局域网没有stun和ice服务器,配置是清空的,呼叫时报错:SIP/2.0 488 Not Acceptable Here
查看fs打印的日志,原因是jssip错误地在sdp中携带了类似:a=candidate:1302296363 1 udp 2113937151 a807f338-fbf2-45a9-ba04-90a2cb5763dd.local 63851 typ host generation 0 network-cost 999
其中:a807f338-fbf2-45a9-ba04-90a2cb5763dd.local是非法地址(其实是chrome浏览器的隐私保护隐藏了本机IP,旧版本就是本地IP地址),导致mod_sofia.c:2588 CODEC NEGOTIATION ERROR.
switch_core_media.c:4197 Drop audio Candidate cid: 1 proto: udp type: host addr: a807f338-fbf2-45a9-ba04-90a2cb5763dd.local:63851 (not an IP address)
回复488消息中携带了:Reason: Q.850;cause=88;text="INCOMPATIBLE_DESTINATION"
解决办法是加入stun或ice服务器,让jssip能够探测到本机的正常ip地址,局域网方案则是内网搭建coTurn服务器:
非常折腾但是没办法,这些问题类似webrtc强制https,wss,强制媒体dtls、srtp加密等等,rtp通信协商也是强制ICE,这些只能去适应webrtc开发者google的规范和定义,要跳过这些限制只能使用原生的C/C++开发客户端SIP软电话,就可以不受制约了。
1 | var options = { |
- 调试jssip
浏览器console中输入:JsSIP.debug.enable('JsSIP:*');
重新加载页面即可看到sip消息日志。
关闭调试则同样的:JsSIP.debug.disable('JsSIP:*');
- 调试webrtc
chrome://webrtc-internals/
可以查看当前活动的rtc媒体通信,例如媒体收发端口,速率,编码等,调试单通、卡顿等SIP通信中常见问题。
提示:目前demo中,始终使用设备默认的麦克风、扬声器和摄像头建立呼叫,并且JSSIP文档中毫无相关介绍怎么修改。这是因为jssip的作者也是SIP over WebSocket RFC 7118的作者,学者一般比较有个性,认为这一块是属于浏览器的webrtc的API管的,jssip不掺和。
所以准备下一篇文章介绍下webrtc通信中怎么配合jssip实现枚举、选取音视频设备,音视频编解码启用、禁用及优先级设置,视频清晰度设置等功能。
介绍一本webrtc好书:https://webrtcforthecurious.com/zh/
- 进一步开发提示
如上,具备了一个sip软电话的基本功能,注册在线,自动发起呼叫。
进一步需要开发:(不定时更新在线demo及源码)
- 将用户名、密码可配置,wss服务器地址配死或自动分发。
- 监测注册事件,提示在线、离线状态和错误(例如:用户名、密码不对,网络中断离线自动重注册等)。
- 通过手工输入号码呼叫,或者拉取通讯录电话号码点击号码,或者实现传统的电话数字拨号盘方式发起呼叫。
- 监测呼叫事件,例如振铃、接通、无人应答、无法接通等状态,来电时提示接通或自动应答。
- 呼叫或应答时,可选仅音频或视频通话。
- 增加通话挂断、呼叫保持(hold)、静音等功能按钮。
- 本地webrtc实现录音、录像保存功能。
- 以及,开发一个友好美观的web功能界面或网页悬浮按钮通话插件。
在线demo:https://mbstudio.cn/mbwebphone-demo (原始版)
https://mbstudio.cn/mbwebphone (最新版)
提示:上述demo演示只有web部署,依然会在你的本地局域网172.21.2.210地址上去找freeswitch连接wss。
源码:https://github.com/meineson/MeConf
注意:如果是直接打开html文件方式进行开发调试,浏览器每次都会弹屏申请摄像头、麦克风权限。
如果以localhost或局域网IP方式,以http方式部署web服务开发调试,chrome系浏览器会提示不安全站点,并禁止打开摄像头、麦克风。
需要手动启用参数,例如:chrome://flags#unsafely-treat-insecure-origin-as-secure=http://172.21.2.203:4444,http://localhost:4444
才能使特定站点(你的局域网web服务地址,精确到端口)在http协议下能够取得多媒体设备权限。并且,此时你可以不使用wss而是ws了,例如修改mbphone.js中的wsServers: 'ws://172.21.2.210:5066'
使用freeswitch的ws端口进行sip websocket通信,为下一篇文章开发客户端程序埋下伏笔。
如果局域网内以https方式部署web服务开发调试,由于ssl证书一般都是自签发的浏览器不认,需要在浏览器手动忽略警告继续访问(包括web服务端口和wss两个端口)。