2018-08-16

???????????????????? Android 語音通話模塊介紹(一)

PJSIP簡介

???? PJSIP是一個開放源代碼的SIP協議棧;官網地址(http://www.pjsip.org/),它支持多種SIP的擴展功能 。PJLIB, PJLIB-UTIL, PJMEDIA, and PJSIP are released under dualopen source GPL?oralternative?license.

PJSIP包括的內容

PJSIP - Open Source SIP Stack[開源的SIP協議棧]

PJMEDIA - Open Source Media Stack[開源的媒體棧]

PJNATH - Open Source NAT Traversal Helper Library[開源的NAT-T輔助庫]

PJLIB-UTIL - Auxiliary Library[輔助工具庫]

PJLIB - Ultra Portable Base Framework Library[基礎框架庫]


PJSIP的優點

a、高度的可移殖性?

只需簡單的編譯一次,它能夠在多種平臺上運行(所有Windows系統列, Windows Mobile, Linux,所有Unix系列,MacOS?X, RTEMS,Symbian?OS,等等)。

b、極小的內存需求?

官方宣稱編譯后的庫,完全實現SIP的功能只需要150K的內存空間,這使得PJISPi不僅僅是嵌入開發的理想平臺,并且實用于那些內存運行于極小內存平臺的應用,這也意味著極小的用戶下載時間。

c、高效的性能?

這意味著極小的CPU運算需求下能同時實現更多的通話。

d、支持多種SIP功能及擴展功能多種SIP功能和擴展功能,例如多人會話,事件驅動框架,會話控制(presence),即時信息,電話傳輸,等等在庫文件里得以實現。

e、豐富的文檔資料

?PJSIP開發人員提供了大量的極有價值的文檔資料供大家使用。

PJMEDIA簡介

?PJMEDIA是一個為PJSIP建立一個完整特性SIP用戶代理應用提供的補充庫,這些應用包括:softphones/hardphones,gateways or B2BUA.?使用PJSIP與PJMEDIA一起開發的應用,具備如下的特性:

a、高度的可移殖性?

與PJSIP/PJLIB一起,PJMEDIA可運行在許多平臺上,包括服務器、桌面、PDA系統,定制的硬件、PDA或移動電話。

b、多種功能?

會議橋接、多種編解碼器、丟包隱蔽/ PLC,音頻發生器,靜音探測器,聲學回聲消除/ AEC,RFC2833,RTP / RTCP協議棧,speex/iLBC/GSM/G.711編解碼器等。

c、高質量?

PJMEDIA支持頻率為16KHz、32Khz的編碼和解碼,事實上能支持任何音頻采樣率,可提供高質量的采樣轉換。PJMEDIA也可以容忍一定量的網絡或聲音設備的不穩定和一些數據包丟失。

d、很好的支持嵌入式/DSP占用內存小,靈活性好。該媒體組件被設計成可替換成相應功能的硬件。

e、較好的文檔資料

???PJMEDIA配備了相當不錯的文檔。

PJNATH簡介

PJNATH是一個新的庫,幫助應用程序進行NAT穿越。它實現了NAT穿越的最新規范:STUN、TURN和ICE。

PJNATH可以作為一個獨立庫,在您的軟件中使用,也可以使用PJSUA- LIB庫,該庫很好的與PJSIP,PJMEDIA和PJNATH整合在一起,使用起來比較簡單。

PJNATH的特點

a、STUNbis實現,

實現符合

RFC5389??標準。既提供需要使用的STUN網絡接口,又提供基于STUN但更高層次的框架,既TURN和ICE。

b、NAT類型檢測,

根據

RFC3489(STUN) ,在前端可以執行NAT類型檢測。該檢測方法不能對所有NAT類型進行穿越,但該信息可能仍然是有用,以便進行故障排除,已經被ICE整合,因此提供了該檢測方式。

c、TURN實現,

?TURN是一個中繼通信協議,通過使用中繼,并結合ICE,提供了高效的最低代價的通信路徑。PJNATH中TURN的實現,符合draft-ietf-behave-turn-14草案。

d、ICE實現,

?ICE是一個發現兩個端點之間的通信路徑協議。PJNATH中ICE的實現符合draft-ietf-mmusic-ice-19.txt草案

e、在未來,將實現更多的協議(如UPnP IGD和SOCKS5)。

PJLIB-UTIL簡介

PJLIB-UTIL是一個輔助庫,為PJMEDIA和PJSIP提供支持。這個庫中的一些功能/組件:占用內存小的XML解析,STUN客戶端庫,異步/緩存DNS解析,哈希/加密功能等 。

PJLIB簡介

q占用內存小,高性能,高可移植性的抽象庫和框架,被PJSIP和PJMEDIA使用。

PJLIB是PJLIB-UTIL、PJMEDIA和PJSIP唯一依賴的庫,因為它提供了完整的抽象,不僅僅是操作系統依賴的屬性,還包括LIBC的抽象,并提供了一些有用的數據結構。

PJLIB基礎框架庫提供的功能

?內存的處理、數據的存儲?

?.數據結構的(hash表、link表、二叉樹、等)


.caching和pool;緩沖池和內存池

?OS抽象?

?.線程、互斥、臨界區、鎖對象、事件對象


.定時器?


.pj_str_t字符串

?操作系統級別的函數抽象?

?.socket的抽象(tcp/udp)

.

文件的讀寫

?使用前的初始化,使用后的清理

pjsip的整體框架圖(圖1.1)


????? 如圖1.1展示了PJSIP框架的各模塊,可以看出從上到下,Application(pjsua)模塊可調用下層所有的模塊,也即是PJSUA處于最高層,其整合了下層模塊的全部功能以

這也就是為什么我們基本的操作都在PJSUA這里進行。是因為通過PJSUA,我們就能很方便的深入到其他模塊中。接著Application模塊往下就是PJSUA_lib層,要讓應用層(

PJSUA)能更好的調用,當然得有個封裝好的庫,這個庫就是PJSUA_LIB庫,稱為高層用戶代理庫,集合SIP,Media以及NAT穿越,所以也就有了往下的PJMEDIA-CODEC和PJMEDIA(負責SDP協商媒體編碼和媒體傳輸),PJNATH(解決NAT穿越),PJSUA-UA(提供SIP用戶代理庫),PJSIP-SIMPLE(實現presence和及時消息),PJSIP(核協議棧,SIP協議),PJLIB-UTIL(提供有用的工具函數)以及PJLIB(每個功能根據其所在的層次以及負責的功能提供豐富的接口)等模塊。

???????從實現上來看,最上層為應用層,該層將在Android SDK的框架內,采用Java語言來實現;第二層為JNI層,SIP協議棧有很多種實現,其中,采用C語言的SIP協議棧在效

、速度、系統占用方面有著超越其他庫(如Java協議棧)的優勢,因此,該方案將在第三層采用純C語言實現的PJSIP協議棧。為了讓Java應用層能調用協議棧層,在兩層之

間需要一個銜接的橋梁,這就是JNI層。最后一層是驅動層,這部分一般是由手機廠商來實現的,此處將不做重點介紹。

?SIP協議棧及UA

??? SIP協議棧直接關系到整個系統的質量與效率,許多開源項目基本上都是采用純C語言開發的PJSIP庫。該庫采用C語言開發,且源碼開放,在兼容性與效率上有明顯優勢,不僅體積?。ㄍ暾腟IP封裝也不過150 KB),同時還實現了一個內存池,使得安全系數與運行效率大為提高

