ppx_softswitch系统是一套轻便级软交换系统,集成SIP呼叫管理,SIP用户管理,媒体处理等功能,面向企业通讯,呼叫中心等行业提供基础通讯服务,IPPBX业务功能等。
ppx_softswitch采用C语言,基于组件化的设计思想来开发。运行在Linux平台上。
build:系统makefile存放路径
conf:软件原始配置文件存放路径
docs:介绍文档存放路径
libs:软件使用的库源代码存放目录
src:功能模块源代码存放目录
target:编译产生的目标文件和临时库文件存放目录
build_beta_ver.sh:生成版本信息
build_ver.sh:生成版本信息
env.mk:设置编译时环境变量等
Makefile:系统入口makefile
libedit:命令行交互命令CLI基础库源代码
sofia-sip:Nokia开发的SIP开源协议栈,负责SIP消息解析和封装,SIP事物管理,SDP消息解析和封装等
Makefile:编译库源代码的入口makefile文件
codes:系统支持的编码源文件存放路径
core:系统入口源文件存放路径,负责配置文件的加载,所有模块的初始化操作等
formats:语音文件的读取和写入源代码存放路径
include:公共头文件存放路径
media_streamer:媒体处理相关源代码存放路径,包括rtp协议解析,封装,RTP会话管理,RFC233 DTMF检测处理,音频挂钩(录音和会议等应用)处理。
meeting:会议处理单元源代码存放路径
pbx:PBX业务功能处理源代码存放路径,包括呼叫对话管理等。
scm:SIP注册管理,SIP通道管理等源代码存放路径
synway_adapter:三汇适配API服务端源代码存放路径
utils:通用函数库源代码存放路径
Makefile:编译业务功能模块的入口makefile,会自动递归调用src目录的业务功能模块
负责编译所有src目录的业务功能模块,采用递归自动扫描src目录的makefile文件,然后进行编译。
INCLUDES = \
-I$(MAKEROOT)/target/include/sofia-sip-1.12 \
-I$(MAKEROOT)/libs/sofia-sip/libsofia-sip-ua/nua \
-I$(LIB_DIR)/libedit \
-I$(MAKEROOT)/src/pbx \
-I$(MAKEROOT)/src/media_streamer \
-I$(MAKEROOT)/src/synway_adapter \
-I$(MAKEROOT)/src/include \
-I$(MAKEROOT)/src/core \
-I$(MAKEROOT)/src/scm \
-I$(MAKEROOT)/src/formats \
-I$(MAKEROOT)/src/codes \
-I$(MAKEROOT)/src/meeting \
-I$(MAKEROOT)/src/utils
export INCLUDES
SUBS := $(shell find . -name "Makefile")
SUBDIRS := $(dir $(filter-out ./Makefile, $(shell find . -name "Makefile")))
include $(MAKEROOT)/evn.mk
define build_sub_obj
for dir in $(SUBDIRS); do \
make -C $$dir; \
done
endef
.PHONY: all
all:
@echo "Buildng all applications start..."
@$(call build_sub_obj)
$(CC) $(TARGET_DIR)/*.o $(TARGET_DIR)/slibs/*.a $(TARGET_DIR)/lib/libedit.a $(LDFLAGS) -o $(TARGET_PROG)
Sofia-SIP is an open-source SIP User-Agent library, compliant with the IETF RFC3261 specification. It can be used as a building block for SIP client software for uses such as VoIP, IM, and many other real-time and person-to-person communication services. The primary target platform for Sofia-SIP is GNU/Linux. Sofia-SIP is based on a SIP stack developed at the Nokia Research Center. Sofia-SIP is licensed under the LGPL.
In libsofia-sip-ua, there are subdirectories for different modules listed below.
Terminal and high-level libraries used for both signaling and media:
Common runtime library:
- "su" - sockets, memory management, threads
- "sresolv" - Asynchronous DNS resolver
- "ipt" - IPT utility library
SIP Signaling:
- "nua" - SIP User Agent library
- "nea" - SIP Event API
- "iptsec" - Digest authentication for HTTP and SIP
- "nta" - SIP transaction engine
- "tport" - Message transport
- "sip" - SIP messages and headers
- "msg" - Message handling
- "url" - URL handling
- "url" - low level parsing
HTTP subsystem:
SDP processing:
Other:
Features provided by Sofia-SIP library:
Documentation:
Sofia-SIP内部使用消息队列对SIP消息进行处理,和应用层直接采用回调函数方式进行通信。
Sofia-SIP库在使用之前,必须进行初始化Sofia-SIP,分配空间和初始化环境变量。
static ppx_int32_t sofia_lib_init()
{
ppx_int32_t level = 0;
su_init();
if (sip_update_default_mclass(sip_extend_mclass(NULL)) < 0)
{
su_deinit();
return -1;
}
/* Redirect out log in sofia */
su_log_redirect(su_log_default, sofia_redirect_log, NULL);
su_log_redirect(tport_log, sofia_redirect_log, NULL);
su_log_redirect(iptsec_log, sofia_redirect_log, NULL);
su_log_redirect(nea_log, sofia_redirect_log, NULL);
su_log_redirect(nta_log, sofia_redirect_log, NULL);
su_log_redirect(nth_client_log, sofia_redirect_log, NULL);
su_log_redirect(nth_server_log, sofia_redirect_log, NULL);
su_log_redirect(nua_log, sofia_redirect_log, NULL);
su_log_redirect(soa_log, sofia_redirect_log, NULL);
su_log_redirect(sresolv_log, sofia_redirect_log, NULL);
sip_set_loglevel("all", level);
return 0;
}
对每对IP和端口,我们都应该创建一个UserAgent来处理SIP呼叫请求,同时将处理呼叫事件的回调函数注册到Sofia-SIP库中。
static void * sip_profile_run(void *argv)
{
sip_profile_t *sip_profile = (sip_profile_t *)argv;;
call_msg_t *call_msg = NULL;
sip_profile->root = su_root_create(NULL);
sip_profile->home = su_home_new(sizeof(*sip_profile->home));
sip_profile->nua = nua_create(sip_profile->root,
sip_channels_event_callback,
sip_profile,
NUTAG_URL(sip_profile->bind_url),
NTATAG_USER_VIA(1),
TAG_IF(!strchr(sip_profile->sip_ip, ':'), SOATAG_AF(SOA_AF_IP4_ONLY)),
TAG_IF(strchr(sip_profile->sip_ip, ':'), SOATAG_AF(SOA_AF_IP6_ONLY)),
TAG_IF(!strchr(sip_profile->sip_ip, ':'), NTATAG_UDP_MTU(65535)),
TAG_IF(0, NTATAG_USE_SRV(0)),
TAG_IF(0, NTATAG_USE_NAPTR(0)),
NTATAG_DEFAULT_PROXY(sip_profile->outbound_proxy),
NTATAG_SERVER_RPORT(sip_profile->server_rport_level),
NTATAG_CLIENT_RPORT(sip_profile->client_rport_level),
NTATAG_SERVER_RPORT(sip_profile->rport_level),
TPTAG_LOG(sip_profile->sip_trace),
TAG_IF(0, NTATAG_SIPFLAGS(MSG_DO_COMPACT)),
TAG_IF(sip_profile->timer_t1, NTATAG_SIP_T1(sip_profile->timer_t1)),
TAG_IF(sip_profile->timer_t1x64, NTATAG_SIP_T1X64(sip_profile->timer_t1x64)),
TAG_IF(sip_profile->timer_t2, NTATAG_SIP_T2(sip_profile->timer_t2)),
TAG_IF(sip_profile->timer_t4, NTATAG_SIP_T4(sip_profile->timer_t4)),
SIPTAG_ACCEPT_STR("application/sdp"),
TAG_END());
if (!sip_profile->nua)
{
ppx_log(PPX_LOG_ERROR, "Create sip profile %s (%s:%d) failed\n", sip_profile->name, sip_profile->sip_ip, sip_profile->sip_port);
sip_profile->state = SP_ST_DESDROY;
return NULL;
}
nua_set_params(sip_profile->nua,
SIPTAG_ALLOW_STR("INVITE, ACK, BYE, CANCEL, OPTIONS, INFO"),
NUTAG_ALLOW("REGISTER"),
NUTAG_APPL_METHOD("REGISTER"),
NUTAG_APPL_METHOD("OPTIONS"),
NUTAG_APPL_METHOD("INFO"),
NUTAG_APPL_METHOD("ACK"),
NUTAG_APPL_METHOD("BYE"),
NUTAG_AUTOANSWER(0),
NUTAG_AUTOACK(0),
NUTAG_AUTOALERT(0),
TAG_IF(0, NUTAG_ALLOW("PRACK")),
NUTAG_ALLOW("INFO"),
NUTAG_SESSION_TIMER(sip_profile->session_timeout),
NTATAG_MAX_PROCEEDING(sip_profile->max_proceeding),
SIPTAG_SUPPORTED_STR(NULL/*supported*/),
SIPTAG_USER_AGENT_STR(g_sofia_ctrl->user_agent),
TAG_END());
ppx_log(PPX_LOG_NOTICE, "********SIP Profile '%s' Running ON %s:%d********\n", sip_profile->name, sip_profile->sip_ip, sip_profile->sip_port);
sip_profile->state = SP_ST_ACTIVE;
while (sip_profile->state < SP_ST_STOP)
{
call_msg = sip_profile_call_msg_pop(sip_profile);
if (call_msg)
{
sip_call_msg_proc(sip_profile, call_msg);
call_msg_free(call_msg);
su_root_step(sip_profile->root, 0);
}
else
{
su_root_step(sip_profile->root, 10);
}
}
ppx_log(PPX_LOG_WARNING, "Stop sip profile %s\n", sip_profile->name);
nua_shutdown(sip_profile->nua);
su_root_run(sip_profile->root);
nua_destroy(sip_profile->nua);
su_home_unref(sip_profile->home);
su_root_destroy(sip_profile->root);
sip_profile->state = SP_ST_DESDROY;
return NULL;
}
应用层将回调函数注册到Soifa-SIP库中,Soifa-SIP协议栈解析SIP消息后,再进行SIP事务处理,根据不同状态和SIP消息,产生相应的呼叫事件消息,然后在回到应用层注册的回调函数。
static void sip_channels_event_callback(nua_event_t msg, ppx_int32_t status, char const *phrase, nua_t *nua, nua_magic_t *profile_magic,
nua_handle_t *nh, nua_hmagic_t *sofia_private_magic, sip_t const *sip, tagi_t tags[])
{
ppx_int32_t check_destroy = 1;
sip_channel_t *chan = NULL;
sip_profile_t *profile = (sip_profile_t *)profile_magic;
sofia_private_t *sofia_private = (sofia_private_t *)sofia_private_magic;
ppx_log(PPX_LOG_DEBUG, "sip msg(%s) status(%d %s) uuid:%s\n", nua_event_name(msg), status, VSTR(phrase), (sofia_private && '\0' != sofia_private->uuid[0]) ? sofia_private->uuid : "");
if (sofia_private)
{
if ('\0' != sofia_private->uuid[0])
{
if ((chan = sip_chan_find(sofia_private->uuid)))
{
if (!chan->call_id && sip && sip->sip_call_id && sip->sip_call_id->i_id)
{
chan->call_id = sip_chan_strdup(chan, (char *)sip->sip_call_id->i_id);
}
if (chan->state >= SCST_WAIT_DESTROY)
{
ppx_log(PPX_LOG_DEBUG, "The sip chan %s is already hungup, waiting for destroy\n", chan->uuid_str);
goto done;
}
}
else
{
ppx_log(PPX_LOG_DEBUG, "sip chan %s not found\n", sofia_private->uuid);
return;
}
}
}
switch(msg)
{
case nua_i_state:
handle_sip_i_state(chan, status, phrase, nua, profile, nh, sofia_private, sip, tags);
break;
case nua_r_get_params:
case nua_i_fork:
case nua_r_info:
break;
case nua_r_bye:
case nua_r_unregister:
case nua_r_unsubscribe:
case nua_r_publish:
break;
case nua_r_cancel:
if (status > 299)
{
if (nh)
{
if (sofia_private)
{
nua_handle_bind(nh, NULL);
}
nua_handle_destroy(nh);
nh = NULL;
}
if (chan)
{
if (sofia_private)
{
ppx_safe_free(sofia_private);
chan->sofia_private = NULL;
}
chan->state = SCST_WAIT_DESTROY;
}
}
break;
case nua_i_error:
break;
case nua_i_active:
case nua_r_set_params:
case nua_i_prack:
case nua_r_prack:
break;
case nua_i_cancel:
if (chan)
{
chan->recv_cancel = PPX_TRUE;
}
break;
case nua_i_terminated:
if (chan)
{
chan->state = SCST_WAIT_DESTROY;
}
break;
case nua_i_ack:
if (chan && sip)
{
if (PPX_FALSE == chan->reinvite)
{
if (!chan->call_id && sip->sip_call_id && sip->sip_call_id->i_id)
{
chan->call_id = sip_chan_strdup(chan, (char *)sip->sip_call_id->i_id);
}
extract_sip_header_vars(profile, sip, chan);
}
}
case nua_r_ack:
break;
case nua_r_shutdown:
if (status >= 200)
{
su_root_break(profile->root);
}
break;
case nua_r_message:
handle_sip_r_message(status, profile, nh, sip);
break;
case nua_r_invite:
if (sip && status >= 400 && sip->sip_reason && sip->sip_reason->re_protocol && (!strcasecmp(sip->sip_reason->re_protocol, "Q.850")) && sip->sip_reason->re_cause)
{
chan->q850_cause = atoi(sip->sip_reason->re_cause);
}
if (status == 407 && sip)
{
if (PPX_TRUE != handle_sip_r_challenge(chan, status, phrase, nua, profile, nh, sofia_private, sip, tags))
{
if (chan)
{
if (SCST_PROGRESS >= chan->state)
{
handle_sip_r_456xx_response(chan, 503, NULL);
}
else if (SCST_ACTIVE == chan->state)
{
sip_channel_build_bye_msg(chan, 0);
}
chan->nh = NULL;
chan->state = SCST_WAIT_DESTROY;
chan->sofia_private = NULL;
}
if (sofia_private)
{
sofia_private->destroy_me = 1;
sofia_private->destroy_nh = 1;
}
}
}
else
{
sofia_handle_sip_r_invite(chan, status, phrase, nua, profile, nh, sofia_private, sip, tags);
}
break;
case nua_r_options:
break;
case nua_i_bye:
handle_sip_i_bye(chan, status, phrase, nua, profile, nh, sofia_private, sip, tags);
break;
case nua_r_notify:
handle_sip_r_notify(chan, status, phrase, nua, profile, nh, sofia_private, sip, tags);
break;
case nua_i_notify:
handle_sip_i_notify(chan, status, phrase, nua, profile, nh, sofia_private, sip, tags);
break;
case nua_r_register:
if (sip)
{
if (status == 401)
{
handle_sip_r_challenge(chan, status, phrase, nua, profile, nh, sofia_private, sip, tags);
}
else
{
handle_sip_r_register(status, phrase, nua, profile, nh, sofia_private, sip, tags);
}
}
break;
case nua_i_options:
handle_sip_i_options(status, phrase, nua, profile, nh, sofia_private, sip, tags);
break;
case nua_i_invite:
if (NULL == chan)
{
handle_sip_i_invite(nua, profile, nh, sip, tags);
}
else
{
chan->reinvite = PPX_TRUE;
handle_sip_i_reinvite(chan, nua, profile, nh, sofia_private, sip, tags);
}
break;
case nua_i_publish:
ppx_log(PPX_LOG_INFO, "%s", "Received PUBLISH request, wo don't handle it\n");
break;
case nua_i_register:
handle_sip_i_register(nua, profile, nh, sip, PPX_TRUE, NULL, 0, tags);
break;
case nua_i_message:
ppx_log(PPX_LOG_INFO, "%s", "Received MESSAGE request, wo don't handle it\n");
break;
case nua_i_info:
handle_sip_i_info(nua, profile, nh, chan, sip, tags);
break;
case nua_i_update:
break;
case nua_r_update:
break;
case nua_r_refer:
break;
case nua_i_refer:
ppx_log(PPX_LOG_INFO, "%s", "Received REFER request, wo don't handle it\n");
break;
case nua_r_subscribe:
ppx_log(PPX_LOG_INFO, "%s", "Received SUBSCRIBE response, wo don't handle it\n");
break;
case nua_i_subscribe:
ppx_log(PPX_LOG_INFO, "%s", "Received SUBSCRIBE request, wo don't handle it\n");
break;
case nua_r_authenticate:
if (status >= 500)
{
if (sofia_private && '\0' !=sofia_private->reg_id[0])
{
ppx_sip_gateway_update(sofia_private->reg_id, 0, status);
}
else
{
nua_handle_destroy(nh);
}
}
break;
default:
if (status > 100)
{
ppx_log(PPX_LOG_INFO, "%s: unknown msg %d: %03d %s\n", nua_event_name(msg), msg, status, phrase);
}
else
{
ppx_log(PPX_LOG_INFO, "%s: unknown msg %d\n", nua_event_name(msg), msg);
}
break;
}
done:
switch (msg)
{
case nua_i_subscribe:
case nua_r_notify:
check_destroy = 0;
break;
case nua_i_notify:
if (sip && sip->sip_event && !strcmp(sip->sip_event->o_type, "dialog") && sip->sip_event->o_params && !strcmp(sip->sip_event->o_params[0], "sla"))
{
check_destroy = 0;
}
break;
default:
;
}
if (check_destroy)
{
if (nh && ((sofia_private && sofia_private->destroy_nh) || !nua_handle_magic(nh)))
{
if (sofia_private)
{
nua_handle_bind(nh, NULL);
}
nua_handle_destroy(nh);
nh = NULL;
}
}
if (sofia_private && sofia_private->destroy_me)
{
if (chan)
{
chan->sofia_private = NULL;
}
if (nh)
{
nua_handle_bind(nh, NULL);
}
ppx_safe_free(sofia_private);
}
if (chan && SCST_WAIT_DESTROY == chan->state)
{
sip_chan_free(&chan);
}
}
业务层模块如下图所示:
下面对业务层的文件实现功能进行简单的描述和介绍
ppx_alaw.c G.711a编码和解码实现源文件
ppx_alaw.h G.711a编解码函数声明头文件
ppx_codec.c Codec模块初始化,释放等函数实现源文件
ppx_codec.h Codec模块初始化,释放等函数声明头文件
ppx_ulaw.c G.711u编码和解码实现源文件
ppx_ulaw.h G.711u编解码函数声明头文件
cli_client.c CLI客户端功能实现源文件,ppx_softswitch –c时使用,即启动CLI客户端连接ppx_softswitch后台程序,然后可以执行ppx_softswitch的CLI命令,包括修改日志级别,查询当前呼叫通道等命令。
cli_client.h CLI客户端模块初始化,启动,退出和释放函数声明头文件
cli_server.c CLI服务端功能实现源文件,主要包括TCP服务端实现,CLI命令接收,解析,分发和处理等。
cli_server.h CLI服务端模块初始化,启动,退出,释放和CLI命令注册等函数声明头文件
main.c 程序启动入口等函数实现源文件,包括main函数,唯一启动功能实现等
ppx_core.c 系统核心模块初始化,启动,退出,释放函数,配置文件读取等函数实现源文件,所有子模块的初始化,启动都在此文件封装的函数中调用
ppx_core.h 系统核心模块初始化,启动,退出和释放函数声明头文件
ppx_format_file.h 格式化音频文件读写声明头文件
ppx_format_wav.c wav格式音频文件读写函数实现源文件
ppx_voice_file.c 格式化音频文件读写模块初始化,启动等函数实现源文件,也包括预加载语言文件管理等函数的实现
ppx_voice_file.h 格式化音频文件读写模块初始化,启动等函数声明头文件
ppx_define.h 系统定义常量
ppx_preinclude.h 系统需要包括头文件
version.h 编译时生成的版本信息文件,由Makefile时自动生成
ppx_audiohook.c 音频媒体帧钩子实现源文件,给录音和会议等应用提供音频媒体帧
ppx_audiohook.h 音频媒体帧钩子声明头文件。
rtp.c rtp协议实现源文件
rtp.h rtp函数声明头文件
rtp_port_allocator.c rtp端口分配和管理实现源文件
rtp_port_allocator.h rtp端口分配和管理声明头文件
rtp_rfc2833.c RFC2833实现源文件
rtp_session.c rtp会话管理实现源文件,包括rtp媒体流的接收,中转等
rtp_session.h rtp会话管理函数声明头文件
ppx_meeting.c 会议模块实现源文件,包括会议的加入,混音处理等
ppx_meeting.h 会议模块函数声明头文件
call_msg.c 模块内部呼叫消息封装和处理实现源文件
call_msg.h 模块内部消息声明头文件
call_session.c 呼叫业务处理实现源文件,比如呼叫接续,桥接,挂掉,播放语言等业务功能
call_session.h 呼叫业务处理函数声明头文件
routing.c 呼叫路由查询实现源文件
routing.h 呼叫路由查询函数声明头文件
sip_chan.c SIP呼叫管理实现源文件,包括sip通道的申请,查询和维护等
sip_chan.h SIP呼叫管理函数声明头文件
sip_register.c SIP注册管理实现模块,包括SIP用户注册,向网关发送注册请求等的实现
sip_register.h SIP注册管理模块函数声明头文件
sip_sofia.c Sofia-SIP协议栈封装处理模块,处理SIP呼叫,转换为内部呼叫消息,然在在分发给呼叫处理层进行处理。SIP Profile文件的加载和管理等。
sip_sofia.h Sofia-SIP协议栈封装处理模块函数声明头文件。
pbx_adapter.c 业务层调用的动态库libppx_adapter.so对应的服务端实现模块,包括TCP服务端实现,适配三汇API内部呼叫消息的收发,解析和处理等
pbx_adapter.h 适配三汇API模块函数声明头文件
ppx_hash.c 哈希表实现源文件
ppx_hash.h 哈希表函数声明头文件
ppx_log.c 日志实现源文件,包括写日志,日志轮询等实现。
ppx_log.h 日志处理函数声明头文件
ppx_md5.c MD5算法实现源文件
ppx_md5.h MD5算法函数声明头文件
ppx_socket.c Linux Socket封装实现源文件
ppx_socket.h Linux Socket封装函数声明头文件
ppx_type.h 系统内部变量类型定义
ppx_utils.c 常用函数实现源文件,包括
ppx_utils.h 常用函数声明头文件
-
- 呼出流程介绍
-
- 挂机流程介绍
-
- 彩铃播放流程介绍
-
- 电话会议流程介绍