虎年开工第一周,放眼望去,到处都是虎虎生威的好景象,咱也不能闲着,本想吟诗一首助助兴,奈何俺没那文化底蕴!
思来想去,还是写个关于ASR的技术文档吧,即可以总结过去的经验,也可放眼未来不是……
一、背景介绍
开始聊MRCP Server的负载均衡解决方案前,我们先简单看看为啥要做这件事。
随着人工智能技术的发展,在呼叫中心业务中,传统的IVR(电话导航)按键导航模式在逐步向IVR智能客服转变(客户与IVR机器人进行语音对话),而这离不开ASR(语音识别)服务的使用。呼叫中心系统底层(如FreeSWITCH)调用实时ASR的方式通常是基于MRCP协议实现。
呼叫中心使用ASR功能,其实包含下面3种组件:
-
MRCP客户端:发送RTP和SIP/MRCP的发起者,如FreeSWITCH(下文简称FS)
-
MRCP服务端:处理MRCP/SIP信令,接收并转发RTP
-
ASR引擎 :解析RTP,将语音转换成文本,并返回给MRCP Server

从上图可以发现,对于呼叫中心ASR调用者而言,只需要关心怎么对接MRCP Server即可,无需关注ASR Engine部分。
在实际使用过程中,如果你采购第三方ASR系统进行私有化部署的话(比如科大讯飞ASR、百度ASR),通常MRCP Server和ASR Engine是打包在一起,并部署在同一机器上。但无论你采购哪家的ASR产品进行集群化部署,厂商都没有提供ASR的负载均衡解决方案,需要客户自行解决。
二、方案分析
调用MRCP Server其实包含SIP(UDP/TCP)、MRCP(TCP)、RTP(UDP)三部分,然而MRCP和RTP的服务端地址是由SIP INVITE的响应 200 OK中SDP指定(如下图),所以只要完成对SIP负载均衡就能解决另外两个,那么要给MRCP Server做负载均衡就变成了给 SIP(UDP/TCP)做负载均衡了。
我们期望负载均衡的效果是:只要服务端集群下有多台机器,即使客户端只有一个,负载均衡设备也能将请求均匀分发给服务端的每一个成员。