PJSIP協議棧

????? ?PJSIP協議棧遵循標準的SIP協議,采用分層架構:SIP/SDP消息編碼解析層、傳輸管理層、SIP終端、事務層、會話層以及應用層等。由于SIP協議采用文本消息發送請求和響應,所以首先需要將SIP消息按照巴斯克范式(ABNF)編碼和解析,這就是SIP/SDP消息編碼解析層所完成的功能。傳輸管理層用來管理用戶代理與服務器之間的請求和相應;SIP終端是PJSIP中轉機制的實現,它主要負責管理各個SIP組建,例如像SIP終端實例注冊組件,分發消息到事務層、會話層及應用層,回傳處理結果,管理定時器、I/O隊列等;事務層通過狀態機機制管理SIP信令,每一次狀態機狀態的改變都將觸發回調函數;會話層負責會話的發起與響應,一般與應用層結合在一起,用于用戶交互,不同的平臺有不同的實現,這里主要使用Andriod的GUI來實現。

?? PJSIP是一個高度封裝的庫,實際上它是通過PJSUA子庫來實現應用的。一個完整的PJSUA生命周期,首先需要初始化,通過函數init()來實現。在這個函數中,將創建代理、初始化變量和堆棧,以及創建一個UDP傳輸并在最后啟動代理;第二步將為UA添加用戶,如果需要的話,還要向服務器注冊用戶;當用戶添加成功后,此時可以建立一個呼叫連接,發起會話;當會話連接成功后,就可以使用SRTP協議實時傳輸加密后的數據,進行通話。最后的過程是掛起或銷毀呼叫。

UA原理

UA(User Agency)是協議棧的具體實現,PJSIP通過封裝了的PJSUA來實現,在這一點上,大部分的SIP庫都大同小異,在此將介紹UA的工作原理。

