freeswitch源码分析.docx
《freeswitch源码分析.docx》由会员分享,可在线阅读,更多相关《freeswitch源码分析.docx(22页珍藏版)》请在冰豆网上搜索。
freeswitch源码分析
总体来说,FreeSWITCH是一个基于组件的架构,如下图:
FreeSWITCH可以仅包括FSCore独立运行,外围各种不同种类的组件(module)增强了FreeSWITCH的功能。
开发者可以使用publicAPI,遵循FreeSWITCH的接口标准,开发各种不同种类的module来增强FreeSWITCH的功能。
分析
(一)
什么是FreeSWITCH
FreeSWITCH是一个可扩展的开源跨平台的电话平台,支持音频、视频、文本或任何其他形式的媒体使用的协议的路由与交互。
它于2006年成立。
FreeSWITCH也提供一个稳定的技术平台,可供许多电话应用开发利用的免费工具。
FreeSWITCH最初由AnthonyMinessale在BrianWest和MichaelJerris的协助下设计和开发。
这三人原先都是asterisk的开发者。
这个项目的设计目标包括模块化、跨平台的支持,可扩展性和稳定性。
今天,许多更多的开发者和使用者都为FreeSWITCH在贡献力量。
FreeSWITCH支持各种通信技术,如Skype,SIP、H.323、GoogleTalk,因此它容易与其他的开源PBX进行对接,如:
sipXecs、CallWeaver、Bayonne、YATE和Asterisk。
FreeSWITCH支持许多高级的SIP特性,如presence、BLF、SLA以及TCPTLS和sRTP。
它也可以作为一个透明代理(有媒体或无媒体),扮演SBC和T.38代理的角色。
FreeSWITCH既支持宽带、窄带编码。
Voicechannel和conferencebridge模块可以支持8k、16k、24k、32k和48k不同的码率,而且这些不同码率的通道可以进行bridge。
如果G.729编解码经过授权,FreeSWITCH也是支持的。
FreeSWITCH支持Windows,MacOSX,Linux,BSD和Solaris的32与64位平台。
FreeSWITCH支持传真,无论是音频,还是T.38,而且可以微微音频和T.38的网关。
FreeSWITCH的很多开发者,都是非常有经验的开发人员。
他们同时也参与其他开源软交换产品的开发,如:
openSER,sipXecs,Asterisk和CallWeaver.
目录结构
├──configure
├──configure.in
├──Makefile.am
├──Makefile.in
├──modules.conf:
需要编译的module列表
├──patches/:
补丁包
│├──MODAPP-293.diff
│├──mod_portaudio_snow_leopard.diff
│├──sofia.diff
│└──zrtp_bnlib_pic.diff
├──src/
│├──CMakeLists.txt
│├──g711.c
│├──include/
│├──inet_pton.c
│├──Makefile.am
│├──Makefile.in
│├──mod/
│├──switch_apr.c
│├──switch_buffer.c
│├──switch.c:
main入口
│├──switch_caller.c
│├──switch_channel.c
│├──switch_config.c
│├──switch_console.c
│├──switch_core_asr.c
│├──switch_core.c
│├──switch_core_codec.c
│├──switch_core_db.c
│├──switch_core_directory.c
│├──switch_core_event_hook.c
│├──switch_core_file.c
│├──switch_core_hash.c
│├──switch_core_io.c
│├──switch_core_media_bug.c
│├──switch_core_memory.c
│├──switch_core_port_allocator.c
│├──switch_core_rwlock.c
│├──switch_core_session.c
│├──switch_core_speech.c
│├──switch_core_sqldb.c
│├──switch_core_state_machine.c
│├──switch_core_timer.c
│├──switch_cpp.cpp
│├──switch_dso.c
│├──switch_event.c
│├──switch_ivr_async.c
│├──switch_ivr_bridge.c
│├──switch_ivr.c
│├──switch_ivr_menu.c
│├──switch_ivr_originate.c
│├──switch_ivr_play_say.c
│├──switch_ivr_say.c
│├──switch_loadable_module.c
│├──switch_log.c
│├──switch_mprintf.c
│├──switch_nat.c
│├──switch_odbc.c
│├──switch_pcm.c
│├──switch_profile.c
│├──switch_regex.c
│├──switch_resample.c
│├──switch_rtp.c
│├──switch_scheduler.c
│├──switch_stun.c
│├──switch_swig.c
│├──switch_swig.i
│├──switch_time.c
│├──switch_utils.c
│├──switch_xml.c
│└──switch_xml_config.c
├──libs/
启动逻辑
FreeSwitch主程序的入库函数在src/switch.c文件中。
Main函数启动:
1.分析启动参数;
2.为全局变量SWITCH_GLOBAL_dirs分配内存和赋值;
3.创建pid文件;
4.调用switch_core.c中的switch_core_init_and_modload()函数,初始化并加载所有的module,内部实现逻辑如下:
a)switch_core_init_and_modload
i.首先调用switch_core_init
1.初始化全局变量runtime的相关参数;
2.对SWITCH_GLOBAL_dirs指定的目录进行创建;
3.load_mime_types加载conf/mime.types加载所有的mime类型;
4.设置运行参数的默认值(注意:
switch_find_local_ip获取本地IP的时候会访问外网);
5.依次调用:
switch_core_session_init、switch_console_init、switch_event_init、switch_xml_init、switch_log_init进行相关的初始化,初始化的内容主要:
hash、mutex。
6.加载"switch.conf"配置文件;
7.switch_core_state_machine_init对state_machine进行初始化;
8.switch_core_sqldb_start,启动数据库相关线程;
9.switch_scheduler_task_thread_start,启动任务调度模块
10.switch_rtp_init:
初始化rtp/zrtp相关的
11.switch_scheduler_add_task(heartbeat_callback),将心跳任务加入任务调度模块;
ii.switch_loadable_module_init()[switch_loadable_module.c]加载各module,freeswitch为了后续高效的运行module中的各种接口,将所有的接口都存放到了hash表中,每种接口建立了一个hash桶:
1.初始化全局变量:
loadable_modules的各类hash。
2.加载集成module:
CORE_SOFTTIMER_MODULE、CORE_PCM_MODULE;
a)switch_loadable_module_load_file进行module加载
i.找到name##_module_interface的全局变量进行加载(module中宏SWITCH_MODULE_DEFINITION定义的);
ii.执行module->load(会初始化参数module_interface,是SWITCH_MODULE_LOAD_FUNCTION定义的函数的一个参数),load中会增加各类接口:
API、CHAT、APP、DIALPLAN、CODEC
b)switch_loadable_module_process
i.遍历每个节点的endpoint_interface、codec_interface、dialplan_interface、timer_interface、application_interface、api_interface、file_interface、speech_interface、asr_interface、directory_interface、chat_interface、say_interface、management_interface,加载并存储到hash。
c)创建线程,执行module->runtime()
3.遍历conf/modules.conf加载所有配置的module;
4.遍历conf/post_load_modules.conf加载所有配置的module;
5.如果除集成module之外配置的模块为0,则从SWITCH_GLOBAL_dirs.mod_dir加载所有的mod;
6.switch_loadable_module_runtime,为每个module的runtime入口创建一个线程并运行runtime;
分析
(二)
事件处理模型
FreeSWITCH内部处理事件的基本数据结构和逻辑如下图:
1.freeSWITCH启动的时候,会创建3个EVENT_QUEUE,用来保存相关api、dialplan或呼叫触发的事件,队列长度为100000,每个队列的优先级不一样,平台根据事件的优先级将事件入队。
注意:
如果符合优先级要求的队列已经满了或其他原因造成入队失败,可能将事件放入到低优先级的队列[c1];
2.每个EVENT_QUEUE会伴随一个event_thread线程,该线程的作用就是将EVENT_QUEUE的事件转移到EVENT_DISPATCH_QUEUE,队列长度为5000。
FreeSWITCH启动的时候会创建1个EVENT_DISPATCH_QUEUE队列,正常情况下,event_thread会将事件放入到EVENT_DISPATCH_QUEUE[0],如果该队列已满或其他原因造成入队失败,再系统会动态创建EVENT_DISPATCH_QUEUE[1],[c2]将事件放入该队列,依次类推;
3.每个EVENT_DISPATCH_QUEUE队列伴随一个event_dispatch_thread,该线程的作用就是将EVENT_DISPATCH_QUEUE的事件转移到对应的事件订约方队列NODE_EVENT_QUEUE,每个订阅会自带一个NODE_EVENT_QUEUE,长度为100000.。
媒体处理方式
FreeSWITCH有三种媒体处理方式,分别是:
default、proxy、bypass,具体的应用和对比参考下表:
Default
Proxy
(proxy_media=true)
Bypass
(bypass_media=true)
FreeSWITCH是否有媒体经过
Y
Y
N
是否可以对媒体流进行编解码
Y
N
N
是否需要终端语音编解码协商一致
N
Y
Y
支持录音、DTMF解析、会议、编解码转换
Y
N
N
[c1]带来了可能的事件时序错误的问题
[c2]带来了可能的事件时序错误的问题
分析(三)
esl
api/bgapi调用逻辑
应用程序以普通方式对api的调用,是顺序、阻塞的,直到api执行完成才返回。
应用程序以bgapi的方式调用,是异步的,mod_event_sockt收到请求后,启动执行线程,直接返回。
如下所示,是api普通方式调用的时序图:
Application调用逻辑
应用程序调用dialplan相关的应用,都是异步模式,mod_event_socket收到请求之后,将请求放入到session的私有队列,接口直接返回。
获取事件
1.mod_event_socket启动的时候会订阅FreeSWITCH的所有事件,产生一个NODE_EVENT_QUEUE,mod_event_socket会根据不同的客户端订阅,给不同的客户端返回需要的事件;
2.平台产生事件之后,会将事件放到mod_event_socket的NODE_EVENT_QUEUE队列,mod_event_socket会遍历每个连接,将每个事件推送给订阅该事件的客户端;
3.客户端调用esl接口获取事件,esl库首先根据check_q变量的设置,判断是否优先从自己的race_event队列(调用esl相关接口esl_send_recv执行程序的时候,为了获取相关api的返回值,esl需要从服务端等待消息,此过程中可能获取到了事件,则esl会将事件放入到race_event中)中获取,如果race_event没有,则首先从自己的缓存packet_buf中取数据处理,如果取到合适的数据直接返回,否则调用socket函数recv,按固定超时时间从mod_event_socket上获取事件,将获取到的内容放入到packet_buf。
媒体应用处理
同步
典型application/api:
playback、play_and_get_digist、read、record、park
以上这些应用,内部会循环调用switch_ivr_parse_all_events处理所有的dptool的请求,直到放音、录音、unpark等操作完成,因此不要在这些应用没有执行完的时候,继续执行其他应用。
如:
在上次playback未完成的时候,又进行一次playback,这样会优先进行第二次playback,然后再进行第一次playback,形成了递归;playback和record未完成操作直接调用,操作也类似,下图描述了一个递归的调用(在放音的过程中,调用record,如果record不被打断,则放音永远无法放出声音):
异步/订阅
典型application/api:
uuid_record、record_session、start_dtmf。
以上这些应用,会给媒体流的read,增加hook(media_bug),不会阻塞媒体流。
典型场景
客户呼入
1)客户呼入,mod_sofia会受到invite消息;
2)Mod_sofia获取一个session,启动session处理线程(session状态机);
3)Mod_sofia收到状态改变消息,设置session未INIT状态,后转为ROUTING状态;
4)Session状态机,根据规则找到对应的dialplan定义,session状态变迁为EXECUTE;
5)状态机根据dialplan的定义,执行对应的application。
平台外呼
1)程序调用esl的接口发送外呼命令;
2)Mod_event_socket收到命令之后,调用mod_command的originate;
3)Originate调用switch_ivr定义的函数,创建session,启动session处理线程(状态机),同时状态变迁未INIT;
4)Mod_sofia发起invite请求,同时设置session状态为ROUTING;
5)外部应答,session状态机变迁未CONSUME_MEDIA;
6)程序通过esl接口,发送执行application命令,同时session的状态变迁未EXECUTE。
freeswitch内核研究
2011-11-2321:
34:
14|分类:
默认分类|标签:
|字号大中小订阅
比较零散,先放这留个脚印,慢慢添加整理。
1.
freeswitch分机号都保存在conf/directory目录下
系统启动时加载分机信息到内存,当收到注册包时在directory目录下的usr被搜寻,搜寻依据是注册请求的to,from头域的域名为系统所在的域名,
分机配置文件的分级结构:
domain
groups
group
usr
usr
group
groups
domain
directory目录下包含若干xml文件,可以每个用户一个xmlprofile,系统启动时动态加载,
除了通过文件方式配置用户外,可以通过mod_xml_curl模块访问webserver,webserver在
访问数据库,实现大批量分机的添加。
可以在用户的配置文件中设置一些附加给此用户的变量。
directory目录的内容加载后可以被系统的所有模块获取,这样减少数据冗余。
dialplan全局变量用$${default_areacode}"访问,通道变量用${default_areacode}"访问
conf/var.xml文件定义了系统的全局变量。
单个用户的配置文件模板
//id代表用户名,认证时用户名。
//此用户的channel会设置此变量,用户的权限
其她可设置的变量:
accountcode:
用户账户,会出现在CDR中。
user_context:
用户打电话时走dialplan中context名为user_context的值
effective_caller_id_name:
呼叫其他用户时显示给对方的用户名,(只有被叫在系统上注册才有效)
effective_caller_id_number:
给对方显示的用户号,(只有被叫在系统上注册才有效)
outbound_caller_id_name:
通过sip中继外乎时给对方送的用户名
outbound_caller_id_number:
通过sip中继外乎时给对方送的用户号码
callgroup:
附加属性,
一个分机默认配置包含:
AusernameforSIPandforauthorization
Avoicemailpassword
Ameansofallowing/restrictingdialling
AmeansofhandlingcallerIDbeingsentout
Severalarbitraryvariablesthatcanbeusedorignoredasneeded
配置一个分机过程:
#>cd/usr/local/freeswitch/conf/directory/default
#>cp1000.xml1030.xml
Replacealloccurrencesof"1000"with"1030"
修改defaultdialplan
重新加载配置文件使配置生效:
reloadxml
控制台查看哪些分机主上已经注册:
sofiastatusprofileinternal
freeswitch的dialplan单独一个目录,分机的conext为dialplan目录下的conext.xml
与外部链接:
freeswitch通过sip网关联系外部世界,freeswitch此时在sipserver来看是一个user.
配置网关方式:
创建中继文件:
conf/sip_profiles/external/test.xml
//sipprovider提供的用户名及密码
--iptel.orgrequiresa'proxy'parameter-->
-->
使配置生效:
cli执行:
sofiaprofileexternalrestartreloadxml(此命令会把正在通话的分机挂掉,更安全的方式是用sofiaprofileexternalrescanreloadxml)
cli执行sofiastatus
返回系统sip配置信息
主要分两类:
1.网关(gateway)2.本地注册用户(profile)
mod_xml_curl:
此模块为与Asteriskrealtime机制差不多,可以通过此模让freeswitch需要时动态访问外部数据库或WebServer.这样可以实现动态控制freeswitch核心。
比如分机的添加可以通过在数据库配置,freeswitch通过此模块来加载分机。
通过此模块可以绑定:
1.dialplan
//localhost:
8080"bindings="Dialplan"/>
每次呼叫,系统都会先访问8080
.....
2.配置文件
sofia.conf文件:
global_settings节点:
子节点: