信也语音服务架构揭秘

信也语音服务架构揭秘 前言 电话销售与贷后联络是公司业务比较重要的一环,而为了实现语音呼叫的功能,公司内部是有一套自研的语音系统来支撑的,那么大家是否好奇语音系统是如何实现的呢?
随着互联网的兴起,VoIP(网络电话)也随之发展起来,VoIP是通过互联网来传输语音和视频的,比如微信的语音和视频通话就是通过VoIP来实现的,所以VoIP的成本和价格低廉,也可以实现很多传统电话无法实现的功能,通过转换网关也可以与PSTN(传统电话网络)对接,而公司内部的语音系统就是基于VoIP来实现的。
语音服务架构说明 一、相关协议 由于语音基础服务是基于VoIP技术来实现的,所以会话控制、媒体传输这些都需要遵循一个标准的协议来进行的。
会话的控制
通话时通过会话初始协议 (Session Initiation Protocal, SIP , RFC 3261)来控制会话的。
SIP是一个应用层的信令控制协议,主要目的是在 IP 网络中建立、修改和释放多媒体会话的应用层协议。其主要的应用包括但不局限于语音、消息、视频、呼叫控制等。会话的参与者可以通过组播(multicast)、网状单播(unicast)或两者的混合体进行通信。
SIP的业务模式是一个点对点协议,其中有两个要素——SIP用户代理(User Agent, UA)和SIP网络服务器。
媒体流的传输
通话时的媒体流(语音流、视频流等)是通过实时传输协议 (Real-time Transport Protocol,RTP )来进行传输的。
RTP是一个网络传输协议,它是由IETF的多媒体传输工作小组1996年在RFC 1889中公布的。是一个网络传输协议,它是由IETF的多媒体传输工作小组1996年在RFC 1889中公布的。
RTP协议详细说明了在互联网上传递音频和视频的标准数据包格式。它一开始被设计为一个多播协议,但后来被用在很多单播应用中。RTP协议常用于流媒体系统(配合RTSP协议),视频会议和一键通(Push to Talk)系统(配合H.323或SIP),使它成为IP电话产业的技术基础。RTP协议和RTP控制协议RTCP一起使用,而且它是创建在UDP协议上的。
SIP信令构成
【信也语音服务架构揭秘】信也语音服务架构揭秘
文章图片

  1. SIP信令可以基于TCP传输,也可以基于UDP传输
  2. SIP信令可以分为请求(图左:Request-Line)和响应(图右:Status-Line),由起始行来确定
  3. Message Header部分用于存放路由信息以及会话中需要处理的信息
  4. Messge Body(payload)是存放媒体信息和一些文本以及XML信息的
信也语音服务架构揭秘
文章图片

  1. 请求信令包含一个Method字段用于确定请求的类型,不同的请求有不同的职能,如INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, REGISTER等等。
  2. 响应信令包含一个Status-Code字段,用于标识响应码,不同的响应码也有不同的含义,如:100, 180, 183, 200, 400, 407, 487, 500, 503等等。
信也语音服务架构揭秘
文章图片

  1. Record-Route、Via、Contact包含了通话的路由以及通话传输的路径,可以让响应找到返回的路径。
  2. From、To包含了通话的主叫和被叫信息
  3. X-开头的一些数据是一些自定义参数,可以用于业务传参
信也语音服务架构揭秘
文章图片

Message Body中承载的SDP标记了媒体的信息,而通过RTP承载的媒体流就会根据这个信息来传输。
媒体信息:
  • 服务器地址:IPv4 119.84.39.82
  • 媒体类型:audio(语音)
  • 媒体端口:12116
  • 媒体协议:RTP/AVP
  • 语音编码:G.711 PCMU
语音通话信令流程
信也语音服务架构揭秘
文章图片

  1. 215向217发起INVITE请求,表示要拨打一通电话
  2. 217向215回复一个响应码为100的响应,表示我已经接收到请求,正在处理,请等待
  3. 217向215回复一个响应码为183的响应,表示我已经开始响铃了
  4. 这里183是携带SDP的,与INVITE中的SDP相互协商出媒体信息,语音流已经可以开始传输了,而语音流是通过RTP来传输的
  5. 217向215回复一个响应码为200的响应,表示我已经接起电话了
  6. 215向217发起一个ACK的请求,表示此次INVITE请求处理完成了
  7. 217向215发起一个BYE请求,表示我挂断电话了(哪一方先挂断,该请求由哪一方发起)
  8. 215向217回复一个响应码为200的响应,表示我知道了,此时双方都挂断了,通话结束
注:180的响应码也是表示振铃,但是180不携带SDP信息,这时候是没有建立语音通道的,要等待接听后(200OK)语音通道才会被打通,所以在回复183的时候,才能够听到彩铃音。
二、架构说明 信也语音服务架构揭秘
文章图片

注册服务器
注册服务器是基于开源SIP服务器OpenSIPS进行开发的,主要是进行分机的管理、认证、注册,用于SIP信令、RTP语音流的代理转发,对媒体服务器进行负载均衡。
电销的客服,通过电话终端(webphone/软电话/硬电话)注册到注册服务器上,注册成功后,就可以通过电话终端进行语音电话的呼叫了。
媒体服务器
媒体服务器是基于开源软交换FreeSWITCH进行开发的,主要是进行呼叫逻辑的控制,提供通话相关的各种信息。
当电销坐席发起呼叫时,由注册服务器将呼叫代理转发到媒体服务器,由媒体服务器对呼叫进行控制,可以对通话进行录音,可以将通话记录通过接口返回给业务系统。
SBC网关代理服务器
SBC网关代理服务器是基于开源SIP服务器OpenSIPS进行开发的,可以进行呼叫并发控制,通过SIP协议对接线路,代理转发SIP信令和RTP语音流,并对线路进行负载均衡。
SBC负载服务器可以对接基于SIP协议的线路服务器,若是传统的电话网络,则需要通过转换网关转换成支持SIP对接的线路,当媒体服务器需要拨打外部的电话时,SBC网关代理会根据媒体服务器发送的SIP信令中携带的信息,通过路由负载代理转发到相应的线路中,最终完成电话的呼出。
系统对接
语音服务对外提供了一套API接口,可以与其他系统进行对接,接入语音服务。
三、相关技术点说明 软电话/硬电话
软电话是安装在PC、手机等终端设备上的一种软件电话,软电话与真是的电话界面上是相似的,功能也是相似的,接入到互联网可以注册到SIP服务器上,完成拨打、接听电话的功能。
这里的硬电话与普通的座机是基本上一样的,但是它可以接入到互联网中,注册到SIP服务器上的,配置好后与传统的座机没有任何区别。
WebPhone
WebPhone是基于WebRTC技术实现的,是运行来浏览器上的,相对软电话/硬电话来说,不需要繁琐的安装和配置,只要浏览器支持WebRTC,就可以跨平台即开即用,基本不存在跨平台问题。
WebRTC,名称源自网页即时通信(Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准。
OpenSIPS
OpenSIPS是一个成熟的开源SIP服务器,除了提供基本的SIP代理及SIP路由功能外,还提供了一些应用级的功能。OpenSIPS的结构非常 灵活,其核心路由功能完全通过脚本来实现,可灵活定制各种路由策略,可灵活应用于语音、视频通信、IM以 及Presence等多种应用。同时OpenSIPS性能上是目前最快的SIP服务器之一,可用于电信级产品构建。
FreeSWITCH
FreeSWITCH 是一个开源的电话交换平台,它具有很强的可伸缩性–从一个简单的软电话客户端到运营商级的软交换设备几乎无所不能。能原生地运行于Windows、Max OS X、Linux、BSD 及 solaris 等诸多32/64位平台。可以用作一个简单的交换引擎、一个PBX,一个媒体网关或媒体支持IVR的服务器等。它支持SIP、H323、Skype、Google Talk等协议,并能很容易地与各种开源的PBX系统如sipXecs、Call Weaver、Bayonne、YATE及Asterisk等通信。 FreeSWITCH 遵循RFC并支持很多高级的SIP特性,如 presence、BLF、SLA以及TCP、TLS和sRTP等。它也可以用作一个SBC进行透明的SIP代理(proxy)以支持其它媒体如T.38等。FreeSWITCH 支持宽带及窄带语音编码,电话会议桥可同时支持8、12、16、24、32及48kHZ的语音。
四、通话处理逻辑 当从前端webphone发起呼叫后,会发送INVITE请求到注册服务器(OpenSIPS)上
if (is_method("INVITE")) { # 需要进行注册认证 if (!is_registered("location", "$fu")) { send_reply("503", "Attack!"); exit; } setflag(AGENT_TO_FS); $var(signid) = $(hdr(X-Sign-ID)[0]); xlog("call-id:$ci, signid:$var(signid), call from agent[$fU] to freeswitch[$tU], set AGENT_TO_FS"); # incoming from user lb to freeswitch lb_start("100", "pstn", "rs"); switch ($retcode) { case -1: xlog("call-id:$ci, 系统错误,无法找到freeswitch"); send_reply("500", "System Error"); exit; case -2: xlog("call-id:$ci, 查找freeswitch失败,freeswitch的并发已满"); send_reply("500", "Service Full"); exit; case -3: xlog("call-id:$ci, 查找freeswitch失败,没有可用的freeswitch"); send_reply("500", "Service Down"); exit; case -4: xlog("call-id:$ci, 查找freeswitch失败,freeswitch未配置pstn资源"); send_reply("500", "No Resource"); exit; } xlog("call-id:$ci, call to freeswitch[$du] success"); } }

webphone发起的呼叫会先通过is_registered进行注册认证,若是已注册的分机号,则会将通话通过lb_start(负载均衡)转发到对应的媒体服务器上,未注册的分机会被认定为盗打。

当电话到达媒体服务器时,媒体服务器(FreeSWITCH)会将每个通话封装成一个session,先通过通过拨叫计划(dialplan)来进行预处理,将电话控制权转交给callout.lua这个脚本,FreeSWITCH会将通话信息解析存放到session中,在lua中可以通过session来控制呼叫流程
local caller = session:getVariable("caller_id_number") local destination_number = session:getVariable("destination_number")

可以从通道中获取到主被叫的号码
function createCall(callNumber, shareChannelVariable) callString = shareChannelVariable .. displayNumber .. "}sofia/gateway/" return callString .. gateway .. routeGroupId .. gwPrefix .. callNumber endlocal channel_variable = "{origination_uuid=" .. uuid .. ",park_after_bridge=true,extension_number=" .. caller .. ",call_number=" .. encodeCallee .. ",sip_h_X-Is-Public=" .. isPublic .. ",origination_caller_id_number="if session:ready() then local dialString = createCall(encodeCallee, channel_variable) session:setVariable("media_bug_answer_req", "true") session:execute("record_session", recordPath) session:setVariable("recordPath", recordPathPara) showLog("info", "dialString", dialString) session:execute("bridge", dialString) local legB = freeswitch.Session(uuid) if legB:ready() and not legB:answered() then showLog("info", "waiting exit", "waiting exit for detect thread") legB:sleep("500") legB:hangup() end if legB:answered() then legB:hangup() end end

  • 通过ready函数来确保当前通话已经准备完毕,可以进行控制了
  • 通过record_session命令可以对通话进行录音
  • 通过hangup函数可以将电话主动挂断
当一切都准备就绪,就需要真正向线路发起呼叫请求了,通过bridge命令向SBC服务器发起呼叫请求
if (is_method("INVITE")) { # 检查呼叫方向 freeswitch if (lb_is_destination("$si", "$sp", "100")) { # incoming from freeswitch lb to gw setflag(FS_TO_GW); xlog("call-id:$ci, call from freeswitch to gateway, set FS_TO_GW"); $var(signid) = $(hdr(X-Sign-ID)[0]); xlog("call-id:$ci, signid:$var(signid), call from $fU, to $tU"); $var(callee) = $ruri.user; # 商户前缀->路由组ID $var(prefix) = $(var(callee){s.substr,0,2}); xlog("call-id:$ci, prefix: $var(prefix), number: $var(callee)"); lb_start("$(var(prefix){s.int})", "pstn", "rs"); switch ($retcode) { case -1: xlog("call-id:$ci, 查找网关失败,系统错误"); send_reply("500", "System Error"); exit; case -2: xlog("call-id:$ci, 查找网关失败,所有网关并发已满"); send_reply("500", "Service Full"); exit; case -3: xlog("call-id:$ci, 查找网关失败,无可用的网关"); send_reply("500", "Service Down"); exit; case -4: xlog("call-id:$ci, 查找网关失败,无pstn网关资源"); send_reply("500", "No Resource"); exit; }# 被叫号码(不带商户前缀) $var(callee) = $(var(callee){s.substr,2,0}); # info的值是 "线路前缀,线路主叫认证,线路domain" dp_translate("$(var(prefix){s.int})", "$du/$var(info)"); xlog("call-id:$ci, select perfix and caller is $var(info)\n"); # 以","分割info # 线路前缀 $var(gw_prefix) = $(var(info){s.select,0,,}); # 线路主叫认证 $var(gw_caller) = $(var(info){s.select,1,,}); # 线路domain $var(dest_uri) = $du; $var(dest_domain) = $(var(dest_uri){s.substr,4,0}); # 拼接request uri $ruri = "sip:" + $var(gw_prefix) + $var(callee) + "@" + $var(dest_domain); # 修改 to uac_replace_to("$ruri"); xlog("call-id:$ci, new to: $ruri"); # 修改 from if ($var(gw_caller) != "" && $var(gw_caller) != null) { if (isflagset(FROM_LOCAL)) { uac_replace_from("$var(gw_caller)", "sip:$var(gw_caller)@LocalIpV4"); xlog("call-id:$ci, new from: sip:$var(gw_caller)@LocalIpV4"); } else { uac_replace_from("$var(gw_caller)", "sip:$var(gw_caller)@NetIpV4"); xlog("call-id:$ci, new from: sip:$var(gw_caller)@NetIpV4"); } } xlog("call-id:$ci, call to gateway success"); } else { xlog("Attack from $si:$sp!!!"); send_reply("500", "Attack!!"); exit; }# account only INVITEs do_accounting("log"); }

当电话到达SBC网关服务器时,会根据预设的信息,通过lb_start去查找真正的线路,最终将电话送达被叫的手机,如果网路和线路都正常,这时被叫的手机将会开始振铃,电话呼叫成功。
语音服务应用 语音服务除了电话营销和贷后联络外,在公司内部还有很多别的应用:
空停检测 系统自动拨打电话,通过分析拨打电话接通之前的声音,比如说:长嘟嘟的回铃音、短嘟嘟的忙音、彩铃、空号、通话中、关机等运营商网络给出来的提示音,来获得被叫的状态,这样可以剔除掉那些空号和停机的号码,提升电销和贷后联络的效率。
智牛语音机器人 语音系统对接ASR(语音转文字)、TTS(文字转语音)、NLP(自然语言理解)的服务,可以实现自动的语音机器人,无需人工的接入,就可以完成电话营销和贷后联络的任务了。
电话告警 语音系统对接TTS,可以自动拨打电话,给用户播放一段文字或预设的语音,可以用于告警和提醒。
作者介绍 Passerby,科技输出团队技术专家。

    推荐阅读