开源 SIP 协议栈 PJSIP 简介

Published: 2020-04-20

Tags: pjsip pjsua2

本文总阅读量

前一段时间简单了解了 pjsip,后来研究别的就耽搁下来,仔细翻官网发现这个协议栈包含的库很多,提供的功能也有侧重,看的眼花缭乱,不知道从那里下手,官网文档列表感受一下:

好在官方还是介绍了它们之间的关系

各层级模块提供的功能描述如下

模块名称 功能描述
PJLIB 使用C语言编写的基础库,它是所有其它库运行的基础。
PJLIB-UTIL 提供辅助工具库,如文本处理,XML;STUN;加密算法;DNS等。
PJMEDIA 多媒体栈,提供音视频处理功能。
PJMEDIA-CODEC 包含多种媒体编解码器实现。
PJSIP-CORE 核心库,提供诸如 Endpoint,Transaction,Dialog 对象。
PJSIP-SIMPLE 基于 Dialog,提供基础的事件功能,实现了状态与呼叫转移功能。
PJSIP-UA 基于 Dialog,提供更高级别的 INVITE 会话抽象,实现了客户端注册与呼叫转移功能。
PJSUA-LIB C语言实现,面向过程,封装了以上所有的功能,易用性提高。

补充一个在介绍文档出现之后的 PJSUA2,图中没有画。

模块名称 功能描述
PJSUA2 C语言实现,对 PJSUA-LIB 进一步封装,面向对象,更加的易用。

应该从哪里开始?

作者推荐刚接触 PJSIP 协议栈的开发者从以下两个方案中选择进行开发

  1. 使用 PJSUA-LIB / PJSUA2,比 PJSIP 更高层级的接口,包含了封装良好的通用 SIP 功能。
  2. 使用 PJSIP + PJMEDIA 可以更加的灵活,当然,也意味着更陡峭的学习曲线。

补充:

  1. 基于 PJSUA-LIB / PJSUA2 进行开发,会方便很多,比如它提供了多客户端注册,高层级的会话,好友列表,在线状态与即时消息,更易用的媒体操作,同时它也有保留了一定的应用自定义的能力。而基于 PJSIP 协议栈和媒体栈开发,有必要阅读一遍图1中文档列表最后一个文档 “PJSIP Developer's Guide (PDF)”,它是了解 PJSIP 协议栈设计概念的终极指南;在代码的 pjsip-apps/src/samples 目录,有一些例子可供参考;PJSUA-LIB 的源代码也有助于了解如何使用 PJSIP / PJMEDIA 开发高层级接口。

  2. 本小节中 PJSIP 是指 PJSIP-CORE,PJSIP-SIMPLE,PJSIP-UA 这些较底层的库。

再次提醒

较底层的接口(如:PJSIP,PJMEDIA,PJNATH)使用困难,如果你不是因为以下这些原因,最好不要直接使用它们。

  1. 你在开发中,只想使用PJSIP协议栈中的一个库,如PJNATH(用于NAT穿透的ICE, STUN和TURN的开源库 )。
  2. 特别需要节省空间的场景(如用几千字节代替几兆字节)。
  3. 你不是要开发一个 SIP UA 应用。

PJSUA2 简介

PJSUA2 是目前官方推荐的方式,在更高级别抽象的 PJSUA2 出现之前,PJSUA-LIB 被大多数的 PJSIP 用户使用。

它基于 PJSUA-LIB 模块,使用 C++ 开发的,面向对象。它的 API 和 PJSUA-LIB 不同,但是更加易用,且拥有更好的文档。

借助 SWIG 提供的绑定功能,Java、Python、C# 也可以使用 PJSUA2 的 API,不过脚本语言使用 PJSUA2 不能访问 PJSUA-LIB 或可能会使用到的C库。

还是推荐使用原生的 C++ 来基于 PJSUA2 进行开发,简单来说就是问题更少,接口更加统一,用到底层接口的时候没有限制,同时我觉得网上的参考资料会相对多些。

使用 PJSUA2 注意事项

  1. API 使用 Exception 方式返回错误,而不是 return。
  2. PJSUA2 使用了 C++ 标准库(STL)。
  3. 在现代设备上,因抽象而带来的额外性能损失应该可以忽略不记。

PJSUA2 主要类

示例代码

int main()
{
  Endpoint ep;
  ep.libCreate();

  // Init endpoint
  EpConfig ep_cfg;
  ep_cfg.uaConfig.userAgent = "sip-server";
  ep.libInit( ep_cfg );

  // create transport.
  TransportConfig tcfg;
  tcfg.port = 5060;
  try {
    ep.transportCreate(PJSIP_TRANSPORT_UDP, tcfg);
  } catch (Error &err) {
    std::cout << err.info() << std::endl;
    return 1;
  }

  // launch endpoint
  ep.libStart();
  std::cout << " *** PJSUA2 STARTED *** " << std::endl;
  Version version = ep.libVersion();
  std::cout << "version: " + version.full << std::endl;

  // init account info
  AccountConfig acfg;
  acfg.idUri = "sip:34020000002000000001@192.168.3.189:5060";

  // create account
  ServerAccount * acc = new ServerAccount;
  acc->create(acfg);

  pj_thread_sleep(2000);

  return 0;
}

1)ENDPOINT 类

ENDPOINT 是单例模式,一个应用有且只有一个实例,在最开始时创建,所有API都是基于它的,换句话说即销毁实例后不能再调用任何API,EpConfig 类用于 endpoint 类的自定义配置,可以设置 UAConfig,MediaConfig,LogConfig

之后需要创建传输(Transports)配置,至少创建一个用于接收发送 SIP 信令。

启动实例,这时它会进行一些初始化,如 STUN 连接,初始化视音频设备等。

2)Accounts 类

Accounts 用于用户的身份确认,一个用户有一个URI。

一个应用中至少应该存在一个 Accounts 实例,因为任何访问请求都需要携带 account 上下文,如果没有用户认证要求,应用可以创建一个 userless 账户。

使用 Accounts 应该创建一个它的子类 MyAccount,用于接收 onRegState、onIncomingCall 等事件。

同时使用 AccountConfig 类来初始化 Account 的配置。

3)Media 类

Media 可以产生媒体或操作媒体,AudioMedia 是重要的子类。 它可以捕获设备音频,回放设备音频,从远端用户获取发送音频,播放本地 WAV 文件,记录音频到本地 WAV 文件等。

4)Calls 类 用于发起请求的类 使用 Calls 也像 Account 一样,应该初始化一个子类,如MyCall,用于回调接收回调事件。

5)Buddy 类 在 Buddy 中,可以设置子类来接收好友的 Presence(在线状态) 变更等。

参考

  1. https://www.pjsip.org/docs/latest-2/pjsip/docs/html/index.htm