linphone是一款老牌的全平臺的多人語音視頻通話業務的軟件(始自2001年),不僅支持視頻和語音通話,還支持即時消息(可惜只支持文本和圖片)。
當然,重要的是:linphone是開源的,畢竟linphone的sip服務和媒體數據處理也大量使用開源框架;更重要的是,linphone官網提供免費的sip服務,這也意味著你不需要自己手動搭建sip服務即可享受voip業務,這對于有voip需求的前端來說無疑是最佳的選擇。但是雖然開源,linphone的工程比較龐大,依賴項較多,編譯的過程中容易出現問題,那么下面,我將介紹自己linphone-iphone的安裝歷程。
linphone-iphone的下載
首先進入官網,可以看到linphone的多個產品線,最感興趣的肯定是liblinphon了,但是這是個坑,一方面,開發文檔幾乎沒有,根本無從下手;另一方面,編譯的過程相當痛苦,成功率不高,就算編譯出來了也沒有成就感可言,不僅需要花費大量的時間寫Demo做測試,你還要考慮如何搭建SIP服務器。
如果想體驗并快速學習相關的接口,我建議去linphone這一欄,在downloads目錄下,你可以看到所有平臺的產品都是可以下載源碼的,這里選擇iphone版。你有兩種選擇,要么選擇官網的git://git.linphone.org/linphone-iphone.git
,要么選擇github上的git://git.linphone.org/linphone-iphone.git
,其實效果差不多,但是記住不要手動下載,而是使用git命令clone
到本地,因為模塊中的很多依賴庫代碼都沒有放在項目中,需要通過git鏈接下載。