常规的负载均衡方案,无外乎基于硬件负载均衡设备实现,如A10(即AX)、F5、NetScaler等;或者基于软负载实现,如LVS、Nginx等。但这些常规方法,都无法真正做到给MRCP Server实现负载均衡。
我们以FS作为MRCP Client,AX作为负载均衡设备为例,其他方案也存在跟AX一样的问题。
FS通过AX设备与MRCP Server之间进行SIP交互时,首先,对于FS和AX设备相对固定的情况下,SIP请求的IP四元组(Source IP、Source port、Destination IP、Destination port)就不会发生变化,因为FS对接MRCP Server时,会在MRCP配置文件中指定客户端和服务端的IP/Port,所以AX每次分配给FS的MRCP Server都是同一台,这显然不符合负载均衡的预期;其次,AX设备默认的会话保持时长为120秒,但电话场景,在收到200 OK后,可能长达半小时不会再有SIP交互,这会导致后续的SIP无法送达。
既然此路不通,我们自然要考虑其他解决方案,咱堂堂七尺男儿,也不能在一棵树上吊死不是……
在这我给出两个解决方案,以及各自的优缺点:
-
方案A:通过FreeSWITCH的distributor模块实现
-
方案B:通过OpenSIPs实现
|
优点
|
缺点
|
方案A
|
1、无需依赖第三方负载均衡组件
|
1、配置繁琐复杂
2、MRCP Server节点增删,都需要调整FS配置文件,而且得在无ASR业务时,才能加载生效
3、端口数量消耗大(每个MRCP Server都需要单独分配端口段)
4、负载均衡策略相对单一,只支持按比例分配
|
方案B
|
1、配置简单
2、MRCP Server节点增删,只需调整OpenSIPs的DB即可,有ASR调用时,也可更改,实时生效
3、端口数量消耗小(只需要配置一个MRCP Profile文件,多个MRCP Server共用端口段)
4、负载均衡方案多种多样,支持按比例、轮询等多种方式
|
1、需要依赖第三方负载均衡组件OpenSIPs
|
三、方案详解
接下来,我们详细看看每种方案的具体实现方式。以下方案运行环境为:CentOS 7.6、FreeSWITCH 1.6.20、OpenSIPs 2.4.2。
本篇文章中,我们不详细讲解每种方式的实现原理,只介绍解决方法,有兴趣的同学可以自行学习FS和OpenSIPs的相关功能点,这里给出几个链接:
假设我们只有一台FS作为MRCP 客户端,并且MRCP Server 集群中有两台服务器,分别是 mrcp1 和 mrcp2,希望FS针对每一通电话执行ASR命令时,请求可均匀分配给两个MRCP Server。
3.1 基于FS的distributor模块实现MRCP Server的LB
该方案的核心思路如下:
- FS直接与MRCP Server对接,为MRCP Server集群下每一个成员配置一个profile
- 将MRCP Server集群下的所有成员配置成 FS 的网关,并开启网关的SIP OPTION探测功能,同时确保gateway的name要与mrcp_profile文件中profile的name一致
- 通过FS的distributor 模块为这些MRCP网关配置负载均衡策略
-
最后,实际执行ASR命令时,先通过 expand eval ${distributor mrcp ${sofia profile external gwlist down}} 负载均衡分配得到一个可用的 MRCP Server Profile的名称,然后用该MRCP Profile的名称作为FS play_and_detect_speech ASR命令的参数即可。
Are you ready!咱们上配置步骤:
- 第一步: FS与MRCP Server对接
在 /usr/local/freeswitch/conf/mrcp_profiles/下配置FS对接MRCP Server的文件 tree /usr/local/freeswitch/conf/mrcp_profiles ├── mrcp1.xml └── mrcp2.xml 下面只给出mrcp1.xml的部分核心配置,只需要确保mrcp1.xml和mrcp2.xml里client-port、rtp-port-min、rtp-port-max配置不同即可 这里可以看到,如果MRCP Server集群有很多机器,那么这里的RTP端口段可能不够用,一通电话进行ASR解析需要2个端口,一个用来传输RTP、一个传RTCP <include> <profile name="mrcp1" version="2"> 【每个MRCP Server这里配置的名称都不一样,但一定有一个相同名称的网关】 <param name="client-ip" value="192.168.1.99"/> <param name="client-port" value="client-port-1"/> <param name="server-ip" value="server-ip-1"/> <param name="server-port" value="8060"/> <param name="sip-transport" value="tcp"/> 【也可以是UDP哦】 <param name="rtp-ip" value="192.168.1.99"/> <param name="rtp-port-min" value="min-port-1"/> <param name="rtp-port-max" value="max-port-1"/> <param name="ua-name" value="FreeSWITCH"/> </profile> </include>
- 第二步:配置FS网关
在 /usr/local/freeswitch/conf/sip_profiles/external/下配置网关对接文件 /usr/local/freeswitch/conf/sip_profiles/external ├── mrcp1.xml └── mrcp2.xml mrcp1.xml 的详细配置如下: <gateway name="mrcp1"> 【gateway的name要与mrcp_profile文件中profile的name一致,或可以按照某种规则转换】 <param name="username" value=""/> <param name="proxy" value="mrcp1-server-ip:8060"/> 【当然这里端口可能是其他值】 <param name="realm" value="mrcp1-server-ip"/> <param name="register" value="false"/> <param name="rtp-autofix-timing" value="false"/> <param name="caller-id-in-from" value="true"/> <param name="ping" value="10"/> 【FS给proxy对应地址发送探测的周期】 <param name="ping-max" value="5"/> <param name="ping-min" value="2"/> </gateway>
- 第三步:配置FS的distributor模块
vim /usr/local/freeswitch/conf/autoload_configs/distributor.conf.xml <configuration name="distributor.conf" description="Distributor Configuration"> <lists> <list name="mrcp"> 【权重配置成一样,相当于两个MCRP server 按 1:1 分配】 <node name="mrcp1" weight="5"/> 【node name值与sip gateway 名称相同】 <node name="mrcp2" weight="5"/> </list> </lists> </configuration>
- 最后,来看看使用效果
按照上述配置,将mrcp2服务宕机后,执行负载均衡的效果如下: freeswitch@LPT0596> sofia profile external gwlist down 【获取宕机的网关】 mrcp2 freeswitch@LPT0596> expand eval ${distributor mrcp ${sofia profile external gwlist down}} 【将宕机的网关排除在外后,获取分配的SM节点】 mrcp1 freeswitch@LPT0596> expand eval ${distributor mrcp ${sofia profile external gwlist down}} 【如果mrcp2没有宕机,这里将返回mrcp2】 mrcp1 使用得到的MRCP Server Profile名称执行ASR命令:play_and_detect_speech /usr/local/freeswitch/sounds/ivr_prompt_voice.wav detect:unimrcp:mrcp1 {start-input-timers=false,no-input-timeout=10000,recognition-timeout=10000}ahlt_ats
其他FS相关命令:
- reload mod_unimrcp : 修改FS与MRCP server对接的文件后,重新加载生效【只有当前没有正在执行的ASR操作时,才能重加载】
- sofia profile external rescan : 重新加载FS的网关配置
3.2 基于OpenSIPs实现MRCP Server的LB
3.2.1核心思路
FS不直接与MRCP Server对接,而是与OpenSIPs进行对接。对接方式是把OpenSIPs配置成一个MRCP profile,文件中的server-ip 和 server-port 地址配置成OpenSIPS 的服务地址即可。
FS执行ASR命令时,先将SIP请求发送给OpenSIPs,再由OpenSIPs负载均衡到MRCP Server集群中的成员,交互的时序图如下:

