Asterisk模块编写指南

1开源项目概述

Asterisk是一个开源的软件包,通常运行在Linux操作系统平台上。Asterisk可以用三种协议来实现VoIP,同时可以与目前电话使用的标准硬件进行交互通信,Asterisk在实现VoIP时,不需要任何附加硬件,本文所采用的也是这种使用方式。但是,如果企业没有与VoIP语音网关运营商建立合作关系,想要自己构建这样的一个平台,那么要和数字电话设备与模拟电话设备进行交互通信,Asterisk需要一个PCI硬件的支持,这个硬件生产商中最著名的是Digium平台提供的。

Asterisk的结构基本上是十分简单,但是它不同于大多数的电话产品。基本上,Asterisk担任的是一个中间件的功能,它连接了底层的电话技术和上层的电话应用。所以,Asterisk 具有很大的柔韧性,特殊的API接口都围绕着PBX核心系统。这个核心处理着PBX内部之间的相互联系。每一部分都是清晰来自于协议、编码或内部电话使用的硬件接口的抽象。这些抽象的接口使Asterisk可以与任何的硬件和技术以及将来的硬件和软件技术完美的结合。从图2.5可以看出,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呼叫流程如图3所示。

(1)通过Asterisk的一个电话呼叫在一个通道驱动接口上到达,如SIP Socket。

(2)通道驱动在该通道上创建一个PBX通道并启动一个pbx线程

(3)拨号方案被执行,拨号方案在一些地方通过dial应用(查看app_dial.c)

强制Asterisk创建一个呼出呼叫,一旦呼出,Asterisk会有以下两个动作将发生。

(1)Dial创建一个呼出的PBX通道并请求一种通道驱动创建一个呼叫

(2)当呼叫被应答时,Asterisk桥接媒体流,于是在第一个通道上的主叫可以和在第

二个通道也就是呼出通道上的被叫通话。




4 RADIUS协议的概述

(1)Radius协议在协议栈中的位置

Radius是一种流行的AAA协议,同时其采用的是UDP协议传输模式,AAA协议在协议栈中位置如图3所示

(2)Radius协议选择UDP作为传输层协议

①NAS和Radius服务器之间传递的是几十上百个字节长度的数据,且Radius要求特别的定时器管理机制,用户可以容忍几十秒的验证等待时间。

②当处理大量用户,服务器端采用多线程,UDP简化了服务器端的实现过程。

③TCP是必须成功建立连接后才能进行数据传输的,这种方式在有大量用户使用的情况下实时性不好。Radius要有重传机制和备用服务器机制,它所采用的定时,TCP不能很好的满足。由于数据包可能会在网络上丢失,如果客户没有收到响应,那么可以重新发送该请求包。多次发送之后如果仍然收不到响应,RADIUS客户可以向备用的RADIUS服务器发送请求包。

④Radius依靠自身协议保证报文重传和服务器备份机制以确保计费可靠性。

5 认证计费功能概述

IP-PBX呼叫控制功能,主要是VoIP终端用户的认证计费控制过程,是VoIP系统商业化运营的核心模块。

Radius Client端,也叫NAS,主要的任务就是根据VoIP终端的呼叫请求携带的各种属性,包括账户ID、被叫号码、通话时间等,封装成标准的Radius数据包发送到Radius Server端,达到账户信息实时更新的效果。整个NAS端程序主要由两个模块构成:认证模块和计费模块,并把这两个模块整合到开源IP-PBX项目 Asterisk中。

5.1 标准RADIUS协议分析

(1)Radius Packet

RADIUS数据包被包装在UDP数据报的数据块(Data field))中,其中的目的端口为1812,RADIUS协议包结构如图4所示。

    0                   1                   2                   3

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |     Code      | Identifier   |            Length             |

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                                                               |

   |                         Authenticator                         |

   |                                                               |

   |                                                               |

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   | Attributes ...(不定长)

   +-+-+-+-+-+-+-+-+-+-+-+-+-

 

Attribute:

0                   1                   2

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |     Type      | Length       | Value …(不定长)    

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

图4  RADIUS协议包结构图

(2)对Radius Packet格式各个域解释

①Code:包类型,一个字节长,指示RADIUS包的类型,包含不合法的Code的Radius包将被直接丢弃,code域主要包含了以下值类型。

1)code=1 Access-Request——认证请求数据包

本文AAA功能就是构建code=1的认证请求数据包。

2)code=2 Access-Accept——认证响应数据包

3) code=3 Access-Reject——认证拒绝数据包

