PJSUA2开发文档--第三章 PJSUA2高级API
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; } } }