通话离不开我们的生活,但通话究竟是如何建立的是我们很少去关注的。比如说,我们拨打中国移动的电话10086,一开始听到的是智能客服,我们根据提示音进行如何,选择自己想要的功能,如果我们想要联系人工客服则会输入0进行等待转接,即使接通之后,这个人工还可能会帮我们转接到另一个人工客服,这一系列的操作对拨号的我们来说都只是按键的操作,但想要知道其内部原理,是一个十分复杂的过程。我们以现在使用最多的通话系统Asterisk为例,具体讲述一下通话是如何建立与进行的。
在Asterisk中,通道表示Asterisk系统与某电话端点的一条连接(如图1.1)。
一路电话呼入到Asterisk系统,就用通道表示这一连接。
下图中的呼叫场景表示电话A拨打并进入语音信箱。
在 Asterisk中,通道表示Asterisk和设备之间的媒体路径,桥接则表示媒体之间如何共享。
当两个话机在同一Asterisk系统上通过SIP协议相互呼叫时,实际上发生了两段呼叫:一段是从起始分机到Asterisk,另一段是从Asterisk到目的分机,在此场景下,连接到Asterisk系统的有两个电话终端,因而分配了两个通道(如图1.2)。Asterisk将这两个通道桥接在一起,他们之间的媒体将会根据桥接类型进行交换。
在呼叫过程中,Asterisk代码内部的通信使用帧(数据结构ast_frame的实例)来实现。帧可以是媒体帧,也可以是信号帧。在一个基本的呼叫过程中,媒体帧的流包含音频或视频等数据。信号帧则用于发送呼叫信号事件相关的消息,如按下数字键,通话保持,挂断电话等。
dialplan是Asterisk的核心。所有进入系统的通道都要经过dialplan,dialplan定义了呼叫如何进入和离开asterisk系统。
通道驱动接口是Asterisk提供的最复杂且最重要的接口。Asterisk通道API提供了电话协议抽象层,使得其他所有Asterisk特性能够独立于所使用的电话协议而工作。此组件负责在Asterisk通道抽象层与其实现的电话技术细节层面之间的转换。如果没有通道驱动,asterisk根本无法拨打或接听呼叫。每个通道驱动都对应于所支持的协议或通道类型(SIP、ISDN等)。通道驱动可以看作是通往asterisk核心的网关, 负责和Asterisk以外的设备进行通信,转换特别的信令或协议到core模块。
常用的通道驱动有:
chan_dahdi:提供与使用DAHDI通道驱动的PSTN卡的连接;
chan_multicast_rtp:提供到多播实时传输协议(RTP)流的连接;
chan_pjsip:SIP通道驱动,它依赖于res_pjsip及其许多相关模块,并通过SIP协议和外部设备进行通信。 它会转换SIP信令到Asterisk core中。
Asterisk通道驱动接口定义称为ast_channel_tech接口。它定义了通道驱动必须实现的一组方法。通道驱动须实现的第一个方法是ast_channel工厂方法,也是ast_channel_tech中的requester方法。当Asterisk为一个呼入或呼出的电话呼叫建立通道后,该通道类型对应的ast_channel_tech的实现方法负责对ast_channel进行实例化和初始化。
下图表示两个桥接通道及各自对应的通道驱动。
ast_channel_tech中常用的方法有:
requester:回调函数,用于请求某个通道驱动实例化一个ast_channel对象,并针对其类型进行初始化。
call:回调函数,用于从端点(由ast_channel表示)发起一个呼叫。
answer:Asterisk决定对呼入的呼叫进行应答时调用。
hangup:系统挂断通话时调用。
indicate:呼叫接通后,有可能产生许多其它的事件,需要给端点发信号。例如,如果电话被保持,则调用该回调进行事件通知。
read:此函数由Asterisk内核调用,用于从端点读取帧数据(ast_frame)。
write:此函数用于向设备发送帧数据(ast_frame)。例如通道驱动取得数据后,将按照RTP协议做适当的打包,并传送至端点。
在Asterisk中,通道是介于终端和Asterisk本身的一个通信媒介。它将相关信息传递到终端,或者从终端传递到Asterisk服务器。这些信息中包含了信令(设备状态或挂机命令)和媒体(从终端发送或者接收的语音和视频)。
常见的通道创建场景:
设备通过网络(如SIP协议)主动呼入Asterisk系统。
用户执行一个命令(例如Originate) 来创建一个通道。
当前通道执行 dialplan ,通过拨号规则呼叫一个应用程序(例如Dial) 创建一个新通道。
Asterisk 收到 API 呼叫,此呼叫创建一个或者多个通道。
一旦通道建立以后,设备和Asterisk就可以开始通信。通信数据流程依赖于通道类型本身。 一个或多个通道可以通过各种桥接和其他的通道进行通信。Asterisk和通道通信的能力完全依赖于通道驱动所采用的技术或协议。
通道状态是反映当前介于Asterisk和终端设备之间的通信介质的状态。通道的状态会影响所支持的操作,什么样的通道状态支持什么样的通道操作,同时也会最终影响终端设备的操作。
通道中可以支持多种状态,最普遍的几种状态是:
Down:通信路径是存在的,在Asterisk和终端之间没有任何媒体。
Ringing:设备正在振铃,在Asterisk和设备之间可能存在媒体。
Up:设备之间已经互相应答。当处于UP状态时,Asterisk和设备之间的媒体可以双向流动。
Inbound channels:由Asterisk外部的呼入到Asterisk产生的通道。
Outbound channels:由 Asterisk 呼出到外部产生的通道。
场景1:一个电话呼叫另外一个电话
电话A创建了 inbound 通道呼叫Asterisk。Asterisk 然后外呼到电话B,创建一个outbound 通道和电话B通信。 两者之间的呼叫建立以后,两个通道被移到到一个桥接(Bridge)中。
场景2:管理员给话机播放提示音
一个用户从AMI 使用命令 originate 发起呼叫。 Asterisk 创建了一个outbound 通道来呼叫A 的SIP终端。当应答以后,Asterisk 把这个通道又看作是一个inbound 通道,并且将通道连接到相应的拨号规则的应用程序(Playback)中。
在这个例子中,用户使用Local通道进行呼叫,Local通道有两个local channel legs,一个leg 当作是outbound ,另外一个当作是inbound。这种情况下的呼叫都是在Asterisk内部进行,一个通道执行拨号规则,另外一个则没有执行拨号规则。执行拨号规则的leg 看作是一个inbound。
场景3:调用API发起呼叫,主叫是电话A,被叫是电话B
API执行一个originate拨打电话A,同时包含一个context指定拨打电话B。Asterisk创建了一个outbound local channel 进入到Asterisk,并桥接此通道到inbound channel,从而连接电话A。Asterisk创建另外一个 local channel 作为"inbound"进入到Asterisk执行拨号规则呼叫指定的电话B。
大部分的通道是介于外部终端和Asterisk本身进行通信,如我们通过SIP协议发起的通话。Asterisk也可以创建内部的通道。这些通道称之为 Local 通道——它们用来帮助处理Asterisk中各种资源之间的媒体转移。
Local channel 是一种非常特别的通道,它们总是以一对通道的方式出现。如果系统创建一个Local "通道"的话,实际上会创建出两个通道。在local通道之间会出现一个virtual endpoint,它负责来发送local通道之间的媒体。Local通道的双方会各自向对端发送媒体。
默认情况下, Local通道在通话建立后将退出自己的所有呼叫路径。也就是说一旦Local 通道帮助Asterisk和目的地之间创建了呼叫,Local 通道将退出呼叫路径,Asterisk将直接和终端进行通信。
下面简单描述这一过程:
(1)通过API发起呼叫,主叫是电话A,被叫是电话B,电话B正在振铃。
此图为:未应答呼叫状态
(2)电话B应答了呼叫以后,Local 通道被优化,退出呼叫路径。
此图为:被呼叫方应答了呼叫以后,Local 通道退出呼叫路径
我们可以使用Local 通道的拨号字符 /n来关闭Local 通道退出,通过在拨号字符串尾加/n,我们会保持Local 通道在整个呼叫流程中。
在Asterisk中,通道伪装是一个基本而又令人费解的概念。它的存在是一个线程“夺取”另一个线程的通道的控制的一种方式。
伪装的工作方式是取两个通道并“交换”其中的一部分。在上面的图中,假设线程A有一个线程B想要接管的通道。线程B创建一个新的通道(“Orignal”),并在线程A拥有的通道(“Clone”)上启动一个Masquerade操作。两个通道都被锁定,Clone通道的状态被移到Original通道中,而Clone通道获得Original通道的状态。为了表示Clone通道即将死亡,将在通道上放置一个特殊的僵尸标志,并将名称重命名为Clone。接下来释放锁,Original通道(它现在有与Clone通道相关的状态)在线程B中执行,而Clone通道则被Hangup并执行相关销毁操作。
通道有入站通道和出站通道之分。当有呼叫接入Asterisk系统时,就创建一个入站通道,执行拨号计划。Asterisk为每个入站通道都创建一个线程来执行拨号计划。这类线程即被称为通道线程。拨号计划应用程序一定是在通道线程的环境中执行的。拨号计划函数亦是如此。通道线程是ast_channel对象的拥有者,控制着该对象的生存周期。
不是所有的通道在Asterisk中都有一个通道线程(pbx_thread)。通常,入站通道有一个线程;出站通道则没有。因此,在两个通道之间创建的两方桥接使用入站通道的pbx_thread为两个通道之间的帧提供服务。
但我们常常需要远远超过两方的桥接。例如,假设我们希望盲传出站通道到一个新的拨号脚本。此时出站通道没有pbx_thread——那么我们该将其放到那里呢? 简单!我们用伪装(Masquerade)。Masquerade创建一个新通道(带有一个pbx_thread),并将出站通道的核心与新通道交换。新的通道开始执行,而旧的出站通道终止。
很多功能(如转接,停泊等)都开始使用伪装来绕过没有通道线程这一事实。
通常来说,当Asterisk知道两个或者多个通道需要通信时,asterisk会创建一个bridge。创建桥接的方式有很多种,常见的有:
Dial:当外呼通道应答时需要创建一个bridge。呼入和呼出的通道会被push到该桥接中。
Bridge:直接创建一个新的 bridge,并且把两个已存在的通道push到这个桥接中。
BridgeWait:创建一个特别的保持bridge(hoding bridge),把通道放置进去。
MeetMe/ConfBridge:这两个应用程序被应用在会议中,可以支持同时桥接多个通道。
Page:一个会议的组播 (类似于 MeetMe/ConfBridge),用来实现会议广播者对其他已拨通道发送语音。
Parking:一个用来实现呼叫停泊的保持桥接(holding bridge)。
桥接模块负责通道间的桥接,常用的桥接模块:
bridge_builtin_features:利用内置的用户功能(例如features.conf中的功能)执行桥接;
bridge_multiplexed:执行大型会议室(多个参与者)所需的复杂多路复用。当前仅由app_confbridge使用;
bridge_simple:执行通道之间的桥接;
bridge_softmix:执行大型会议室中所需的简单多路复用(多个参与者)。
在asterisk中有很多不同类型的bridges, 每一种方式都决定媒体在桥接的参与方之间如何混合。 通常情况下,Asterisk一般使用两种类别的桥接类型: 两方和多方桥接。两方桥接的方式包括 core bridges,local native bridges,和remote native bridges。 多方桥接包括 mixing 和 holding。
在asterisk中,桥接相对比较智能。它会基于这个通道自动选择在这个桥接环境中当前系统中最佳的 mixing 通信接口,并且,如果有必要,它会根据环境的变化动态选择mixing type of the bridge。例如,如果用户通过 atxferthreeway DTMF 选项发起咨询转功能,一个两方的 core bridge 可能自动转换成三方的bridge。
两方的 bridge 桥接在两个通道中共享媒体。因为在这个bridge中只有两个参与者,另外还可以根据通道的类型进行某些优化。例如,两方bridges 中有sub-types,Asterisk 会使用它们来提高性能。
core bridge 是一个非常基础的两方的 bridge。任何通道类型都可以使用这类桥接和其他通道进行通信。该桥接类型下Asterisk可以直接访问通道之间的媒体,并可以执行对媒体编码转换,媒体参数修改变换,呼叫录音等操作。
尽管这是一种最灵活的桥接方式,却是最低效的。
如果连接到Asterisk的两个通道使用相同的媒体传输技术,则势必有一种比通过抽象层更为高效的连接方式,因为抽象层是为使用不同技术的通道之间连接而准备的。例如,对于某些VoIP协议而言,可能通过端点向对方直接发送媒体流,这时只有呼叫信令是不断流过Asterisk服务器的。
当两个参与者在使用同样的通信接口时(例如,它们都使用SIP时),则会启用native bridge。 当发生native bridge时,Asterisk 只是简单检测桥接的通道状态(也可能因为其他原因例如挂机,超时或者其他的条件设置)。 因为媒体是在通道的驱动或者协议栈处理,没有编码转换,媒体属性变换,或者其他依赖asterisk本身处理的功能。native bridging 的一个好处是提高了Asterisk性能。
Local
当两个通道之间的媒体通过自己的通道驱动或者协议栈来处理时,asterisk 就会启用 local native bridge,但是媒体仍然从各自设备发送到Asterisk。在这种情况下,Asterisk作为一个媒体代理将媒体流发送到各自的设备。Asterisk中大部分的 native bridging 类型是local 类型。
上图表示:Local native bridge
Remote
当两个通道绕过asterisk直接进行媒体发送时,asterisk会使用 remote native bridge 。在这种场景下,媒体交换完全是在Asterisk之外的,也就是我们通常所说的 “direct media”。因为媒体交换不是在Asterisk内部进行,所以这种 bridge 状态下的asterisk的性能是最好的。但是这种情况只能局限于某种环境下(仅SIP通道支持这种类型):
上图表示:Remote native bridge
多方的 bridges 和一个或者多个通道进行交互,并且可以在多方桥接的通道之间路由媒体。
有以下多种方式可以创建多方bridges:
MeetMe:这是一个旧版本的会议桥应用模块,依赖于DAHDI,仅限于窄带的语音。
MeetMe要求DAHDI支持语音混合处理,采样率仅能支持8kHz。
ConfBridge:这是一个基于会议桥的应用,支持宽带语音混音。
Ad-hoc Multiparty Bridges:支持通过DTMF功能创建多方会议桥,如3-way咨询转。
在混音桥的模式中,Asterisk会在所有通道中共享媒体。当桥接中出现了两个以上的通道时,Asterisk会对通道中的参与者通过signed linear方式编码转换,把对方媒体进行混音编码,混音后的新媒体重新打包成一个新的帧数据发送回所有的参与方。
可能用户没有完全准备好执行会议功能,它为这些通道提供一个等待区域。保持会议的功能可以对等待的通道执行语音等待,振铃,会议保持等等功能。Holding bridges 可以通过BridgeWait来访问。Asterisk会对通道的媒体混音,混音的方式取决于桥接中的角色类型。