4)code=4 Accounting-Request——计费请求数据包

本文Asterisk的AAA功能另外一个重点任务就是构建code=4的计费请求数据包,Accounting-Request 数据包中的两种状态类型(Acct-Status-Type)的计费请求数据包:Start(Value=1):Client开始对指定用户提供服务,计费开始;Stop(Value=2):Client停止对指定用户提供服务,计费结束。

5)code=5 Accounting-Response——计费响应数据包

因为是要更新账户信息,所以目前本文不需要处理计费响应数据包。

②Identifier:包标识符,一个字节长,用于匹配请求包和响应包,同一组请求包和响应包的Identifier应相同。协议规定:

1) 在任何时间,发给同一个RADIUS服务器的不同包的Identifier域不能相同,如果出现相同的情况,RADIUS将认为后一个包是前一个包的拷贝而不对其进行处理。

2) Radius针对某个请求包的响应包应与该请求包在Identifier上相匹配(相同)。

③Length:包长度,两个字节长,说明数据包的长度,是code、identifier、length、authenticator attribute fields的长度总和,有效范围是20~4096,超出范围的数据将被视为附加数据(Padding)或直接被忽略。

④Authenticator:验证字,16字节长,用于验证消息的负载,对包进行签名,该验证字分为两种。

1) 请求验证字---Request Authenticator,用在请求报文中,必须为全局唯一的随机值。

2) 响应验证字---Response Authenticator,用在响应报文中,用于鉴别响应报文的合法性。响应验证字=MD5(Code+ID+Length+请求验证字+Attributes+Key)。

⑤Attributes:Type指示了Atribute的类型,通用的有几十种,在系统中使用到的,如表4.1所示。Asterisk AAA模块的构建主要是构建表1列出的这些属性值的RADIUS数据包。

表1 Atribute的属性列表





5.2   选择一个合适的Radius Client API  

 

上个小节介绍的RADIUS数据包格式,是构建应用协议层数据包的封装所关注的,在Asterisk中如果需要亲自把标准RADIUS数据包的发送、接收等过程从零开始写起,那本文就把重点放在了RADIUS UDP数据包与服务器通信过程的编写中了,实际本文关注的是在Asterisk中根据VoIP通信中的业务需求,构建RADIUS认证计费模块,重点是业务应用层的开发,即如何组织认证包、计费包的数据结构等,而RADIUS数据包传输层直接调用现成的开源API,目前主要有两种这样的开源项目。

(1)pam_radius     

一个PAM模块提供了RADIUS客户端的功能。它是从开源项目Freeradius中提取出来的,如果要使用需要对代码做大量的修改、打补丁后才能使用。

(2)radiusclient-ng

相对比PAM的pam_radius模块而言,radiusclient-ng的动态库代码不用修改就可以拿过来使用,只需安装radiusclient-ng的动态库,然后根据配置文件、开放的API接口修改Asterisk代码就可以完成Asterisk AAA模块的构建。

所以在本文使用radiusclient-ng开源软件包。

6、详细的呼叫流程