一個典型的UA包含UAC(User Agency Client)和UAS(User Agency Server)兩部分。會話由UAC發起。當呼叫發起時,UAC將首先發送“IN-VITE”消息給SIP代理服務器,服務器收到“INVITE”消息后將返回一個應答“200 OK”,并回答“ACK”進行確認,同時通知主叫用戶(即會話發起用戶)上線通話。如果主叫端(用戶端)主動結束會話,UAC將返回“BYE”消息,同時通知服務器;如果用戶端收到服務器傳來的“BY-E”消息,回答“200”,并結束會話。

服務器端,

UAS收到UAC(用戶端)發來的“INVITE”消息,首先從消息中提取出主、被叫對象,然后檢查當前是否有空閑信道,若沒有則返回“486 BUSY HERE”(即系統忙)消息;接著將檢查被叫用戶是否在服務區,如果被叫對象不在服務范圍,則返回“404 NOT FOUND”(即用戶不在服務區);若被叫用戶成功上線,則返回“200 OK”,同時準備開始會話。

SIP協議棧一般使用SIP統一資源定位符(URL)來標識,它根據URL來尋址,如集群用戶“200”,“300”分別對應SIP用戶為“200@192.168. 1.100”,300@192.168.1.100”。本文中也使用這種方式來測試通信。

Pjsip實例分析

?代碼:simple_pjsua.c

/**

* simple_pjsua.c

*

* This is a very simple but fully featured SIP user agent, with the

* following capabilities:

*? - SIP registration

*? - Making and receiving call

*? - Audio/media to sound device.

*

* Usage:

*? - To make outgoing call, start simple_pjsua with the URL of remote

*? ? destination to contact.

*? ? E.g.:

* simpleua sip:user@remote

*

*? - Incoming calls will automatically be answered with 200.

*

* This program will quit once it has completed a single call.

*/

#include

#define THIS_FILE"APP"

#define SIP_DOMAIN"example.com"

#define SIP_USER"alice"

#define SIP_PASSWD"secret"

/* Callback called by the library upon receiving incoming call */

staticvoidon_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,

? ? pjsip_rx_data *rdata)

{

? ? pjsua_call_info ci;

? ? PJ_UNUSED_ARG(acc_id);

? ? PJ_UNUSED_ARG(rdata);

? ? pjsua_call_get_info(call_id, &ci);

PJ_LOG(3,(THIS_FILE,"Incoming call from %.*s!!",

(int)ci.remote_info.slen,

ci.remote_info.ptr));

/* Automatically answer incoming calls with 200/OK */

pjsua_call_answer(call_id,200, NULL, NULL);

}

/* Callback called by the library when call's state has changed */

staticvoidon_call_state(pjsua_call_id call_id, pjsip_event *e)

{

? ? pjsua_call_info ci;

? ? PJ_UNUSED_ARG(e);

? ? pjsua_call_get_info(call_id, &ci);

PJ_LOG(3,(THIS_FILE,"Call %d state=%.*s", call_id,

(int)ci.state_text.slen,

ci.state_text.ptr));

}

/* Callback called by the library when call's media state has changed */

staticvoidon_call_media_state(pjsua_call_id call_id)

{

? ? pjsua_call_info ci;

? ? pjsua_call_get_info(call_id, &ci);

if(ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {

// When media is active, connect call to sound device.

pjsua_conf_connect(ci.conf_slot,0);

pjsua_conf_connect(0, ci.conf_slot);

? ? }

}

/* Display error and exit application */

staticvoiderror_exit(constchar*title, pj_status_t status)

{

? ? pjsua_perror(THIS_FILE, title, status);

? ? pjsua_destroy();

exit(1);

}

/*

* main()

*

* argv[1] may contain URL to call.

*/

intmain(intargc,char*argv[])

{

? ? pjsua_acc_id acc_id;

? ? pj_status_t status;

//? 創建PJSIP

/* Create pjsua first! */

? ? status = pjsua_create();

if(status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status);

//? 校驗被叫SIP地址是否正確

/* If argument is specified, it's got to be a valid SIP URL */

if(argc >1) {

status = pjsua_verify_url(argv[1]);

if(status != PJ_SUCCESS) error_exit("Invalid URL in argv", status);

? ? }

//? ? 初始化PJSUA,設置回調函數

/* Init pjsua */

? ? {

pjsua_config cfg;

pjsua_logging_config log_cfg;

pjsua_config_default(&cfg);

cfg.cb.on_incoming_call = &on_incoming_call;

cfg.cb.on_call_media_state = &on_call_media_state;

cfg.cb.on_call_state = &on_call_state;

pjsua_logging_config_default(&log_cfg);

log_cfg.console_level =4;

status = pjsua_init(&cfg, &log_cfg, NULL);

if(status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status);

? ? }

//? ? 創建PJSIP的傳輸端口

/* Add UDP transport. */

? ? {

pjsua_transport_config cfg;

pjsua_transport_config_default(&cfg);

cfg.port =5060;

status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);

if(status != PJ_SUCCESS) error_exit("Error creating transport", status);

? ? }

//? ? 啟動PJSIP

/* Initialization is done, now start pjsua */

? ? status = pjsua_start();

if(status != PJ_SUCCESS) error_exit("Error starting pjsua", status);

//? ? 設置SIP用戶帳號

/* Register to SIP server by creating SIP account. */

? ? {

pjsua_acc_config cfg;

pjsua_acc_config_default(&cfg);

cfg.id = pj_str("sip:"SIP_USER"@"SIP_DOMAIN);

cfg.reg_uri = pj_str("sip:"SIP_DOMAIN);

cfg.cred_count =1;

cfg.cred_info[0].realm = pj_str(SIP_DOMAIN);

cfg.cred_info[0].scheme = pj_str("digest");

cfg.cred_info[0].username = pj_str(SIP_USER);

cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;

cfg.cred_info[0].data = pj_str(SIP_PASSWD);

status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);

if(status != PJ_SUCCESS) error_exit("Error adding account", status);

? ? }

//? ? 發起一個呼叫

/* If URL is specified, make call to the URL. */

if(argc >1) {

pj_str_t uri = pj_str(argv[1]);

status = pjsua_call_make_call(acc_id, &uri,0, NULL, NULL, NULL);

if(status != PJ_SUCCESS) error_exit("Error making call", status);

? ? }

//? ? 循環等待

/* Wait until user press "q" to quit. */

for(;;) {

charoption[10];

puts("Press 'h' to hangup all calls, 'q' to quit");

if(fgets(option, sizeof(option), stdin) == NULL) {

puts("EOF while reading stdin, will quit now..");

break;

}

if(option[0] =='q')

break;

if(option[0] =='h')

? ? pjsua_call_hangup_all();

? ? }

/* Destroy pjsua */

? ? pjsua_destroy();

return0;

}

simple_pjsua.c的main函數主要流程:


這里可以分析一下它的代碼及流程圖:

1、一開始是回調使用的函數,例如on_incoming_call當來電話的時候,pjsip會自動去調用你寫的這個函數,前提是你在初始化pjsua的時候設置了on_incoming_call?= &on_incoming_call,

2、error_exit退出應用所需要的操作

3、main函數:

?(1)pjsua_create()創建pjsua的第一步,如果是要打電話要確認URL是否是正確的pjsua_verify_url

?(2)初始化pjsua,pjsua_config_default(&cfg)來初始化配置,然后設置一些回調函數,設置日志,最后初始化pjsua_init(&cfg, &log_cfg, NULL);

?(3)創建UDP的傳輸,設置端口號

?(4)接下來就是啟動pjsua,通過pjsua_start();

?(5)創建賬戶,這個是重點所在,pjsua_acc_config_default初始化配置,然后設置相關的內容,id對應這url,realm是服務器的域名,還有密碼和用戶名,最后調用?pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);來實現帳號的注冊。

4、打電話,上面也提到過,你打電話的話需要驗證URL是否正確的?pjsua_verify_url,然后調用pjsua_call_make_call來打電話。

5、掛電話,調用?pjsua_call_hangup_all();

6、最后銷毀,pjsua_destroy();

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 米族金融:投資人別怕,監管層與各大媒體頻繁釋放維穩信號 8月4號,國家隊媒體新華社發文稱:答應合規組織持續運營,條...
    kenan806閱讀 181評論 0 0
  • 對中國科技實力的評價,一向是個熱門話題。最近,這個話題的熱度更是達到了一個歷史高峰。 許多人認識到了核心技術的重要...
    50bc7ab696df閱讀 157評論 0 0
  • 離婚的時候要注意以下幾點,一個是孩子,2歲以前歸母親,10歲以后要看孩子愿意跟誰,離婚后也要付給孩子贍養費一直到1...
    笑傲江湖201710閱讀 131評論 0 0
  • ??這篇文章其實昨天就該寫了,但是昨天輕易就浪費掉了一次阿里巴巴的面試機會,自己沒辦法面對昨天的自己,所以昨天就沒...
    lin_zone___三月閱讀 400評論 6 1
  • 大鵬一日同風起,扶搖直上九萬里。假令風歇時下來,猶能簸卻滄溟水。世人見我恒殊調,聞余大言皆冷笑。宣父猶能畏后生,丈...
    冰玲_7387閱讀 788評論 2 4