现在,我们已经对FreeSwitch的XML配置及其强大的XML拨号方案的工作原理有了更多的基本了解。
现在是时候超越那种“我知道怎么做,但不完全理解为什么他们会那样做”的感觉了。
这是漫长而且困难的一章,请给我点耐心。读完这一章,你肯定会有所收获,对所有FreeSWITCH灵活性和多功能性的机制会有良好的理解。
我们将讨论以下这些主题:
- 如何组合条件
- 如何避开拨号方案陷阱
- 变量相关的一切及其用法
- 如果在拨号方案里执行API命令
- 切割清洗变量和字符串的特定习惯
- 与活跃通话交互的APP(停泊与热键)
- 一个食谱风格的实用拨号方案宝典
之前的拨号方案季在哪?
我们在之前的章节里已经看过许多相关内容,测试驱动演示配置和XML拨号方案。我偿了解了支持FreeSWITCH运转的基本概念和基础技术,愉快地促进实时通信流量的增长。
不要欺骗自己,如果你没了解这些内容,现在去读也不晚。这会花费一点阅读时间,如果你是FreeSWITCH老鸟,也请你花点时间浏览一下,这将确保我们在相同的页面使用相同的概念和术语,这会让您感到愉悦。
我不会在这一章里重复前面章节的大部分内容,所以,请你先读一下它们。
XML 拨号方案概述
接下来,以极简的方式说明拨号方案是如何工作的;它的各种组件;组件间的相互作用;以及所有重要的术语定义:
- 来电首先着陆到一个拨号方案contexts的入口处。这是呼叫处理的“路由”阶段,构建TODO列表的阶段
- 来电着陆到哪个context,取决于模块的配置(比如接受呼叫的SIPprofile----IP地址/端口对)或用户目录(授权用户的配置)
- context就是一个extension列表,呼叫从头到尾处理每个extension,如果走到context尽头,那就自动挂断
- 如果一个extension被评估为“匹配”(后叙),那么是否继续下一个extension,取决 于当前extension的"continue"参数值
- 每个extension由一个或多个条件(condition)组成,第个条件包含一个或多个actions/anti-action定义
- 呼叫的特征(比如说被叫号码)、配置(在vars.xml或其它地方设置的变量),以及系统信息(一天中的时间),都可以通过变量访问
- 每个条件(condition)都会检查一些特定的变量值与某些(正则)表达式的匹配
- 根据表达式的匹配结果,条件(condition)值被评估为true或false
- 如果condition值为true,它的action将被添加到TODO列表;如果是false,那么anti-action将被添加到TODO列表中
- 在一个condition内,"break-on"参数值决定是否继续判断后续的condition
- actions (和 anti-actions)都是拨号方案的APP(由FreeSWITCH及附加模块提供,大部分由mod_dptools模块提供)
- 遍历拨号方案的context并检查完extension之后,结束“路由”阶段,并进入“执行阶段”
- 在“执行阶段”,所有堆积在TODO列表中的action将挨个执行(先进先出)
- “路由阶段”与“执行阶段”的分离,意味着你不能在拨号方案同一阶段中同时设置并检查变量(不能在同一extension的下一个condition中检查,也不能在后续extension的condition中检查)。如果确实需要,使用"set" APP提供的"inline"参数
- 初始进入拨号方案的呼叫称为"A-leg",FreeSWITCH发起的,并最终与"A-leg"桥接的呼叫,称为"B-leg"
深入了解condition
Condition是XML拨号方案中的控制元素。在extension内部,至少有一个"condition"标记,根据(正则)表达式匹配呼叫的配置/系统/来源这些特征,而特征描述为一个变量。如果condition评估为true,那么condition内指定的action将被添加到TODO列表中;如果评估为false,anti-action(如果有的话)将被添加到TODO列表中。Condition的一般格式为:
<extension name="is_looking_for_2900" continue="true">
<condition field="destination_number" expression="^2900$" break="on- true">
<action application="log" data="WARNING first TRUE"/>
<anti-action application="log" data="ERR first FALSE"/>
</condition>
<condition field="destination_number" expression=".*">
<action application="log" data="WARNING second TRUE"/>
<action application="set" data="var01=value01" inline="true"/>
<action application="set" data="var02=value02"/>
<anti-action application="log" data="ERR second FALSE"/>
</condition>
<condition field="${var01}" expression="^value01$">
<action application="log" data="WARNING third TRUE"/>
<anti-action application="log" data="ERR third FALSE"/>
</condition>
<condition field="${var02}" expression="^value02$">
<action application="log" data="WARNING fourth TRUE"/>
<anti-action application="log" data="ERR fourth FALSE"/>
</condition>
</extension>
<extension name="after_is_looking_for_2900">
<condition field="destination_number" expression="^2900$">
<action application="log" data="WARNING first of after TRUE"/>
<anti-action application="log" data="ERR first of after FALSE"/>
</condition>
</extension>
让我们快速过一下这个例子,如果感觉哪里晦涩难懂,请回顾前面几章的内容。
第一个extension的"continue"参数值设为true,因此,即使这个extension“匹配”成功(它的condition评估为true),还是会继续检查下一个extension的模式匹配。缺省情况下"continue"参数值是false,因此拨号方案的遍历过程会在找到第一个匹配extension后停止。
其中第一条condition携带"break"参数,参数值是"on-true",因此,如果表达式评估为true,就不再判断后续的condition。缺省情况下,break参数值是"on-false"( condition指定条件不成立时,不继续条件判断逻辑),break参数的其它可选值是"always"和"never"。
第一条condition表达式可以解读为:“一个精确匹配'2900'的字符串”。因为插入符和美元符都是正则的元字符,分别表示字符串的开始和结束。
只有在第一条condition匹配失败的情况下,才会检查第二条condition。第二条condition的anti-action将永远不会被加到TODO列表中(因为第二条condition的条件表达式是".*",它将匹配所有内容)。
如果目标号码是2900之外的任何内容,那么第三条condition将匹配成功(因为如果目标号码是2900,在第一条condition处就被拦截了,而第二条condition实际上没有判断什么内容,只是设置了变量);但是第四条condition将永远匹配失败。原因是第三条condition判断的变量定义时有"inline"参数,而第四条的变量不带。如果调用"set"设置变量时不带"inline"参数,那么变量设置实际发生在拨号方案遍历结束后的执行阶段。
这里的第二条extension只是为了映衬第一条而存在的,因为"continue"参数的设置,拨号方案匹配第一条extension之后会继续。下面截图告诉我们,当呼叫"2900"之外的号码时,都发生了些什么:
正则操作符(多变量和/或表达式)
检查变量时,你可以写出你可以写出一个是非常长的正则表达式。我的意思是,你可以有是非常复杂的正则表达式,就像从老式调制解调器中识别线路噪声那样复杂。在某些场景下,你可能需要检查两个或多个变量音的关系,或者检查多个不易组合的表达式。
这时,你可以用正则操作符来组合多个字段和表达式,把它们揉合到一个条件判断中。你把它们以一种标准组合起来:
- all:所有表达式必须都为true,condition才判断为true(逻辑与)
- any:至少一个表达式值为true,condition才判断为true(逻辑或)
- xor:有并且只有一个表达式值为true,condition才判断为true(如果有两个表达式值为true,condition结果还是false)
让我们看一个逻辑与的条件判断例子:
<extension name="Regex OR example">
<condition regex="any">
<!-- If one is true then add actions in TODO-->
<regex field="caller_id_name" expression="Sara Adasi"/>
<regex field="caller_id_number" expression="^1001$"/>
<action application="log" data="WARNING At least one matched!"/>
<!-- If *none* are true then add anti-actions -->
<anti-action application="log"
data="ERR Call is neither from Sara Adasi nor from 1001!"/>
</condition>
</extension>
嵌套条件判断
条件(Condition) 可以彼此嵌套使用。嵌套条件很难,非常非常难,或许你需要用脚本替代(是的,是的,你需要使用脚本)。还是看看前一章Lua脚本相关的内容吧。
当一个(主)条件有嵌套条件时,首先计算(主)表达工,并把(主) action压入TODO列表,然后计算嵌套条件,压入它们的action。
每个条件都有一个隐含参数叫"require-nested",缺省设置为"true"。如果一个条件的require-nested参数值为"true",那么它的所有嵌套条件都必须为true(其表达式值为true),条件表达式才能计算为true。如果"require-nested"值为false,那么只要它的表达式值为true,条件就为true,不管嵌套条件是什么。
以通常的方式检查条件判断(遵守"break"参数定义)并处理它内部的action。
让我们看一个嵌套条件的extension实例,你可以玩玩,测试检验所有可能出现的场景。
主条件的表达式检查来电的目的号码是否为"2901",如果是,接着处理嵌套条件。第一个嵌套条件检查用户的caller_id_number是否为"1011";第二个嵌套条件查查caller_id_name是否为"Giovanni":
<extension name="nested_example">
<condition field="destination_number" expression="^2901$" require- nested="false">
<action application="log" data="ERR 00 CIDnum is
${caller_id_number} CIDname is ${caller_id_name}" />
<action application="set" data="var_01=N/A" inline="true"/>
<action application="set" data="var_02=N/A" inline="true"/>
<action application="set" data="var_03=N/A" inline="true"/>
<action application="set" data="var_04=N/A" inline="true"/>
<action application="set" data="var_05=N/A" inline="true"/>
<action application="log" data="ERR 01 I'm before..."/>
<action application="set" data="var_01=01" inline="true"/>
<action application="log" data="ERR 02 I'm before ${var_01}
${var_02} ${var_03} ${var_04} ${var_05}"/>
<condition field="caller_id_number" expression="1011" break="on-false">
<action application="log" data="ERR 03 I'm the first..."/>
<action application="log" data="ERR 04 I'm the first CIDnum is
${caller_id_number}" />
<action application="set" data="var_02=02" inline="true"/>
<action application="log" data="ERR 05 I'm the first ${var_01}
${var_02} ${var_03} ${var_04} ${var_05}"/>
</condition>
<action application="log" data="ERR 06 I'm in between..."/>
<action application="set" data="var_03=03" inline="true"/>
<action application="log" data="ERR 07 I'm in between ${var_01}
${var_02} ${var_03} ${var_04} ${var_05}"/>
<condition field="${caller_id_name}" expression="Giovanni" break="on-false">
<action application="log" data="ERR 08 I'm the second..."/>
<action application="log" data="ERR 09 I'm the second CIDname is ${caller_id_name}" />
<action application="set" data="var_04=04" inline="true"/>
<action application="log" data="ERR 10 I'm the second ${var_01}
${var_02} ${var_03} ${var_04} ${var_05}"/>
</condition>
<action application="log" data="ERR 11 I'm after..."/>
<action application="set" data="var_05=05" inline="true"/>
<action application="log" data="ERR 12 I'm after ${var_01}
${var_02} ${var_03} ${var_04} ${var_05}"/>
</condition>
</extension>
<extension name="call_has_not_stopped_before_here">
<condition field="destination_number" expression=".*">
<action application="log" data="ERR NOT STOPPED BEFORE HERE"/>
</condition>
</extension>
你可以修改示例中的很多内容来测试:
- 所有“表达式”值:2901、1011、Giovanni
- 主条件中,require-nested参数值,设置为"true" 或 "false"
- "set"的"inline"参数值,可以设为"true" 或 "false"
- 嵌套条件的break"参数,可以设为"on-false"、on-true"、"never"或"always"
让我们首先测试"require-nested"值为"false";"set" APP的"inline"参数为"true";条件的"break"参数为"on-false"的场景:
如果我们呼叫2901,同时caller_id_number=1011并且caller_id_name=Giovanni(也就是让所有条件成立),那么我们会得到这样的输出:
所有变量从一开始就存在。
接下来编辑extension,并所所有"inline"属性改为"false",重载配置并重新呼叫,这时的输出:
变量是逐步设置的。
让我们进一步研究一下,如果只有一条嵌套条件值为true会发生什么,还是呼叫2901,但这回caller_id_number=1011 ,caller_id_name=Sara:
如果我们编辑extension中的第一个条件的"require-nested"参数,设置为true,然后reloadxml,再次呼叫,这时我们得到的输出:
与前一个的执行结果完全相同,但这回extension的计算结果不是true(因为它的嵌套条件并不全为true),这时继续计算下一条extension。注意,在这种场景下,计算为true的条件所属的action已经被压入TODO列表。
你可以修改这个实例的各种不同部分进行测试。
底线:你可能拥有嵌套条件的完美用例,但我更倾向于写一个Lua脚本。
避开陷阱
对FreeSWITCH新手来说,最常犯的一个拨号方案错误就是先设置一个变量,然后在条件判断中检查变量值。千万不要这样做。
在本书中,我已经多次强调这些概念,重要的事情说三遍,让我们再来回顾一下这些内容:
- 收到呼叫时,首先遍历拨号方案,检查extension、condition这些,寻找匹配的见容。只有遍历结束后,才会执行TODO列表中的指令。
- 如果找到匹配的condition,它所设定的action将会被放入TODO列表
- 有一个名为"set"的action,它的功能是为变量赋值
- 因此,拨号方案中的变量赋值动作,也是加到TODO列表中的
- 如果我们在条件判断中检查变量值,那么取到的只是它的旧值(或不存在的值)。
- 原因是我们还处于路由阶段,"set"指令根本没有执行
- 明白了吗?
- 如果你希望在路由阶段执行"set"指令,并在后续的条件判断行中检查变量值,你必须在调用"set"时指定inline="true"参数:
<action application="set" data="myvar=false" inline="true"/>
此外,当你调试赋值情况时,你可能为了确定赋值的正确性,在检查变量之前或之后加上打印语句,嗯,也就是调用"log" APP。
<extension name="check_var">
<condition field="destination_number" expression="^2902$">
<action application="set" data="var99=yes" inline="true"/>
<action application="set" data="var100=yes"/>
<action application="log" data="ERR var99=${var99} var100=${var100}"/>
</condition>
<condition field="${var99}" expression="yes">
<action application="log" data="ERR COND var99 TRUE var99=${var99} var100=${var100}"/>
<anti-action application="log" data="ERR COND var99 FALSE var99=${var99} var100=${var100}"/>
</condition>
<condition field="${var100}" expression="yes">
<action application="log" data="ERR COND var100 TRUE var99=${var99} var100=${var100}"/>
<anti-action application="log" data="ERR COND var100 FALSE var99=${var99} var100=${var100}"/>
</condition>
</extension>
不幸的是,你又一次掉入陷阱,"log" APP的执行时刻是在遍历阶段之后。还记得不,为了在遍历阶段执行"set",我们需要"inline"属性。尝试一下,注意加上"inline"属性,然后你就能看到这样令人难以置信的输出:
变量
在FreeSWITCH配置与工作中,变量的身影无处不在。在FreeSWITCH中,几乎所有事情都是因为您设置或更改了一个变量而发 生的。此外,所有发生的事情都会设置和更改freeswitch变量。
通道变量(以及FreeSWITCH变量)的另一个重要层面是:你不仅可以用它们检查系统/呼叫/用户等状态,还可以用变量影响FreeSWITCH的行为!(参考本章后面的内容:“设置变量与呼叫建立”)。
在这里,我们将在前面说明的基础上进行添加和构建,评述可以在拨号计划条件中使用的各种变量。
全局变量
一些全局变量是在FreeSWITCH启动过程中自动设置(也可能是计算)的,比如配置中的所有缺省目录,使用的IP地址,NAT相关信息,等等。
这里给出这些变量的简表:
- Hostname
- local_ip_v4
- local_mask_v4
- local_ip_v6
- switch_serial
- base_dir
- recordings_dir
- sound_prefix
- sounds_dir
- conf_dir
- log_dir
- run_dir
- db_dir
- mod_dir
- htdocs_dir
- script_dir
- temp_dir
- grammar_dir
- certs_dir
- storage_dir
- cache_dir
- core_uuid
- zrtp_enabled
- nat_public_addr
- nat_private_addr
- nat_type
其它全局变量由XML的预处理器设置,通常它们定义在/usr/local/freeswitch/conf/vars.xml文件中(请参考第3章预处理变量相关内容)。
配置文件(或拨号方案)中,随处都可以引用所有的全局变量,这通常用于设置模块的缺省值。
你可以在拨号方案中以$${globalvar}格式引用全局变量(注意使用两个美元符)。此外,你可以在FreeSWITCH控制台通过"eval"命令查看某个全局变量的值:
你还可以通过API命令global_setvar对全局变量设值,并通过global_getvar取值:
在拨号方案中,你可以在表达式和data字段中引用全局变量(以及其他地方):
<
extension name="check_global">
<condition field="destination_number" expression="^2906$">
<action application="set" data="var101=lab.opentelecomsolutions.com" inline="true"/>
</condition>
<condition field="${var101}" expression="$${domain_name}">
<action application="log" data="ERR YES, var101 is $${domain_name}"/>
<anti-action application="log" data="ERR NOPE, var101 is NOT
$${domain_name}"/>
</condition>
</extension>
一天中的某个时间、一周中的某一天、节假日
所有时间相关的条件都可以直接表示,用一种特殊语法检查变量(不用${}结构)。你可以在一个条件判断中组合时间,或使用时间范围:
<conditionwday="6"hour="8-12">
可用“时间”变量有:
- year : 日历年,0-9999
- yday :一年中的某一天,1-366
- mon :月份,1-12(一月份=1)
- mday : 一个月中的某一天,1-31
- week :一年中的某一周,1-53
- mweek:一个月中的某一周,1-6
- wday:一周中的某一天,1-7(周日=1,周一=2…),或者英语缩写"sun"、 "mon"、 "tue"等
- hour:一天中的某小时,0-23
- minute:一小时中的某一分钟,0-59
- minute-of-day:一天中的某一分钟,(1-1440)(午夜=1,凌晨1点=60,正午=720)
- time-of-day:格式化的时间范围,格式:hh:mm[:ss]-hh:mm[:ss] (秒数是可选的),例如:"08:00-17:00"
- date-time:格式化日期/时间范围:格式YYYY-MM-DD hh:mm[:ss]~YYYY-MM-DD hh:mm[:ss](秒数是可选的,注意中间的波形号),例如:2010-10-01 00:00:01~2010-10-15 23:59:59
下面的extension改编自演示配置拨号方案。
请注意,如果你想要测试最后一条示例extension里的变量${open},需要加上inline="true"的描述。
此外,还请注意一个常见错误(至少我犯过,因为它必须是是常见的):我们假设时间的限定范围是hour="9-18",这时,从09:00到18:59,条件判断都为true。它计算当前时间的小时值。嗯,我盲目地以为描述的是从09:00 到 18:00,别问我为什么。
<extension name="tod_example" continue="true" >
<condition wday="2-6" hour="9-18">
<action application="set" data="open=true"/>
</condition>
</extension>
<extension name="holiday_example" continue="true">
<condition mday="1" mon="1">
<!-- new year's day -->
<action application="set" data="open=false" inline="true"/>
</condition>
<condition wday="2" mweek="3" mon="1">
<!-- martin luther king day is the 3rd monday in january -->
<action application="set" data="open=false" inline="true"/>
</condition>
<condition wday="2" mweek="3" mon="2">
<!-- president's day is the 3rd monday in february -->
<action application="set" data="open=false" inline="true"/>
</condition>
<condition wday="2" mon="5" mday="25-31">
<!-- memorial day is the last monday in may (the only monday between the 25th and the 31st) -->
<action application="set" data="open=false" inline="true"/>
</condition>
<condition mday="4" mon="7">
<!-- independence day -->
<action application="set" data="open=false" inline="true"/>
</condition>
<condition wday="2" mday="1-7" mon="9">
<!-- labor day is the 1st monday in september (the only monday between the 1st and the 7th) -->
<action application="set" data="open=false" inline="true"/>
</condition>
<condition wday="2" mweek="2" mon="10">
<!-- columbus day is the 2nd monday in october -->
<action application="set" data="open=false" inline="true"/>
</condition>
<condition mday="11" mon="11">
<!-- veteran's day -->
<action application="set" data="open=false" inline="true"/>
</condition>
<condition wday="5-6" mweek="4" mon="11">
<!-- thanksgiving is the 4th thursday in november and usually there's an extension for black friday -->
<action application="set" data="open=false" inline="true"/>
</condition>
<condition mday="25" mon="12">
<!-- Christmas -->
<action application="set" data="open=false" inline="true"/>
</condition>
</extension>
<extension name="is_open_or_not">
<condition field="destination_number" expression="^2904$"/>
<condition field="${open}" expression="true">
<action application="log" data="ERR YES, WE'RE OPEN FOR BUSINESS"/>
<anti-action application="log" data="ERR GET A LIFE, DUDE, WE'RE CLOSED"/>
</condition>
</extension>
通道变量
关于通道变量的介绍,请参考第六章,XML 拨号方案中的“通道变量”一节。
官方文档有很多关于通道变量的页面,我们试图在以下页面中集中相关信息:
https://freeswitch.org/confluence/display/FREESWITCH/Channel+Variables
以下是部分通道变量列表,按“主题”分类,供你将来参考(具体信息,请查询https://freeswitch.org/confluence)。
此外,请注意,如果你需要一个变量(有关通道、呼叫、系统、用户或调整参数的方法这些方面的信息),那么几乎可以肯定它是存在的:如果有相关参数或信息,那么它有可能以变量形式提供。可以搜索我们的文档站点,搜索特定变量可能相关的模块,搜索归档的邮件列表,从谷歌搜索,或在社区提问(邮件列表、IRC、HipChat)。
- CDR相关
- process_cdr
- transfer_to
- skip_cdr_causes
- hangup_complete_with_xml
- hold_events
- accountcode
- 挂断原因相关
- bridge_hangup_cause
- last_bridge_proto_specific_hangup_cause
- last_bridge_hangup_cause
- proto_specific_hangup_cause
- sip_hangup_disposition
- hangup_cause_q850
- hangup_cause
- disable_q850_reason
- DTMF相关
- pass_rfc2833
- drop_dtmf_masking_file
- drop_dtmf_masking_digits
- drop_dtmf
- dtmf_type
- 媒体处理
- monitor_early_media_fail
- monitor_early_media_ring
- 超时相关
- call_timeout
- park_timeout
- originate_continue_on_timeout
- leg_timeout
- 保持音乐相关
- hold_music
- temp_hold_music
- 区域相关
- default_language
- tod_tz_offset
- timezone
- 桥接相关
- api_after_bridge
- uuid_bridge_continue_on_cancel
- rtp_jitter_buffer_during_bridge
- no_throttle_limits
- signal_bond
- park_after_bridge
- originate_timeout
- outbound_redirect_fatal
- loopback_export
- loopback_bowout_on_execute
- last_bridge_to
- hold_hangup_xfer_exten
- hangup_after_bridge
- force_transfer_dialplan
- force_transfer_context
- failure_causes
- enable_file_write_buffering
- transfer_on_fail
- continue_on_fail
- bridge_terminate_key
- bridge_filter_dtmf
- bridge_early_media
- auto_hunt
- 会议相关
- conference_auto_outcall_announce
- conference_auto_outcall_caller_id_name
- conference_auto_outcall_caller_id_number
- conference_auto_outcall_flags
- conference_auto_outcall_prefix
- conference_auto_outcall_timeout
- conference_auto_outcall_maxwait
- conference_controls
- conference_enter_sound
- conference_last_matching_digits
- last_transferred_conference
- conference_member_id
- conference_uuid
- hangup_after_confere
- 代码执行相关
- api_hangup_hook
- bridge_pre_execute_aleg_app
- bridge_pre_execute_aleg_data
- bridge_pre_execute_bleg_app
- bridge_pre_execute_bleg_data
- exec_after_bridge_app
- exec_after_bridge_arg
- origination_nested_vars
- The execute_on family
- The api_on family
- failed_xml_cdr_prefix
- fail_on_single_reject
- intercept_unbridged_only
- intercept_unanswered_only
- CallerID相关
- caller_id_name
- effective_sip_cid_in_1xx
- sip_cid_type
- effective_caller_id_number
- effective_caller_id_name
- caller_id_number
- CalleeID 相关
- initial_callee_id_name
- origination_callee_id_number
- origination_callee_id_name
- 呼叫录音相关
- Audio File Metadata
- Example
- recording_follow_transfer
- record_waste_resources
- record_sample_rate
- record_restart_limit_on_dtmf
- record_post_process_exec_app
- record_post_process_exec_api
- RECORD_DISCARDED
- RECORD_HANGUP_ON_ERROR
- record_fill_cng
- 编解码相关
- absolute_codec_string
- suppress-cng
- conference_enforce_security
- sip_renegotiate_codec_on_reinvite
- passthru_ptime_mismatch
- write_codec
- read_codec
- media_mix_inbound_outbound_codecs
- inherit_codec
- codec_string
- IVR相关
- ivr_menu_terminator
- detect_speech_result
- SIP相关变量
- rtp_disable_hold
- deny_refer_requests
- ignore_display_updates
- timer_name
- sip_wait_for_aleg_ack
- rtp_secure_media_suites
- _outbound
- rtp_secure_media
- _inbound
- rtp_secure_media
- rtp_secure_media
- rtp_sdes_suites
- sip_has_crypto
- sip_ignore_reinvites
- sip_invite_to_uri
- sip_invite_from_uri
- sip_recover_via
- sip_recover_contact
- sip_force_full_to
- sip_force_full_from
- sip_handle_full_to
- sip_handle_full_from
- sip_invite_full_to
- sip_invite_full_from
- sip_invite_route_uri
- sip_invite_record_route
- sip_invite_req_uri
- rtp_force_audio_fmtp
- sip_callee_id_name
- sip_auto_simplify
- sip_auth_password
- sip_auth_username
- sip_network_destination
- sip_invite_tel_params
- sip_invite_contact_params
- sip_invite_to_params
- sip_invite_from_params
- sip_from_display
- sip_invite_domain
- sip_invite_params
- sip_copy_multipart
- sip_acl_token
- sip_acl_authed_by
- SDP处理
- rtp_append_audio_sdp
- sdp_secure_savp_only
- sip_enable_soa
- sip_mirror_remote_audio_codec_payload
- sip_recovery_break_rfc
- sip_local_sdp_str
- verbose_sdp
- switch_m_sdp
- switch_l_sdp
- switch_r_sdp
- sdp_m_per_ptime
- FIFO相关变量
- fifo_bridged
- transfer_after_bridge
- fifo_role
- fifo_position
- fifo_manual_bridged
- fifo_consumer_caller_import
- fifo_caller_consumer_import
- Playback 相关变量
- playback_terminators
- playback_timeout_sec
- sleep_eat_digits
- playback_delimiter
- playback_sleep_val
- playback_last_offset_pos
- playback_samples
- playback_ms
- playback_terminator_used
- sound_prefix
- Originate相关变量
- execute_on_originate
- originator_codec
- originator
- origination_uuid
- origination_privacy
- origination_cancel_key
- origination_caller_id_number
- origination_caller_id_name
- origination_channel_name
- originating_leg_uuid
- originate_timeout
- originate_retry_sleep_ms
- originate_retries
- originate_disposition
- leg_delay_start
- RTP/媒体相关变量
- bypass_media
- rtp_negotiate_near_match
- rtp_disable_hold
- transfer_ringback
- instant_ringback
- ringback
- ignore_early_media
- bridge_answer_timeout
- progress_timeout
- disable_rtp_auto_adjust
- rtp_autoflush_during_bridge
- rtp_autoflush
- proxy_media
- bypass_keep_codec
- bypass_media_after_bridge
- RTCP 相关
- rtcp_packet_count
- rtp_assume_rtcp
- rtcp_mux
- N/Artcp_audio_interval_msec
- rtcp_octet_count
- Camp-on 相关变量
- campon
- campon_announce_sound
- campon_stop_key
- campon_hold_music
- campon_fallback_context
- campon_fallback_dialplan
- campon_fallback_exten
- campon_sleep
- campon_timeout
- campon_retries
- Answer confirmation variables
- group_confirm_file
- group_confirm_cancel_timeout
- group_confirm_key
- 语音信箱相关变量
- voicemail_alternate_greet_id
- voicemail_authorized
- skip_instructions
- skip_greeting
- vm_cc
- vm_message_ext
- voicemail_greeting_number
通道变量与字段快捷方式
部分通道变量可以在"field"里直接引用而不用${}语法结构:
- username :呼叫发起方的用户名
- dialplan :使用的拨号 方案类型,几乎总是 "XML"
- caller_id_name
- caller_id_number
- callee_id_name
- callee_id_number
- network_addr :呼叫发起端的网络地址
- ani: Automatic Number Identification 自动号码识别(实际来电号码)
- aniii Automatic Number Identification 2自动号码识别(实际来电号码第2版)
- rdnis Redirected Dialed Number Information Service,重定向拨号号码信息服务,呼叫转接前的目的号码
- destination_number
- source:呼叫使用的模块,比如SIP呼叫的mod_sofia模块
- uuid:呼叫的通用唯一标识符
- context:拨号方案的context
<
extension name="check_sip" continue="true">
<condition field="source" expression="mod_sofia">
<action application="log" data="ERR YES, THIS CALL IS A SIP CALL"/>
<anti-action application="log" data="ERR NOPE, NOT A
SIP CALL"/>
</condition>
</extension>
通道变量与主叫配置字段
这类变量是呼叫通过认证授权时设置的变量,通常存在于注册用户发起的电话。一些主叫配置字段的值来源于用户目录的配置。请参考第4章,用户目录相关内容。
你在用户目录中设置一个变量:
<user id="1011">
<variables>
<variable name="myvariable" value="1234"/>
</variables></user>
然后在拨号方案中引用:
<extension name="check_caller_profile" continue="true">
<condition field="${myvariable}" expression="1234">
<action application="log" data="ERR myvariable exists and is 1234"/>
<anti-action application="log" data="ERR myvariable has NOT been set or is NOT 1234"/>
</condition>
</extension>
通道变量与呼叫设置
FreeSWITCH通道变量(及其它变量)的一个重要层面是:变量不仅可用于检查系统/呼叫/用户的状态,还可以影响FreeSWITCH的行为!
FreeSwitch对一个呼叫所做的大部分操作都是由其配置设置的,并且可以在具体呼叫的基础上任意重写。
让我们看一下SIP或Verto的最基本特征:FreeSWITCH会向远端终端推荐什么编解码。你在/usr/local/freeswitch/conf/vars.xml 中设置缺省编码列表:
<X-PRE-PROCESS cmd="set"
data="outbound_codec_prefs=OPUS,G722,PCMU,PCMA,VP8"/>
FreeSWITCH的预处理器会处理这一行配置,用它设置全局变量$${outbound_codec_prefs},在SIP或Verto发起呼叫时,会引用这个变量去与远端协商编解码。
因此,如果你用一个简单的拨号方案,这个列表中的元素将会被逐字使用:
<extension name="bridge_test_01">
<condition field="destination_number" expression="^2908$">
<action application="bridge"
data="sofia/internal/888@conference.freeswitch.org"/>
</condition>
</extension>
在拨号串中设置通道变量
你可以在拨号串中设置变量来改变一些行为。具体语法是:"{variable=value,variable01=value02}dialstring"。
如果你想覆盖outbound_codec_prefs的值,你可以在拨号串中设置"codec-string"变量(注意使用单引号来转义包含逗号的字符串。逗号分隔变量值):
<extension name="bridge_test_02">
<condition field="destination_number" expression="^2908$">
<action application="bridge" data="{codec_string='PCMA,PCMU,VP8'}user/1011"/>
</condition>
</extension>
如果你不想改变编码偏好列表,只想强制FreeSWITCH使用一个确定的编码表,那么你需要设置"absolute-codec-string"变量:
<extension name="bridge_test_02">
<condition field="destination_number" expression="^2909$">
<action application="bridge" data="{absolute_codec_string='PCMA,PCMU,VP8'}user/1011"/>
</condition>
</extension>
你可以在FreeSWITCH控制台上使用这项技术:
bgapi originate {absolute_codec_string='PCMA,PCMU,VP8'}user/1011 5000
在下面的例子中,我们首先在拨号串中把编码设置为PCMA,然后呼叫成功。接下来把编码设置为被叫不支持的格式,最终呼叫失败:
在B-leg上设置通道变量:export 与 nolocal
你可能希望在拨号方案中早点覆盖缺省的变量值,而不必等待最后时刻的拨号串编辑。
如果你希望在当前话务(A-leg)和将来发起的话务(B- leg,之后由bridge APP发起的)中设置一个变量,你可以使用"export" APP导入(注意,这里没有用于转义字符串的单引号):
<extension name="bridge_test_03">
<condition field="destination_number" expression="^2910$">
<action application="export" data="absolute_codec_string=PCMA,PCMU"/>
<action application="bridge" data="user/1011"/>
</condition>
</extension>
在上面这个独特示例中,我们做了一个微妙的“不适当”的行为:我们同时在A-leg和将来的B-leg中导入"absolute_codec_string"变量。但它对A-leg没有实质影响,因为A-leg已经建立(也就是说,主叫与FreeSWITCH间的协商已经完成。这个变量或许会影响将来的re-invite,但我们可能不期望这样的事情发生)。
因此,我们最好是能够只在将来的B-leg中导入变量,很简单,使用nolocal参数。
<extension name="bridge_test_04">
<condition field="destination_number" expression="^2911$">
<action application="export" data="nolocal:absolute_codec_string=PCMA,PCMU"/>
<action application="bridge" data="user/1011"/>
</condition>
</extension>
导出变量到SIP自定义(X-)报头
有些时候,你需要向自己或远端的SIP实体传递变量。这常用于记账、授权或运营目的。设置或导出名字以"sip_h_"打头的通道变量,你可以向FreeSWITCH生成的SIP消息中添加任意的头域。如果你用的是非标SIP头域(这种场景最好不用标准头域),你可以按惯例把头域命名为"X-Something",那么,你导入的变量就是"sip_h_X- Something":
<extension name="bridge_test_05">
<condition field="destination_number" expression="^2912$">
<action application="set" data="sip_h_X-AccountCode=4321"/>
<action application="bridge" data="sofia/gateway/gw03/2125551212"/>
</condition>
</extension>
这个extension 将发起一个B-leg。SIP INVITE 消息中将会包含类似这样的头域:
X-AccountCode=4321
接下来网关应该能读取自定义头域并处理。SIP代理有它们自己的读取自定义X-头域的该当,而其它FreeSWITCH将通过通道变量${sip_h_X-AccountCode}读取。
在拨号方案中执行API命令
所有能够在CLI执行的API命令,都可以在拨号方案中使用。
许多API命令是由mod_commands模块提供的(可以在http://freeswitch.org/confluence搜索),但是,通常其它模块会添加一些自己的API命令,这些命令同样可以在控制台上执行。控制台的"help"命令可以快速浏览这些API。
在拨号方案中执行API命令的通常语法是:
${api_command(argument01 argument02)}
你可以使用"set"操作,并把从控制台命令行返回的字符串赋值给一个通道变量(你可能不会这么用)。
API命令的参数用圆括符括起,以空格分隔,如果没有参数,就使用一对空的圆括符。
下面示例的第三个action行将尝试从FreeSWITCH卸载mod_verto;第五个action将打印用SIP呼叫主叫时的拨号串(如果话务是由一部注册话机发起的话):
<extension name="API">
<condition field="destination_number" expression="^2913$">
<action application="log" data="ERR ${status()}"/>
<action application="log" data="ERR ${sofia(status profile internal reg)}"/>
<action application="set" data="api_result=${unload(mod_verto)}"/>
<action application="log" data="ERR ${module_exists(mod_verto)}"/>
<action application="log" data="ERR ${sofia_contact(${username}@$${domain_name})}"/>
</condition>
</extension>
FreeSWITCH字符串转换函数
有两种结构可用于增强FreeSWITCH配置的表达能力。它们是从编程语言中借用的,当为了效率起见,我们不想使用Lua或Perl这样的脚本语言时,它们使用起来特别方便。
Cond,C风格的 "expr ? truevalue : falsevalue"
如果"cond"结构里的expr表达式计算为true,那么取truevalue的值,否则取falsevalue。Cond函数的语法结构如下(注意问号和冒号两边的空格):
${cond(<expr> ? <truevalue> : <falsevalue>)}
让我们看一个"cond" 结构的使用实例:
<extension name="COND">
<condition field="destination_number" expression="^2914$">
<action application="log" data="ERR ${cond(${username} == 1011 ?
YESSSS : NOOOOT)}"/>
</condition>
</extension>
在用户1011的注册话机上呼叫2914,控制台上输出:
比较运算符有:
- == 等于
- != 不等于
- > 大于
- >= 大于或等于
- < 小于
- <= 小于或等于
你可以拿字符串与字符串比较,数字与数字比较,但如果你拿一个字符串与数字进行比较,它将会比较宽以字符串与数字的长度。
变量截取
你可以把变量包装成${var:offset:length}格式,以便截取变量值中的部分内容(就像许多编程语言中的substr函数)。参数解释如下:
- var:一个字符串变量。它可以是文本字符串或诸如${caller_id}的变量
- offset:偏移量,开始拷贝数据的位置,0表示第一个字符,负数表示从最后一个字符开始倒数的偏移量
- length:选中的字符长度。这是个可选参数,如果没有指定,就完整拷贝字符串的剩余部分
下面用几个实例说明字符串截取的结果:
set data="varname=1234567890"
${varname:offset:length}
${varname:0:1} | // | 1 |
${varname:1} | // | 234567890 |
${varname:-4} | // | 7890 |
${varname:-4:2} | // | 78 |
${varname:4:2} | // | 56 |
下面extension 展示变量截取的过程:
<extension name="SUBSTR">
<condition field="destination_number" expression="^2915$">
<action application="log" data="ERR ${username}"/>
<action application="log" data="ERR ${username:1:2}"/>
</condition>
</extension>
在用户1011的注册话机上呼叫2915,控制台输出:
db 与 hash: 简单的key/value存储
你可以随心所欲地在内部的FreeSWITCH数据库中插入、删除、更新数据。
数据库工作于持久表之上,它可能是/usr/local/freeswitch/db里的SQLite文件,也可能是通过ODBC连接的远程表单。
哈希表工作于驻留内存之上,非常快速高效,它没有持久化处理,FreeSWITCH重启时,数据不保。
数据库命令的通用格式是:
${db(command/table/key/value)}
或
${hash(command/table/key/value)}
同时提供了相应的拨号方案APP: "db"和"hash"。Command参数可以是insert、select或delete,第二个参数是表名,第三个参数是key,第四个是key的对应值。
<action application="hash" data="insert/${domain_name}- last_dial/${caller_id_number}/${destination_number}"/>
上面这个action(摘自演示拨号方案的"global" extension),它将在${domain_name}- last_dial这张表中插入${caller_id_number}/${destination_number}"数据,记录话务发起方的ID和所拨号码。这有助于后续实现“重拨”功能。
你可以写一个extension来获取某个用户最近拨打的号码:
<extension name="LASTDIALED">
<condition field="destination_number" expression="^2916$">
<action application="log" data="ERR ${hash(select/${domain_name}- last_dial/${caller_id_number})}"/>
</condition>
</extension>
"Hotkeys", Listening, Barging
没人计算过拨号方案可用的APP有多少个,但我打赌至少有几百个。无论如何,我可以数数:1,2,3,4,5……数不清。好吧,有很多。
大部分拨号方案的APP都由mod_dptools(拨号方案工具箱)模块提供,但大部分其它FreeSWITCH模块都会添加它们自己的APP。让我们快速浏览几个允许超越普通呼叫与桥接的APP。
bind_meta_*, 菜单之外的DTMF操作
bind_meta_app:这个命令在指定的call leg上绑定一个APP。在呼叫桥接期间,在绑定的call leg上输入指定的DTMF串,将触发APP的执行。没有绑定APP的call leg不会听到所拨的DTMF。你只能绑定一个单位数字,而且通常以星号作为前缀。举例说明,假设按*2开始录音:
<action application="bind_meta_app" data="2 a s record_session::recording.wav"/>
<action application="bridge" data="sofia/sipprovider/+14158867900">
注意: 除非特别指定,否则bind_meta_app将以星号(*)作为“指定元键”。设定通道变量bind_meta_key可以改变这个行为。比如,用井号键代替星号键:
<action application="set" data="bind_meta_key=#"/>
注意bind-meta-app参数的格式:
<action application="bind_meta_app" data="KEY LISTEN_TO FLAGS APPLICATION[::PARAMETERS]"/>
参数解析:
- KEY:按下*之后的响应键。如果你想响应*1,那么就用1取代KEY。这里限制只能是单位的数字,*或#将被转换为0。
- LISTEN_TO:指定哪条call leg将响应输入。可选参数值有'''a'''、'''b''' 或 '''ab'''
- FLAGS:改变行为的标志。可用标志:
- a – 响应A leg
- 1 – 触发一次后解绑meta_app
- i – 内联执行 (后叙)
- s – 在同一端的leg上响应
- - 对另一端的leg响应
- b – 响应B leg
- APPLICATION:指定需要执行的APP
- PARAMETERS:指定提供给APPLICATION的参数。你必须在APPLICATION后加上::,参数表才会正常解析
Eavesdrop (窃听,呼叫插入)
这个APP允许监听其它通道/呼叫,并与之交互。
这是一个非常复杂的APP,请搜索http://freeswitch.org/confluence以获取所有特性细节信息。
它的调用格式:
eavesdrop UUID|all
以下是几个具体操作实例:
<action application="eavesdrop" data="${db(select/spymap/$1)}"/>
<action application="eavesdrop" data"all"/>
第一条改编自演示拨号方案,假设我们在一张持久表(参考本章之前的"db 与 hash"一节)中插入呼叫的UUID。我们从注册的正则表达式中获取"$1",并从DB中查询相应的UUID,尝试窃听这个通道。
第二条将会依次连接所有进程中的呼叫。
eavesdrop 的DTMF 命令
- 2:与UUID说话
- 1:与UUID的另一端说话
- 3:进入三方通话
- 0:恢复窃听状态
- *:窃听下一个通道
如果窃听对象是特定的UUID(不是所有通道),那么"*" 将结束窃听。
拨号方案菜谱
我们在这里提供一些你可能会时不时参考的场景,因为它们比较常见。这一节里所提供的示例,将以传统菜谱的方式呈现,里面充满了各种菜肴,你可以自由尝试,并根据自己的口味调整拨号方案。
匹配IP地址并呼叫一个号码
下面这个示例,只有主叫终端IP为192.168.1.1的时候才能进入。在第二个条件判断中,把拨打的号码完整地存入$1,并在bridge时引用,直接指定外呼的IP为192.168.2.2。
<extension name="Test1">
<condition field="network_addr" expression="^192\.168\.1\.1$"/>
<condition field="destination_number" expression="^(\d+)$">
<action application="bridge" data="sofia/profilename/$1@192.168.2.2"/>
</condition>
</extension>
第一个条件领域以斜杠结束。最后一个条件领域包含action标记并以</condition>标记结束。此外,注意前面示例与下面示例处理不同:
<extension name="Test1Wrong">
<condition field="destination_number" expression="^(\d+)$"/>
<condition field="network_addr" expression="^192\.168\.1\.1$">
<action application="bridge" data="sofia/profilename/$1@192.168.2.2"/>
</condition>
</extension>
示例Test1Wrong不能正确路由,因为$1取不到值。之所以会这样,是因为目标号码判断是在另一个条件匹配语句中执行的,引用时,已经超出寄存器变量的作用的作用域。
你可以在第一个条件判断中设置一个变量来解决Test1Wrong示例中碰到的问题,即以通道变量替代寄存器变量:
<extension name="Test1.2">
<condition field="destination_number" expression="^(\d+)$">
<action application="set" data="dialed_number=$1"/>
</condition>
<condition field="network_addr" expression="^192\.168\.1\.1$">
<action application="bridge" data="sofia/profile/${dialed_number}@192.168.2.2"/>
</condition>
</extension>
你不能先设置变量,然后在后续的条件判断/匹配中引用变量,因为变量赋值的执行是在条件判断之后的阶段进行的。
如果你确实需要根据extension的内部变量执行不同的操作,那么你有两种方式可以选择:其一,利用execute_extension传递呼叫变量的设置;其二,利用inline(内联)处理。
同时匹配IP地址和被叫号码
这个示例中,话务必须同时匹配两个条件才处理:拨打的号码以1为前缀,并且是从特定IP发起的:
<extension name="Test2">
<condition field="network_addr" expression="^192\.168\.1\.1$"/>
<condition field="destination_number" expression="^1(\d+)$">
<action application="bridge" data="sofia/profilename/$0@192.168.2.2"/>
</condition>
</extension>
这里,虽然匹配表达式是^1(\d+)$,但桥接时并没有用到变量$1,而是用$0。两者差异在于:$1会剥离前缀1,而$0包含完整的原始字符串。
匹配号码并剥离前缀
这个示例,我们匹配以00打头的号码,并把前缀两位00剥离。假设FreeSWITCH收到呼叫00123456789的号码,它需要去掉前面两个零,然后再向外呼叫,即呼叫123456789,我们可以这样设计extension:
<extension name="Test3.1">
<condition field="destination_number" expression="^00(\d+)$">
<action application="bridge" data="sofia/profilename/$1@192.168.2.2"/>
</condition>
</extension>
此外,如果你期望处理非数字类型的字符串,或者你希望拨号内容中可以包含非数字字符,那么你要用.+取代\d+。因为\d+只匹配(十进制)数字位,而.+将匹配所有的字符:
<extension name="Test3.2">
<condition field="destination_number" expression="^00(.+)$">
<action application="bridge" data="sofia/profilename/$1@192.168.2.2"/>
</condition>
</extension>
匹配号码,剥离前缀,并添加另一个前缀
这个示例,在前一个示例剥离两位00前缀之后,在号码前加上一个新的前缀。假设FreeSWITCH收到的号码还是00123456789,这回我们要把00变成011,即最终呼叫011123456789,改写后的extension:
<extension name="Test4">
<condition field="destination_number" expression="^00(\d+)$">
<action application="bridge" data="sofia/profilename/011$1@x.x.x.x"/>
</condition>
</extension>
呼叫注册的设备
这个示例展示了如何桥接注册到FreeSWITCH系统上的设备。我们假设你配置了一个Sofia profile叫local_profile,并且你的话机以域名example.com注册到平台上。请注意:拨号串里里用%代替@:
<extension name="internal">
<condition field="source" expression="mod_sofia"/>
<condition field="destination_number" expression="^(4\d+)$">
<action application="bridge" data="sofia/local_profile/$0%example.com"/>
</condition>
</extension>
以%替代@是FreeSWITCH特有的用法。user%domain格式告诉FreeSWITCH用户注册的域名是domain,并且这个域名是由FreeSWITCH的目录定义提供的。
先找小伙伴A,再找小伙伴B
下面这个示例展示了一个action调用失败后继续调用另一个action的可行性。
如果第一个action调用成功,那么话务桥接到1111@example1.company.com ,其中一方挂断后就结束退出。因为主叫通道已经关闭,所以不继续处理后续指令(换句话说,不会呼叫1111@example2.company.com)。
如果呼叫1111@example1.company.com 处理失败,那么主叫通道不会关闭,继续执行后面的action:
<extension name="find_me">
<condition field="destination_number" expression="^1111$">
<action application="set" data="hangup_after_bridge=true"/>
<action application="set" data="continue_on_fail=true"/>
<action application="bridge" data="sofia/local_profile/1111@example1.company.com"/>
<action application="bridge" data="sofia/local_profile/1111@example2.company.com"/>
</condition>
</extension>
向extension路由DID
通过要public context传入某个DID入局呼叫,路由到内部context的某个固定extension,请做类似下面的配置:
<context name="public">
<extension name="test_did">
<condition field="destination_number" expression="^\d{6}(\d{4})$">
<action application="transfer" data="$1 XML inhouse"/>
</condition>
</extension>
</context>
这个处理会截取10位被叫号码的后四位,连同主叫号码信息一起传递给内部context。注意\d{4}两边的圆括符,它是截取后四位操作的关键之处。
交替出局网关
这个示例假设OfficeA与OfficeB共用一台FreeSWITCH,它们内部分机号都是四位短号,OfficeA以2打头而OfficeB以3打头。他们的出局号码长度都是10位,OfficeA走gateway1,而OfficeB走gateway2。也就是说,需要为它们定制不同的出局路由规则:
<extension name="officeA_outbound">
<condition field="caller_id_number"
expression="^2\d{3}$"/>
<condition field="destination_number" expression="^(\d{10})$">
<action application="set" data="effective_caller_id_number=8001231234"/>
<action application="set" data="effective_caller_id_name=Office A"/>
<action application="bridge" data="sofia/gateway/myswitch.com/$1"/>
</condition>
</extension>
<extension name="officeB_outbound">
<condition field="caller_id_number"
expression="^3\d{3}$"/>
<condition field="destination_number" expression="^(\d{10})$">
<action application="set" data="effective_caller_id_number=8001231235"/>
<action application="set" data="effective_caller_id_name=Office B"/>
<action application="bridge" data="sofia/gateway/otherswitch.com/$1"/>
</condition>
</extension>
企业级的多终端呼叫
考虑一下这种场景:客户希望把他们的呼叫同时路由给两个不同的人。其中Alice喜欢让她的座机先振铃,如果没人接听再转到她的手机上;而Bob喜欢让座机和手机同时振铃。无论谁先接听电话,其它的话务都会取消,终端停止振铃。
实现这样复杂的场景需要用到FreeSWITCH的企业级呼叫。企业级呼叫的基本思想是在一个更大的“企业”中,存在相互联系的“个体”。在这个示例中,呼叫Alice的话机可能需要这样组织:
<action application="bridge" data="[leg_timeout=10]user/Alice| [leg_timeout=20]sofia/gateway/my_gw/${alice_mobile}"/>
而呼叫Bob则是:
<action application="bridge" data="[leg_timeout=10]user/Bob| [leg_timeout=20]sofia/gateway/my_gw/${bob_mobile}"/>
但是上述描述只能单独呼叫Alice或Bob,并不能同呼叫他们俩。要达成这个目的,只需要把它们放到同一个bridge中,并以:_:分隔,配置实例:
<action application="bridge" data="<ignore_early_media=true>[leg_timeout=10]user/Alice| [leg_timeout=20]sofia/gateway/my_gw/${alice_mobile}:_: [leg_timeout=10]user/Bob| [leg_timeout=20]sofia/gateway/my_gw/${bob_mobile}"/>
在这个场景中,当来电触发bridge APP时,FreeSWITCH将发起两个独立的呼叫,其中一路联系Alice,另一路联系Bob。结果就是尝试联系Alice是替先呼叫她的桌面电话再呼叫手机,而呼叫Bob时,他的桌面电话和手机会同时振铃。如果其中有个终端摘机,那么整个“企业”呼叫停止,呼叫桥接到摘机的终端上。
请注意,这里我们要强制设置ignore_early_media=true,因为同时发起这么多call leg,根本处理不了早期媒体。如果需要向呼叫方发送某种振铃信令,可以设置ringback或transfer_ringback变量。
总结
这一章,我们深入地研究了FreeSWITCH拨号方案的运作。在第6章XML拨号方案的基础上,我们讨论了许多高级拨号方案概念:
- 拨号方案解析过程是如何工作的
- 全局变量与通道变量的使用
- 正则表达式的高级用法
- 各种高级路由概念
FreeSWITCH中的拨号方案系统是你可以学习的最重要的概念之一。FreeSWITCH的强大功能在拨号方案系统中得到了真正的释放,理解FreeSWITCH中各种功能的复杂性,是确保FreeSWITCH按照的期望执行的关键。
下一章,我们将抛开静态的的XML文件,为构建强大的FreeSWITCH配置奠定基础。