我们以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进行处理。

 
 

 
 
  1. static void *do_monitor(void *data)
  2. {
  3. int res;
  4. struct sip_pvt *sip;
  5. struct sip_peer *peer = NULL;
  6. time_t t;
  7. int fastrestart = FALSE;
  8. int lastpeernum = -1;
  9. int curpeernum;
  10. int reloading;
  11. /* Add an I/O event to our SIP UDP socket */
  12. if (sipsock > -1)
  13. /*io.c实现了asterisk跟外部交互时的I/O管理,如chan_sip为了从外部接收SIP信令,调用ast_io_add添加IO接口,并调用ast_io_wait实现外部消息接收。*/
  14. sipsock_read_id =ast_io_add(io, sipsock,sipsock_read,AST_IO_IN, NULL);
  15. 。。。
  16. }
  17. static int sipsock_read(int *id, int fd, short events, void *ignore)
  18. {
  19. /*当sip用户拨叫被叫号码后,chan_sip的do_monitor调用sipsock_read函数,在sip端口收到invite消息,然后就调用handle_request和handle_request_invite进行处理。*/
  20. if (handle_request(p, &req, &sin, &recount, &nounlock) == -1) {
  21. /* Request failed */
  22. if (option_debug)
  23. ast_log(LOG_DEBUG, "SIP message could not be handled, bad request: %-70.70s\n", p->callid[0] ? p->callid : "<no callid>");
  24. }
  25. static int handle_request(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int *recount, int *nounlock)
  26. {
  27. 。。。
  28. switch (p->method) {
  29. case SIP_OPTIONS:
  30. res = handle_request_options(p, req);
  31. break;
  32. case SIP_INVITE:
  33. res = handle_request_invite(p, req, debug, seqno, sin, recount, e, nounlock);
  34. break;
  35. case SIP_REFER:
  36. res = handle_request_refer(p, req, debug, ignore, seqno, nounlock);
  37. break;
  38. 。。。
  39. }
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资源:

 
 

 
 
  1. 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)
  2. {
  3. /* Don't hold a sip pvt lock while we allocate a channel */
  4. /*ast_channel_alloc定义在channel.c中,每个呼叫都会调用ast_channel_alloc来申请ast_channel*/
  5. 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);
  6. }
  7. if (!tmp) {
  8. ast_log(LOG_WARNING, "Unable to allocate AST channel structure for SIP channel\n");
  9. ast_mutex_lock(&i->lock);
  10. return NULL;
  11. }
  12. ast_mutex_lock(&i->lock);
  13. if (ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INFO)
  14. /*Channel.c/channel.h定义了channel操作的结构体和接口函数。
  15. struct ast_channel_tech结构体是所有channel都要用到的关键结构体,它定义channel操作的一系列回调函数指针,如call、hangup、answer等。每个channel模块都会定义ast_channel_tech的实体,并将各自的回调函数赋值给它。*/
  16. tmp->tech = &sip_tech_info;
  17. else
  18. tmp->tech = &sip_tech;
  19. (struct ast_channel结构体定义了channel的上下文参数,它是每个参与呼叫的channel必不可少的,都会调用ast_channel_alloc来申请)
  20. /*channel.h*/
  21. struct ast_channel {
  22. /*! \brief Technology (point to channel driver) */
  23. const struct ast_channel_tech *tech;
  24. }
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_**文件中各协议注册自己对应这些过程的回调函数

 
 
  1. /*channel.h*/
  2. /*! \brief
  3. Structure to describe a channel "technology", ie a channel driver
  4. See for examples:
  5. \arg chan_iax2.c - The Inter-Asterisk exchange protocol
  6. \arg chan_sip.c - The SIP channel driver
  7. \arg chan_zap.c - PSTN connectivity (TDM, PRI, T1/E1, FXO, FXS)
  8. If you develop your own channel driver, this is where you
  9. tell the PBX at registration of your driver what properties
  10. this driver supports and where different callbacks are
  11. implemented.
  12. */
  13. struct ast_channel_tech {
  14. const char * const type;
  15. const char * const description;
  16. int capabilities; /*!< Bitmap of formats this channel can handle */
  17. int properties; /*!< Technology Properties */
  18. /*! \brief Requester - to set up call data structures (pvt's) */
  19. struct ast_channel *(* const requester)(const char *type, int format, void *data, int *cause);
  20. int (* const devicestate)(void *data); /*!< Devicestate call back */
  21. /*! \brief Start sending a literal DTMF digit 开始传送DTMF数据*/
  22. int (* const send_digit_begin)(struct ast_channel *chan, char digit);
  23. /*! \brief Stop sending a literal DTMF digit 停止传送DTMF数据*/
  24. int (* const send_digit_end)(struct ast_channel *chan, char digit, unsigned int duration);
  25. /*! \brief Call a given phone number (address, etc), but don't
  26. take longer than timeout seconds to do so. */
  27. int (* const call)(struct ast_channel *chan, char *addr, int timeout);
  28. /*! \brief Hangup (and possibly destroy) the channel 挂断通道*/
  29. int (* const hangup)(struct ast_channel *chan);
  30. /*! \brief Answer the channel 响应通道*/
  31. int (* const answer)(struct ast_channel *chan);
  32. /*! \brief Read a frame, in standard format (see frame.h) 以标准祯格式读一个祯*/
  33. struct ast_frame * (* const read)(struct ast_channel *chan);
  34. /*! \brief Write a frame, in standard format (see frame.h) 以标准祯格式写一个祯*/
  35. int (* const write)(struct ast_channel *chan, struct ast_frame *frame);
  36. /*! \brief Display or transmit text 显示或发送文本*/
  37. int (* const send_text)(struct ast_channel *chan, const char *text);
  38. /*! \brief Display or send an image 显示或发送图片*/
  39. int (* const send_image)(struct ast_channel *chan, struct ast_frame *frame);
  40. /*! \brief Send HTML data 发送HTML数据*/
  41. int (* const send_html)(struct ast_channel *chan, int subclass, const char *data, int len);
  42. /*! \brief Handle an exception, reading a frame 处理读取祯时发生的异常*/
  43. struct ast_frame * (* const exception)(struct ast_channel *chan);
  44. /*! \brief Bridge two channels of the same type together 桥接两种相同类型的通道*/
  45. enum ast_bridge_result (* const bridge)(struct ast_channel *c0, struct ast_channel *c1, int flags,
  46. struct ast_frame **fo, struct ast_channel **rc, int timeoutms);
  47. /*! \brief Indicate a particular condition (e.g. AST_CONTROL_BUSY or AST_CONTROL_RINGING or AST_CONTROL_CONGESTION 指示一种特殊的状况如sip的486error*/
  48. int (* const indicate)(struct ast_channel *c, int condition, const void *data, size_t datalen);
  49. /*! \brief Fix up a channel: If a channel is consumed, this is called. Basically update any ->owner links */
  50. int (* const fixup)(struct ast_channel *oldchan, struct ast_channel *newchan);
  51. /*! \brief Set a given option */
  52. int (* const setoption)(struct ast_channel *chan, int option, void *data, int datalen);
  53. /*! \brief Query a given option */
  54. int (* const queryoption)(struct ast_channel *chan, int option, void *data, int *datalen);
  55. /*! \brief Blind transfer other side (see app_transfer.c and ast_transfer() */
  56. int (* const transfer)(struct ast_channel *chan, const char *newdest);
  57. /*! \brief Write a frame, in standard format */
  58. int (* const write_video)(struct ast_channel *chan, struct ast_frame *frame);
  59. /*! \brief Find bridged channel 查询已经找到的通道*/
  60. struct ast_channel *(* const bridged_channel)(struct ast_channel *chan, struct ast_channel *bridge);
  61. /*! \brief Provide additional read items for CHANNEL() dialplan function */
  62. int (* func_channel_read)(struct ast_channel *chan, char *function, char *data, char *buf, size_t len);
  63. /*! \brief Provide additional write items for CHANNEL() dialplan function */
  64. int (* func_channel_write)(struct ast_channel *chan, char *function, char *data, const char *value);
  65. /*! \brief Retrieve base channel (agent and local) */
  66. struct ast_channel* (* get_base_channel)(struct ast_channel *chan);
  67. /*! \brief Set base channel (agent and local) */
  68. int (* set_base_channel)(struct ast_channel *chan, struct ast_channel *base);
  69. };

