3. PJSUA2高級API
PJSUA2是PJSUA API以上的面向對象抽象。它為構建會話發起協議(SIP)多媒體用戶代理應用程序(也稱為IP / VoIP軟電話)提供高級API。它將信令,媒體和NAT穿越功能結合到易於使用的呼叫控制API,帳戶管理,好友列表管理,在線狀態和即時消息中,以及多媒體功能,如本地會議,文件流,本地播放和語音錄制和強大的NAT穿越技術,利用STUN,TURN和ICE。
PJSUA2在PJSUA-LIB API之上實現。SIP和媒體功能和對象建模遵循PJSUA-LIB提供的(例如,我們還有帳戶,通話,好友等),但訪問它們的API是不同的。這些功能將在本章后面介紹。PJSUA2是一個C ++庫,你可以找到在pjsip
目錄中的PJSIP分布。C ++庫可以直接由本地C ++應用程序使用。但PJSUA2不僅僅是一個C ++庫。從一開始,它被設計為可以從高級非本地語言(如Java和Python)訪問。這是通過SWIG綁定來實現的。感謝SWIG,將來可以相對容易地添加與其他語言的綁定。
PJSUA2 API聲明可以pjsip/include/pjsua2
在源代碼所在的位置找到pjsip/src/pjsua2
。當您編譯PJSIP時,它將自動構建。
3.1 PJSUA2主類
以下是PJSUA2的主要類別:
3.1.1終端 Endpoint
這是PJSUA2的主要類別。您需要實例化這個類中的一個,並且從實例中可以初始化並啟動庫。
3.1.2 帳號 Account
帳戶指定SIP會話一側的人員(或端點)的身份。在其他任何事情之前,至少需要創建一個帳戶實例,並且可以從帳戶實例開始創建/接收電話以及添加好友。
3.1.3 媒體 Media
這是一個抽象基類,表示能夠生成媒體或傳播媒體的媒體元素。將 AudioMedia
子類化,然后將其子類實例化成具體類,如 AudioMediaPlayer
和 AudioMediaRecorder
3.1.4 呼叫 Call
該類表示正在進行的呼叫(或者說技術上是INVITE會話),並且可以用於操縱它,例如應答呼叫,掛斷呼叫,保持呼叫,轉接呼叫等。
3.1.5 搭檔 Buddy
該類代表一個遠程伙伴(一個人或一個SIP端點)。您可以訂閱好友的狀態來了解好友是否在線/離線等等,您可以向/從伙伴發送和接收即時消息。
3.2 一般概念
3.2.1 類使用模式
使用上面的主類的方法,可以很容易地調用對象的各種操作。但是我們如何從這些類中獲取事件/通知?以上每個主要類(Media除外)將在回調方法中獲取他們的事件。所以要處理這些事件,只需從對應的類(Endpoint,Call,Account或Buddy)派生一個類,並實現/重載相關的方法(取決於想要處理的事件)。更多內容將在后面的章節中進行說明。
3.2.2錯誤處理
使用異常作為報告錯誤的手段,因為這將使程序更自然地流動。產生錯誤的操作會引起錯誤異常。如果希望以更結構化的方式顯示錯誤,則Error類有幾個成員來解釋錯誤,例如引發錯誤的操作名稱,錯誤代碼和錯誤消息本身。
3.2.3 異步操作
如果您已經使用PJSIP開發應用程序,那么您已經了解了這些應用程序。在PJSIP中,涉及發送和接收SIP消息的所有操作都是異步的,這意味着調用該操作的功能將立即完成,您將在回調中獲得完成狀態。
例如Call類的makeCall( ) 方法。此功能用於啟動到目的地的呼出。當此函數成功返回時,並不意味着該呼叫已經建立,而是意味着該呼叫已成功啟動。您將在Call類的onCallState()回調方法中獲取呼叫進度和/或完成的報告。
3.2.4 線程
對於需要輪詢的平台,PJSUA2模塊提供自己的工作線程來輪詢PJSIP,因此無需實例化您的輪詢線程。如前所述,應用程序應該准備好讓主線程調用不同線程的回調。PJSUA2模塊本身是線程安全的。
通常,尤其是如果使用高級語言(如Python)調用PJSUA2,則需要通過將EpConfig.uaConfig.threadCnt 設置為0,來禁用PJSUA2內部工作線程。因為高級環境不喜歡被外部線程調用(如PJSIP的工作線程)。
3.2.5 垃圾收集問題
垃圾收集(Garbage collection,GC)存在於Java和Python(和其他語言,但現在我們不支持這些),並且在PJSUA2使用方面存在一些問題:
- 在Java和Python空間中創建的PJSUA2對象的過早析構,並傳遞給本機空間,而不保留對對象的引用
- 它延遲了對象(包括PJSUA2對象)的析構,導致對象的析構函數中的代碼無序執行
- GC的銷毀操作可以在之前未注冊到PJLIB的不同線程上運行,從而導致斷言assertion
當使用 Account.addBuddy()或者通過調用 EpConfig.LogConfig.setLogWriter()設置LogWriter,將Buddy對象添加到一個帳戶時,問題1的一些示例(這些示例絕不是完整的列表)。為了避免這個問題,應用程序需要維護在其應用程序中創建的對象的顯式引用,而不是依賴於PJSUA2本機庫來跟蹤這些對象,如:
class MyApp { private MyLogWriter logWriter; public void init() { /* Maintain reference to log writer to avoid premature cleanup by GC */ logWriter = new MyLogWriter(); epConfig.getLogConfig.setWriter(logWriter); } }
對於問題2和3,應用程序必須立即(使用對象的delete()方法(在Java中))來銷毀PJSUA2對象,而不是依靠GC來清理對象。例如,刪除一個帳戶,是不能夠讓它離開控制范圍的。應用程序必須手動刪除它(在Java中):
acc.delete();
3.2.6 對象持久化
PJSUA2包括 PersistentObject(持久對象) 類,用於提供從文檔(字符串或文件)讀取/寫入數據的功能。數據可以是簡單的數據類型,如布爾值,數字,字符串和字符串數組,或用戶定義的對象。目前的實現了支持從JSON文件讀取和寫入到JSON文件([ http://tools.ietf.org/html/rfc4627 RFC 4627]),但該框架允許應用來擴展API以支持其他的文檔格式。
因此,從PersistentObject繼承的類,如EpConfig(端點配置),AccountConfig(帳戶配置)和BuddyConfig(好友配置),可以從文件加載/保存到文件。
舉個例子來保存配置文件:
EpConfig epCfg; JsonDocument jDoc; epCfg.uaConfig.maxCalls = 61; epCfg.uaConfig.userAgent = "Just JSON Test"; jDoc.writeObject(epCfg); jDoc.saveFile("jsontest.js");
從文件加載:
EpConfig epCfg; JsonDocument jDoc; jDoc.loadFile("jsontest.js"); jDoc.readObject(epCfg);
3.3 構建(Building) PJSUA2
PJSUA2 C ++庫將由PJSIP構建系統默認構建。需要標准C++庫。
3.4 構建Python和Java SWIG模塊
對於Python和Java的SWIG模塊,是通過在目錄“pjsip-apps/src/swig” 下調用內置 make
和手動make install。make install將安裝Python SWIG模塊到用戶的 site-packages 目錄
3.4.1要求
- SWIG
JDK
。Python
,建議使用2.7或更高版本(我們的Python示例應用程序pygui
需要2.7或更高版本,但是pjsua2
Python綁定應該能夠在舊版本上運行)。對於Linux / UNIX,還需要Python developent package(python-devel(如在Fedora上)或python2.7-dev(如在Ubuntu上))。對於Windows,需要MinGW和Python SDK如ActivePython的-2.7.5(來自ActiveState)。
3.4.2測試安裝
要測試安裝,只需運行python和import pjsua2
module:
$ python > import pjsua2 > ^Z
3.5 在C++應用程序中使用
正如在前面的章節中提到的,一個C++應用程序可以使用pjsua2本身,而在同一時間仍然有權訪問低層對象和擴展庫的能力(如果需要)。使用API將與本書中編寫的API參考完全相同。
這是一個完整的C++應用程序示例,可以讓您了解API。下面的代碼段,初始化庫,並創建一個注冊到我們pjsip.org 的SIP服務器的帳戶。
#include <pjsua2.hpp> #include <iostream> using namespace pj; // Subclass to extend the Account and get notifications etc. class MyAccount : public Account
{ public: virtual void onRegState(OnRegStateParam &prm)
{ AccountInfo ai = getInfo(); std::cout << (ai.regIsActive? "*** Register:" : "*** Unregister:") << " code=" << prm.code << std::endl; } }; int main() { Endpoint ep; ep.libCreate(); // Initialize endpoint EpConfig ep_cfg; ep.libInit( ep_cfg ); // Create SIP transport. Error handling sample is shown TransportConfig tcfg; tcfg.port = 5060; try
{ ep.transportCreate(PJSIP_TRANSPORT_UDP, tcfg); }
catch (Error &err)
{ std::cout << err.info() << std::endl; return 1; } // Start the library (worker threads etc) ep.libStart(); std::cout << "*** PJSUA2 STARTED ***" << std::endl; // Configure an AccountConfig AccountConfig acfg; acfg.idUri = "sip:test@pjsip.org"; acfg.regConfig.registrarUri = "sip:pjsip.org"; AuthCredInfo cred("digest", "*", "test", 0, "secret"); acfg.sipConfig.authCreds.push_back( cred ); // Create the account MyAccount *acc = new MyAccount; acc->create(acfg); // Here we don't have anything else to do.. pj_thread_sleep(10000); // Delete the account. This will unregister from server delete acc; // This will implicitly shutdown the library return 0; }
3.6 在Python應用程序中使用
中上面的C ++示例代碼等價如下Python代碼:
# Subclass to extend the Account and get notifications etc. class Account(pj.Account): def onRegState(self, prm): print "***OnRegState: " + prm.reason # pjsua2 test function def pjsua2_test(): # Create and initialize the library ep_cfg = pj.EpConfig() ep = pj.Endpoint() ep.libCreate() ep.libInit(ep_cfg) # Create SIP transport. Error handling sample is shown sipTpConfig = pj.TransportConfig(); sipTpConfig.port = 5060; ep.transportCreate(pj.PJSIP_TRANSPORT_UDP, sipTpConfig); # Start the library ep.libStart(); acfg = pj.AccountConfig(); acfg.idUri = "sip:test@pjsip.org"; acfg.regConfig.registrarUri = "sip:pjsip.org"; cred = pj.AuthCredInfo("digest", "*", "test", 0, "pwtest"); acfg.sipConfig.authCreds.append( cred ); # Create the account acc = Account(); acc.create(acfg); # Here we don't have anything else to do.. time.sleep(10); # Destroy the library ep.libDestroy() # # main() # if __name__ == "__main__": pjsua2_test()
3.7 在Java應用程序中使用
上面的C ++示例代碼等價如下Java代碼:
import org.pjsip.pjsua2.*; // Subclass to extend the Account and get notifications etc. class MyAccount extends Account { @Override public void onRegState(OnRegStateParam prm) { System.out.println("*** On registration state: " + prm.getCode() + prm.getReason()); } } public class test { static { System.loadLibrary("pjsua2"); System.out.println("Library loaded"); } public static void main(String argv[]) { try { // Create endpoint Endpoint ep = new Endpoint(); ep.libCreate(); // Initialize endpoint EpConfig epConfig = new EpConfig(); ep.libInit( epConfig ); // Create SIP transport. Error handling sample is shown TransportConfig sipTpConfig = new TransportConfig(); sipTpConfig.setPort(5060); ep.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP, sipTpConfig); // Start the library ep.libStart(); AccountConfig acfg = new AccountConfig(); acfg.setIdUri("sip:test@pjsip.org"); acfg.getRegConfig().setRegistrarUri("sip:pjsip.org"); AuthCredInfo cred = new AuthCredInfo("digest", "*", "test", 0, "secret"); acfg.getSipConfig().getAuthCreds().add( cred ); // Create the account MyAccount acc = new MyAccount(); acc.create(acfg); // Here we don't have anything else to do.. Thread.sleep(10000); /* Explicitly delete the account. * This is to avoid GC to delete the endpoint first before deleting * the account. */ acc.delete(); // Explicitly destroy and delete endpoint ep.libDestroy(); ep.delete(); } catch (Exception e) { System.out.println(e); return; } } }