打開終端,有iTerm2更好,cd到項目放置的目錄,輸入
git clone git://git.linphone.org/linphone-iphone.git
命令開始下載,你會發現一切順利,很快便下載完成,你很高興的cd進linphone-iphone
,輸入"./prepare.py",以為腳本會為你解決剩下的一切,然而出現了下面的提示:
這時打開
submodules
文件夾就會發現里面的模塊全部是空的,看工程所依賴的模塊都沒下載過來,只是下載了前端的這個空殼。那就照著它說的做好了,輸入命令git submodule update --init --recursive
,嗯,下載進度又開始了,剛開始也許一切正常,但是過一段時間會發現大多卡在了libxml2
這個xml解析模塊的下載上了,我用了比較笨的方法:請再來一次...,之前下載失敗的部分只需要再輸入一次git submodule update --init --recursive
就可以繼續下載,可惜不支持斷點續傳,模塊需要重頭下載,TOT。但是有更好的解決辦法,實際上github是有
libxml2
分支的,想辦法把git中的鏈接替換掉就行,下面我們通過git命令來修改git配置中的鏈接來實現下載:首先在工程目錄下,終端中輸入
git config -l
,看到如下信息:
可以看到libxml2用的源是
git://git.gnome.org/libxml2
,我們需要換一個能下的,運行git config -e
就可以用vim編輯git配置文件了,替換后的樣子如下所示:
修改后保存,運行git submodule update --init --recursive
就可以繼續下載了,最終所有源碼下載成功,可以開心的編譯了,是的,托腳本的福,編譯無壓力。
linphone-iphone的編譯
下面的工作進行的比較順利,無非照著readme一步一步來,缺哪個就去裝哪個。
- 首先,為了支撐腳本順利運行,我們需要安裝[HomeBrew]((http://brew.sh),有了它編譯缺少依賴的日子一去不復返了。
-
./prepare.py
這個時候可以運行該命令來安裝linphone-iphone的依賴項了,提示我們需要使用brew安裝coreutils
automake
autoconf
libtool
intltool
wget
pkgconfig
cmake
yasm
nasm
doxygen
ImageMagick
optipng
libantlr3c
gettext
,這時homebrew派上了用場,運行命令brew install coreutils automake autoconf libtool intltool wget pkgconfig cmake yasm nasm doxygen ImageMagick optipng libantlr3c gettext
就可以自動安裝完成,不需要手動配置環境。 - 再次運行``./prepare.py`,嗯,提示我們要安裝java JDK,那就去裝一下好了。這個更簡單,直接去網上下載java SE開發套裝的軟件安裝就可以了。
- 終于可以開始
./prepare.py
了,腳本做的比較智能,只要前面步驟都完成了,這一步成功率較高。 - 完成這些準備工作后,可以開始重頭戲,編譯了,運行
./prepare.py -c && ./prepare.py && make
,大概20分鐘左右就可以編譯好??梢钥吹阶幽夸浵露喑隽艘粋€liblinphone-sdk
文件夾,里面是編譯好的liblinphone在各個平臺下的靜態庫。 - 打開
linphone.xcodepro
,工程文件夾中自帶了企業推送證書,可以實現推送,連接真機后,按?R
就可以享受了。
linphone_iphone的調試
是的,其實到這里基本就結束了。但是我們費這么大功夫不是為了裝個Voip玩一玩了事的,而是借鑒里面對接口的調用方式。這里主要了解通話業務相關的代碼。
工程目錄結構
可能是工程維護時間較長的原因,但說實話,現在還在用xib實在是...不過好處是UI結構簡單一看就懂。
與工程相關的類全部放在Classes
中,子目錄有各頁面的ViewController以及liblinphone的中間件,而LinphoneUI
中存放自定義的各種控件,Utils
中存放用到的工具類和第三方視圖類,都不是什么著名的,引起注意的一個是XMLRPC,遠程過程調用的一個ios框架;另一個TPMultiLayoutViewController比較重要,工程中幾乎所有的UIViewController都繼承自它,嗯,似乎它的目的是早年(5年前)用來解決一種布局方式的UIViewController可以同時適用橫向和豎向的問題,也是夠老了。
跟通話相關的類型主要有:
-
CallIncomingView
:電話打入的視圖; -
CallOutgoingView
:電話打出的視圖; -
CallView
:電話接通后的視圖、包括語音聊天、視頻聊天、會議電話的視圖; -
PhoneMainView
: 電話視圖的容器,單例模式;監聽大部分的通話狀態變化,負責管理不同通話視圖間的跳轉和主要業務邏輯; -
LinphoneManager
:對liblinphone中各種狀態的封裝以及功能的補充(音頻播放、音頻設備設置,音頻設備占用處理,低電量,新消息處理,前后臺處理),中間件。
運行與調試
- 注冊與使用
我們來運行一下這個demo(完成度很高,已上架),終于看到了Xcode的"Build success",程序進入后長這個樣子:
Demo效果圖
選擇CREAT ACCOUNT
進行帳號的注冊,注冊完成即可享受免費的sip服務,進入注冊頁面后,建議用戶名最好填寫電話號碼或者純數字帳號,因為sip地址是根據用戶名來定義的,例如用戶名為username的用戶的sip地址為username@sip.linphone.org
,而撥號盤只能輸入數字...這意味著如果你想直接通過撥號盤或者手機聯系人來撥打sip電話(這些sip帳號都是跟電話號碼綁定的),就必須是純數字帳號。雖然也可以通過添加聯系人的方式來添加帶字母的sip帳號,但這并不方便。注冊后在郵箱收到郵件并點擊鏈接驗證后帳號注冊完成,可以向另一臺注冊了帳號的設備發送voip通話了。 - 代碼分析
- UI
與通話相關的頁面一共有3個:CallIncomingView
、CallOutgoingView
、CallView
,分別為打入電話、呼出電話與通話期間的視圖。沒有特別難懂的地方,用通知來進行UI更新。
而"PhoneMainView"則比較特殊,該單例不僅起到業務層的作用,而且是上面3個頁面的容器,通過它來進行通話頁面之間的切換和管理。 - 業務,位于
linphoneManager
中-
基本業務:
- 撥號
- 判斷網絡狀況
- 檢查是否在GSM通話中
- 檢測提供的addr是否合法
- 2g網絡下是否使用low_bandwidth模式
- linphone_core_invite_address_with_params
- 接聽
- 2g網絡是否是同low_bandwidth模式
-
linphone_call_params_enable_video
是否允許視頻流 -
linphone_core_accept_call_with_params
接電話
狀態監聽:
voip通話狀態改變的監聽是通過通知kLinphoneCallUpdate
下發的,注冊該通知后,就可以從userInfo
中的keycall
和state
中分別拿到當前通話的實例以及通話狀態碼。
那么,該通知又是怎么來的呢,看了voipManager.m
便知,在linphonecore.h
文件中有一個void (*LinphoneCoreCallStateChangedCb)(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate, const char *message)
類型的blockcall_state_changed
,該回調會在通話狀態改變時觸發,所以將方法static void linphone_iphone_call_state(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState state, const char *message)
賦值給了該blcok,在該方法中會對各種state進行處理,最后將state、call、message封裝到dict后發送通知。-
音頻設備相關
需要處理兩個問題:- 音頻設備占用,這里又分為GSM電話和普通音頻應用的占用;
- 音頻設備監測,主要針對藍牙設備的接入。
-
前臺與后臺
為了保證應用進入后臺也能長時間通話,做了如下處理:- 觸發
applicationDidEnterBackground
時:enterBackgroundMode
- refreshRegisters
- 設置超時處理
[[UIApplication sharedApplication] setKeepAliveTimeout: 600 handler:^{ linphone_core_iterate }
- 有通話則延長后臺存活時間
startCallPausedLongRunningTask
- [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler
- 停止視頻預覽
linphone_core_stop_dtmf_stream
- 如果不進入后臺模式,destroy voip socket if any
- 觸發
applicationDidBecomeActive
時:調用becomeActive
- refreshRegisters
- 停止后臺任務
pausedCallBgTask
和incallBgTask
linphone_core_start_dtmf_stream
- 觸發
-
其他部分就不一一列舉了,說的不夠清楚,建議如果感興趣的話自己實際編譯調試一遍,從而對linphone的ios端接口有更深的了解。