如在chan_sip.c文件中实现如下:

 
 
  1. /*! \brief Definition of this channel for PBX channel registration */
  2. static const struct ast_channel_tech sip_tech = {
  3. .type = "SIP",
  4. .description = "Session Initiation Protocol (SIP)",
  5. .capabilities = ((AST_FORMAT_MAX_AUDIO << 1) - 1),
  6. .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER,
  7. .requester = sip_request_call,
  8. .devicestate = sip_devicestate,
  9. .call = sip_call,
  10. .hangup = sip_hangup,
  11. .answer = sip_answer,
  12. .read = sip_read,
  13. .write = sip_write,
  14. .write_video = sip_write,
  15. .indicate = sip_indicate,
  16. .transfer = sip_transfer,
  17. .fixup = sip_fixup,
  18. .send_digit_begin = sip_senddigit_begin,
  19. .send_digit_end = sip_senddigit_end,
  20. .bridge = ast_rtp_bridge,
  21. .send_text = sip_sendtext,
  22. .func_channel_read = acf_channel_read,
  23. };

并调用ast_pbx_start函数启动一个pbx_thread线程来专门处理该呼叫。

 
 
  1. static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *title)
  2. {
  3. tmp = ast_channel_alloc(…
  4. /*ast_pbx_start函数启动一个pbx_thread线程来专门处理该呼叫*/
  5. if (state != AST_STATE_DOWN &&ast_pbx_start(tmp)) {
  6. ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name);
  7. tmp->hangupcause = AST_CAUSE_SWITCH_CONGESTION;
  8. ast_hangup(tmp);
  9. tmp = NULL;
  10. }
  11. }
  12. enum ast_pbx_result ast_pbx_start(struct ast_channel *c)
  13. {
  14. pthread_t t;
  15. pthread_attr_t attr;
  16. if (!c) {
  17. ast_log(LOG_WARNING, "Asked to start thread on NULL channel?\n");
  18. return AST_PBX_FAILED;
  19. }
  20. if (increase_call_count(c))
  21. return AST_PBX_CALL_LIMIT;
  22. /* Start a new thread, and get something handling this channel. */
  23. pthread_attr_init(&attr);
  24. pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  25. if (ast_pthread_create(&t, &attr,pbx_thread, c)) {
  26. ast_log(LOG_WARNING, "Failed to create new channel thread\n");
  27. pthread_attr_destroy(&attr);
  28. return AST_PBX_FAILED;
  29. }
  30. pthread_attr_destroy(&attr);
  31. return AST_PBX_SUCCESS;
  32. }