3.2.2 方案分析
通过OpenSIPs来实现对MRCP的负载均衡需要解决下面几个问题:
- 问题1、如何判断收到的INVITE请求是要执行ASR命令,还是普通呼叫命令?
- 问题2、知道是执行ASR命令后,如何选择MRCP Server,进行分配?
- 问题3、如果有多套MRCP Server集群,比如一套百度MRCP,一套阿里MRCP,客户端希望能指定引擎使用,该如何解决?
既然有问题,那咱们就解决问题,决不能被困难吓倒。等我大碗喝酒,大口吃肉后,我就能将他们各个击破,你信不信……
- KO-问题1
我们来看一条FS发送给OpenSIPs,请求执行MRCP负载均衡的SIP INVITE信息,其中 192.168.1.99是FS,192.168.1.18是OpenSIPs。

INVITE sip:192.168.1.18:5070 SIP/2.0 Via: SIP/2.0/UDP 192.168.1.99:5102;rport;branch=z9hG4bKQ21yZS46ytrgF Max-Forwards: 70 From: <sip:192.168.1.99:5102>;tag=4B8SvQe66FNvc To: <sip:192.168.1.18:5070> Call-ID: ed9f5f6b-0673-123b-199a-fa163e72d95e CSeq: 47770741 INVITE Contact: <sip:192.168.1.99:5102> User-Agent: FreeSWITCH Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE Supported: timer, 100rel Content-Type: application/sdp Content-Disposition: session Content-Length: 306 v=0 o=FreeSWITCH 2480643166757753319 6144298267054033408 IN IP4 192.168.1.99 s=- c=IN IP4 192.168.1.99 t=0 0 m=application 9 TCP/MRCPv2 1 a=setup:active a=connection:existing a=resource:speechrecog a=cmid:1 m=audio 31799 RTP/AVP 0 8 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=sendonly a=mid:1
从上面信令可以看到,FS发起的INVITE中,没有主被叫号码信息,只有FS和OpenSIPs的IP和端口信息。如果我们的OpenSIPs只用来给MRCP Server做负载均衡,那么就很简单,收到INVITE请求,都认为是请求执行ASR命令,分配给MRCP Server即可。但是,OpenSIPs只给MRCP Server做负载岂不是大材小用了!所以,实际我们不会这样使用,我们的OpenSIPs通常还会给其他呼叫中心组件做负载均衡,比如给FreeSWITCH、语音网关、分机注册服务等做LB。这样OpenSIPs就会收到来自各种组件的SIP INVITE请求,那么我们该如何判断收到的 INVITE 是要执行ASR命令,还是要做其他业务呢?
常规思路,自然是OpenSIPs分析INVITE的SIP消息头,从中进行判断。可是由于FS的mod_unimrcp模块的限制,FS执行ASR命令时,发送的SIP INVITE里不支持增加自定义SIP消息头,所以我们只能从标准SIP 消息头中进行挖掘。
- 跟据INVITE请求的源IP:不可行,因为同一个源IP可能发起多种请求的INVITE,比如FS可能是请求执行ASR,也可能是请求呼叫手机;此外,即使可行,源IP也不方便维护。
- 跟据INVITE请求的目的IP:不可行,所有INVITE请求的该值都一样
- 跟据INVITE请求的User-Agent头:可行,OpenSIPs通过$ua就能获取该值。虽然不能针对每次INVITE自定义不同的UA头,但FS对接MRCP Server的Profile中可以指定一个统一的User-Agent头,默认是FreeSWITCH。
- 跟据INVITE请求SDP信息中的‘m’头:可行,OpenSIPs通过$(rb{sdp.line,m})就能获取该值。如 上面报文中“m=application 9 TCP/MRCPv2 1” 里面有MRCPv2,可跟据这个判断是执行ASR。
建议使用User-Agent头进行区分,取值方便,效率高。所以,FS对接OpenSIPs时,配置的MRCP Profile时,指定一个特别的User-Agent,比如叫ASR_MRCP_CLIENT_FS,OpenSIPs收到INVITE请求,优先判断UA信息,如果是ASR_MRCP_CLIENT_FS,那么就是要执行ASR命令。
- KO-问题2
我们可以使用OpenSIPS的load_balancer 或 dispatcher 模块来实现对 MRCP Server 服务端的负载均衡,两种方式的特点如下:
优点 | 缺点 | |
load_balancer |
可控制每个MRCP Server的最大并发量
支持监控分配给每个MRCP Server的实时并发量
|
分配策略单一:只支持空闲优先策略分配和按比例分配两种策略,无法支持记忆轮训,这就导致但MRCP Server集群新增成员时,会将流量全部分配给新增的机器 |
dispatcher | 分配策略多种多样:如支持记忆轮训、Hash分配等 |
不能控制每个MRCP Server的最大并发量,话务量暴涨时,存在雪崩隐患
不能监控分配给每个MRCP Server的实时并发量(但可以自行通过OpenSIPs其他模块实现)
|
- KO-问题3
在FS上为每一套MRCP Server集群,配置一个MRCP Profile并且都指向OpenSIPs,但User-Agent的值配置成不一样,OpenSIPs跟据UA的不同,来选择该给哪个集群做LB。
baidu_mrcp_lb.xml 下面只给出特有配置,其他配置被省略了 <include> <profile name="baidu_mrcp_lb" version="2"> 【阿里的配置,name为ali_mrcp_lb】 <param name="server-ip" value="opensips-ip"/> <param name="ua-name" value="ASR_MRCP_CLIENT_FS_BAIDU"/> 【阿里的配置,ua-name为ASR_MRCP_CLIENT_FS_ALI】 <param name="sdp-origin" value="FS_MRCP"/> </profile> </include>
OpenSIPs给MRCP Server做负载均衡的处理流程图如下:依赖dialplan模块进行选择具体通过哪个模块来执行LB

