PigeonCall:一款Android VoIP網絡電話App架構分析

1.概述
android


PigeonCall,中文名「飛鴿電話」,是一款Android平臺的VoIP網絡電話應用,但只工做於局域網,支持給任意局域網內使用該App的其餘用戶撥打網絡電話,能夠在各大應用市場下載安裝,也能夠直接點擊這裏直接下載。git


本應用是我利用了斷斷續續將近大半年的業餘時間開發出來的,目的是想研究一下Android平臺的P2P語音傳輸技術,開發過程當中重構了不少次,也嘗試了不少不一樣的方案,本文則是對此的一個總結,從宏觀上分析了整個應用的架構和所涉及到的技術,歡迎持續關注本博客,後續有時間會慢慢分享更多的細節。github


2.需求分析服務器


2.1 功能定義微信


本應用支持的功能以下所示:網絡


(1) 運行於Android平臺架構

(2) 自動搜索和顯示局域網內的其餘用戶app

(3) 支持撥打電話和來電提醒框架

(4) 通話過程流暢清晰無卡頓,低延時ide


2.2 性能指標


ITU-TG.114規定,對於高質量語音可接受的時延是300ms。通常來講,若是時延在300~400ms,通話的交互性比較差,但還能夠接受。時延大於400ms時,則交互通訊很是困難。


2.3 開發難點


(1)低延遲,語音通話對延時很是敏感

(2)下降噪聲、回聲消除,靜音檢測(省流量)

(3)無服務器,去中心化,全雙工P2P通訊


3 軟件架構


整個軟件分爲四大模塊: Android UI,VoipSdk(主控模塊),設備發現與通話協議,語音編解碼與傳輸模塊,語音採集與輸出模塊,如圖所示:

wKiom1bULRqS9-RxAACKddgIibo055.png


3.1 Android UI(平臺相關,採用Java開發)


Android UI 主要有2個界面,一個是 MainAcitivity,以列表的形式顯示當前局域網內的全部其餘用戶,另外一個則是電話撥打/接聽界面,當用戶點擊撥打電話或者收到來電時顯示。


爲了保證App進入後臺依然可以收到來電消息,所以須要開啓一個Service服務,該服務封裝了整個應用最核心的邏輯和接口,包括:搜索局域網內其餘用戶、撥打電話、監聽來電、語音傳輸等等。


UI界面以下所示,因爲沒有美工,本身設計的界面不是很協調和美觀,這個後期再慢慢改進吧:


wKiom1bUMzGyhfO0AAJ9eBynAn8868.png



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.2 SIP協議


SIP協議被普遍用於VoIP網絡通話,可是更多地用於面向廣域網的語音電話應用場景,它須要一個SIP網絡服務器的參與,該網絡服務器負責各個SIP終端之間的會話創建、維護和終止。


本應用是局域網內的P2P網絡電話,去中心化,並不須要"服務器"的存在,所以並不適合採用SIP協議。


3.2.3 自定義設備發現與通話協議


基於上述考慮,最終我選擇了本身來寫一套簡單且知足本應用場景的設備發現與通話協議。

首先,協議的網絡傳輸部分採用UDP組播,相比與廣播包,對本地局域網的影響更小。其次,採用二進制格式的協議,相比於XML、JSON等格式,效率更高,佔用帶寬更少。

本協議採用「T-L-V」連接格式,每一個組播包由一個或多個「T-L-V」子包連接而成,示例以下:


wKioL1bULkjgQzIlAAAQaqGVwgc367.png


當前協議中已存在的子包以下所示:


wKioL1bULo3jjpkIAAAneIfbJXA746.png


每個Device都有一個惟一的Id值,由 Source Id 和 Target Id 的值決定該組播包的發送者和目標接受者,當 Target Id == 0 的時候,表明該組播包是發給全部人的。


由 Packet Id 決定此包的種類,不一樣種類的包有着不一樣的 optional 子包,例如:


wKioL1bUNTLhVZRxAAAo2rgmnhs657.png


Device Info 包是當前惟一發給全部人的組播包,用來通知局域網內其餘對象本身的設備名稱和IP地址,目前的設計是默認每一個5秒鐘發一次,超過10s未收到包則認爲該設備已掉線。


具體協議實現的過程當中,「T-L-V」協議部分,採用了我本身編寫的開源庫(TLV編×××),能夠快速實現多個「T-L-V」格式的序列化與反序列化,而多播的部分則能夠參考個人clib庫:multicast


3.3 語音編解碼傳輸模塊(平臺無關,採用C++開發)


3.3.1 概述


一個完整的語音數據流圖以下所示,從採集到遠端播放,須要通過多項處理,包括:回聲消除、去噪、編碼、網絡傳輸、解碼等等,本模塊就是負責實現音頻數據的 "編解碼和網絡傳輸" 部分。


wKioL1bULzjgYDr8AAAgJ_Z8-OU450.png


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音頻開發有興趣的小夥伴們本身動手實踐一下,這樣才能真正地獲得提升,開發過程當中有任何疑問歡迎來信 lujun.hust@gmail.com 交流,也能夠關注個人新浪微博 @盧_俊 或者微信公衆號 @Jhuster 獲取最新的文章和資訊。

相關文章
相關標籤/搜索