pbx_thread线程调用__ast_pbx_run。

 
 
  1. static void *pbx_thread(void *data)
  2. {
  3. /* Oh joyeous kernel, we're a new thread, with nothing to do but
  4. answer this channel and get it going.
  5. */
  6. /* NOTE:
  7. The launcher of this function _MUST_ increment 'countcalls'
  8. before invoking the function; it will be decremented when the
  9. PBX has finished running on the channel
  10. */
  11. struct ast_channel *c = data;
  12. __ast_pbx_run(c);
  13. decrease_call_count();
  14. pthread_exit(NULL);
  15. return NULL;
  16. }


__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语句呼叫被叫。

 
 
  1. static int __ast_pbx_run(struct ast_channel *c)
  2. {
  3. while (ast_exists_extension(c, c->context, c->exten, c->priority, c->cid.cid_num)) {
  4. }
  5. int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
  6. {
  7. returnpbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCH);
  8. }
  9. /*!
  10. * \brief The return value depends on the action:
  11. *
  12. * E_MATCH, E_CANMATCH, E_MATCHMORE require a real match,
  13. * and return 0 on failure, -1 on match;
  14. * E_FINDLABEL maps the label to a priority, and returns
  15. * the priority on success, ... XXX
  16. * E_SPAWN, spawn an application,
  17. * and return 0 on success, -1 on failure.
  18. *
  19. * \note The channel is auto-serviced in this function, because doing an extension
  20. * match may block for a long time. For example, if the lookup has to use a network
  21. * dialplan switch, such as DUNDi or IAX2, it may take a while. However, the channel
  22. * auto-service code will queue up any important signalling frames to be processed
  23. * after this is done.
  24. */
  25. static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
  26. const char *context, const char *exten, int priority,
  27. const char *label, const char *callerid, enum ext_match_t action)
  28. {
  29. ast_rdlock_contexts();
  30. e =pbx_find_extension(c, con, &q, context, exten, priority, label, callerid, action);
  31. if (e) {
  32. if (matching_action) {
  33. ast_unlock_contexts();
  34. return -1; /* success, we found it */
  35. } else if (action == E_FINDLABEL) { /* map the label to a priority */
  36. res = e->priority;
  37. ast_unlock_contexts();
  38. return res; /* the priority we were looking for */
  39. } else { /* spawn */
  40. app = pbx_findapp(e->app);
  41. ast_unlock_contexts();
  42. if (!app) {
  43. ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority);
  44. return -1;
  45. }
  46. if (c->context != context)
  47. ast_copy_string(c->context, context, sizeof(c->context));
  48. if (c->exten != exten)
  49. ast_copy_string(c->exten, exten, sizeof(c->exten));
  50. c->priority = priority;
  51. '''/*passdata应该是在此赋值*/'''
  52. pbx_substitute_variables(passdata, sizeof(passdata), c, e);
  53. if (option_debug) {
  54. ast_log(LOG_DEBUG, "Launching '%s'\n", app->name);
  55. }
  56. if (option_verbose > 2) {
  57. char tmp[80], tmp2[80], tmp3[EXT_DATA_SIZE];
  58. ast_verbose( VERBOSE_PREFIX_3 "Executing [%s@%s:%d] %s(\"%s\", \"%s\") %s\n",
  59. exten, context, priority,
  60. term_color(tmp, app->name, COLOR_BRCYAN, 0, sizeof(tmp)),
  61. term_color(tmp2, c->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)),
  62. term_color(tmp3, passdata, COLOR_BRMAGENTA, 0, sizeof(tmp3)),
  63. "in new stack");
  64. }
  65. /* 管理事件 */
  66. manager_event(EVENT_FLAG_CALL, "Newexten",
  67. "Channel: %s\r\n"
  68. "Context: %s\r\n"
  69. "Extension: %s\r\n"
  70. "Priority: %d\r\n"
  71. "Application: %s\r\n"
  72. "AppData: %s\r\n"
  73. "Uniqueid: %s\r\n",
  74. c->name, c->context, c->exten, c->priority, app->name, passdata, c->uniqueid);
  75. returnpbx_exec(c, app, passdata); /* 0 on success, -1 on failure */
  76. }

Pbx_exec函数体为:

 
 
  1. /*
  2. \note This function is special. It saves the stack so that no matter
  3. how many times it is called, it returns to the same place */
  4. int pbx_exec(struct ast_channel *c, /*!< Channel */
  5. struct ast_app *app, /*!< Application */
  6. void *data) /*!< Data for execution */
  7. {
  8. int res;
  9. const char *saved_c_appl;
  10. const char *saved_c_data;
  11. if (c->cdr && !ast_check_hangup(c))
  12. ast_cdr_setapp(c->cdr, app->name, data);
  13. /* save channel values */
  14. saved_c_appl= c->appl;
  15. saved_c_data= c->data;
  16. c->appl = app->name;
  17. c->data = data;
  18. /* XXX remember what to to when we have linked apps to modules */
  19. if (app->module) {
  20. /* XXX LOCAL_USER_ADD(app->module) */
  21. }
  22. /*应该是在此执行了应用*/
  23. res = app->execute(c, S_OR(data, ""));
  24. if (app->module) {
  25. /* XXX LOCAL_USER_REMOVE(app->module) */
  26. }
  27. /* restore channel values */
  28. c->appl = saved_c_appl;
  29. c->data = saved_c_data;
  30. return res;
  31. }


在等待被叫接通的过程中,完成媒体协商过程,向主叫发送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。

 
 
  1. static int load_module(void)
  2. {
  3. int res;
  4. res = ast_register_application(app, dial_exec, synopsis, descrip);
  5. res |= ast_register_application(rapp, retrydial_exec, rsynopsis, rdescrip);
  6. return res;
  7. }
  8. static int dial_exec(struct ast_channel *chan, void *data)
  9. {
  10. struct ast_flags peerflags;
  11. memset(&peerflags, 0, sizeof(peerflags));
  12. return dial_exec_full(chan, data, &peerflags, NULL);
  13. }
  14. static int dial_exec_full(struct ast_channel *chan, void *data, struct ast_flags *peerflags, int *continue_exec)
  15. {
  16. 。。。
  17. if (!(c =chan->tech->requester(type, capabilities | videoformat, data, cause)))
  18. return NULL;
  19. 。。。
  20. }

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用户。

 
 
  1. static struct ast_channel *sip_request_call(const char *type, int format, void *data, int *cause)
  2. {
  3. /*申请资源*/
  4. if (!(p = sip_alloc(NULL, NULL, 0, SIP_INVITE))) {
  5. ast_log(LOG_ERROR, "Unable to build sip pvt data for '%s' (Out of memory or socket error)\n", (char *)data);
  6. *cause = AST_CAUSE_SWITCH_CONGESTION;
  7. return NULL;
  8. }


然后该呼叫线程会调用wait_for_answer等待被叫接通。
在呼叫接通后,也即wait_for_answer函数返回,在dial_exec_full中调用ast_bridge_call桥接媒体,这样呼叫就正式接通了。
当chan_sip的侦听线程接收到BYE消息,则调用handle_request_bye找到相应的channel,执行hangup释放呼叫。

 
 
  1. if (!ast_strlen_zero(get_header(req, "Also"))) {
  2. ast_log(LOG_NOTICE, "Client '%s' using deprecated BYE/Also transfer method. Ask vendor to support REFER instead\n",
  3. ast_inet_ntoa(p->recv.sin_addr));
  4. if (ast_strlen_zero(p->context))
  5. ast_string_field_set(p, context, default_context);
  6. res = get_also_info(p, req);
  7. if (!res) {
  8. c = p->owner;
  9. if (c) {
  10. bridged_to = ast_bridged_channel(c);
  11. if (bridged_to) {
  12. /* Don't actually hangup here... */
  13. ast_queue_control(c, AST_CONTROL_UNHOLD);
  14. ast_async_goto(bridged_to, p->context, p->refer->refer_to,1);
  15. } else
  16. ast_queue_hangup(p->owner);
  17. }
  18. } else {
  19. ast_log(LOG_WARNING, "Invalid transfer information from '%s'\n", ast_inet_ntoa(p->recv.sin_addr));
  20. if (p->owner)
  21. ast_queue_hangup(p->owner);
  22. }
  23. } else if (p->owner) {
  24. ast_queue_hangup(p->owner);
  25. ...
  26. }




  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值