hash 是内存中的哈希表数据结构。它可以设置一个键-值对(Key-Value pair)。如,上面最后一行上向 ${domain_name}-last_dial_ext 这个哈希表中插入 global 这么一个键,它的值是 ${uuid},就是本 Channel 的唯一标志。
不管是上面的 set, 还是 hash,都是保存一些数据为后面做准备的。
<action application="set" data="called_party_callgroup=${user_data(${dialed_extension}@${domain_name} var callgroup)}"/>
<!--<action application="export" data="nolocal:sip_secure_media=${user_data(${dialed_extension}@${domain_name} var sip_secure_media)}"/>-->
这一行默认是注释掉的,因此不起作用。 nolocal 的作用我们已前也讲到过,它告诉 export 只将该变量设置到 b-leg 上,而不要设置到 a-leg 上。
<action application="hash" data="insert/${domain_name}-last_dial/${called_party_callgroup}/${uuid}"/>
还是 hash.
<action application="bridge" data="{sip_invite_domain=$${domain}}user/${dialed_extension}@${domain_name}"/>
bridge 是最关键的部分。其实上面除 bridge 以外的 action 都可以省略,只是会少一些功能。
回忆一下第四章中的内容。用户 1000 其实是一个 SIP UA(UAC),它向 FreeSWITCH(作为UAS) 发送一个 INVITE 请求。然后 FreeSWITCH 建立一个 Channel,从 INVITE 请求中找到被叫号码(destination_number=1001),然后在 Dialplan 中查找 1001 就一直走到这里。
bridge 的作用就是把 FreeSWITCH 作为一个 SIP UAC,再向 1001 这个 SIP UA(UAS)发起一个 INVITE 请求,并建立一个 Channel。这就是我们的 b-leg。1001 开始振铃,bridge 把回铃音传回到 1000,因此,1000 就能听到回铃音(如果 1001 有自己的回铃音,则 1000 能听到,否则,将会听到默认的回铃音 ${us-ring})。
当然,实际的情况比我们所说的要复杂,因为在呼叫之前。FreeSWITCH 首先要查找 1001 这个用户是否已经注册,否则,会直接返回 USER_NOT_REGISTERED,而不会建立 b-leg。
bridge 的参数是一个标准的呼叫字符串(Dial string),以前我们也讲到过。domain 和 domain_name 都是预设的变量,默认就是服务器的 IP 地址。user 是一个特殊的 endpoint,它指本地用户。所以,呼叫字符串翻译出来就是(假设 IP 是 192.168.7.2):
{sip_invite_domain=192.168.7.2}user/1001@192.168.7.2
其中,“{ }”里是设置变量,由于 bridge 在这里要建立 b-leg,因此,这些变量只会建立在 b-leg 上。与 set 是不一样的。但它等价于下面的 export :
<action application="export" value="nolocal:sip_invite_domain=192.168.7.2"/>
<action application="bridge" value="user/1001@192.168.7.2"/>
好了,到此为止电话路由基本上就完成了,我们已经建立了 1000 到 1001 之间的呼叫,就等 1001 接电话了。接下来会有几种情况:
- 被叫应答
- 被叫忙
- 被叫无应答
- 被叫拒绝
- 其它情况 ...
我们先来看一下被叫应答的情况。1001 接电话,与 1000 畅聊。在这个时候 bridge 是阻塞的,也就是说,bridge 这个 APP 会一直等待两者挂机(或者其它错误)后才返回,才有可能继续执行下面的 Action。好吧,让我们休息一下,等他们两个聊完吧。
最后,无论哪一方挂机,bridge 就算结束了。如果 1000 先挂机,则 FreeSWITCH 会将挂机原因发送给 1001,一般是 NORMAL_RELEASE(正常释放)。同时 Dialplan 就再也没有往下执行的必要的,因此会发送计费信息,并销售 a-leg 。
如果 1001 先挂机,b-leg 就这样消失了。但 a-leg 依然存在,所以还有戏看。
b-leg 会将挂机原因传到 a-leg。在 a-leg 决定是否继续往下执行之前,会检查一些变量。其中,我们在前面设置了 hangup_after_bridge=true。它的意思是,如果 bridge 正常完成后,就挂机。因此,a-leg 到这里就释放了,它的挂机原因是参考 b-leg 得出的。
但由于种种原因 1001 可能没接电话。1001 可能会拒接(CAlL_REJECTED,但多数 SIP UA都会在用户拒接时返回 USER_BUSY)、忙(USER__BUSY)、无应答(NO_ANSWER 或 NO_USER_RESPONSE)等。出现这些情况时,FreeSWITCH 认为这是不成功的 bridge,因此 hangup_after_bridge 变量就不管用了。这时候它会检查另一个变量 continue_on_fail。由于我们上面设置的 continue_on_fail=true,因此在 bridge 失败(fail)后会继续执行下面的 Action。
这里值得说明的是,通过给 continue_on_fail 不同的值,可以决定在什么情况下继续。如:
<action application="set" data="continue_on_fail=USER_BUSY,NO_ANSWER"/>
将只在用户忙和无应答的情况下继续。其它的值有:NORMAL_TEMPORARY_FAILURE(临时故障)、TIMEOUT(超时,一般时SIP超时)、NO_ROUTE_DESTINATION(没有路由)等。
<action application="answer"/>
最后,无论什么原因导致 bridge 失败(我们没法联系上 1001),我们都决定继续执行. 首先 FreeSWITCH 给 1000 回送应答消息。这时非常重要的。
<action application="sleep" data="1000"/>
<action application="voicemail" data="default ${domain_name} ${dialed_extension}"/>
接下来,暂停一秒,并转到 1001 的语音信箱。语音信箱的知识等我们以后再讲。另外,默认配置中使用的是 loopback endpoint 转到 voicemail,为了方便说明,我直接改成了 voicemail。
回声
没什么好解释的,如果拨 9196,就能听到自己的回声
<extension name="echo">
<condition field="destination_number" expression="^9196$">
<action application="answer"/>
<action application="echo"/>
</condition>
</extension>
延迟回声
与 echo 基本一样,但回声会有一定延迟,5000 是毫秒数。
<extension name="delay_echo">
<condition field="destination_number" expression="^9195$">
<action application="answer"/>
<action application="delay_echo" data="5000"/>
</condition>
</extension>
内连拨号计划 - Inline Dialplan
首先,Inline dialplan 与上面我们讲的 Action 中的 inline 参数是不同的。
XML Dialplan 支持非常丰富的功能,但在测试或编写程序时,我们经常用到一些临时的,或者是很简单的 Dialplan,如果每次都需要修改 XML,不仅麻烦,而且执行效率也会有所折扣。所以,我们需要一种短小、轻便的 Dialplan 以便更高效地完成任务。而且,通过使用 Inline dialplan,也可以很方便的在脚本中生成动态的 Dialplan 而无需使用复杂的 reloadxml 以及 mod_xml_curl 技术等。
与 XML Dialplan 不同,它没有 Extension,也没有复杂的 Condition。而只是象 XML Dialplan中那样简单的 Action 的叠加。它有一种很紧凑的语法格式:
app1:arg1,app2:arg2,app3:arg3
从语法可以看出,它只是多个 APP 以及参数组成的字符串,APP之间用逗号分隔,而APP与参数之间用冒号分隔。如果参数中有空格,则整个字符串都需要使用单引号引起来。在我们上面的例子中,你通过拨打 9196 来找到对应的 XML dialplan,在这里,我们可以直接在命令行上写出对应的 inline 形式:
originate user/1000 echo inline
originate user/1000 answer,echo inline
读到这里,你可能要问,它与“originate user/1000 &echo” 有什么区别呢?在回答这个问题之前,我们需要先看一下 originate 的语法:
originate <call_url> <exten>|&<application_name>(<app_args>)
[<dialplan>] [<context>] [<cid_name>] [<cid_num>] [<timeout_sec>]
首先,它的第一个参数是呼叫字符串, 第二个参数可以是 & 加上一个 APP,APP的参数要放到 ( ) 里,如:
originate user/1000 &echo
originate user/1000 &playback(/tmp/sound.wav)
originate user/1000 &record(/tmp/recording.wav)
这是最简单的形式,首先,originate 会产生一个 Channel,它会呼叫 user/1000 这个用户。请注意,这是一个单腿的通话,因此只有一个 Channel。但一个 Channel 有两端,一端是 1000 这个用户,另一端是 FreeSWITCH。在 user/1000 接电话后(严格说是收到它的 earlymedia 后),FreeSWITCH 即开始在该 Channel 上执行 & 后面的 APP。但这种形式只能执行一个 APP,如果要执行多个,就需要将电话转入 Dialplan:
originate user/1000 9196
originate user/1000 9196 XML default
上面两个命令是一样的。它的作用是,在 user/1000 接电话后,电话的另一端(也就是 FreeSWITCH)需要对电话进行路由,在这里,它要将电话路由到 9196 这个 Extension 上,第一条命令由于没有指定是哪个 Dialplan,因此它会在默认的 XML Dialplan 中查找,同时,XML Dialplan 需要一个 Context, 它默认就是 default。它的效果是跟你直接用软电话拨打 9196 这个分机一样的(所不同的是呼叫的方向问题,这种情况相当于回拨)。
当然,除此之外,它还可以加一些可选的参数,用于指定来电显示(Caller ID)的名字(cid_name)和号码(cid_number),以及超时的秒数,如:
originate user/1000 9196 XML default 'Seven Du' 9196 30
当然,我们这里学了 Inline Dialplan,所以你也可以这样用:
originate user/1000 echo inline
请注意,在 XML Dialplan 中, 9196 是一个分机号,而 Inline 中的 echo 则是一个 APP,当然你也可以顺序执行多个 APP:
originate user/1000 answer,playback:/tmp/pleace_leave_a_message.wav,record:/tmp/recording.wav inline
originate user/1000 playback:/tmp/beep.wav,bridge:user/1001 inline
有时候,APP 的参数中可能会有逗号,因而会与默认的 APP 间的逗号分隔符相冲突,以下的 m 语法形式将默认的逗号改为 ^ 分隔(以下三行实际上为一行)。
originate user/1000
'm:^:playback/tmp/beep.wav^bridge:
{ignore_early_media=true,originate_caller_id_number=1000}user/1001'
当然你也可以用在任何需要 Dialplan 的地方,如(以下两行实为一行)
uuid_transfer 2bde6598-0f1a-48fe-80bc-a457a31b0055
'set:test_var=test_value,info,palyback:/tmp/beep.wav,record:/tmp/recording.wav'
除此之外,还有其它的 Dialplan 形式,我们在这里就不再介绍了,要查看你的系统支持多少 Dialplan,使用如下命令:
freeswitch@seven-macpro.local> show dialplan
type,name,ikey
dialplan,LUA,mod_lua
dialplan,XML,mod_dialplan_xml
dialplan,asterisk,mod_dialplan_asterisk
dialplan,enum,mod_enum
dialplan,inline,mod_dptools