freeSwitch入门
- windows 安装 FreeSwitch
- 标题sip客户端安装、登陆、呼叫
- FreeSwitch 基本命令
- FreeSWITCH 架构
- 总体架构
- 外围接口实现
- 目录结构
- XML 用户目录
- 呼叫相关概念
- 拨号计划 DialPlan
- 从配置文件看工作流程
- 工作机制
- 内联拨号计划
- 调用 API
- 调试技巧
- originate 命令详解
- 呼叫的完整逻辑
- FS GUI
windows 安装 FreeSwitch
freeswitch 默认开启了 1000~1019 的号码,默认密码为 1234。
可通过配置文件 安装目录下\conf\directory\default\ 内查看各个号码的配置信息,其中的变量如 $${default_password} 在 安装目录\conf\vas.xml 中定义。
启动 freeswitch 在开始菜单的列表中 右键程序-以管理员身份运行 进行启动,当出现如下界面时即启动完成。
标题sip客户端安装、登陆、呼叫
在 Windows 中用的是 MicroSip,安装和使用都很方便(仅使用于 Windoes)。下载链接
手机端用的是 SipDroid,在手机浏览器中找的下载链接。配置登陆号码见下方截图(域名即 FreeSwitch 所安装机器的 IP 地址)。
手机端和 MicroSip 的配置几乎是一样的,不过需要在同一个局域网中。我是用手机分出了 Wifi 让主机连接,然后 VirtualBox 使用桥接的方式连上主机网络。多个终端也可以通过多开几个虚拟机安装 MircoSip 的方式实现。
不同客户端通过登陆不同的号码,就可以通过电话进行呼叫和通话了。
启动 FS 后,在安装包中还有一个 fs-cli 是可以连接到 FS 的客户端,和 FS 一样的输入输出,但关闭不影响服务程序。
FreeSwitch 基本命令
sofia status profile internal reg # 查看已注册(登陆)设备(号码)
originate user/1000 &park # 通过 1000 拨打电话到 park 程序
# 程序(APP)其实为 freeswitch 内置的函数(注意使用时加上 & 符号):
# park 挂起(听不到任何声音)
# hold 挂起(能听到声音,Music On Hold, MOH)
# playback(/root/welcome.wav) 播放特定的声音文件
# record(/tmp/rec.wav) 录音文件
# bridge(user/1001) 转接到 1001
show channels # 显示通话中的一些信息,包含 UUID
uuid_bridge <uuid1> <uuid2> # 将两个 channel 桥接起来
help # 帮助
sofia help # 模块帮助
sofia global siptrace on # 开启 sip 信息的显示,用 off 可以关闭。
FreeSWITCH 架构
总体架构
总体来说包括 核心 和 外围模块 组成。核心短小精悍高稳定高安全,外围模块通过调用核心提供的 API 与核心进行通信,核心通过让外围模块注册回调函数执行外围模块代码。
核心主要有四部分:DB、公共接口(Public API)、抽象接口和事件(Event)。
- DB 默认使用 SQLite,SQLite 是一种嵌入式数据库。FS 使用核心数据库(在 安装目录/db/core.db)来记录系统接口、任务(tasks)及当前的通道(channels)、通话(calls)等实时数据。模块也有自己的数据库(表)在 db 目录下。
- 公共接口(Public API)可以被外围模块调用。如创建或释放媒体流、Json处理函数等等。
- 抽象接口,抽象接口是核心没有实现的接口,一般由外围模块负责实现并向核心层 注册,核心通过 回调 的方式调用具体的实现。
- 事件(Event),FS 在内部使用消息和 事件机制 进行进程间和模块间通信。事件的产生和消费是异步的,事件可以在 FS 中通过 绑定(Bind)回调函数 进行捕获,即 FS 在时间发生时会依次回调这些函数。
外围接口实现
- Endpoint,是 FS 的最外围(再向外就不是 FS 了),主要 包含了不同 呼叫控制协议的接口,实现与不同电话系统的通信。
- Dialplan,拨号计划,提供电话路由功能。系统默认是由 mod_dialplan_xml 提供。
- Chatplan,聊天计划,提供对文本消息的路由,由 mod_sms 实现。
- APP,应用程序,FS 提供了一些内置的 APP。如 mod_voicemail 实现语音留言;mod_conference 实现多方会议。
- FSAPI,命令接口,是对外的命令接口。
- XML 接口,支持多种 XML 的读取,如 本地文件、DB、远程 HTTP 请求等。但对于应用和扩展在外围模块中完成,如 mod_xml_rpc、mod_xml_curl 等。
- Codec,编解码器(是 COde 与 DECode 的组合)。FS 的实现可以桥接不同采样频率的电话或会议电话。
- 语音识别及语音合成(ASR/TTS)。
- 格式、文件接口(Format, File Interface),支持不同格式文件的回放、录音。
- 日志(Logger),控制日志写到 控制台、文件、系统日志(syslog)、远程日志服务器等。
- 定时器(Timer),FS 最理想的工作始终频率是 1000Hz,许多默认 Linux 发行版内核默认是 100Hz 或 250 Hz,在这种情况下可以重新编译内核调整始终频率。
- 嵌入式语言(Embeded Language),支持 Lua、Javascript、Perl 等控制呼叫流程。
- 事件套接字(Event Socket),可以使用任何其他语言通过事件套接字控制呼叫流程、扩展 FS 功能。
目录结构
在安装目录下
sounds 提供各种声音文件,sounds/music 提供 MOH(Music On Hold,保持音乐)
storage 存放从其他 HTTP 服务器下载下来的语音文件缓存及录音留言文件
conf 存放配置文件
着重介绍下配置文件,配置文件由众多 XML 组成系统装载时,会将 XML 组织在一起 Load 到内存,成为 XML 注册表。
-
conf/freeswitch.xml
是主入口,是所有 XML 文件的黏合剂。标签 X-PRE-PROCESS 是预处理命令,是在加载阶段只进行简单的替换不会被解析,所以对它进行注释仍然会发生替换,需要注意一些影响。 -
vars.xml
是通过 X-PRE-PROCESS 定义的一些全局变量,在后续以 $${var} 的方式进行引用。可以通过 global_getvar 命令来查看变量值。 autoload_configs
目录下是模块级的配置文件,命令方式 模块名.conf.xml(无前缀 mod_),会在系统启动的时候 Load。各模块查找是通过configuration
标签的name
属性查找的。如mod_sofia
在启动时会向 XML 注册表中查找configuration
标签为sofia.conf
的配置。
-
autoload_configs
下的modules.conf.xml
定义了启动时自动加载哪些模块。 -
autoload_configs
下的post_load_modules.conf.xml
中定义的模块是最后加载的。
-
conf/dialplan
目录中的 XML 是路由计划 -
conf/ivr_menues
中存放了默认的 IVR 菜单 -
conf/directory
中存放了用户配置目录(用户目录)。FS 的 用户目录 支持多个域(Domain
)。
XML 用户目录
SIP 不要求一定要注册才可以打电话,但通话前仍需用户认证,认证参数即中用户目录中进行配置。即用户目录决定了哪些用户能注册到 FS 中。
<include>
<domain name="$${domain}">
<params>
<param name="dial-string" value="..."/>
</params>
<variables>
<variable name="record_stereo" value="true"/>
</variables>
<groups>
<group name="sales">
<users>
<user id="1000" type="pointer"/>
</users>
</group>
</groups>
</domain>
</include>
- default.xml 是自带默认的配置文件。其中
$${domain}
默认变量值是主机 IP 地址,可以将他修改为一个域名。 params
标签中定义domain
下所有用户的公共参数。params
定义的 dial-string 变量很重要,FS 会使用user/username
或sofia/internal/username@domain
呼叫时会根据username
等信息找到dial-string
最终扩展成用户实际 SIP 地址。variables
标签定义了一些Channel
级别的公共变量,在通话中会绑定到相应的Channel
上形成Channel Variables
。groups、group
组标签,是不必要的,但可以方便地进行群呼、代接之类的业务。users、user
标签可以是完整的 XML,也可以是指向已存在用户的“指针”(type="pointer"
,通过<user id="xxx">
来找到)。
-注:params
和variables
可以出现在user
、group
或domain
中,优先级按作用域的减小而增大。
呼叫相关概念
两种典型流程:
Bob -> FS -> Alice
FS -> Bob && FS -> Alice
市场上有对方式二的变种,流程为 a)B -> FS
随即 FS 挂掉电话;b)FS -> B && FS -> A
。好处:接电话不会被收费。(华为中 Welink 呼叫就是这个流程)。- 来话,针对与 FS 是到达 FS 的呼叫
- 去话,针对于 FS 是从 FS 出去的呼叫
Session
,无论来话去话 FS 都会启动一个Session
(会话)用于控制整个呼叫Channel
,每个Session
控制这一个Channel
(信道、通道),是一对 UA 间通信的实体,相当于 FS 的一条腿。每个Channel
都用一个唯一的 UUID 来标识,称为Channel UUID;
每个Channel
上可以绑定一些呼叫参数,称为Channel Variables
(通道变量)。Call
,FS 的作用是将两个Channel
桥接 到一起组成一个通话,称为一个 Call。- 回铃音和 Early Media,假设A、B 不在同一交换机(服务器)上通话,中间会经过两台交换机 a、b:
A <-> a <-> b <-> B
。在早期,A 呼 B 在 B 开始振铃时,A 能听到单一的回铃音(Ring Back Tone),这里 b 只向 a 传送了个信令传达到 B 的信号,由 a 交换机生成来铃流;后来,为了支持让 A 听 B 端定制的铃流,必须由交换机 b 返回铃流,这就是Early Media
(早期媒体)。在 SIP 通信中是由 183 消息(带有 SDP)描述的。
Early Media 的流量不包含在通信费中,一般是在月租或套餐中的收费的,所以可以将真正的话音数据伪装成 Early Media
实现“免费通话”。但这种应用有一定的限制,大多数交换机允许的 Early Media 不会太常,如 1 分钟,以避免这种免费通话。
- 全局变量和局部变量,全局变量在服务加载时只求值一次,用
$${var}
形式引用;局部变量即Channel Variables
,在每次创建Channel
时求值(生命周期),用${var}
引用。部分变量在显示时有variable_
前缀,但在使用时不需要此前缀。
拨号计划 DialPlan
参与引用的连接 freeswitch: dialplan 简介
从配置文件看工作流程
配置文件的拨号计划又叫 XML DialPlan,下文还会降到 内联拨号计划。
拨号计划默认用 XML 格式配置。DailPlan 的完整结构的配置是这样的嵌套结构(简写了 xml 文件):
- document
- section(name: dialplan)
- context
- extension # extension 与 extension 之间在逻辑上是隔离的
- condition(field="xxx" expression="^echo|1234$") # 测试条件,指定表达式
- action(application="info" data="xxx") # 执行动作
- 修改配置文件后要用
reloadxml
命令或 F6键 重载配置文件DailPlan
的匹配顺序是按先后顺序进行匹配的,所以注意(不清楚整体情况时)自己修改的放到前面 注意修改权限,我在 sublime 中修改保存成功但实际reloadxml
不成功,在整个文件夹的上加修改权限后成功 - 按 F8 或
console loglevel debug
设置console
的log
级别为debug
extension
标签有个continue="true"
属性,表示匹配完当前extension
后还可以匹配后方的extension
,默认continue
为false
。condition
标签,为空表示匹配所有条件
- 验证用户信息的
field
(可以是内置变量,用户目录中的外置变量要用${}
)与expression
(正则)是否匹配 -
condition
标签可叠加,表示与关系condition
标签中break
参数可以做逻辑判断,如break="on-true"
表示 在执行完本cond
中的action
后if true break
,另外有on-false
(默认),always
,never
action
标签是执行动作,application
表示设置执行的应用,data
为传进去的字段,现知的应用有:
-
info
:输出所有变量到日志中 -
answer
:接听电话,相当于 FS 响应应答消息,后面可以设置一些媒体流如放音、转到语音信箱等。 -
echo
(数字号码 9196):回声,可以听到自己的声音,另外还有个延迟回声delay_echo
(9195)。 -
log
:输出日志, -
hangup
:挂断电话 -
set
:设置一个当前leg
的变量绑定到Channel
上。如设置 greeting 变量的 data:data="greeting=good-morning.wav"
-
export
:设置一个双方leg
的变量,同时还设置一个export_vars=greeting
,当data
开头为nolocal
: 时表示只设置到对方leg
上。 -
hash
:设置变量到内存哈希表中,data
的格式为operator/key/scope/value
-
bridge
:作用,将两条腿桥接起来(在这里创建b-leg
),data
是标准的呼叫字符串(Dial string
),内部{c=d}
可以设置 b-leg 的通道变量c = d
。birdge
会一直阻塞等待b-leg
接听或挂机等操作。 -
sleep
:表示暂停的 ms 数 -
conference
,会议,可以配置condition
将呼叫某一(类)号码的用户都转到会议中(会议分组看 data 格式)。 -
bind_meta_app
,根据用户输入的number
(加星号)执行操作。 -
transfer
,将当前通话重新转移到Routing
阶段。 -
playback
(9664),播放一个声音文件 -
record
,开启录音,data
中传保存位置
<extension name="My Echo Test"> <!-- 测试时名字没改,忽略其意义就好 -->
<condition expression="^1234(\d+)$" field="destination_number">
<action application="log" data="INFO you called ${destination_number}"/> <!-- log 数据首单词可设置级别 -->
<action application="log" data="NOTICE the suffix is $1"/>
<action application="hangup"/>
</condition>
</extension>
注意执行阶段,解析阶段,执行阶段
日志级别:CONSOLE
、ALERT
、CRIT
、ERR
、WARNING
、NOTICE
、INFO
、DEBUG
反动作标签 anti-action
,当 condition
不匹配时可以执行 内部 的 anti-action
标签。
工作机制
Channel
状态机转换工程: NEW - INIT - ROUTING / HUNTING - EXECUTE - HANGUP - REPORTING - DESTROY
新建 Channel - 初始化 - 路由(查找解析 Dialplan) - 执行动作 - 挂机(某一方执行)- 包好(统计计费) - 销毁(释放资源)
在 执行 阶段,也可以发生转移(Transfer
),转移到同一个 Context
下不同 Extension
,转移后会重新进入 Routing
阶段。
注意:
- 默认情况下,
Routing
阶段会查到到 执行计划 中的所有Extension
,并把action
放到一个队列中,然后才进入Execute
阶段执行。所以在action
标签中改变某值去影响路由的逻辑是不对的(除非用inline
属性)。 -
action
标签上inline="true"
属性可以让action
在Routing
阶段执行可用inline
属性的 app 不多,一般都是很快地存取变量的操作。
内联拨号计划
可以把前面讲到的拨号计划成为“XML 拨号计划”。内联拨号计划(Inline Dialplan),用于快速测试不同的 action,可以直接在命令行中写出对应的命令:
originate user/1000 answer,playback:/tmp/a.wav,record:/tmp/b.wav inline
# 解释:使用 1000 拨打,首先 answer,然后放音 a.wav,然后录音到 b.wav
如上注释中的解释,不再重复了,其中:
- 不同于之前用
&
指定的APP
,用内联形式不需加&
且可以指定多个APP
(是个流程) - 带参数的
APP
用APP:args
格式来书写 多个APP
间默认用 逗号 分隔 - 当参数中有空格时,用单引号括住参数
- 当参数中有逗号时,可以用 m 语法修改分隔符为其他字符,如
m:^:xxx
表示 xxx 中用 ^ 表示分隔符
调用 API
在拨号计划中可以调用一些 API,使用方法和引用变量一样,不过变量为函数,如 ${version()}
获取版本。 ${expr(1+1)}
计算一个表达式。
调试技巧
一般流程:发现问题 - 定位问题 - 分析问题 - 解决问题。
- 拨打内置的 APP 看现象缩小定位范围,如
echo
(9196)、playback
(9664) - 通过
uuid_debug_media <uuid> both on
打开媒体调试开关(uuid 通过 show channels 查看) - 通过
console loglevel debug
打开 FSdebug
日志,检查消息的到达 检查日志中挂机原因(Hangup Cause
),一般CALL_REJECTED
表示呼叫拒绝,可能是认证错误,USER_NOT_REGISTERED
说明对方未注册。 - 经过网关通过
bgapi originate sofia/gateway/gw1/Bob &echo
“分段”查看网关后半段通信,bgapi 在后台执行线程,不阻塞控制台 -
sofia profile external siptrace on
打开external
的 profile siptrace,sofia global siptrace on 打开所有 Profile 的。 -
sofia loglevel all 9
打开 Sofia 底层协议栈的调试,换为 0 为关闭 - 使用 fs_cli 进行如上操作,可以随时关闭打开调试信息、固定消息内容等操作,而不影响主进程
- 抓包工具:
tcpdump
、wireshark
、tshark
(wireshark
的命令行版,参考)、ngrep
(类似于 grep 在文本界面中方便),pcapsipdump
(能将不同通话 IP 包存到不同的文件中,在通话量大时很好用) -
tcpdump -nq -s 0 -A -vvv -i eth0 -w abc.pcap port 5060
,-n、-q 表示不进行域名翻译及减少输出内容,-s 0 表示不限制包长,-A 表示以易读的 ASCII 方式输出,-v 表示详细程度,v 越多越详细,-i eth0 表示指定网卡 etho,-w 为写出到指定文件。对于搜索条件 udp 指定抓 udp 包,可以分析 RTP 流;host 1.2.3.4
过滤 IP 地址;与用 and,或用 or。
originate 命令详解
originate # 使用 FS 发起呼叫(默认主叫号码是 000000000)
-USAGE: <call url> <exten>|&<application_name>(<app_args>) [<dialplan>] [<context>] [<cid_name>] [<cid_num>] [<timeout_sec>]
- 同振,同时呼叫多个用户,某个接听另一个自动挂断:
originate user/1000,user/1001 &echo
- 顺振,呼叫某一个号码,如果失败呼叫下一个:
originate user/1000|user/1001 &echo
-
call url
即呼叫字符串,格式 类型/参数/参数,如user/1000
,类型表示Channel
的类型,不存在的类型会报错ERR CHAN_NOT_IMPLEMENTED
-
exten
参数是分机号(exten)或者 &app。若是分机号时,会转入 Dialplan 去路由,路由的目的是查找到enten
-
dialplan
第三个参数是Dialplan
的类型,如果不设置默认是 XML -
context
是Dialplan
的Context
,对于inlineDialplan
可忽略 -
cid_name
,cid_num
是主叫名称 和 主叫号码(CallID Number
),用于界面及 FROM 头中显示 -
timeout_sec
是不回 100 Trying 的超时时间。 originate
命令是阻塞的,可以在前方加上使用 bgapi 转为后台执行。若已经发生阻塞:
- 可以用 fs_cli 执行
show channels
查到 uuid,然后用uuid_kill
uuid 结束此呼叫。 - 或用
hupup
挂断所有电话。
- 命令中使用通道变量
originate {var1=1}{var2=2}user/1000 &echo
,细节略。 - 忽略早期媒体的影响用
originate {ignore_early_media=true}sofia/gateway/gw/13800000000 &playback(/a.wav)
,因为originate
命令是受到媒体指令就返回,如 183 或 200。由于软电话会回复 180 而不是 183,183 相当于携带媒体的 180,而在 PSTN 场景下一般都是回复 183 的。加入此参数后可以忽略 Early Media 对我们呼叫的影响。 -
originate user/1000 &bridge(user/1001)
流程:建立channel
然后呼叫user/1000
,1000
接听后执行bridge
,bridge
再建立一个channel
并呼叫user/1001
。此时双方在信令上建立了桥接关系;在1001
接听后,媒体也会被桥接起来,进入正常通话。
实际上,
bridge
和oiginate
底层用的同一个函数实现,伪代码是originate(session, new_session, dial_str)
差别在于originate
调用函数是session
字段为null
。
- 上方用
bridge
的逻辑中,是先拨通a-leg
后再建立b-leg
的,如果b-leg
回的是 183 则媒体流正常转发到a-leg
中,若为 180 则由于无媒体流 a-leg 听不到任何声音,为了解决这个问题,可以让 FS 回收一个假的回铃音,方法:1)设置{transfer_ringback=local_stream://moh}
变量,此变量控制在 b-leg 回复 180 时开始播放声音。2)在 1 的基础上再加{instant_ringback=true}
变量,可以让bridge
立即播放回铃音,而不等待 180。
呼叫的完整逻辑
假设 1000 呼叫 1001
- 1000 发送
INVITE
到达mod_sofia
的inernal Profile(conf/sip_profiles/internal.xml
,通过 5060 都是先到这里) - FS 收到后立即返回 100,由于
internal.xml
配置了auth-calls=true
所以会进行鉴权(使用Digest Auth
),一般首次会鉴权失败,所以回复 401 - UAC(即1000)重新发送带鉴权信息的
INVITE
到internal Profile(UAS)
- UAS 收到后,将鉴权信息提交到上层 FS 代码,FS 通过
Directory
(用户目录)找到相应用户信息,并根据配置的密码鉴权(失败 403) - 鉴权通过,FS 通过 user_context 配置项找到应该进入哪个路由。如
1000.xml
中配的user_context
为default
,则进入conf/dialplan/default.xml
。(已经进入路由阶段) -
Dialplan
会查到 1001 用户,找到匹配的Extension
执行里面的action
,action
有bridge
命令及data
,所以执行bridge \<data\>
,此时会再次查Directory
(用户目录)找到 1001 的配置信息 - 找到 1001 的 dial-string 配置项,此项会配置在
conf/directory/default.xml
中(由于此域下所有用户的规则一样,所以放在这里),其中sofia_contact
这个 API 会查找数据库,找到 1001 的实际注册Contact
地址,返回真正的呼叫字符串。(如通过sofia_contact 1000
可快速查看sofia/internal/sip:1000@10.0.2.15:63757;ob
) - 当找到
dial-string
后,FS 会另外启动一个会话给 1001 发送 INVITE 请求 - 如果 1001 摘机(接听),则 1001 向 FS 回送 200 OK,FS 再向 1000 回送 200 OK,通话开始。
总结主流程:1000 Invite -> sofia profile -> FS context -> dialplan -> action (bridge 1001) -> invite 1001
。
external.xml
配置(5080 会走这里)auth-calls
为false
,所以不进行鉴权context=public
其中也没有每一个user
上配置的user_context
(internal
中也有context=public
但走user_context
)
FS GUI
图形化界面实现一般有两种方式:
- 通过界面提供的操作方式修改 FS XML 配置文件,及 reloadxml 等操作使之生效
- 通过 http 服务器给 FS 提供 XML