編輯:關於Android編程
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 獲取最新的文章和資訊。
Android6.0 SystemUI啟動簡析及圖標顯示刷新。Android系統的SystemUI包含狀態欄、導航欄、快捷設置、通知欄及鎖屏界面等等;主要流程是從Syst
本文實例分析了Android開發之TimePicker控件用法。分享給大家供大家參考,具體如下:新建項目:New Android Project->Project
說起架構的話,稍微有點寫程序經驗的人來說,都可以理解架構對於整個服務的重要性。架構最核心的三個點就是:穩定性、擴展性、性能。一個好的架構主要通過這三點來看。會不會宕機,你
確定取消對話框(帶圖標) //(上下文,主題) new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE