Asterisk 1是基于GPLv2協(xié)議發(fā)布的一款開源電話應(yīng)用平臺。簡單地說,這是一個服務(wù)端程序,用于處理電話的撥出、接入以及自定義流程。
此項目由Mark Spencer于1999年創(chuàng)始。當(dāng)時Mark有一個自己的公司,叫做Linux支持服務(wù)公司,他需要一個電話系統(tǒng)來幫助自己操作業(yè)務(wù)。但他沒有那么多錢去買這樣一個系統(tǒng),因此他決定自己做。隨著Asterisk知名度的提升,Linux支持服務(wù)公司的業(yè)務(wù)重點也轉(zhuǎn)向Asterisk,公司也更名為Digium。
Asterisk得名于Unix通配符:*,該項目的宗旨是能做所有與電話相關(guān)的事情。通過對自己宗旨的不懈追求,如今的Asterisk已經(jīng)支持一系列用于接撥電話的技術(shù)。這些技術(shù)包括諸多VoIP(Voice over IP,語音IP)協(xié)議,與傳統(tǒng)電話網(wǎng)絡(luò)的模/數(shù)連接性,以及PSTN(Public Swithed Telephone Network,公共交換電話網(wǎng)絡(luò))。對多種不同類型電話的撥出與接入能力是Asterisk的拿手好戲之一。
當(dāng)Asterisk系統(tǒng)有電話接入或撥出時,系統(tǒng)有很多附加特性可用于電話的自定義處理。有些特性是較大型的預(yù)置常用應(yīng)用,如語音郵件(voicemail);另外還有一些小特性,可配合使用,用于創(chuàng)建自定義應(yīng)用,如回放音頻文件、讀數(shù)字按鍵、語音識別等。
本節(jié)討論一些跟Asterisk每一部分息息相關(guān)的架構(gòu)概念。這些思想是Asterisk架構(gòu)的基礎(chǔ)。
1.1.1 通道
在Asterisk中,通道表示Asterisk系統(tǒng)與某電話端點的一條連接(如圖1)。一個最常見的例子是,一路電話呼叫接入了Asterisk系統(tǒng),就用通道表示這一連接。在Asterisk代碼中,通道是數(shù)據(jù)結(jié)構(gòu)ast_channel的實例。圖中這個呼叫場景可以視為呼叫方與某一系統(tǒng)應(yīng)用(比如語音郵件)的交互。
圖1.2 兩個通道表示兩條呼叫線路
如上圖連接的Asterisk通道稱之為通道橋接。為了達(dá)到在通道間傳輸媒體的目的而把通道連接起來,這樣的行為即稱為通道橋接。然而,在電話呼叫過程中也可能有視頻或文本的數(shù)據(jù)流。即使有多于一種類型的媒體流,也是由Asterisk系統(tǒng)中負(fù)責(zé)呼叫連接兩端的通道獨立處理。在圖1.2中,兩個通道分別對應(yīng)電話A和電話B,橋接的作用是將媒體從電話A傳輸?shù)诫娫払,同理也可從電話A傳輸?shù)诫娫払。所有的媒體流都是通過Asterisk系統(tǒng)傳輸?shù)?。Asterisk不允許傳輸無法識別或不能完全控制的媒體流。這意味著Asterisk可以做如下事情:記錄媒體、處理音頻、在不同技術(shù)間進(jìn)行轉(zhuǎn)換等。
有兩種方法可以完成兩個通道的橋接:通用橋接和專用橋接。通用橋接時,無論通道使用的什么技術(shù)都能夠工作。它通過Asterisk的抽象通道接口傳輸所有的音頻和信號。盡管這是一種最靈活的橋接方式,卻是最低效的,因為完成橋接必須有多層抽象。圖1.2描述的就是通用橋接。
專用橋接是面向特定技術(shù)的通道連接方式。如果連接到Asterisk的兩個通道使用相同的媒體傳輸技術(shù),則勢必有一種比通過抽象層更為高效的連接方式,因為抽象層是為使用不同技術(shù)的通道之間連接而準(zhǔn)備的。例如,如果有這樣一種專業(yè)化硬件用于連接電話網(wǎng)絡(luò),那么在硬件上橋接通道就成為了可能,媒體流根本無需通過應(yīng)用程序。對于某些VoIP協(xié)議而言,可能通過端點向?qū)Ψ街苯影l(fā)送媒體流,這時只有呼叫信號的信息是不斷流過服務(wù)器的。
在橋接兩個通道的時候,系統(tǒng)通過比較兩通道來決定使用通用橋接還是專用橋接。如果兩通道都標(biāo)識出支持同一種專用橋接方式,那么就是用專用橋接;反之使用通用方式。判決兩通道是否支持同一種專用橋接方式,通過簡單的比較C函數(shù)指針即可做到。此法固然絕非上策,但我們還沒有遇到不適用此法的情況。1.2部分還要討論更多有關(guān)專用橋接函數(shù)的細(xì)節(jié)。圖1.3描述的是專用橋接的一個實例。
圖1.4 通道技術(shù)層和抽象通道層
ast_channel_tech中最重要的方法有:
呼叫結(jié)束后,Asterisk內(nèi)核中負(fù)責(zé)抽象通道處理的代碼調(diào)用ast_channel_tech的hangup回調(diào)函數(shù),銷毀ast_channel對象。
1.2.2 撥號計劃應(yīng)用
Asterisk管理員使用Asterisk撥號計劃(存于/etc/asterisk/extensions.conf文件)來設(shè)置呼叫路由表。撥號計劃是由一系列被稱為擴展規(guī)則的呼叫規(guī)則組成的。當(dāng)有一個電話呼叫接入,系統(tǒng)用被叫號碼在撥號計劃中查找擴展規(guī)則,用以處理本次呼叫。擴展規(guī)則包括一組撥號計劃應(yīng)用程序,由通道執(zhí)行。撥號計劃中可用于執(zhí)行的應(yīng)用由一個應(yīng)用注冊表維護(hù)。模塊被加載時,在運行期間填充注冊表。
Asterisk內(nèi)置近200個應(yīng)用。應(yīng)用定義非常松散,并可任意使用系統(tǒng)內(nèi)部API與通道交互。有些應(yīng)用程序執(zhí)行單個任務(wù),如回放(用于向呼叫方回放一個音頻文件);還有一些應(yīng)用程序則復(fù)雜得多,要執(zhí)行大量操作,如語音郵件。
你可以集成諸多使用Asterisk撥號計劃的應(yīng)用,用于自定義呼叫處理。如果你需要對內(nèi)置撥號計劃語言的能力做些自定義擴展,系統(tǒng)也有腳本接口,允許使用任意編程語言做自定義呼叫處理。即使通過另一編程語言使用這些腳本接口,也需要調(diào)用撥號計劃應(yīng)用來實現(xiàn)與通道交互。
舉例說明之前,我們先看一個Asterisk撥號計劃的語法,此撥號計劃用于處理對號碼1234的呼叫。注意,這里1234這個號碼系信手拈來。共有3個撥號程序被調(diào)用:首先,應(yīng)答呼叫;其次,回放音頻文件;最后,掛斷呼叫。
; Define the rules for what happens when someone dials 1234.
;
exten => 1234,1,Answer()
same => n,Playback(demo-congrats)
same => n,Hangup()
關(guān)鍵字exten用于定義擴展。在exten一行的右側(cè),1234的意思是我們?yōu)楹艚?234定義了一組處理規(guī)則;緊接著,1的意思是此號碼被撥叫后的第一個處理步驟;最后,Answer指示系統(tǒng)應(yīng)答此呼叫。下面兩行都以關(guān)鍵字same起始,是為最后一個擴展(此例指1234)指定的規(guī)則。n是下一步(next)的簡寫;該行的最后一項指定了采取的動作。
下面是一個Asterisk撥號計劃的應(yīng)用實例。此例做的事情是應(yīng)答接入的一個呼叫。系統(tǒng)向呼叫方播放蜂鳴音,然后從呼叫方讀入最多4個數(shù)字,存入變量DIGITS,接著讀入的數(shù)字重復(fù)播放給呼叫方,最后結(jié)束呼叫。
exten => 5678,1,Answer()
same => n,Read(DIGITS,beep,4)
same => n,SayDigits(${DIGITS})
same => n,Hangup()
如前所述,應(yīng)用定義得非常松散--注冊的回調(diào)函數(shù)原型非常簡單:
int (*execute)(struct ast_channel *chan, const char *args);
然而,應(yīng)用的實現(xiàn)卻要使用/asterisk/目錄下幾乎所有的API。
1.2.3 撥號計劃函數(shù)
大多數(shù)撥號計劃應(yīng)用帶有字符串參數(shù)。其中有些值是硬編碼,但在某些地方的行為需要有更多的動態(tài)處理,這時應(yīng)使用變量。下面這個例子是一個撥號計劃的代碼片段,其作用是設(shè)置一個變量,并使用Verbose應(yīng)用在Asterisk命令行界面上打印這個變量值。
exten => 1234,1,Set(MY_VARIABLE=foo)
same => n,Verbose(MY_VARIABLE is ${MY_VARIABLE})
調(diào)用撥號計劃函數(shù)的語法與上例相同。Asterisk模塊可注冊撥號計劃函數(shù),取得一些信息并返回給撥號計劃;反之,函數(shù)也可以從撥號計劃中取數(shù)據(jù)并有所動作。一個通用規(guī)則是:撥號計劃可設(shè)置或獲取通道的元數(shù)據(jù),但不發(fā)任何信號,也不做任何媒體處理,這些工作留給撥號計劃應(yīng)用來做。
下面這個例子展示了撥號計劃函數(shù)的用法。此函數(shù)首先向Asterisk命令行界面打印當(dāng)前通道的CallerID,然后調(diào)用Set應(yīng)用更改CallerID。此例中,Verbose和Set是應(yīng)用,CALLERID是函數(shù)。
exten => 1234,1,Verbose(The current CallerID is ${CALLERID(num)})
same => n,Set(CALLERID(num)=<256>555-1212)
CallerID信息存于數(shù)據(jù)結(jié)構(gòu)ast_channel的實例中,但這里需要的是一個撥號計劃函數(shù),而不僅僅是一個變量。撥號計劃函數(shù)中的代碼能夠從這些數(shù)據(jù)結(jié)構(gòu)中存取數(shù)據(jù)。
還有一個撥號計劃函數(shù)的用法示例--在呼叫日志中添加自定義信息,即CDR(呼叫詳細(xì)記錄)。CDR函數(shù)允許呼叫詳細(xì)記錄信息的獲取以及自定義信息的添加。
exten => 555,1,Verbose(Time this call started: ${CDR(start)})
same => n,Set(CDR(mycustomfield)=snickerdoodle)
1.2.4 編解碼譯碼器
在VOIP領(lǐng)域有許多編解碼器用于媒體編碼及跨網(wǎng)絡(luò)發(fā)送。多種技術(shù)選擇為媒體質(zhì)量、CPU消耗、帶寬需求等方面提供了折中方案。Asterisk支持多種不同的編解碼器,必要時能夠在其中兩者之間進(jìn)行轉(zhuǎn)碼。
Asterisk設(shè)置完呼叫后,將會嘗試使用公共媒體編解碼器來溝通兩個端點,這樣就不需要轉(zhuǎn)碼。然而實際上這種情況不太可能發(fā)生。即使使用公共編解碼器,也需要轉(zhuǎn)碼。比如,如果通過配置使Asterisk對流經(jīng)系統(tǒng)的音頻做信號處理(如增大或減小音量),就需要將音頻信號轉(zhuǎn)換為未壓縮形式之后,才能執(zhí)行信號處理。也可以通過配置使Asterisk做呼叫錄音。如果配置的錄音格式與呼叫的音頻格式不同,也需要轉(zhuǎn)碼。
編解碼的協(xié)調(diào)
使用什么編碼協(xié)調(diào)方法來處理媒體流,這與連接到Asterisk系統(tǒng)的呼叫所使用的技術(shù)有關(guān)。對于某些情況,如基于傳統(tǒng)電話網(wǎng)絡(luò)(PSTN)的呼叫,其容量和優(yōu)先級都已明示,通用的編解碼器也已達(dá)成一致,因而不需要任何協(xié)調(diào)機制。
例如,對于SIP(最常用的VOIP協(xié)議),從高層視角來看,當(dāng)呼叫送達(dá)Asterisk系統(tǒng)時,編解碼器的協(xié)調(diào)執(zhí)行如下:
端點向Asterisk發(fā)送新的呼叫請求中包含其優(yōu)先使用的編解碼器列表。
對于更復(fù)雜的編解碼,尤其是視頻方面,Asterisk對此領(lǐng)域處理得還不夠好。在過去十年里,編解碼器的協(xié)調(diào)選修變得愈加復(fù)雜。我們還有更多的工作要做,才能更好的處理最新的視頻編解碼,才能使系統(tǒng)對視頻的支持比現(xiàn)在更好。在Asterisk下一個主要發(fā)布版的諸多新開發(fā)任務(wù)中,這項工作是重中之重。
編解碼轉(zhuǎn)碼器的模塊提供了ast_translator接口的一種或多種實現(xiàn)。轉(zhuǎn)碼器有原格式和目標(biāo)格式兩種屬性,還提供了一個回調(diào)函數(shù),用于將媒體數(shù)據(jù)塊從原格式轉(zhuǎn)換為目標(biāo)格式。轉(zhuǎn)碼器不涉及電話呼叫的概念,它只知道如何將一種媒體格式轉(zhuǎn)換為另一種媒體格式。
轉(zhuǎn)碼器API更多細(xì)節(jié)信息,參見include/asterisk/translate.h和main/translate.c文件。轉(zhuǎn)碼器抽象類的實現(xiàn)存于編解碼器目錄。
Asterisk是重量級的多線程應(yīng)用程序,使用POSIX線程API來管理線程,并使用了相關(guān)服務(wù),如加鎖。Asterisk中所有與線程交互的代碼都會這樣做,但要通過一組用于調(diào)試的包裝器。Asterisk的大多數(shù)線程可歸類為網(wǎng)絡(luò)監(jiān)視線程或通道線程(有時亦稱為PBX線程,因為其主要目的是在通道運行用戶級交換機PBX)。
1.3.1 網(wǎng)監(jiān)線程
Asterisk每個主通道的驅(qū)動都有網(wǎng)監(jiān)線程,負(fù)責(zé)監(jiān)視本通道連接的任何網(wǎng)絡(luò)(IP網(wǎng)絡(luò),或PSTN,等等),以及接入的呼叫或其它類型的請求。這類線程還要處理初始連接的設(shè)置步驟,如認(rèn)證及撥號驗證。呼叫設(shè)置完成后,監(jiān)視線程將創(chuàng)建Asterisk通道(ast_channel)的一個實例,并啟動一個通道線程,用于處理此呼叫生存期的其它操作。
1.3.2 通道線程
前面討論過,通道是Asterisk的基本概念。通道有入站通道和出站通道之分。當(dāng)有呼叫接入Asterisk系統(tǒng)時,就創(chuàng)建一個入站通道,執(zhí)行撥號計劃。Asterisk為每個入站通道創(chuàng)建一個線程來執(zhí)行撥號計劃。這類線程即被稱為通道線程。
撥號計劃應(yīng)用程序一定是在通道線程的環(huán)境中執(zhí)行。撥號計劃函數(shù)幾亦如此。盡管也可能從諸如Asterisk CLI的異步接口讀寫撥號計劃函數(shù),但通常情況下,通道線程仍是ast_channel結(jié)構(gòu)的擁有者,控制著其對象的生存期。
前兩節(jié)介紹了Asterisk組件的重要接口以及線程執(zhí)行模型。本節(jié)將對常用的呼叫場景進(jìn)行分解,闡述Asterisk組件之間如何合作處理電話呼叫。
1.4.1 檢查語音郵件
有這樣一個呼叫場景的實例:呼叫接入電話系統(tǒng),檢查語音郵件。此場景涉及的第一個主要組件是通道驅(qū)動。通道驅(qū)動負(fù)責(zé)處理接入系統(tǒng)的電話呼叫請求,此動作發(fā)生在通道驅(qū)動的監(jiān)視線程中。實現(xiàn)對系統(tǒng)的呼叫依賴于所使用的電話技術(shù),因而可能會要求某種協(xié)調(diào)來設(shè)置呼叫。協(xié)調(diào)方法通常通過呼叫方撥叫的號碼來指定。然而,在某些情況下并沒有可用的指定號碼,因為實現(xiàn)呼叫所用的技術(shù)不支持指定撥叫的號碼。例如模擬電話線路上接入的呼叫。
如果撥號計劃(呼叫路由配置)為撥叫的號碼定義了擴展,而通道驅(qū)動也查到了Asterisk配置有這樣的擴展,系統(tǒng)將為分配一個Asterisk通道對象(ast_channel),并創(chuàng)建一個通道線程。通道線程主要負(fù)責(zé)處理呼叫的余下動作。
圖1.6 VoicemailMain的調(diào)用
在某一時刻,呼叫者完成與語音郵件系統(tǒng)的交互,掛斷了呼叫。這時通道驅(qū)動檢測到此動作的發(fā)生,并將其轉(zhuǎn)換為Asterisk通道的一個通用信號事件。語音郵件代碼接收到這一信號事件后退出,因為呼叫方掛斷后就沒有什么可執(zhí)行的了。然后通道線程中的控制流程將返回到主循環(huán),繼續(xù)執(zhí)行撥號計劃。
1.4.2 呼叫橋接
Asterisk中還有一個很常用的呼叫場景叫做兩通道間的呼叫橋接。此場景即一方電話通過系統(tǒng)呼叫另一方電話。呼叫的初始設(shè)置過程與前例相同。呼叫設(shè)置完畢,通道線程開始執(zhí)行呼叫計劃時,之后的處理流程是不同的。
下面這個撥號計劃是呼叫橋接的一個簡單示例。如果系統(tǒng)使用了此擴展,當(dāng)一方電話撥叫1234時,撥號計劃執(zhí)行應(yīng)用Dial,這正是發(fā)起出站呼叫的主應(yīng)用。
exten => 1234,1,Dial(SIP/bob)
Dial應(yīng)用的參數(shù)SIP/bob的含義是,系統(tǒng)應(yīng)發(fā)起一個出站呼叫,發(fā)送到設(shè)備SIP/bob。此參數(shù)的SIP部分指定了傳送呼叫應(yīng)使用SIP協(xié)議,bob部分由實現(xiàn)SIP協(xié)議的通道驅(qū)動chan_sip負(fù)責(zé)解釋。假設(shè)此通道驅(qū)動有一個叫做bob的賬戶已經(jīng)配置正確,那么它就知道如何將呼叫送達(dá)Bob的電話。
首先,應(yīng)用程序Dial要求Asterisk內(nèi)核根據(jù)SIP/bob標(biāo)識符分配一個新的Asterisk通道。然后,內(nèi)核請求SIP通道驅(qū)動執(zhí)行針對所用技術(shù)的初始化操作。通道驅(qū)動也會發(fā)起出站呼叫過程。隨著請求操作的繼續(xù)執(zhí)行,將會有事件傳回給Asterisk內(nèi)核,并由Dial應(yīng)用程序接收。這些事件包括呼叫響應(yīng)、目標(biāo)忙、網(wǎng)絡(luò)擁塞、呼叫被拒,或者其它很多可能的響應(yīng)。理想情況下,呼叫會被應(yīng)答。然而事實上,被應(yīng)答的呼叫又被傳回到入站通道上。Asterisk在應(yīng)答出站呼叫之前,都不會應(yīng)答這一部分接入系統(tǒng)的呼叫。當(dāng)兩個通道都有應(yīng)答時,通道橋接就開始了(如圖1.7)。
圖1.8 橋接中處理音頻幀的順序圖
呼叫完成時,掛斷流程與前例很相似。主要不同之處在于此處涉及兩個通道。通道線程結(jié)束之前,兩個通道都要執(zhí)行針對技術(shù)的掛斷處理操作。
迄今為止,Asterisk的架構(gòu)已有十年以上的歷史。然而,盡管這個行業(yè)在不斷發(fā)展,Asterisk的一些東西,如通道的基本概念、使用撥號計劃進(jìn)行靈活的呼叫處理,仍然支持著復(fù)雜電話系統(tǒng)的開發(fā)。有一個領(lǐng)域Asterisk的架構(gòu)還沒有處理的太好,即如何使系統(tǒng)在多服務(wù)器間可伸縮。Asterisk開發(fā)社區(qū)正在開發(fā)一個叫做Asterisk SCF(可伸縮通信框架)的伙伴項目,目的就是解決可伸縮性的課題。未來幾年,我們期待看到Asterisk以及Asterisk SCF繼續(xù)稱雄電話市場,包括更大型的系統(tǒng)安裝項目。
更多建議: