怎樣將Linphone移植到自己的項(xiàng)目

我自己集成的Demo & SDK源碼路徑

最新版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.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容