Asterisk是一个开源的软件包,通常运行在Linux操作系统平台上。Asterisk可以用三种协议来实现VoIP,同时可以与目前电话使用的标准硬件进行交互通信,Asterisk在实现VoIP时,不需要任何附加硬件,本文所采用的也是这种使用方式。但是,如果企业没有与VoIP语音网关运营商建立合作关系,想要自己构建这样的一个平台,那么要和数字电话设备与模拟电话设备进行交互通信,Asterisk需要一个PCI硬件的支持,这个硬件生产商中最著名的是Digium平台提供的。
Asterisk 的结构基本上是十分简单,但是它不同于大多数的电话产品。基本上,Asterisk担任的是一个中间件的功能,它连接了底层的电话技术和上层的电话应用。所以,Asterisk 具有很大的柔韧性,特殊的API接口都围绕着PBX核心系统。这个核心处理着PBX内部之间的相互联系。每一部分都是清晰来自于协议、编码或内部电话使用的硬件接口的抽象。这些抽象的接口使Asterisk可以与任何的硬件和技术以及将来的硬件和软件技术完美的结合。从下图可以看出,Asterisk由内部核心和外围动态可加载模块组成。内部核心由以下六个部分组成:PBX交换核心模块(PBX Switching Core)、调度和I/O管理模块(Scheduler and I/O Manager)、应用调用模块(Application Launcher)、编解码转换模块(Codec Translator)、动态模块加载器模块(Dynamic Module Loader)和CDR生成模块(CDR Core) 。
Asterisk是一个开源的PBX架构;但它并不是一个成品。通常情况下,由于企业应用的多样性,很难有一个成型的PBX产品可以满足企业的各种需求。传统的PBX成品,要么功能和灵活性不足,要么配置和维护复杂;而且都具有一个致命的缺点,那就是开放性、可扩展性。
Asterisk具有传统PBX无法比拟的优点,那就是其灵活性,可扩展能力;Asterisk的扩展能力是通过开放相应的架构和接口来实现的。这就意味着Asterisk是一个组件而不是一个成型的产品,Asterisk的核心提供了一个基本的可运行环境,而外围相应的能力则可以通过加载和配置相关的插件和模块来实现。
Asterisk是一个开源的PBX架构;但它并不是一个成品。Asterisk的扩展能力是通过开放相应的架构和接口来实现的。这就意味着Asterisk是一个组件而不是一个成型的产品,Asterisk的核心提供了一个基本的可运行环境,而外围相应的能力则可以通过加载和配置相关的插件和模块来实现。
因此,使用Asterisk,一定会面临二次开发问题,这些二次开发主要围绕以下几个方面:
(1)内部核心模块
①开发扩展编解码能力模块
②开发扩展相应的通道模块
(2)外围动态可加载模块
①开发应用部分
②开发外围管理部分
一般来说,Asterisk使用者很少需要去开发编解码能力模块和通道模块等内部核心模块;而需要开发最多的情况则是外围动态可加载模块,即外围管理部分和应用开发,本文也是指这些方面的开发。
Asterisk通道是指通过asterisk建立起来的一路通话。这类通话都包含一个incoming连接和一个outbound连接。每个电话都是通过一种通道驱动程序建立起来的,比如SIP,ZAP,IAX2等等。每一类的通道驱动,都拥有自己私有的通道数据结构,这些私有的结构从属于一个通用的Asterisk通道数据结构中,具体定义在channel.h和channel.c中。
Asterisk PBX呼叫流程如图2所示。
(1)通过Asterisk的一个电话呼叫在一个通道驱动接口上到达,如SIP Socket。
(2)通道驱动在该通道上创建一个PBX通道并启动一个pbx线程
(3)拨号方案被执行,拨号方案在一些地方通过dial应用(查看app_dial.c)
强制Asterisk创建一个呼出呼叫,一旦呼出,Asterisk会有以下两个动作将发生。
(1)Dial创建一个呼出的PBX通道并请求一种通道驱动创建一个呼叫
(2)当呼叫被应答时,Asterisk桥接媒体流,于是在第一个通道上的主叫可以和在第
二个通道也就是呼出通道上的被叫通话。
我们以sip的呼叫过程为例来描述,其他channel的呼叫过程基本类似。
Astersik下注册的sip用户主动发起一个呼叫的函数调用过程如下:
do_monitor->sipsock_read->handle_request->handle_request_invite->sip_new/ast_pbx_start->pbx_thread->__ast_pbx_run
-> ast_spawn_extension ->pbx_extension_helper->pbx_exec->执行dialplan
当Chan_sip模块被加载时,会启动一个独立的监听线程do_monitor,不断侦听sip端口上的外部消息;
当sip用户拨叫被叫号码后,chan_sip的do_monitor调用sipsock_read函数,在sip端口收到invite消息,然后就调用handle_request和handle_request_invite进行处理。
static void *do_monitor(void *data) { int res; struct sip_pvt *sip; struct sip_peer *peer = NULL; time_t t; int fastrestart = FALSE; int lastpeernum = -1; int curpeernum; int reloading; /* Add an I/O event to our SIP UDP socket */ if (sipsock > -1) /*io.c实现了asterisk跟外部交互时的I/O管理,如chan_sip为了从外部接收SIP信令,调用ast_io_add添加IO接口,并调用ast_io_wait实现外部消息接收。*/ sipsock_read_id =ast_io_add(io, sipsock,sipsock_read,AST_IO_IN, NULL); 。。。 } static int sipsock_read(int *id, int fd, short events, void *ignore) { /*当sip用户拨叫被叫号码后,chan_sip的do_monitor调用sipsock_read函数,在sip端口收到invite消息,然后就调用handle_request和handle_request_invite进行处理。*/ if (handle_request(p, &req, &sin, &recount, &nounlock) == -1) { /* Request failed */ if (option_debug) ast_log(LOG_DEBUG, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : "
"); } 在 static int handle_request(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int *recount, int *nounlock) { 。。。 switch (p->method) { case SIP_OPTIONS: res = handle_request_options(p, req); break; case SIP_INVITE: res = handle_request_invite(p, req, debug, seqno, sin, recount, e, nounlock); break; case SIP_REFER: res = handle_request_refer(p, req, debug, ignore, seqno, nounlock); break; 。。。 }
在handle_request_invite中,首先解析invite消息,对该sip用户的业务属性分析,确认被叫可达,然后就调用sip_new申请channel资源:
static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, struct sockaddr_in *sin, int *recount, char *e, int *nounlock) { … /* Don't hold a sip pvt lock while we allocate a channel */ /*ast_channel_alloc定义在channel.c中,每个呼叫都会调用ast_channel_alloc来申请ast_channel*/ tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, i->amaflags, "SIP/%s-%08x", my_name, (int)(long) i); } if (!tmp) { ast_log(LOG_WARNING, "Unable to allocate AST channel structure for SIP channel\n"); ast_mutex_lock(&i->lock); return NULL; } ast_mutex_lock(&i->lock); if (ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INFO) /*Channel.c/channel.h定义了channel操作的结构体和接口函数。 struct ast_channel_tech结构体是所有channel都要用到的关键结构体,它定义channel操作的一系列回调函数指针,如call、hangup、answer等。每个channel模块都会定义ast_channel_tech的实体,并将各自的回调函数赋值给它。*/ tmp->tech = &sip_tech_info; else tmp->tech = &sip_tech; (struct ast_channel结构体定义了channel的上下文参数,它是每个参与呼叫的channel必不可少的,都会调用ast_channel_alloc来申请) /*channel.h*/ struct ast_channel { /*! \brief Technology (point to channel driver) */ const struct ast_channel_tech *tech; … }
里面ast_channel_tech应该是最主要的一个结构体,定义呼叫流程中标准的过程有那些,在具体的chan_**文件中各协议注册自己对应这些过程的回调函数
/*channel.h*/ /*! \brief Structure to describe a channel "technology", ie a channel driver See for examples: \arg chan_iax2.c - The Inter-Asterisk exchange protocol \arg chan_sip.c - The SIP channel driver \arg chan_zap.c - PSTN connectivity (TDM, PRI, T1/E1, FXO, FXS) If you develop your own channel driver, this is where you tell the PBX at registration of your driver what properties this driver supports and where different callbacks are implemented. */ struct ast_channel_tech { const char * const type; const char * const description; int capabilities; /*!< Bitmap of formats this channel can handle */ int properties; /*!< Technology Properties */ /*! \brief Requester - to set up call data structures (pvt's) */ struct ast_channel *(* const requester)(const char *type, int format, void *data, int *cause); int (* const devicestate)(void *data); /*!< Devicestate call back */ /*! \brief Start sending a literal DTMF digit 开始传送DTMF数据*/ int (* const send_digit_begin)(struct ast_channel *chan, char digit); /*! \brief Stop sending a literal DTMF digit 停止传送DTMF数据*/ int (* const send_digit_end)(struct ast_channel *chan, char digit, unsigned int duration); /*! \brief Call a given phone number (address, etc), but don't take longer than timeout seconds to do so. */ int (* const call)(struct ast_channel *chan, char *addr, int timeout); /*! \brief Hangup (and possibly destroy) the channel 挂断通道*/ int (* const hangup)(struct ast_channel *chan); /*! \brief Answer the channel 响应通道*/ int (* const answer)(struct ast_channel *chan); /*! \brief Read a frame, in standard format (see frame.h) 以标准祯格式读一个祯*/ struct ast_frame * (* const read)(struct ast_channel *chan); /*! \brief Write a frame, in standard format (see frame.h) 以标准祯格式写一个祯*/ int (* const write)(struct ast_channel *chan, struct ast_frame *frame); /*! \brief Display or transmit text 显示或发送文本*/ int (* const send_text)(struct ast_channel *chan, const char *text); /*! \brief Display or send an image 显示或发送图片*/ int (* const send_image)(struct ast_channel *chan, struct ast_frame *frame); /*! \brief Send HTML data 发送HTML数据*/ int (* const send_html)(struct ast_channel *chan, int subclass, const char *data, int len); /*! \brief Handle an exception, reading a frame 处理读取祯时发生的异常*/ struct ast_frame * (* const exception)(struct ast_channel *chan); /*! \brief Bridge two channels of the same type together 桥接两种相同类型的通道*/ enum ast_bridge_result (* const bridge)(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms); /*! \brief Indicate a particular condition (e.g. AST_CONTROL_BUSY or AST_CONTROL_RINGING or AST_CONTROL_CONGESTION 指示一种特殊的状况如sip的486error*/ int (* const indicate)(struct ast_channel *c, int condition, const void *data, size_t datalen); /*! \brief Fix up a channel: If a channel is consumed, this is called. Basically update any ->owner links */ int (* const fixup)(struct ast_channel *oldchan, struct ast_channel *newchan); /*! \brief Set a given option */ int (* const setoption)(struct ast_channel *chan, int option, void *data, int datalen); /*! \brief Query a given option */ int (* const queryoption)(struct ast_channel *chan, int option, void *data, int *datalen); /*! \brief Blind transfer other side (see app_transfer.c and ast_transfer() */ int (* const transfer)(struct ast_channel *chan, const char *newdest); /*! \brief Write a frame, in standard format */ int (* const write_video)(struct ast_channel *chan, struct ast_frame *frame); /*! \brief Find bridged channel 查询已经找到的通道*/ struct ast_channel *(* const bridged_channel)(struct ast_channel *chan, struct ast_channel *bridge); /*! \brief Provide additional read items for CHANNEL() dialplan function */ int (* func_channel_read)(struct ast_channel *chan, char *function, char *data, char *buf, size_t len); /*! \brief Provide additional write items for CHANNEL() dialplan function */ int (* func_channel_write)(struct ast_channel *chan, char *function, char *data, const char *value); /*! \brief Retrieve base channel (agent and local) */ struct ast_channel* (* get_base_channel)(struct ast_channel *chan); /*! \brief Set base channel (agent and local) */ int (* set_base_channel)(struct ast_channel *chan, struct ast_channel *base); };
如在chan_sip.c文件中实现如下:
/*! \brief Definition of this channel for PBX channel registration */ static const struct ast_channel_tech sip_tech = { .type = "SIP", .description = "Session Initiation Protocol (SIP)", .capabilities = ((AST_FORMAT_MAX_AUDIO << 1) - 1), .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, .requester = sip_request_call, .devicestate = sip_devicestate, .call = sip_call, .hangup = sip_hangup, .answer = sip_answer, .read = sip_read, .write = sip_write, .write_video = sip_write, .indicate = sip_indicate, .transfer = sip_transfer, .fixup = sip_fixup, .send_digit_begin = sip_senddigit_begin, .send_digit_end = sip_senddigit_end, .bridge = ast_rtp_bridge, .send_text = sip_sendtext, .func_channel_read = acf_channel_read, };
并调用ast_pbx_start函数启动一个pbx_thread线程来专门处理该呼叫。
static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *title) { … tmp = ast_channel_alloc(… … /*ast_pbx_start函数启动一个pbx_thread线程来专门处理该呼叫*/ if (state != AST_STATE_DOWN &&ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name); tmp->hangupcause = AST_CAUSE_SWITCH_CONGESTION; ast_hangup(tmp); tmp = NULL; } … } enum ast_pbx_result ast_pbx_start(struct ast_channel *c) { pthread_t t; pthread_attr_t attr; if (!c) { ast_log(LOG_WARNING, "Asked to start thread on NULL channel?\n"); return AST_PBX_FAILED; } if (increase_call_count(c)) return AST_PBX_CALL_LIMIT; /* Start a new thread, and get something handling this channel. */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (ast_pthread_create(&t, &attr,pbx_thread, c)) { ast_log(LOG_WARNING, "Failed to create new channel thread\n"); pthread_attr_destroy(&attr); return AST_PBX_FAILED; } pthread_attr_destroy(&attr); return AST_PBX_SUCCESS; }
pbx_thread线程调用__ast_pbx_run。
static void *pbx_thread(void *data) { /* Oh joyeous kernel, we're a new thread, with nothing to do but answer this channel and get it going. */ /* NOTE: The launcher of this function _MUST_ increment 'countcalls' before invoking the function; it will be decremented when the PBX has finished running on the channel */ struct ast_channel *c = data; __ast_pbx_run(c); decrease_call_count(); pthread_exit(NULL); return NULL; }
__ast_pbx_run是一个衔接dialplan和内核的关键函数,它首先调用ast_exists_extension函数,根据分机号码的context属性,匹配到对应的dialplan;然后进入一个for死循环,逐条执行dialplan对应的context中的语句。
pbx_extension_helper函数调用pbx_extension_helper,在pbx_extension_helper中调用pbx_find_extension找到对应的context后,通过verbose打印dialplan执行语句“Executing ……”,同时调用pbx_exec执行该dialplan。执行到dial语句呼叫被叫。
static int __ast_pbx_run(struct ast_channel *c) { … while (ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) { … } int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid) { returnpbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCH); } /*! * \brief The return value depends on the action: * * E_MATCH, E_CANMATCH, E_MATCHMORE require a real match, * and return 0 on failure, -1 on match; * E_FINDLABEL maps the label to a priority, and returns * the priority on success, ... XXX * E_SPAWN, spawn an application, * and return 0 on success, -1 on failure. * * \note The channel is auto-serviced in this function, because doing an extension * match may block for a long time. For example, if the lookup has to use a network * dialplan switch, such as DUNDi or IAX2, it may take a while. However, the channel * auto-service code will queue up any important signalling frames to be processed * after this is done. */ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con, const char *context, const char *exten, int priority, const char *label, const char *callerid, enum ext_match_t action) { … ast_rdlock_contexts(); e =pbx_find_extension(c, con, &q, context, exten, priority, label, callerid, action); if (e) { if (matching_action) { ast_unlock_contexts(); return -1; /* success, we found it */ } else if (action == E_FINDLABEL) { /* map the label to a priority */ res = e->priority; ast_unlock_contexts(); return res; /* the priority we were looking for */ } else { /* spawn */ app = pbx_findapp(e->app); ast_unlock_contexts(); if (!app) { ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority); return -1; } if (c->context != context) ast_copy_string(c->context, context, sizeof(c->context)); if (c->exten != exten) ast_copy_string(c->exten, exten, sizeof(c->exten)); c->priority = priority; '''/*passdata应该是在此赋值*/''' pbx_substitute_variables(passdata, sizeof(passdata), c, e); if (option_debug) { ast_log(LOG_DEBUG, "Launching '%s'\n", app->name); } if (option_verbose > 2) { char tmp[80], tmp2[80], tmp3[EXT_DATA_SIZE]; ast_verbose( VERBOSE_PREFIX_3 "Executing [%s@%s:%d] %s(\"%s\", \"%s\") %s\n", exten, context, priority, term_color(tmp, app->name, COLOR_BRCYAN, 0, sizeof(tmp)), term_color(tmp2, c->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)), term_color(tmp3, passdata, COLOR_BRMAGENTA, 0, sizeof(tmp3)), "in new stack"); } /* 管理事件 */ manager_event(EVENT_FLAG_CALL, "Newexten", "Channel: %s\r\n" "Context: %s\r\n" "Extension: %s\r\n" "Priority: %d\r\n" "Application: %s\r\n" "AppData: %s\r\n" "Uniqueid: %s\r\n", c->name, c->context, c->exten, c->priority, app->name, passdata, c->uniqueid); returnpbx_exec(c, app, passdata); /* 0 on success, -1 on failure */ … }
Pbx_exec函数体为:
/* \note This function is special. It saves the stack so that no matter how many times it is called, it returns to the same place */ int pbx_exec(struct ast_channel *c, /*!< Channel */ struct ast_app *app, /*!< Application */ void *data) /*!< Data for execution */ { int res; const char *saved_c_appl; const char *saved_c_data; if (c->cdr && !ast_check_hangup(c)) ast_cdr_setapp(c->cdr, app->name, data); /* save channel values */ saved_c_appl= c->appl; saved_c_data= c->data; c->appl = app->name; c->data = data; /* XXX remember what to to when we have linked apps to modules */ if (app->module) { /* XXX LOCAL_USER_ADD(app->module) */ } /*应该是在此执行了应用*/ res = app->execute(c, S_OR(data, "")); if (app->module) { /* XXX LOCAL_USER_REMOVE(app->module) */ } /* restore channel values */ c->appl = saved_c_appl; c->data = saved_c_data; return res; }
在等待被叫接通的过程中,完成媒体协商过程,向主叫发送180、200OK消息接通呼叫。
当其他用户呼叫asterisk的sip用户时,函数调用过程如下:Dial->dial_exec->dial_exec_full->ast_request/ast_call/wait_for_answer/ ast_bridge_call
呼叫执行到dial时,pbx_exec调用application dial的接口函数dial_exec,dial_exec调用dial_exec_full。
static int load_module(void) { int res; res = ast_register_application(app, dial_exec, synopsis, descrip); res |= ast_register_application(rapp, retrydial_exec, rsynopsis, rdescrip); return res; } static int dial_exec(struct ast_channel *chan, void *data) { struct ast_flags peerflags; memset(&peerflags, 0, sizeof(peerflags)); return dial_exec_full(chan, data, &peerflags, NULL); } static int dial_exec_full(struct ast_channel *chan, void *data, struct ast_flags *peerflags, int *continue_exec) { 。。。 if (!(c =chan->tech->requester(type, capabilities | videoformat, data, cause))) return NULL; 。。。 }
chan->tech->requester在chan_sip.c中的ast_channel_tech实体中即为.requester = sip_request_call
在dial_exec_full中,首先调用ast_request,在ast_request调用chan_sip对应的回调函数sip_request_call为该被叫sip用户申请channel资源。然后调用ast_call,在ast_call中调用chan_sip对应的回调函数sip_call向被叫发送INVITE消息,呼叫被叫SIP用户。
static struct ast_channel *sip_request_call(const char *type, int format, void *data, int *cause) { … /*申请资源*/ if (!(p = sip_alloc(NULL, NULL, 0, SIP_INVITE))) { ast_log(LOG_ERROR, "Unable to build sip pvt data for '%s' (Out of memory or socket error)\n", (char *)data); *cause = AST_CAUSE_SWITCH_CONGESTION; return NULL; } …
然后该呼叫线程会调用wait_for_answer等待被叫接通。
在呼叫接通后,也即wait_for_answer函数返回,在dial_exec_full中调用ast_bridge_call桥接媒体,这样呼叫就正式接通了。
当chan_sip的侦听线程接收到BYE消息,则调用handle_request_bye找到相应的channel,执行hangup释放呼叫。
if (!ast_strlen_zero(get_header(req, "Also"))) { ast_log(LOG_NOTICE, "Client '%s' using deprecated BYE/Also transfer method. Ask vendor to support REFER instead\n", ast_inet_ntoa(p->recv.sin_addr)); if (ast_strlen_zero(p->context)) ast_string_field_set(p, context, default_context); res = get_also_info(p, req); if (!res) { c = p->owner; if (c) { bridged_to = ast_bridged_channel(c); if (bridged_to) { /* Don't actually hangup here... */ ast_queue_control(c, AST_CONTROL_UNHOLD); ast_async_goto(bridged_to, p->context, p->refer->refer_to,1); } else ast_queue_hangup(p->owner); } } else { ast_log(LOG_WARNING, "Invalid transfer information from '%s'\n", ast_inet_ntoa(p->recv.sin_addr)); if (p->owner) ast_queue_hangup(p->owner); } } else if (p->owner) { ast_queue_hangup(p->owner); ... }