1
目录
Asterisk项目概述
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) 。
2 Asterisk二次开发概述
Asterisk是一个开源的PBX架构;但它并不是一个成品。通常情况下,由于企业应用的多样性,很难有一个成型的PBX产品可以满足企业的各种需求。传统的PBX成品,要么功能和灵活性不足,要么配置和维护复杂;而且都具有一个致命的缺点,那就是开放性、可扩展性。
Asterisk具有传统PBX无法比拟的优点,那就是其灵活性,可扩展能力;Asterisk的扩展能力是通过开放相应的架构和接口来实现的。这就意味着Asterisk是一个组件而不是一个成型的产品,Asterisk的核心提供了一个基本的可运行环境,而外围相应的能力则可以通过加载和配置相关的插件和模块来实现。
Asterisk是一个开源的PBX架构;但它并不是一个成品。Asterisk的扩展能力是通过开放相应的架构和接口来实现的。这就意味着Asterisk是一个组件而不是一个成型的产品,Asterisk的核心提供了一个基本的可运行环境,而外围相应的能力则可以通过加载和配置相关的插件和模块来实现。
因此,使用Asterisk,一定会面临二次开发问题,这些二次开发主要围绕以下几个方面:
(1)内部核心模块
①开发扩展编解码能力模块
②开发扩展相应的通道模块
(2)外围动态可加载模块
①开发应用部分
②开发外围管理部分
一般来说,Asterisk使用者很少需要去开发编解码能力模块和通道模块等内部核心模块;而需要开发最多的情况则是外围动态可加载模块,即外围管理部分和应用开发,本文也是指这些方面的开发。
3 Asterisk通道模型与呼叫流程
3.1什么是asterisk通道?
Asterisk通道是指通过asterisk建立起来的一路通话。这类通话都包含一个incoming连接和一个outbound连接。每个电话都是通过一种通道驱动程序建立起来的,比如SIP,ZAP,IAX2等等。每一类的通道驱动,都拥有自己私有的通道数据结构,这些私有的结构从属于一个通用的Asterisk通道数据结构中,具体定义在channel.h和channel.c中。
3.2基本的呼叫流程
Asterisk PBX呼叫流程如图2所示。
(1)通过Asterisk的一个电话呼叫在一个通道驱动接口上到达,如SIP Socket。
(2)通道驱动在该通道上创建一个PBX通道并启动一个pbx线程
(3)拨号方案被执行,拨号方案在一些地方通过dial应用(查看app_dial.c)
强制Asterisk创建一个呼出呼叫,一旦呼出,Asterisk会有以下两个动作将发生。
(1)Dial创建一个呼出的PBX通道并请求一种通道驱动创建一个呼叫
(2)当呼叫被应答时,Asterisk桥接媒体流,于是在第一个通道上的主叫可以和在第
二个通道也就是呼出通道上的被叫通话。
3.3详细呼叫流程分析
我们以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 : "<no 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释放呼叫。
转载自:http://blog.csdn.net/ren911/article/details/6652395 ren99的专栏
- 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);
- ...
- }