Asterisk[1]是一款GPLv2协议下的开源电话应用平台。简单来说,Asterisk是一个server应用。可以完毕发起电话呼叫、接受电话呼叫、对电话呼叫进行定制处理。
1.2.1 通道驱动
asterisk的通道驱动接口是最复杂也是最重要的可用接口。asteisk的通道API提供了对各种通信协议的抽象,使得asterisk的各种功能特性不必关心详细的通信协议。该组件主要是负责在asterisk通道抽象和详细的通信协议实现中的通信。
asterisk通道驱动接口的定义是ast_channel_tech接口。这个接口中定义了一些通道驱动必需要实现的方法。
通道驱动首先要实现的方法是ast_channel工厂方法,即ast_channel_tech中的requester。当一个asterisk通道创建后。不管该通道是incoming方向的还是outgoing方向的,与该通道相关联的ast_channel_tech实现负责实例化和初始化该路通话相应的ast_channel。
ast_channel创建完毕后,该结构中有一个创建该通道的ast_channel_tech指针。
当然有非常多其它的操作须要依照详细技术相关的方式来处理。图1.2中展示了asterisk中的两个通道,图1.4进行了扩展,展示了两个桥接的通道,以及通道技术怎样实现的。
在ast_channel_tech中最重要的方法包含:
· requester:用于向通道驱动请求并实例化一个ast_channel对象。依据通道类型进行适当的初始化工作。
· call: 用户向ast_channel表示的终端发起一个出局呼叫。
· answer: 当asterisk决定应该对ast_channel关联的入局呼叫进行应答时调用。
· hangup: 当系统决定当前的呼叫应该挂断时调用。通道驱动须要与终端依照一定的协议进行通信。
· indicate: 通话開始后,还会产生一些其它的事件。须要将这些事件通知给终端。
比如。假设设备被保持住了,这个函数就会被调用。
· send_digit_begin: 当终端设备開始向asterisk发送按键DTMF的时候,调用该函数。
· send_digit_end: 当终端设备向asterisk发送按键DTMF结束的时候,调用该函数。
· read: 当asterisk核心须要从终端读入一个ast_frame数据帧的时候调用read函数。
ast_frame帧是asterisk中用来封装媒体(诸如音频或者视频)和信号的抽象结构。
· write: 使用该函数向终端设备发送一个ast_frame帧。
通常是由通道驱动来完毕数据的处理(採集等)和打包使得数据包可以适合所採用的通信协议。然后将打包后的数据发送到终端。
· bridge:该通道类型中的本地桥接函数。前面提到了,进行本地桥接是通道驱动为同样类型的两个通道提供了一种更高效的桥接方法,而不是将全部的信令流和媒体流都通过额外的抽象层来完毕。这对于提供性能极其重要。
通话结束后,asterisk核心中的抽象通道处理代码会调用ast_channel_tech中的hangup函数,然后销毁ast_channel对象。
1.1.2 通道桥接
一个更为熟悉一点的呼叫场景是两个电话间的连接。在这个场景里,有两个电话终端与Asterisk系统连接,所以这个通话里存在两个通道。
图1.2 两个呼叫leg,代表了两个通道
展示了asterisk中的两个通道,图1.4进行了扩展,展示了两个桥接的通道,以及通道技术怎样实现的。
图1.4
通道技术和通道抽象层
在我们进入样例之前,让我们一起看看asterisk拨号方案中处理呼叫1234这个号码的语法。
注意,1234这个号码是随便选的。
呼叫该号码后,调用了3个拨号方案应用,
首先接听该通话。然后播放一个声音文件,最后挂断该通话。
; Define the rules forwhat happens when someone dials 1234. ;
exten => 1234,1,Answer()
same=> n,Playback(demo-congrats)
same => n,Hangup() .csharpcode, .csharpcode
pre { font-size: small; color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin:0em; } .csharpcode .rem { color: #008000;
} .csharpcode .kwrd { color: #0000ff;} .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color:#ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr {
color:#ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin:0em; } .csharpcode .lnum { color: #606060; }
extenkeyword是用来定义extension的。
在exten行的右側,1234是为呼叫1234这个号码定义的呼叫规则。
以下的1是拨打1234这个号码时运行的第一步操作,Answer是告诉系统接听这个呼叫。
以下的两行,是用same这个keyword开头的,是定义的上面的extension接下来的规则,这里就是1234接下来的规则。
n是下一步要运行的操作,后面的项指明了拨号方案要运行的详细动作。
1.1.1 通道
1.1.2 通道桥接
一个更为熟悉一点的呼叫场景是两个电话间的连接。在这个场景里,有两个电话终端与Asterisk系统连接,所以这个通话里存在两个通道。
图1.2 两个呼叫leg,代表了两个通道
当asterisk的通道像上面这样连接在一起的。就称之为一个通道桥接。运行通道桥接后将两个通道桥接在一起,其目的是在这两个通道间能够传递媒体信息
全部的媒体流都是通过asterisk来协商的。asterisk能够在不同的技术之间进行录音、音频操作、和转码。
两个通道桥接在一起,能够通过例如以下两个方法来完毕:通用桥接和本地桥接。
通用桥接是无论使用什么样的通道技术均能正常工作,这样的桥接是通过asterisk的抽象通道接口来传递全部的音频和信令数据。
这样的桥接方法是最复杂的,也是最有效的。
本地桥接是和通话所使用的技术相关的一种桥接。假设两个通道使用同样的媒体传输技术,能够使用一种更高效的方式而不用通过像不同技术那种方式要通过asterisk的抽象层来完毕
决定使用通用桥接还是本地桥接是在桥接的时候通过两个通道的比較完毕的。
假设两个通道均支持本地桥接,则採用本地桥接。反之,则使用通用桥接。
为了推断两个通道是否支持同样的本地桥接方法,能够简单的通过c函数指针的比較。
这样的比較方法。并非最优雅的方法。可是我们还没有遇到不能满足我们须要的情况。关于通道的本地桥接将会在1.2节讨论。
图1.3说明的是一个本地桥接的样例。
图3 本地桥接
1.1.3 帧
在asterisk代码里一个通话的通信是通过使用帧来完毕的。帧是数据结构ast_frame的一个实例。
Asterisk中支持的帧类型列表是静态定义的,每种类型的帧是通过数字编码的类型(type)和子类型(subtype)标识的。完整的帧类型列表中include/asterisk/frame.h文件里。一些样例例如以下:
· VOICE: 这些帧携带部分语音流
· VIDEO: 这些帧携带部分视频流
· MODEM: 这样的帧里面的数据的编码,诸如T.38是通过IP网络来发送传真的。
这样的帧类型主要是用来处理传真。一定要注意的一点是这样的数据帧,一定要连续不能中断。以保证对端能对数据进行正确的解码。这与AUDIO帧不同,音频帧能够通过不同的音频编码进行转码尽管牺牲了音频质量但节省了网络带宽。
· CONTROL: 这样的帧中包含的是通话的信令消息。
这些帧通经常使用来说明通话信令时间,包含电话接通。挂断,保持等。
· DTMF_BEGIN: 数字開始处。这样的帧通常是通话者在电话机上開始按一个DTMF按键。
(双音多频:dual-tonemultifrequency双音多频 DTMF)
· DTMF_END: 数字结束处。这样的帧是通话者在结束电话机上的DTMF按键。
1.2 Asterisk组件抽象
Asterisk是一个高度模块化的应用程序。包含一个核心的应用,能够通过Asterisk代码树的main文件夹编译构建。可是,光这个核心通常没有什么用。
核心应用主要处理模块注冊,也有代码包含怎样连接抽象接口来完毕电话通话。
详细的实现接口是通过能够在执行时刻载入到系统中的模块完毕的。
默认情况下,全部的模块均放在asterisk提前定义好的模块文件文件夹中,该文件夹下的全部模块会由主应用启动后进行载入。
之所以这样设计。就是为了保持更加简单。在asterisk中另一个配置文件,在这个配置文件里能够定义载入的模块和载入模块的顺序。这样会显得配置起来有点麻烦,只是能够让用户指定哪些模块中不须要时。能够不载入。
这样最大的优点就是降低应用的内存占用。当然有时候也会有助于提高系统安全。最好的做法是假设不是很须要,不要载入那些能够接受网络连接的模块。
当模块载入完毕后,会向asterisk主应用注冊本模块所实现的组件抽象接口。模块能够实现并向asterisk核心注冊的接口有多种类型。一般而言,相关联的功能会放在一个模块中。
1.2.1 通道驱动
asterisk的通道驱动接口是最复杂也是最重要的可用接口。asteisk的通道API提供了对各种通信协议的抽象,使得asterisk的各种功能特性不必关心详细的通信协议。该组件主要是负责在asterisk通道抽象和详细的通信协议实现中的通信。
asterisk通道驱动接口的定义是ast_channel_tech接口。这个接口中定义了一些通道驱动必需要实现的方法。
通道驱动首先要实现的方法是ast_channel工厂方法,即ast_channel_tech中的requester。当一个asterisk通道创建后,不管该通道是incoming方向的还是outgoing方向的,与该通道相关联的ast_channel_tech实现负责实例化和初始化该路通话相应的ast_channel。
ast_channel创建完毕后,该结构中有一个创建该通道的ast_channel_tech指针。当然有非常多其它的操作须要依照详细技术相关的方式来处理。图1.2中展示了asterisk中的两个通道,图1.4进行了扩展。展示了两个桥接的通道。以及通道技术怎样实现的。
图1.4
通道技术和通道抽象层
在ast_channel_tech中最重要的方法包含:
· requester:用于向通道驱动请求并实例化一个ast_channel对象。依据通道类型进行适当的初始化工作。
· call: 用户向ast_channel表示的终端发起一个出局呼叫。
· answer: 当asterisk决定应该对ast_channel关联的入局呼叫进行应答时调用。
· hangup: 当系统决定当前的呼叫应该挂断时调用。通道驱动须要与终端依照一定的协议进行通信。
· indicate: 通话開始后,还会产生一些其它的事件。须要将这些事件通知给终端。
比如,假设设备被保持住了,这个函数就会被调用。
· send_digit_begin: 当终端设备開始向asterisk发送按键DTMF的时候。调用该函数。
· send_digit_end: 当终端设备向asterisk发送按键DTMF结束的时候,调用该函数。
· read: 当asterisk核心须要从终端读入一个ast_frame数据帧的时候调用read函数。
ast_frame帧是asterisk中用来封装媒体(诸如音频或者视频)和信号的抽象结构。
· write: 使用该函数向终端设备发送一个ast_frame帧。通常是由通道驱动来完毕数据的处理(採集等)和打包使得数据包可以适合所採用的通信协议。然后将打包后的数据发送到终端。
· bridge:该通道类型中的本地桥接函数。前面提到了。进行本地桥接是通道驱动为同样类型的两个通道提供了一种更高效的桥接方法。而不是将全部的信令流和媒体流都通过额外的抽象层来完毕。这对于提供性能极其重要。
通话结束后,asterisk核心中的抽象通道处理代码会调用ast_channel_tech中的hangup函数,然后销毁ast_channel对象。
1.2.2 拨号应用
asterisk管理员通过/etc/asterisk/extensions.conf中的拨号规划来设置呼叫路由。
拨号方案是一系列的呼叫路由规则(称为extension)构成。
电话呼叫进到系统中后,系统使用被叫号码在该呼叫应该使用的拨号方案中查找相应的extension。
extension包含一系列能够在该通道上运行的拨号方案应用。拨号方案中使用的应用是由asterisk中的应用注冊机制来维护的。
应用的注冊是在相应的模块载入的时候就完毕。
通过asterisk的拨号方案,多个应用能够一起使用来定制呼叫的处理过程。
对于使用提供的拨号方案无法完毕的复杂定制。\
1.2.4 编码转换
在VOIP世界中,我们使用多种不同的编码来对媒体进行编码,并将编码后的数据发送到网络上。眼下存在有非常多的编码供选择,但在媒体质量、CPU消耗、带宽需求上会有所牺牲。
Asterisk支持多种不同的压缩编码,而且在必要的时候能够进行编码格式间的转换。
在呼叫建立时,Asterisk会尝试让两个终端使用同样的媒体编码格式。这样就能够不用进行转码。
可是,这仅仅是理想情况。即便是有共同的编码,转码也仍然须要。比如,假设通过配置让asterisk对经过系统的音频数据进行信号处理诸如添加或者减少音量。Asterisk也能够通过配置进行通话录音,假设配置的录音文件的格式与通话的编码格式不一致,仍然须要编码。
说明:编码协商
协商媒体流使用何种编码的方法对于连接到asterisk所使用的各种技术是确定的。在有些时候。比如通过传统电话网络的呼叫,就不须要进行不论什么协商。可是。在另外一些情况下。特别是使用IP协议,须要使用一种协商机制,通过描写叙述的终端的能力和优先选择的编码,协商出共同的编码。
以SIP协议为例,这里说明一下呼叫到asterisk后怎样进行编码协商的。
说明:编码协商
协商媒体流使用何种编码的方法对于连接到asterisk所使用的各种技术是确定的。在有些时候,比如通过传统电话网络的呼叫,就不须要进行不论什么协商。可是,在另外一些情况下,特别是使用IP协议,须要使用一种协商机制,通过描写叙述的终端的能力和优先选择的编码,协商出共同的编码。
以SIP协议为例,这里说明一下呼叫到asterisk后怎样进行编码协商的。
1.终端向asterisk发起呼叫请求,在该请求中包括了终端希望採用的编码格式。
2.Asterisk通过查询管理员配置好的语音编码优先顺序。选择一种最优先的编码。该编码既是asterisk优先顺序表的编码。同一时候终端也能够支持。
Asterisk编码处理不太好的地方是对于较为复杂的编码,特别是视频编码。编码协商的需求在过去的10年内已经非常复杂。为了更好的处理新的音频编码和能更好的支持视频编码,我们还有非常多工作须要做。这是asterisk下一发行版本号中优先考虑的新需求。
编码转换模块提供了一个或者多个ast_translator接口的实现。编码转换器具有源和目的格式属性,提供了一个回调函数能够完毕一段媒体信息从源格式到目的格式的转换。
编码转换器本身对通话本身并不知道,所需知道的不过怎样完毕媒体从一个格式到还有一个格式的转换。
关于转换器API的很多其它信息,能够參考include/asterisk/translate.h和main/translate.c。转换器抽象的实现能够在codecs 文件夹中找到。