3.2.3 具体实现
如果OpenSIPs本身也是集群化部署,那么可以通过本文3.1章节的方法实现对OpenSIPs的负载均衡。
下面代码涉及OpenSIPs对dialplan、dispatcher、load_balancer几个模块的使用,本文不讲解这部分的使用方法。
- 数据库初始化
说明:
(1) 下方配置了百度、阿里两个MRCP Server集群,并且每个集群都部署在了两个IDC(IDC_A和IDC_B)
(2) OpenSIPs跟据dialplan拨号方案来为阿里和百度选择负载均衡的方式,dialplan表中字段“attrs”配置逻辑是:[MRCP集群第一路由的集群ID:负载均衡实现方式:集群名称],如“90:DS:ASR_MRCP_SERVER_CTRIP_ALI”代表,阿里MRCP第一路由的集群ID是90,采用dispacher模块实现LB;"90:DS:ASR_MRCP_SERVER_CTRIP_ALI”代表,百度MRCP第一路由的集群ID是91,采用load_balancer模块实现LB
(3) 无论是dispacher,还是load_balancer,都配置了单IDC下负载均衡的基础上,增加了逃生路由的功能。集群ID为 90/91代表第一路由,10090/10091代表第二路由
dialplan的attrs字段被赋予了特殊用途
INSERT INTO `dialplan`(`dpid`,`pr`,`match_op`,`match_exp`,`match_flags`,`subst_exp`,`repl_exp`,`timerec`,`disabled`,`attrs`) VALUES (90,1000,1,'^ASR_MRCP_CLIENT_CTRIP_FS_ALI$',0,NULL,NULL,NULL,0,'90:DS:ASR_MRCP_SERVER_CTRIP_ALI'), (90,1000,1,'^ASR_MRCP_CLIENT_CTRIP_FS_BAIDU$',0,NULL,NULL,NULL,0,'91:LB:ASR_MRCP_SERVER_BAIDU');
dispatcher的attrs字段没有实际作用 INSERT INTO `dispatcher` (`setid`, `destination`, `state`, `weight`, `priority`, `attrs`, `description`) VALUES (90, 'sip:192.168.1.190:8060', 0, 1, 100, 'pstn=100', 'IDC_A:ASR_MRCP_SEVER_ALI'), (90, 'sip:192.168.1.191:8060', 0, 1, 100, 'pstn=100', 'IDC_A:ASR_MRCP_SEVER_ALI'), (10090, 'sip:192.168.2.198:8060', 0, 1, 100, 'pstn=100', 'IDC_B:ASR_MRCP_SEVER_ALI');
load_balancer的resources字段可以控制最大并发数 INSERT INTO `load_balancer`(`group_id`,`dst_uri`,`resources`,`probe_mode`,`description`) VALUES (91,'sip:192.168.1.180:8060','pstn=50',2,'IDC_A:ASR_MRCP_SEVER_BAIDU'), (91,'sip:192.168.1.181:8060','pstn=50',2,'IDC_A:ASR_MRCP_SEVER_BAIDU'), (10091,'sip:192.168.2.188:8060','pstn=50',2,'IDC_B:ASR_MRCP_SEVER_BAIDU');
配置好后,可查看集群内MRCP成员的状态:

1 sudo /usr/local/opensips/sbin/opensipsctl fifo lb_list 2 Destination:: sip:192.168.1.180:8060 id=1 group=90 enabled=yes auto-reenable=on 3 Resources:: 4 Resource:: pstn max=50 load=50 5 Destination:: sip:192.168.1.180:8060 id=2 group=90 enabled=no auto-reenable=on 6 Resources:: 7 Resource:: pstn max=50 load=0 8 Destination:: sip:192.168.2.188:8060 id=3 group=90 enabled=yes auto-reenable=on 9 Resources:: 10 Resource:: pstn max=50 load=10 11 12 sudo /usr/local/opensips/sbin/opensipsctl fifo ds_list 13 PARTITION:: default 14 SET:: 90 15 URI:: sip:192.168.1.190:5080 state=Active first_hit_counter=8 16 attr:: pstn=500 17 URI:: sip:192.168.1.191:5080 state=Inactive first_hit_counter=0 18 attr:: pstn=500 19 SET:: 10090 20 URI:: sip:192.168.2.198:8060 state=Active first_hit_counter=0 21 attr:: pstn=100
- OpenSIPs代码实现
1 route{ 2 #省略N多代码... 3 4 # check sip INVITE message source ip and port 5 if (is_method("INVITE")) { 6 xlog("ua = $ua , callid = $ci, fu = $fu , tu = $tu , ru = $ru , du =$du src:$si, $(rb{sdp.line,m}))"); 7 $var(dlgPingTag) = "Pp"; 8 if ( $ua == "ASR_MRCP_CLIENT_FS" ) { #to_asr_mrcp_server 9 $var(dlgPingTag) = ""; # ASR 的SIP通道不能做OPTION探测 10 } 11 if ( !create_dialog("$var(dlgPingTag)") ) { 12 route(PRINT_LOG, "create_dialog error : Internal Server Error"); 13 send_reply("500","SM Internal Server Error"); 14 exit(); 15 } 16 17 if ( $ua =~ "^ASR_MRCP_CLIENT_CTRIP_FS*" ) { #to_asr_mrcp_server 【需要修改FS mrcp client配置文件,<param name="ua-name" value="ASR_MRCP_CLIENT_FS..."/>】 18 if ( dp_translate("90", "$ua/$avp(dest)", "$var(attrs)") ) { #拨号方案判断 19 route(exeLb, $(var(attrs){s.int}), "pstn", $(var(attrs){s.select, 1,:}), $(var(attrs){s.select, 2,:})); 20 } 21 } else { #处理其他呼叫类型,如呼叫手机等 22 #省略N多代码... 23 } 24 } 25 exit(); 26 } 27 28 #usage : route(exeLb, lb_group_id, resource_type, node_type, lb_method) 29 #e.g. route(exeLb, 90, "pstn", "ASR_MRCP_SERVER_ALI", "LB") 30 #e.g. route(exeLb, 90, "pstn", "ASR_MRCP_SERVER_ALI", "DS") 31 route[exeLb]{ 32 $var(lb_group_id) = $param(1); 33 $var(lb_group_id_bak) = $param(1) + 10000; 34 $var(resource_type) = $param(2); 35 $var(node_type) = $param(3); 36 $var(lb_method) = $param(4); 37 38 xlog("[$fU->$rU] Route $rU to '$var(node_type)' by load_balancer group_id : '$var(lb_group_id)' [back_group_id:'$var(lb_group_id_bak)'], resource_type : '$var(resource_type)', node_type : '$var(node_type)' [ci:$ci] [xcid:$hdr(X-CID)]"); 39 40 $var(lbRst) = 0; 41 if( $var(lb_method) == "DS" ) { 42 $var(lbRst) = ds_select_dst("$var(lb_group_id)", "4"); 43 if($var(lbRst) == -1) { 44 xlog("[exeLb4CM] [$fU->$rU] Failed --->lbRst=$var(lbRst) Route $rU to '$var(node_type)' by dispatcher group_id : '$var(lb_group_id)', resource_type : '$var(resource_type)' [ci:$ci]"); 45 $var(lbRst) = ds_select_dst("$var(lb_group_id_bak)", "4"); 46 if(!$var(lbRst)) { 47 xlog("[exeLb4CM] [$fU->$rU] Failed ===>lbRst=$var(lbRst) Route $rU to '$var(node_type)' by dispatcher [back_group_id:'$var(lb_group_id_bak)'], resource_type : '$var(resource_type)' [ci:$ci]"); 48 } 49 } 50 51 } else { 52 $var(lbRst) = lb_start_or_next("$var(lb_group_id)", "$var(resource_type)", "s"); 53 if( $var(lbRst) < 0) { 54 xlog("[$fU->$rU] Failed --->lbRst=$var(lbRst) Route $rU to '$var(node_type)' by load_balancer group_id : '$var(lb_group_id)', resource_type : '$var(resource_type)' [ci:$ci]"); 55 $var(lbRst) = lb_start("$var(lb_group_id_bak)", "$var(resource_type)", "s"); 56 if( $var(lbRst) < 0) { 57 xlog("[$fU->$rU] Failed ===>lbRst=$var(lbRst) Route $rU to '$var(node_type)' by load_balancer [back_group_id:'$var(lb_group_id_bak)'], resource_type : '$var(resource_type)' [ci:$ci]"); 58 } 59 } 60 61 } 62 63 if ( $var(lbRst) > 0) { 64 if ( $rU == null ) { #对于FS 发起的 MRCP INVITE 请求, $rU 为 null, 而不设置 $rU 将导致 Load balancer 失败,所以需要初始化一个值 65 xlog("[$fU->$rU] rU is null, then initialize to 'Null2Sm' [ci:$ci] [xcid:$hdr(X-CID)]"); 66 #$rU = "Null2SM"; 67 $ru = "sip:" + $(du{uri.host}) + ":" + $dp; 68 } else { 69 $ru = "sip:" + $rU + "@" + $(du{uri.host}) + ":" + $dp; 70 } 71 xlog("[$fU->$rU] Route to '$var(node_type)' --> [$du] [ci:$ci] [xcid:$hdr(X-CID)]"); 72 route(relay); 73 } else { 74 xlog("[$fU->$rU] No available '$var(node_type)' now [ci:$ci] [xcid:$hdr(X-CID)]"); 75 t_reply("480", "$var(node_type) Unavailable"); 76 exit(); 77 } 78 79 }
如果按照上面脚本执行了 $ru = "sip:" + $rU + "@" + $(du{uri.host}) + ":" + $dp;,但是$rU== null 并且不设置 $rU="Null2SM"或者其他非空值,会爆如下错误
解决办法:
- 设置$rU 为一个非空值
- 直接不修改$ru 的值
- 修改 $ru = "sip:" + $(du{uri.host}) + ":" + $dp;

1 Feb 12 22:27:35 fat5410 /usr/local/opensips/sbin/opensips[3710]: ERROR:core:parse_uri: bad char '@' in state 0 parsed: <sip:> (4) / <sip:@192.168.1.190:8060> (20) 2 Feb 12 22:27:35 fat5410 /usr/local/opensips/sbin/opensips[3710]: ERROR:core:parse_sip_msg_uri: bad uri <sip:@192.168.1.190:8060> 3 Feb 12 22:27:35 fat5410 /usr/local/opensips/sbin/opensips[3710]: ERROR:core:pv_get_ruri_attr: failed to parse the R-URI
3.2.4 信令记录:
- FS 发送INVITE给 OpenSIPs

1 2022-02-13 13:50:53 +0800 : 192.168.1.99:5221 -> 192.168.1.18:5070 2 INVITE sip:192.168.1.18:5070 SIP/2.0 你可以看到,这里没有被叫号码,所以到了OpenSIPs 后 $rU是null 3 Via: SIP/2.0/UDP 192.168.1.99:5221;rport;branch=z9hG4bKFUXeX5r0Q0gXp 4 Max-Forwards: 70 5 From: <sip:192.168.1.99:5221>;tag=3pU7FrrQBQ1NS 6 To: <sip:192.168.1.18:5070> 7 Call-ID: 5c97aeaf-64b4-123a-02b4-fa163ea03f01 8 CSeq: 38878534 INVITE 9 Contact: <sip:192.168.1.99:5221> 10 User-Agent: ASR_MRCP_CLIENT_FS_ALI 11 Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE 12 Supported: timer, 100rel 13 Content-Type: application/sdp 14 Content-Disposition: session 15 Content-Length: 299 16 17 v=0 18 o=FS_MRCP 1321904497415698834 1659019553944433241 IN IP4 192.168.1.99 19 s=- 20 c=IN IP4 192.168.1.99 21 t=0 0 22 m=application 9 TCP/MRCPv2 1 23 a=setup:active 24 a=connection:new 25 a=resource:speechrecog 26 a=cmid:1 27 m=audio 16416 RTP/AVP 0 8 28 a=rtpmap:0 PCMU/8000 29 a=rtpmap:8 PCMA/8000 30 a=sendonly 31 a=mid:1
- OpenSIPS 转发INVITE给 MRCP server

2022-02-13 13:50:53 +0800 : 192.168.1.18:5070 -> 192.168.1.190:8060 INVITE sip:192.168.1.18:5070 SIP/2.0 [如果修改$rU, 这里就是 INVITE sip:Null2SM@192.168.1.18:5070 SIP/2.0] Record-Route: <sip:192.168.1.18:5070;lr;did=de7.2517abb1> Via: SIP/2.0/UDP 192.168.1.18:5070;branch=z9hG4bK9443.c1265465.0 Via: SIP/2.0/UDP 192.168.1.99:5221;received=192.168.1.99;rport=5221;branch=z9hG4bKFUXeX5r0Q0gXp Max-Forwards: 69 From: <sip:192.168.1.99:5221>;tag=3pU7FrrQBQ1NS To: <sip:192.168.1.18:5070> Call-ID: 5c97aeaf-64b4-123a-02b4-fa163ea03f01 CSeq: 38878534 INVITE Contact: <sip:192.168.1.99:5221> User-Agent:ASR_MRCP_CLIENT_FS_ALI Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE Supported: timer, 100rel Content-Type: application/sdp Content-Disposition: session Content-Length: 299 X-UUI: &XCID=0dc0196031626864653EXCIDEND X-CID: 0dc0196031626864653EXCIDEND v=0 o=FS_MRCP 1321904497415698834 1659019553944433241 IN IP4 192.168.1.99 s=- c=IN IP4 192.168.1.99 t=0 0 m=application 9 TCP/MRCPv2 1 a=setup:active a=connection:new a=resource:speechrecog a=cmid:1 m=audio 16416 RTP/AVP 0 8 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=sendonly a=mid:1
- MRCP Server 回复200 OK,返回后续接收RTP的真实地址

1 2022-02-13 13:50:53 +0800 : 192.168.1.190:8060 -> 192.168.1.18:5070 2 SIP/2.0 200 OK 3 Via: SIP/2.0/UDP 192.168.1.18:5070;branch=z9hG4bK9443.c1265465.0 4 Via: SIP/2.0/UDP 192.168.1.99:5221;received=192.168.1.99;rport=5221;branch=z9hG4bKFUXeX5r0Q0gXp 5 Record-Route: <sip:192.168.1.18:5070;lr;did=de7.2517abb1> 6 From: <sip:192.168.1.99:5221>;tag=3pU7FrrQBQ1NS 7 To: <sip:192.168.1.18:5070>;tag=45D4K1DvpQQXK 8 Call-ID: 5c97aeaf-64b4-123a-02b4-fa163ea03f01 9 CSeq: 38878534 INVITE 10 Contact: <sip:192.168.1.190:8060> 11 User-Agent: BaiduSpeech SofiaSIP 1.5.0 12 Accept: application/sdp 13 Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE 14 Supported: timer, 100rel 15 Session-Expires: 600;refresher=uac 16 Min-SE: 120 17 Content-Type: application/sdp 18 Content-Disposition: session 19 Content-Length: 303 20 21 v=0 22 o=BaiduSpeechServer 8512797916186481341 4497985761629564802 IN IP4 192.168.1.190 【接收RTP的IP】 23 s=- 24 c=IN IP4 192.168.1.190 25 t=0 0 26 m=application 1544 TCP/MRCPv2 1 27 a=setup:passive 28 a=connection:new 29 a=channel:b250b76cea1011eb@speechrecog 30 a=cmid:1 31 m=audio 18380 RTP/AVP 0 【接收RTP的端口】 32 a=rtpmap:0 PCMU/8000 33 a=recvonly 34 a=mid:1
- FS发送ACK给OpenSIPs

1 2022-02-13 13:50:53 +0800 : 192.168.1.99:5221 -> 192.168.1.18:5070 2 ACK sip:192.168.1.190:8060 SIP/2.0 3 Via: SIP/2.0/UDP 192.168.1.99:5221;rport;branch=z9hG4bKFUXeX5r0Q0gXp 4 Route: <sip:192.168.1.18:5070;lr;did=355.53f8e331> 5 Max-Forwards: 70 6 From: <sip:192.168.1.99:5221>;tag=3pU7FrrQBQ1NS 7 To: <sip:192.168.1.18:5070> 8 Call-ID: 5c97aeaf-64b4-123a-02b4-fa163ea03f01 9 CSeq: 38878534 ACK 10 Contact: <sip:192.168.1.99:5221> 11 Content-Length: 0
- 最后,OpenSIPs将ACK转发给MRCP Server
后记:
Congratulations!这么长的文章,你居然看完啦。
不管你是否接受,我都必须送你一朵小红花……

拿走不谢,哥就是这么大气,我浪里个浪,浪里个浪……
