转自出处:https://www.jianshu.com/p/e80f6dc3423c
freeswitch 默认开启了 1000~1019 的号码,默认密码为 1234。
可通过配置文件 安装目录下\conf\directory\default\ 内查看各个号码的配置信息,其中的变量如 $${default_password} 在 安装目录\conf\vas.xml 中定义。
启动 freeswitch 在开始菜单的列表中 右键程序-以管理员身份运行 进行启动,当出现如下界面时即启动完成。
在 Windows 中用的是 MicroSip,安装和使用都很方便(仅使用于 Windoes)。下载链接
手机端用的是 SipDroid,在手机浏览器中找的下载链接。配置登陆号码见下方截图(域名即 FreeSwitch 所安装机器的 IP 地址)。
手机端和 MicroSip 的配置几乎是一样的,不过需要在同一个局域网中。我是用手机分出了 Wifi 让主机连接,然后 VirtualBox 使用桥接的方式连上主机网络。多个终端也可以通过多开几个虚拟机安装 MircoSip 的方式实现。
不同客户端通过登陆不同的号码,就可以通过电话进行呼叫和通话了。
启动 FS 后,在安装包中还有一个 fs-cli 是可以连接到 FS 的客户端,和 FS 一样的输入输出,但关闭不影响服务程序。
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 可以关闭。
总体来说包括 核心 和 外围模块 组成。核心短小精悍高稳定高安全,外围模块通过调用核心提供的 API 与核心进行通信,核心通过让外围模块注册回调函数执行外围模块代码。
核心主要有四部分:DB、公共接口(Public API)、抽象接口和事件(Event)。
DB 默认使用 SQLite,SQLite 是一种嵌入式数据库。FS 使用核心数据库(在 安装目录/db/core.db)来记录系统接口、任务(tasks)及当前的通道(channels)、通话(calls)等实时数据。模块也有自己的数据库(表)在 db 目录下。
公共接口(Public API)可以被外围模块调用。如创建或释放媒体流、Json处理函数等等。
抽象接口,抽象接口是核心没有实现的接口,一般由外围模块负责实现并向核心层 注册,核心通过 回调 的方式调用具体的实现。
事件(Event),FS 在内部使用消息和 事件机制 进行进程间和模块间通信。事件的产生和消费是异步的,事件可以在 FS 中通过 绑定(Bind)回调函数 进行捕获,即 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
)。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"
,通过
来找到)。
-注: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_
前缀,但在使用时不需要此前缀。参与引用的连接 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}"/>
<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:^:xxx
表示 xxx 中用 ^ 表示分隔符在拨号计划中可以调用一些 API,使用方法和引用变量一样,不过变量为函数,如 ${version()}
获取版本。 ${expr(1+1)}
计算一个表达式。
一般流程:发现问题 - 定位问题 - 分析问题 - 解决问题。
echo
(9196)、playback
(9664)uuid_debug_media both on
打开媒体调试开关(uuid 通过 show channels 查看)console loglevel debug
打开 FS debug
日志,检查消息的到达 检查日志中挂机原因(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 为关闭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 # 使用 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
的类型,如果不设置默认是 XMLcontext
是 Dialplan
的 Context
,对于 inlineDialplan
可忽略cid_name
, cid_num
是主叫名称 和 主叫号码(CallID Number
),用于界面及 FROM 头中显示timeout_sec
是不回 100 Trying 的超时时间。originate
命令是阻塞的,可以在前方加上使用 bgapi 转为后台执行。若已经发生阻塞:
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
INVITE
到达 mod_sofia
的 inernal Profile(conf/sip_profiles/internal.xml
,通过 5060 都是先到这里)internal.xml
配置了 auth-calls=true
所以会进行鉴权(使用 Digest Auth
),一般首次会鉴权失败,所以回复 401INVITE
到 internal Profile(UAS)
Directory
(用户目录)找到相应用户信息,并根据配置的密码鉴权(失败 403)1000.xml
中配的 user_context
为 default
,则进入 conf/dialplan/default.xml
。(已经进入路由阶段)Dialplan
会查到 1001 用户,找到匹配的 Extension
执行里面的 action
,action
有 bridge
命令及 data
,所以执行 bridge \
,此时会再次查 Directory
(用户目录)找到 1001 的配置信息conf/directory/default.xml
中(由于此域下所有用户的规则一样,所以放在这里),其中 sofia_contact
这个 API 会查找数据库,找到 1001 的实际注册 Contact
地址,返回真正的呼叫字符串。(如通过 sofia_contact 1000
可快速查看 sofia/internal/sip:[email protected]:63757;ob
)dial-string
后,FS 会另外启动一个会话给 1001 发送 INVITE 请求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
)
图形化界面实现一般有两种方式: