我自己集成的Demo & SDK源碼路徑
開(kāi)始前先安利下江湖哥簡(jiǎn)書(shū),當(dāng)初都是照著這一步步摸索的http://m.itdecent.cn/p/2ba2d4aa168e
更新ipv6
見(jiàn)文末
正文開(kāi)始。
很多場(chǎng)景下,我們的App會(huì)使用到VOIP通話,雖然目前各大通訊公司都推出了各自的SDK,相比Linphone,它們對(duì)開(kāi)發(fā)者更為友好(體現(xiàn)在有更及時(shí)的技術(shù)支持、豐富的開(kāi)發(fā)文檔),但有些時(shí)候,迫于某些需求,也只有掏出Linphone SDK,硬著頭皮上了。而我是為了實(shí)現(xiàn)純SIP通話,以及客戶需求,所以進(jìn)行了對(duì)Linphone SDK的集成、開(kāi)發(fā),本文的目的一為記錄以便日后翻閱,二為了讓后來(lái)的開(kāi)發(fā)者少跳一些坑,白白耽誤時(shí)間。
首先我們要下載一個(gè)重要的Linphone Demo,需要CSDN的2積分,如果沒(méi)有可以留言問(wèn)我要。只能在真機(jī)上運(yùn)行。
1. 準(zhǔn)備工作
1.1 添加依賴庫(kù)
為支持新版ipv6 SDK,需要再添加VideoToolBox.framework

1.2 相關(guān)配置

1.3 http請(qǐng)求設(shè)置
從iOS9開(kāi)始,蘋(píng)果建議所有的網(wǎng)絡(luò)請(qǐng)求都要使用https,如果還想保留原有的http請(qǐng)求方式,開(kāi)發(fā)者需要修改配置文件(注:xcode7.0以下則不需要修改),在工程名的文件夾下面的Supporting Files文件夾中找到并且選擇(工程名)lnfo.pList在右邊出現(xiàn)的窗口中添加Key:NSAppTransportSecurity,在下面添加項(xiàng):NSAllowsArbitraryLoads,設(shè)置Boolean值為YES,這樣平臺(tái)SDK內(nèi)部工程才能支持http請(qǐng)求方式。

1.4 后臺(tái)運(yùn)行
一般的iOS程序進(jìn)入后臺(tái)后會(huì)被系統(tǒng)掛起,就會(huì)停止執(zhí)行,不能執(zhí)行任何操作。
從iOS4開(kāi)始,蘋(píng)果增加了特性,很好的支持了語(yǔ)音通話功能:
蘋(píng)果支持應(yīng)用可以在后臺(tái)播放和錄制聲音;
蘋(píng)果支持網(wǎng)絡(luò)托管,保證應(yīng)用在后臺(tái)時(shí),還能保持網(wǎng)絡(luò)連接,能接收到來(lái)電;
應(yīng)用可以設(shè)置一個(gè)超時(shí)處理,程序在后臺(tái)運(yùn)行時(shí),周期性地喚醒應(yīng)用,保證客戶端和服務(wù)器有長(zhǎng)連接,使網(wǎng)絡(luò)不斷開(kāi)。
SDK封裝了這些特性,保證了在iOS平臺(tái)上,有很好的語(yǔ)音通話體驗(yàn)。
開(kāi)發(fā)者需要修改配置文件,這樣iOS工程才能支持這些特性。
在工程名的文件夾下面的Supporting Files文件夾中找到并且選擇(工程名)lnfo.pList在右邊出現(xiàn)的窗口中添加Key: Required background modes,在下面添加兩個(gè)項(xiàng):App plays audio和App provides Voice over IP services。(注:如果只是使用發(fā)起通話,并無(wú)接聽(tīng)功能,則不需要添加App provides Voice over IP services)

2. 導(dǎo)入Linphone SDK
上面工作做完后就可以開(kāi)始導(dǎo)入Linphone SDK了。
2.1 首先再回到這個(gè)熟悉的頁(yè)面,點(diǎn)擊Add Other

2.2 找到下載好的sdk目錄,把所有.a庫(kù)都選擇,如下圖。


2.3 查看是否關(guān)聯(lián)
Build Settings搜索 Search Paths,在下列兩個(gè)選項(xiàng)下觀察是否已經(jīng)關(guān)聯(lián),如圖



順帶說(shuō)一下這個(gè)libxml2,大多數(shù)時(shí)候是不需要的,如果報(bào)一些莫名其妙的錯(cuò)誤不妨將其導(dǎo)入,且在Build Phases里添加相應(yīng)庫(kù)libxml2.tbd
2.4 編譯
如果出現(xiàn)了形如“file not found”的錯(cuò)誤提示,回到第三步,檢查頭文件、靜態(tài)庫(kù)是否關(guān)聯(lián)成功
3. 功能實(shí)現(xiàn)
這一部分可以參考CSDN Linphone Demo(以下統(tǒng)稱為CDemo),也可以直接下載我在github代碼里的SDK代碼部分,更為直接。
3.1 初始化
需要關(guān)注CSDN Demo里的主要功能類LinphoneManage.m
實(shí)現(xiàn)如下方法即可
/**
@author Jozo, 16-06-30 11:06:18
初始化
*/
- (void)startUCSphone {
[[LinphoneManager instance] startLibLinphone];
[UCSIPCCSDKLog saveDemoLogInfo:@"初始化成功" withDetail:nil];
}
3.2 注冊(cè)
注冊(cè)方法在CDemo的manager類里沒(méi)有直接的體現(xiàn),值得注意的是我標(biāo)注“三個(gè)大坑”的地方,如果有需要用到displayName這個(gè)參數(shù)的同學(xué)一定要注意了,設(shè)置好displayName后一定要通過(guò)linphone_address_as_string(..)方法再次獲取identity,然后再調(diào)用linphone_proxy_config_set_identity(proxyCfg, identity);將新的identity設(shè)置到設(shè)置文件中,因?yàn)槲议_(kāi)始沒(méi)理清這個(gè)proxyCfg的實(shí)現(xiàn)邏輯,始終沒(méi)注意到這一步,還有就是使用了另一個(gè)函數(shù)linphone_address_as_string_uri_only的誤導(dǎo),是誤導(dǎo),后者獲取到的僅有“sip:”以及之后的那個(gè)字符串,并不會(huì)將昵稱也帶進(jìn)去傳入proxyCfg,導(dǎo)致displayName一直設(shè)置不成功,掉坑三日終于在某個(gè)狂風(fēng)亂作的下午解決此bug,導(dǎo)致天氣都變得風(fēng)和日麗起來(lái)。
注:以下賬號(hào)、密碼、IP、端口需要自己注冊(cè)。
/**
@author Jozo, 16-06-30 11:06:13
登陸
@param username 用戶名
@param password 密碼
@param displayName 昵稱
@param domain 域名或IP
@param port 端口
@param transport 連接方式
*/
- (BOOL)addProxyConfig:(NSString*)username password:(NSString*)password displayName:(NSString *)displayName domain:(NSString*)domain port:(NSString *)port withTransport:(NSString*)transport {
LinphoneCore* lc = [LinphoneManager getLc];
if (lc == nil) {
[self startUCSphone];
lc = [LinphoneManager getLc];
}
LinphoneProxyConfig* proxyCfg = linphone_core_create_proxy_config(lc);
NSString* server_address = domain;
char normalizedUserName[256];
linphone_proxy_config_normalize_number(proxyCfg, [username cStringUsingEncoding:[NSString defaultCStringEncoding]], normalizedUserName, sizeof(normalizedUserName));
const char *identity = [[NSString stringWithFormat:@"sip:%@@%@", username, domain] cStringUsingEncoding:NSUTF8StringEncoding];
LinphoneAddress* linphoneAddress = linphone_address_new(identity);
linphone_address_set_username(linphoneAddress, normalizedUserName);
if (displayName && displayName.length != 0) {
linphone_address_set_display_name(linphoneAddress, (displayName.length ? displayName.UTF8String : NULL));
}
if( domain && [domain length] != 0) {
if( transport != nil ){
server_address = [NSString stringWithFormat:@"%@:%@;transport=%@", server_address, port, [transport lowercaseString]];
}
// when the domain is specified (for external login), take it as the server address
linphone_proxy_config_set_server_addr(proxyCfg, [server_address UTF8String]);
linphone_address_set_domain(linphoneAddress, [domain UTF8String]);
}
// 添加了昵稱后的identity(此處是大坑!大坑!大坑)
identity = linphone_address_as_string(linphoneAddress);
linphone_address_destroy(linphoneAddress);
LinphoneAuthInfo* info = linphone_auth_info_new([username UTF8String]
, NULL, [password UTF8String]
, NULL
, linphone_proxy_config_get_realm(proxyCfg)
,linphone_proxy_config_get_domain(proxyCfg));
[self setDefaultSettings:proxyCfg];
[self clearProxyConfig];
linphone_proxy_config_set_identity(proxyCfg, identity);
linphone_proxy_config_set_expires(proxyCfg, 2000);
linphone_proxy_config_enable_register(proxyCfg, true);
linphone_core_add_auth_info(lc, info);
linphone_core_add_proxy_config(lc, proxyCfg);
linphone_core_set_default_proxy_config(lc, proxyCfg);
ms_free(identity);
[UCSIPCCSDKLog saveDemoLogInfo:@"登陸信息配置成功" withDetail:[NSString stringWithFormat:@"username:%@,\npassword:%@,\ndisplayName:%@\ndomain:%@,\nport:%@\ntransport:%@", username, password, displayName, domain, port, transport]];
return TRUE;
}
3.3 撥打電話
接著簡(jiǎn)單的調(diào)用如下方法即可進(jìn)行SIP呼叫。`
[[LinphoneManager instance] call:address displayName:displayName transfer:transfer];
值得注意的是,呼叫狀態(tài)、登陸狀態(tài)的回調(diào)都是通過(guò)通知來(lái)傳遞的,所以要想獲取這些回調(diào),可以監(jiān)聽(tīng)這些回調(diào),具體通知名在LinphoneManager.h里,形如“kLinphoneCallUpdate”,也可以像我一樣,在LinphoneManager.m的各個(gè)回調(diào)處,將相應(yīng)的通知改為自己想要設(shè)置的代理方法,并設(shè)置好代理,可以看我在Github Demo里的LinphoneManager.m處如下方法里的實(shí)現(xiàn)。
- (void)onRegister:(LinphoneCore *)lc cfg:(LinphoneProxyConfig*) cfg state:(LinphoneRegistrationState) state message:(const char*) message;
- (void)onCall:(LinphoneCall*)call StateChanged:(LinphoneCallState)state withMessage:(const char *)message;
4. 為支持ipv 6而導(dǎo)入新版Linphone SDK
舊版SDK不支持ipv6,更后到新版后linphonecore.h增加了一個(gè)新的宏LINPHONE_DEPRECATED,有此標(biāo)記表示該函數(shù)已棄用,而我由于只用到了SIP電話,故直接替換sdk后只報(bào)了如下錯(cuò)誤,下面一一解決

4.1 替換LINPHONE_DEPRECATED標(biāo)識(shí)的函數(shù)
棄用的函數(shù)都有對(duì)應(yīng)的新函數(shù),在注釋里可以輕易找到
/**
* @return the default proxy configuration, that is the one used to determine the current identity.
* @deprecated Use linphone_core_get_default_proxy_config() instead.
**/
LINPHONE_PUBLIC LINPHONE_DEPRECATED int linphone_core_get_default_proxy(LinphoneCore *lc, LinphoneProxyConfig **config);
/**
* @ingroup media_parameters
* Get default call parameters reflecting current linphone core configuration
* @param lc LinphoneCore object
* @return LinphoneCallParams
* @deprecated use linphone_core_create_call_params()
*/
LINPHONE_PUBLIC LINPHONE_DEPRECATED LinphoneCallParams *linphone_core_create_default_call_parameters(LinphoneCore *lc);
4.2 刪除棄用庫(kù)的初始化
在上圖中提現(xiàn)為“_libmsilbc_init”, referenced from:..這是因?yàn)樾掳鎙inphone sdk里lib文件夾里的.a庫(kù)變動(dòng),有刪有減少,而libmsilbc.a則被移除了,故要在LinphoneManager createLinphoneCore]里刪除libmsilbc_init();
4.3 第三個(gè)bug是上兩個(gè)錯(cuò)誤所導(dǎo)致
解決1、2,自然也會(huì)解決3。
(后續(xù)貌似還有些許問(wèn)題,待續(xù))
基本就是這么多了,其實(shí)回想起來(lái)也不是特別復(fù)雜,只是初次上手時(shí)網(wǎng)路上沒(méi)有太多資料文檔,大多靠自己摸索,以及在少得可憐的博客中慢慢領(lǐng)悟也是挺辛酸的,希望大家少走彎路吧。
如有其它麻煩可以電郵我 ischenzht@gmail.com
That's all, thank you.