PigeonCall:一款Android VoIP網路電話App架構的介紹
1.概述
PigeonCall,中文名“飛鴿電話”,是一款Android平台的VoIP網路電話應用,但只工作於區域網路,支援給任意區域網路內使用該App的其他使用者撥打網路電話,可以在各大應用市場下載安裝,也可以直接點擊這裡直接下載。
本應用是我利用了斷斷續續將近大半年的業餘時間開發出來的,目的是想研究一下Android平台的P2P語音傳輸技術,開發過程中重構了很多次,也嘗試了很多不同的方案,本文則是對此的一個總結,從宏觀上分析了整個應用的架構和所涉及到的技術,歡迎持續關注本部落格,後續有時間會慢慢分享更多的細節。
2.需求分析
2.1功能定義
本應用支援的功能如下所示:
(1) 運行於Android平台
(2) 自動搜尋和顯示區域網路內的其他使用者
(3) 支援撥打到電話和來電提醒
(4) 通話過程流暢清晰無卡頓,低延時
2.2效能指標
ITU-TG.114規定,對於高品質語音可接受的時延是300ms。一般來說,如果時延在300~400ms,通話的互動性比較差,但還可以接受。時延大於400ms時,則互動通訊非常困難。
2.3開發痛點
(1)低延遲,語音通話對延時非常敏感
(2)降低雜訊、回聲消除,靜音檢測(省流量)
(3)無伺服器,去中心化,全雙工系統P2P通訊
3軟體架構
整個軟體分為四大模組: Android UI,VoipSdk(主控模組),裝置發現與通話協議,語音編解碼與傳輸模組,語音採集與輸出模組,:
3.1Android UI(平台相關,採用Java開發)
Android UI 主要有2個介面,一個是 MainAcitivity,以列表的形式顯示當前區域網路內的所有其他使用者,另一個則是電話撥打/接聽介面,當使用者點擊撥打到電話或者收到來電時顯示。
為了保證App進入後台依然能夠收到來電訊息,因此需要開啟一個Service服務,該服務封裝了整個應用最核心的邏輯和介面,包括:搜尋區域網路內其他使用者、撥打到電話、監聽來電、語音傳輸等等。
UI介面如下所示,由於沒有美工,自己設計的介面不是很協調和美觀,這個後期再慢慢改進吧:
3.2裝置發現與通話協議 (平台無關,採用C++開發)
這一模組我研究和嘗試過三種方案,分別介紹如下:
3.2.1成熟的 UPnP 架構
UPnP架構天生就是為對等網路串連(P2P)的結構設計的,可用於區域網路之間的裝置發現、遠程服務調用。官方提供了各種實現了該協議架構的第三方庫,可以快速實現裝置發現功能。
UPnP協議規定,每個UPnP裝置節點通過組播來發送裝置描述、服務描述(XML文檔),網路中的其他節點即可知道對方的資訊,以及所提供的服務,因此,我們需要設計一套簡單的通話協議的“服務描述”XML文檔,包含:Make Call、Cancel Call、Accept Call、Refuse Call、End Call 等命令,這樣,其他的裝置節點即可通過"RPC"遠端程序呼叫的方式,實現通話的請求和響應過程。
這就是採樣UPnP方案的基本思路,我採用UPnP官網提供的"PlatinumKit"庫實現了這套功能,後來發現本應用並不需要搞得如此複雜,沒必要引入UPnP架構,因此又自己編寫了一套更加簡單的方案。
3.2.2SIP協議
SIP協議被廣泛用於VoIP網路通話,但是更多地用於面向廣域網路的語音電話應用情境,它需要一個SIP網路伺服器的參與,該網路伺服器負責各個SIP終端之間的會話建立、維護和終止。
本應用是區域網路內的P2P網路電話,去中心化,並不需要"伺服器"的存在,因此並不適合採用SIP協議。
3.2.3自訂裝置發現與通話協議
基於上述考慮,最終我選擇了自己來寫一套簡單且滿足本應用情境的裝置發現與通話協議。
首先,協議的網路傳輸部分採用UDP組播,相比與廣播包,對本地區域網路的影響更小。其次,採用二進位格式的協議,相比於XML、JSON等格式,效率更高,佔用頻寬更少。
本協議採用“T-L-V”連結格式,每個組播包由一個或多個“T-L-V”子包連結而成,樣本如下:
當前協議中已存在的子包如下所示:
每一個Device都有一個唯一的Id值,由 Source Id 和 Target Id 的值決定該組播包的寄件者和目標接受者,當 Target Id == 0 的時候,代表該組播包是發給所有人的。
由 Packet Id 決定此包的種類,不同種類的包有著不同的 optional 子包,例如:
Device Info 包是當前唯一發給所有人的組播包,用來通知區域網路內其他對象自己的裝置名稱和IP地址,目前的設計是預設每個5秒鐘發一次,超過10s未收到包則認為該裝置已掉線。
具體協議實現的過程中,“T-L-V”協議部分,採用了我自己編寫的開源庫(TLV轉碼器),可以快速實現多個“T-L-V”格式的序列化與還原序列化,而多播的部分則可以參考我的clib庫:multicast。
3.3語音編解碼傳輸模組(平台無關,採用C++開發)
3.3.1概述
一個完整的語音資料流圖如下所示,從採集到遠端播放,需要經過多項處理,包括:回聲消除、去噪、編碼、網路傳輸、解碼等等,本模組就是負責實現音頻資料的 "編解碼和網路傳輸" 部分。
3.3.2編解碼
一套雙聲道數字音頻若取樣頻率為44.1KHz,每樣值按16bit量化,則其碼率為:44.1kHz*16bit*2 = 1.411Mbit/s
對於網路電話應用,語音傳輸是雙向的,因此上述碼率還要乘以2,可見其資料量還是蠻大的,因此,必須進行編碼壓縮之後再通過網路進行傳輸,這樣才能達到更好的通話效果。
Opus是一個有損聲音編碼的格式,通過諸多的對比測試,低碼率下Opus完勝曾經優勢明顯的HE AAC,中碼率就已經可以媲敵碼比它率高出30%左右的AAC格式,而高碼率下更接近原始音頻。因此非常適合作為VoIP語音電話首選的壓縮格式。
其官方網站:http://www.opus-codec.org,該網站上提供了基於C語言的編解碼庫,可以很容易地移植到其他平台。
3.3.3網路傳輸
網路傳輸協議可以選擇TCP、UDP或者RTP,像TCP這樣的可靠傳輸協議,通過逾時和重傳機制來保證傳輸資料流中的每一個bit的正確性,從而帶來了明顯的延時,因此並不適合作為音視頻傳輸的首先方案。關於TCP與UDP/RTP的討論,網上資料很多,在此不再贅述,有興趣的朋友也可以看看我的這篇《為什麼要使用RTP》來瞭解一下RTP協議的種種好處。
本應用中,既可以採用RTP協議,也可以簡單地採樣UDP來完成語音資料的網路傳輸,如果採樣RTP協議,則可以考慮常見的RTP庫,包括:Jrtplib和ortp,前者是C++開發,後者採用C語言開發,都很不錯,我最後實現了兩個版本,一個是採用ortp,另一個是採用udp,其實,如果不做RTCP控制的話,還是採用udp更加簡單點。
3.3.4去噪和回聲消除
去噪和回聲消除也是語音電話非常重要的一部分,必須得做,否則你會發現做出來的應用根本無法使用,噪音、嗞嗞聲和回聲影響實在是太大了,這也是做語音開發的痛點所在,對雜訊、回聲、延時超級敏感,想做好,還需要下一番很大的功夫。
本應用採用了著名的Speex庫來完成去噪和回聲消除,它介面非常簡單易用,目前效果還不夠好,估計它的詳細配置我還研究得不夠,以後還需要繼續研究研究,慢慢最佳化通話效果。
3.3.5語音採集輸出模組(平台相關)
Android 語音的採集和輸出有兩種方案,第一種方案是採用 Android SDK提供的 Java 端的 API,即 MediaRecoder類(採集)和 AudioTrack類(播放)來完成,第二種方案則是採用Android NDK提供的 Android OpenSL ES 介面,在 Native 層直接完成語音的採集與輸出。
兩種方案我都嘗試過,最後決定採用 Android OpenSL ES 方案,因為不需要頻繁在 Java 和 Native 層直接傳遞資料,無論是代碼的編寫還是程式啟動並執行效率,優勢都非常明顯。
有一個老外,Victor Lazzarini,封裝了一套 OpenSL ES 的 API,非常好用,可以作為參考,地址點擊這裡。
4.小結
限於篇幅,本文只是簡單列出了本應用的一些關鍵的設計和方案,並沒有完全詳細地展開,真正著手實現的過程中,你會發現還有很多很有價值值得研究和積累的地方,源碼我就不公開了,但我會慢慢寫一些文章剖析其中涉及到的技術,希望對Android音頻開發有興趣的小夥伴們自己動手實踐一下,這樣才能真正地得到提高,開發過程中有任何疑問歡迎來信 [email protected] 交流,也可以關注我的新浪微博 @盧_俊 或者公眾號 @Jhuster 擷取最新的文章和資訊。