第九章 深入拨号方案

        现在,我们已经对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)值被评估为truefalse
  • 如果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 呼叫发起端的网络地址
  • aniAutomatic 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配置奠定基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值