开源应用架构之asterisk
作者:Russell Bryant 翻譯:jiazhengfeng
Asterisk[1]是一款GPLv2協議下的開源電話應用平臺。簡單來說,Asterisk是一個服務器應用,能夠完成發起電話呼叫、接受電話呼叫、對電話呼叫進行定制處理。
Asterisk這個項目是由Mark Spencer于1999年開創的。Mark當時有一個名為Linux技術支持服務公司,公司需要一套電話系統來開展業務。由于Mark當時沒有足夠的錢購買,就決定自己研發一套。隨著Asterisk逐漸流行,Linux支持服務公司逐漸將業務重點轉移到Asterisk,并將公司的名字改為Digium。
Asterisk的命名是從Unix的通配符*來的,可以看出Asterisk的目標是能夠做通訊里面的任何事情。為完成該目標,Asterisk現在已經支持了很多種能夠發起和接受電話呼叫的技術,包括多種VoIP協議,與傳統電話網絡或者公用電話網絡的模擬連接和數字連接。Asterisk的一個主要優勢是能夠讓不同類型的電話呼叫可以呼入到Asterisk中,以及從Asterisk呼出到不同類型的電話。
電話呼入到asterisk或者從asterisk呼出,就具有了許多額外的特性,我們可以利用這些額外的特性來對電話進行定制處理。有些特性是經常用到的,諸如語音郵件。還有一些其他的特性可以與別的特性結合在一起用以創建和定制語音應用,諸如播放一個提示音,獲取數字輸入,或者語音識別等。
1.1 概念本部分討論對Asterisk各個部分都比較重要的結構概念,這些思想都是Asterisk體系結構的基礎。
1.1.1 通道??? Asterisk中的通道,代表了Asterisk系統與一些電話終端之間的連接(圖1.1)。最通常的例子就是電話呼叫到asterisk系統中。這個連接是由一個單側的通道來表示的。在Asterisk代碼里,通道是作為ast_channel這個數據結構的實例存在。這種單側的呼叫場景比如一個主叫用戶使用Asterisk中的語音郵件服務。
??????????????????????????????????????????????????????????????????????????? 圖1.1 單側呼叫leg,用以表示一個單側通道
1.1.2 通道橋接一個更為熟悉一點的呼叫場景是兩個電話間的連接。在這個場景里 ,有兩個電話終端與Asterisk系統連接,所以這個通話里存在兩個通道。
圖1.2 兩個呼叫leg,代表了兩個通道
當asterisk的通道像上面這樣連接在一起的,就稱之為一個通道橋接。執行通道橋接后將兩個通道橋接在一起,其目的是在這兩個通道間可以傳遞媒體信息。媒體流最常見的是音頻流。當然,也可以在呼叫中包括視頻流或者文本流。即便是包含多種媒體流,也是由單個通道來處理的。在圖2中,有電話A和電話B對應的通道,橋接負責從電話A向電話B傳遞媒體和從電話B向電話A船體媒體。所有的媒體流都是通過asterisk來協商的。asterisk可以在不同的技術之間進行錄音、音頻操作、和轉碼。
兩個通道橋接在一起,可以通過如下兩個方法來完成:通用橋接和本地橋接。通用橋接是不管使用什么樣的通道技術均能正常工作,這種橋接是通過asterisk的抽象通道接口來傳遞所有的音頻和信令數據。這種橋接方法是最復雜的,也是最有效的。
本地橋接是和通話所使用的技術相關的一種橋接。如果兩個通道使用相同的媒體傳輸技術,可以使用一種更高效的方式而不用通過像不同技術那種方式要通過asterisk的抽象層來完成。例如,如果連接到電話網絡中硬件是固定的,可以通過在硬件上將兩個通道橋接在一起,不用放到應用層來完成。有些VoIP協議,可以讓終端之間直接互發媒體信息,只讓信令信息通過服務器。
決定使用通用橋接還是本地橋接是在橋接的時候通過兩個通道的比較完成的。如果兩個通道均支持本地橋接,則采用本地橋接,反之,則使用通用橋接。為了判斷兩個通道是否支持相同的本地橋接方法,可以簡單的通過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按鍵。
· 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提供了將近200個應用。應用的定義非常松散。應用可以使用asterisk的內部API與通道進行交互。有些應用只完成非常單一的工作,如playback應用,只負責給主叫播放一個聲音文件。另外一些應用比較負責,執行多個操作,比如voicemail應用。
通過asterisk的撥號方案,多個應用可以一起使用來定制呼叫的處理過程。對于使用提供的撥號方案無法完成的復雜定制,可以使用腳本接口對呼叫進行個性化處理。腳本接口可以使用任意一種編程語言。在使用腳本的時候,撥號方案中的應用仍然可以與通道進行交互。
在我們進入例子之前,讓我們一起看看asterisk撥號方案中處理呼叫1234這個號碼的語法。注意,1234這個號碼是隨便選的。呼叫該號碼后,調用了3個撥號方案應用,首先接聽該通話,然后播放一個聲音文件 ,最后掛斷該通話。
; Define the rules for what 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; }exten關鍵字是用來定義extension的。在exten行的右側,1234是為呼叫1234這個號碼定義的呼叫規則。下面的1是撥打1234這個號碼時執行的第一步操作,Answer是告訴系統接聽這個呼叫。下面的兩行,是用same這個關鍵字開頭的,是定義的上面的extension接下來的規則,這里就是1234接下來的規則。n是下一步要執行的操作,后面的項指明了撥號方案要執行的具體動作。
接下來是使用asterisk撥號方案的另一個例子。在這個例子中,進來的呼叫首先被接聽,給主叫播放一個beep,然后從主叫讀取4位的按鍵輸入,并存儲到DIGITS變量中,然后將讀取到的數字播放給主叫,最后通話結束。
exten = > 5678,1,Answer() same = > n,Read(DIGITS,beep,4) same = > n,SayDigits(${DIGITS}) 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; }前面提到過,應用的定義非常松散,注冊函數定義非常簡單:
int (*execute)(struct ast_channel *chan, const char *args);
應用實現的函數可以使用include/asterisk/.下所有的API函數。
1.2.3 撥號方案函數很多撥號方案應用使用字符串作為參數。由于很多數值是硬編碼的,因此在需要更多動態數值的時候,往往需要適時的使用變量。下面的例子中展示了撥號方案中如何設置變量,然后通過asterisk中的verbose應用在asterisk的命令行打印出變量的值。
exten => 1234,1,Set(MY_VARIABLE=foo)
??? same => n,Verbose(MY_VARIABLE is ${MY_VARIABLE})
撥號方案的函數使用語法與前面提到的應用的語法一樣。asterisk模塊可以注冊為撥號方案的函數,這些函數可以獲取信息然后返回到撥號方案中,也可以接受一些信息并對信息進行處理。一般的原則是,撥號方案中的函數可以設置或者獲取通道的原數據,但不能做信令或者媒體處理的事情,這些是由撥號方案中的應用來完成的。
下面的例子說明了撥號方案的函數用法。首先,在asterisk的命令行里打印出當前通道的主叫ID,然后通過Set函數修改主叫ID。
在這個例子中Verbose和Set都是應用,而CALLERID是函數。
exten => 1234,1,Verbose(The current CallerID is ${CALLERID(num)})
??? same => n,Set(CALLERID(num)=<256>555-1212)
由于CallerID信息是存儲在ast_channel數據結構中的,所以我們需要使用撥號方案函數而不僅僅是一個簡單的變量。撥號方案函數代碼知道如何從數據結構中設置和獲取值。
另一個例子是使用撥號方案函數為呼叫日志(即呼叫詳細記錄CDR)中增加額外的信息。CDR函數可以獲取呼叫詳細信息,也可以添加定制的信息。
exten => 555,1,Verbose(Time this call started: ${CDR(start)})
??? same => n,Set(CDR(mycustomfield)=snickerdoodle)
1.2.4 編碼轉換在VOIP世界中,我們使用多種不同的編碼來對媒體進行編碼,并將編碼后的數據發送到網絡上。目前存在有很多的編碼供選擇,但在媒體質量、CPU消耗、帶寬需求上會有所犧牲。Asterisk支持多種不同的壓縮編碼,并且在必要的時候可以進行編碼格式間的轉換。
在呼叫建立時,Asterisk會嘗試讓兩個終端使用相同的媒體編碼格式,這樣就可以不用進行轉碼。但是,這只是理想情況。即便是有共同的編碼,轉碼也仍然需要。例如,如果通過配置讓asterisk對經過系統的音頻數據進行信號處理諸如增加或者降低音量。Asterisk也可以通過配置進行通話錄音,如果配置的錄音文件的格式與通話的編碼格式不一致,仍然需要編碼。
說明:編碼協商
協商媒體流使用何種編碼的方法對于連接到asterisk所使用的各種技術是確定的。在有些時候,例如通過傳統電話網絡的呼叫,就不需要進行任何協商。但是,在另外一些情況下,特別是使用IP協議,需要使用一種協商機制,通過描述的終端的能力和優先選擇的編碼,協商出共同的編碼。
以SIP協議為例,這里說明一下呼叫到asterisk后如何進行編碼協商的。
1.終端向asterisk發起呼叫請求,在該請求中包含了終端希望采用的編碼格式。
2.Asterisk通過查詢管理員配置好的語音編碼優先順序,選擇一種最優先的編碼,該編碼既是asterisk優先順序表的編碼,同時終端也可以支持。
Asterisk編碼處理不太好的地方是對于較為復雜的編碼,特別是視頻編碼。編碼協商的需求在過去的10年內已經非常復雜。為了更好的處理新的音頻編碼和能更好的支持視頻編碼,我們還有很多工作需要做。這是asterisk下一發行版本中優先考慮的新需求。
編碼轉換模塊提供了一個或者多個ast_translator接口的實現。編碼轉換器具有源和目的格式屬性,提供了一個回調函數可以完成一段媒體信息從源格式到目的格式的轉換。編碼轉換器本身對通話本身并不知道,所需知道的僅僅是如何完成媒體從一個格式到另一個格式的轉換。
關于轉換器API的更多信息,可以參考include/asterisk/translate.h和main/translate.c。轉換器抽象的實現可以在codecs 目錄中找到。
1.3 線程Asterisk是一個典型的多線程應用程序,主要使用POSIX線程API來管理線程和相關的服務例如加鎖。在Asterisk中所有和線程相關的代碼為便于調試,都進行了一層封裝。Asterisk中的大部分線程可以分為兩大類網絡監控線程和通道線程。通道線程有時候也指的是PBX線程,因為通道線程的主要目的是在一個通道上執行PBX。
1.3.1 網絡監控線程網絡監控線程主要是在asterisk通道驅動模塊中。他們主要負責監聽驅動模塊連接網絡,監聽來自網絡的呼叫和其他類型的請求信息。監聽線程會進行初始連接建立階段的工作,諸如認證和被叫號碼的驗證。一旦呼叫建立完成,監聽線程會創建一個asterisk通道,然后再創建的通道上開啟一個通道線程來負責接下來的事情。
1.3.2 通道線程正如前面討論的,通道在asterisk中是一個基本概念,通道包含入局通道和出局通道。入局通道是呼叫進入到asterisk系統后創建的,撥號方案的執行就是在入局通道上進行的。對于每一個執行撥號方案的入局通道均會創建一個線程,稱為通道線程。
撥號方案應用一般是在通道線程中執行。撥號方案函數基本上也是如此。撥號方案函數可以通過異步接口來進行讀和寫操作,比如Asterisk的命令行。但是,多數情況下是由ast_channel數據結構的所屬通道線程來完成ast_channel生命周期。
1.4 呼叫場景前面兩部分介紹了asterisk組件的重要接口,和線程執行模型。本部分主要通過一些常見的呼叫場景來說明asterisk的組件間如何相互操作來完成呼叫處理電話呼叫的。
1.4.1 語音郵件檢查第一個呼叫場景是某人呼叫到電話系統中來查看自己的語音郵件。在這個場景中用到的第一個重要組件是通道驅動。通道驅動是負責處理從電話終端進來的呼叫請求,主要是在通道驅動的監控線程中完成的。取決于呼叫到系統中具體所使用的技術,可能會有某些協商來建立通話。建立通話的下一步就是決定呼叫目的號碼,這一般是由主叫撥打的號碼。但有些時候由于所使用的技術不支持被叫號碼的傳送,就不存在被叫號碼。比如,從模擬線進來的呼叫就是這樣。
如果通道驅動驗證了asterisk撥號方案的配置文件中有被叫號碼對應的extension,就會分配一個asterisk通道對象(ast_channel),并創建一個通道線程。通道線程將負擔起呼叫剩下的主要任務,如圖1.5所示。
圖1.5 呼叫建立順序流程圖
通道線程的大循環來負責撥號方案的執行,首先獲取到被叫號碼對應extension定義的規則,然后按照預定義好的順序依次執行。下面是一個按照extensions.conf語法定義的例子。呼叫到*123時,首先接聽該呼叫,然后執行VoicemailMain應用。
Exten => *123,1,Answer()
??? same => n,VoicemailMain()
當通道線程執行Answer應用時,Asterisk將應答入局呼叫。接聽呼叫需要與通道使用技術的處理,所以在通用應答處理之外,還要調用ast_channel_tech結構中定義的answer函數來處理接聽通話,比如向IP網絡發送專門的數據包,或者模擬線摘機等。
通道線程下一步是執行VoicemailMain,該應用是app_voicemail模塊提供的。需要注意的一點是Voicemail代碼雖然處理了很多和呼叫相關的交互,但應用本身對于呼叫到asterisk系統使用的何種技術并不知情,asterisk通道抽象將這些細節隱藏起來了。
用戶通過呼叫訪問語音郵件時,提供了很多特性。所有的特性主要是根據主叫輸入的數字按鍵來讀或者寫音頻文件。DTMF數字傳送到asterisk中有很多種方式,這是由通道驅動來處理的。一旦某個鍵按下傳送到asterisk中,就會轉化成一個鍵按下事件,然后傳遞到voicemail代碼中。
前面已經討論過,在asterisk中一個重要的接口是編碼轉換。在這個場景中非常重要。當voicemail代碼想播放一個聲音文件給主叫時,音頻文件中音頻的格式和主叫與asterisk系統通信的音頻格式未必一致。如果必須進行音頻的轉碼,就會建立一個由一個或者多個編碼轉換器構成的轉碼路徑來完成從源格式到目標格式的轉換。
圖1.6 一個到VoicemailMain的呼叫
在某個時候,主叫會結束與語音郵件系統的交互,然后掛機。通道驅動將檢測到這些動作,然后轉換成asterisk通道信令事件。語音郵件代碼會接收到該信令事件,然后退出。控制權將會返回到通道線程的大循環中繼續撥號方案的執行。由于在這個例子里面沒有接下來的撥號方案,通道驅動會優先處理通道相關的掛斷處理,然后ast_channel對象會被銷毀。
1.4.2 橋接呼叫在asterisk中,另外一個比較常見的呼叫場景是在兩個通道間橋接呼叫。這種場景下,一個電話呼叫通過asterisk系統。初始呼叫建立過程與前面的語音留言類似,不同之處在于從通話建立開始和通道線程執行撥號方案開始。
下面的撥號方案是一個建立橋接呼叫的簡單例子。使用這個規則,當一個電話呼叫1234,撥號方案會執行Dial應用,Dial是發起一個出局呼叫的主要應用。
exten => 1234,1,Dial(SIP/bob)
Dial應用中的參數說明系統呼叫到的終端是SIP/bob。參數的SIP部分說明發起這個呼叫應該使用SIP協議。Bob是有通道驅動來負責解釋使用的,實現是在sip協議chan_sip中。假設SIP通道驅動中事先定義好了一個帳號叫bob的,這個呼叫就會到達Bob的電話上。
Dial應用會向asterisk核心請求分配一個標識符為SIP/bob的asterisk通道。核心會請求sip通道驅動,完成技術相關的初始化工作。通道驅動會初始化外呼到終端電話的過程。通道驅動在呼叫進行過程中,會向asterisk核心傳送一些事件,進而傳送到Dial應用中。這些事件包括呼叫被應答,目標用戶正忙,網絡阻塞,呼叫被拒絕,以及其他的一些響應。理想的情況是,呼叫被應答。呼叫被接聽會傳回到入局通道中,asterisk不會對呼叫到asterisk系統中的入局呼叫進行接聽除非出局呼叫被接聽。兩個通道均被接聽后,通道間的橋接開始,如圖1.7所示。
圖1.7 通用橋接中橋接通話的流程塊
在一個通道橋接過程中,音頻和信令事件從一個通道中傳送到另一個通道,直到結束橋接的事件發生,例如一方掛斷。圖1.8的順序流程圖說明了橋接通話中一個音頻幀上的關鍵操作。
圖1.8 橋接中音頻幀處理流程圖
通話結束后,掛斷過程與上面的例子類似,最大不同的地方是這里有兩個通道。在通道線程結束之前,通道技術相關的掛斷過程需要在兩個通道上分別執行。
1.5 最后注釋Asterisk現在的體系結構已經超過10年了。但是通道的基本概念和asterisk撥號方案中靈活的呼叫處理仍然支持著復雜電話系統的繼續發展。Asterisk沒有很好的解決的一個領域是擴大到多個服務器。Asterisk開發社區目前正在開發一個項目叫Asterisk SCF主要就是為了解決伸縮相關的問題。在未來的幾年里,我們希望看到Asterisk與Asterisk SCF一起繼續在電信市場乃至大型安裝上占據較大份額。
腳注 1. http://www.asterisk.org/2.DTMF代表雙音-多頻。電話通話中當有人按下電話按鍵時,發送的音頻提示音。總結
以上是生活随笔為你收集整理的开源应用架构之asterisk的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基金销售平台倒闭怎么办?找到答案了!
- 下一篇: 使用Wireshark进行SIP包解析