PJSIP是一个包含了SIP、SDP、RTP、RTCP、STUN、ICE等协议实现的开源库。它把基于信令协议SIP的多媒体框架和NAT穿透功能整合成高层次、抽象的多媒体通信API,这套API能够很容易的一直到各种构架中,不管是桌面计算机,还是嵌入式设备等。

一,PJSIP的编译与安装

PJSIP的下载地址 :

http://www.pjsip.org/release/2.6/pjproject-2.6.tar.bz2

生成makefile

./configure

编译与安装

make && make dep && make install

注意,如果使用老版本的gcc编译pjsip-2.6可能出现错误,此时或升级gcc或在./configure的时候disable某些模块。

二、PJSIP的组织架构介绍

    PJSIP开源库中主要包含两部分,一部分是SIP协议栈(SIP消息处理),另一部分媒体流处理模块(RTP包的处理)。

    SIP协议栈模块

SIP协议栈这个模块,开源库由底层往上层做了各个层次的封装。

pjlib是最底层的,最基础的库,它实现的是平台抽象与框架(数据结构、内存分配、文件I/O、线程、线程同步等),是SIP协议栈的基石。其他所有与SIP相关的模块都是基于PJLIB来实现的。

pjlib-util则是封装了一些常用的算法,例如MD5、CRC32等,除此之外封装了一些涉及到字符串、文件格式解析操作的API,例如XML格式解析。

pjsip-core则是SIP协议栈的核心,在该库中,包含了三个非常重要的模块,分别是SIP endpoint、SIP transaction module、SIP dialog module、transport layer。后续会着重介绍前三个模块。

pjsip-simple则是SIP事件与出席框架,如果你程序中要实现出席制定,则该库是必备的。

pjsip-ua是INVITE会话的高层抽象,使用该套API比较容易创建一个SIP会话。此外该库还实现了SIP client的注册API。

pjsua是PJSIP开源库中能够使用到的最高层次抽象API,该库是基于pjsip-ua及以下库做了高层次的分装。

基于上图,SIP endpoint是整个协议栈模块的核心,一般来说,一个进程内只能创建一个SIP endpoint。因此SIP endpoint是基于PJSIP开发的应用程序内所有SIP objects的管理与拥有者。根据官方文档的描述,SIP endpoint主要承担了一下角色:

  • 为所有的SIP对象管理内存池的分配与释放(在基于pjsip的应用程序中,动态内存的分配是在内存池基础上进行的)
  • 接收来自于传输层的消息,并将消息分配到上层,这里的上层指的是图中的SIP transaction module、SIP dialog module、application module。优先级顺序是SIP transaction module > SIP dialog module > application module。如果消息被上层接收处理,则消息由接收的那层继续往上传递处理。例如,SIP endpoint收到的SIP消息,会先较低给SIP transaction module,如果SIP transaction module在transaction hash table中找到消息所对应的transaction,则SIP transaction module在完成相应的处理后,会将消息尝试继续往上传递;如果SIP transaction module在transaction hash table中没有找到消息所对应的transaction,则该SIP消息由SIP endpoint继续往上传递。当SIP消息不能被上层所有module处理,则该消息由SIP endpoint来做默认处理。
  • SIP endpoint负责管理模块(module),module在这里是对该库进行扩展的一种方法,在代码里代表的是一个结构体数据,上面会定义module名字、优先级、以及一些函数指针。开发者可以自己定义一些优先级高于SIP transaction module的module来截获对SIP消息的处理。
  • 它为所有对象和分发事件提供单个轮询函数。

transport layer是sip消息的接收与发送模块,目前支持TCP、UDP、TLS三种方式。

媒体流处理模块

    该模块主要包含两部分,一是media transport,负责接收媒体流;二是媒体端口(media port)框架,该框架实现了各种媒体端口,每一个media port上定义各种操作(创建、销毁、get/put等),常用媒体端口有:File writer(记录媒体文件),File player(播放媒体文件)、stream port 、conference port(可以实现多方通话)、master port等。

media transport目前支持RTP(UDP)、SRTP(加密)、ICE(NAT穿透)

当SIP会话建立后,底层的媒体流处理流程可参考下图:

在上图上,从左往右,以此是media transport、stream port、conference port、sound device port、sound device 。前四个需要自己在程序里创建,最后一个sound device 是与sound device port相关联的,创建sound device port的时候便会关联到sound device。媒体流数据是通过各个media port操作进行传递的,在上图中驱动媒体流由左往右流动的“驱动器是”sound device port,该端口是通过sound device的硬件驱动不停向与它连接的media port实施/get or put fram 操作,从而媒体流得以流动。

在媒体流处理模块中,像sound device port的端口,我们成为主动型端口或者驱动型端口。媒体流处理模块中另外一个主动型端口就是master port。

在上图中最重要的是stream port ,如果你使用了pjmedia库,则必少不了stream port 。在stream port 中,从接收RTP包的角度讲,RTP包会被做一下处理:

                         decode RTP into frame ---> put each frame into jitter buffer ---> decode frame into samples with codec

    从发送RTP包的角度讲,除了包含媒体流数据的RTP包外,还会存在keep alive UDP pakcet。

stream port 与media transport之间的连接是通过attach和detach操作完成的,该操作是在创建stream port执行。除此之外,为了能正常接收RTP流,我们需要为media transport提供轮训机制,通常我们使用SIP endpoint的I\O queue即可,这个是在创建media transport时通过参数设置的。

注:* jitter buffer是一种缓冲技术,主要用于音频视频流的缓冲处理。

三,基于PJSIP的VOIP程序开发

  1.     创建module,目的处理来自于SIP UAS的invite 请求,因为该invite 请求初次被处理的时候,它不属于任何Dialog或者transaction。所以由自己创建的application module 处理。

    static pjsip_module mod_pjsip =
    {
    NULL, NULL, /* prev, next. */
    { "mod-pjsip", 9 }, /* Name. */
    -1, /* Id */
    PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
    NULL, /* load() */
    NULL, /* start() */
    NULL, /* stop() */
    NULL, /* unload() */
    &on_rx_request, /* on_rx_request() */
    NULL, /* on_rx_response() */
    NULL, /* on_tx_request. */
    NULL, /* on_tx_response() */
    NULL, /* on_tsx_state() */
    };

      

  2. 初始化: (一下代码仅供参考,如有疑问邮件联系vslinux@qq.com)
    PJLIB-UTIL初始化 ---> 创建 pool factory ---> 创建SIP endpoint ---> 创建SIP transport ---> 初始化transaction layer ---> 初始化UA layer ---> 初始化 100rel module(处理临时响应) ---> 创建invite session module ---> 创建media endpoint ---> 创建media transport
        /* Then init PJLIB-UTIL: */
    status = pjlib_util_init();
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Must create a pool factory before we can allocate any memory. */
    pj_caching_pool_init(&sip_config.cp, &pj_pool_factory_default_policy, 0);
    sip_config.pool = pj_pool_create(&sip_config.cp.factory, "pjsip-app", 1000, 1000, NULL); /* Create global endpoint: */
    {
    const pj_str_t *hostname;
    const char *endpt_name; /* Endpoint MUST be assigned a globally unique name.*/
    hostname = pj_gethostname();
    endpt_name = hostname->ptr; /* Create the endpoint: */ status = pjsip_endpt_create(&sip_config.cp.factory, endpt_name,
    &sip_config.g_endpt);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
    } for (i=0; i<sip_config.thread_count; ++i) {
    pj_thread_create( sip_config.pool, "app", &sip_worker_thread, NULL,
    0, 0, &sip_config.sip_thread[i]);
    }
    /*
    * Add UDP transport, with hard-coded port
    * Alternatively, application can use pjsip_udp_transport_attach() to
    * start UDP transport, if it already has an UDP socket (e.g. after it
    * resolves the address with STUN).
    * */
    {
    /* ip address of localhost */
    pj_sockaddr addr; pj_sockaddr_init(AF, &addr, NULL, (pj_uint16_t)SIP_PORT); if (AF == pj_AF_INET()) {
    status = pjsip_udp_transport_start( sip_config.g_endpt, &addr.ipv4, NULL,
    1, &sip_config.tp);
    } else if (AF == pj_AF_INET6()) {
    status = pjsip_udp_transport_start6(sip_config.g_endpt, &addr.ipv6, NULL,
    1, &sip_config.tp);
    } else {
    status = PJ_EAFNOTSUP;
    } if (status != PJ_SUCCESS) {
    app_perror(THIS_FILE, "Unable to start UDP transport", status);
    return 1;
    }
    PJ_LOG(3,(THIS_FILE, "SIP UDP listening on %.*s:%d",
    (int)sip_config.tp->local_name.host.slen, sip_config.tp->local_name.host.ptr,
    sip_config.tp->local_name.port));
    }
    /* Set transport state callback */
    {
    pjsip_tp_state_callback tpcb;
    pjsip_tpmgr *tpmgr; tpmgr = pjsip_endpt_get_tpmgr(sip_config.g_endpt);
    tpcb = pjsip_tpmgr_get_state_cb(tpmgr); if (tpcb != &on_tp_state_callback) {
    sip_config.old_tp_cb = tpcb;
    pjsip_tpmgr_set_state_cb(tpmgr, &on_tp_state_callback);
    }
    }
    /*
    * Init transaction layer.
    * This will create/initialize transaction hash tables etc.
    * */
    status = pjsip_tsx_layer_init_module(sip_config.g_endpt);
    if (status != PJ_SUCCESS) {
    app_perror(THIS_FILE, "Unable to initialize transaction layer", status);
    return status;
    }
    /*
    * Initialize UA layer module.
    * This will create/initialize dialog hash tables etc.
    * */
    status = pjsip_ua_init_module( sip_config.g_endpt, NULL );
    if (status != PJ_SUCCESS) {
    app_perror(THIS_FILE, "Unable to initialize UA layer", status);
    return status;
    } status = pjsip_100rel_init_module(sip_config.g_endpt);
    if (status != PJ_SUCCESS) {
    app_perror(THIS_FILE, "Unable to initialize 100rel", status);
    return status;
    }
    /*
    * Init invite session module.
    * The invite session module initialization takes additional argument,
    * i.e. a structure containing callbacks to be called on specific
    * occurence of events.
    * We use on_media_update() callback in this application to start
    * media transmission.
    * */
    {
    /* Init the callback for INVITE session: */
    pj_bzero(&sip_config.inv_cb, sizeof(sip_config.inv_cb));
    sip_config.inv_cb.on_state_changed = &call_on_state_changed;
    sip_config.inv_cb.on_new_session = &call_on_forked;
    sip_config.inv_cb.on_media_update = &call_on_media_update; /* Initialize invite session module: */
    status = pjsip_inv_usage_init(sip_config.g_endpt, &sip_config.inv_cb);
    if (status != PJ_SUCCESS) {
    app_perror(THIS_FILE, "Unable to initialize invite session module", status);
    return 1;
    }
    }
    /* Register our module to receive incoming requests. */
    status = pjsip_endpt_register_module( sip_config.g_endpt, &mod_pjsip);
    if (status != PJ_SUCCESS) {
    app_perror(THIS_FILE, "Unable to register pjsip app module", status);
    return 1;
    } /*
    * Initialize media endpoint.
    * This will implicitly initialize PJMEDIA too.
    * */
    status = pjmedia_endpt_create(&sip_config.cp.factory, NULL, 1, &sip_config.g_med_endpt);
    if (status != PJ_SUCCESS) {
    app_perror(THIS_FILE, "Unable to create media endpoint", status);
    return 1;
    } /* Add PCMA/PCMU codec to the media endpoint. */
    status = pjmedia_codec_g711_init(sip_config.g_med_endpt);
    if (status != PJ_SUCCESS) {
    app_perror(THIS_FILE, "Unable to add codec", status);
    return 1;
    } /* Create media transport used to send/receive RTP/RTCP socket.
    * One media transport is needed for each call. Application may
    * opt to re-use the same media transport for subsequent calls.
    * */
    rtp_port = (pj_uint16_t)(sip_config.rtp_start_port & 0xFFFE);
    /* Init media transport for all calls. */
    for (i=0, count=0; i<sip_config.max_calls; ++i, ++count) {
    unsigned j;
    for (j = 0; j < PJ_ARRAY_SIZE(sip_config.call[i].transport); ++j) {
    /* Repeat binding media socket to next port when fails to bind
    * * to current port number.
    * */
    int retry;
    sip_config.call[i].media_index = j;
    for (retry=0; retry<100; ++retry,rtp_port+=2) {
    status = pjmedia_transport_udp_create3(sip_config.g_med_endpt, AF, NULL, NULL,
    rtp_port, 0,
    &sip_config.call[i].transport[j]);
    if (status == PJ_SUCCESS) {
    rtp_port += 2;
    /*
    * Get socket info (address, port) of the media transport. We will
    * need this info to create SDP (i.e. the address and port info in
    * the SDP).
    * */
    pjmedia_transport_info_init(&sip_config.call[i].tpinfo[j]);
    pjmedia_transport_get_info(sip_config.call[i].transport[j], &sip_config.call[i].tpinfo[j]);
    PJ_LOG(3,(THIS_FILE,"create media TP for call %d success!",i));
    break;
    }
    }
    }
    if (status != PJ_SUCCESS) {
    app_perror(THIS_FILE, "Unable to create media transport", status);
    goto err;
    }
    }

      

  3. none

PJSIP开源库详解的更多相关文章

  1. Lua的协程和协程库详解

    我们首先介绍一下什么是协程.然后详细介绍一下coroutine库,然后介绍一下协程的简单用法,最后介绍一下协程的复杂用法. 一.协程是什么? (1)线程 首先复习一下多线程.我们都知道线程——Thre ...

  2. Python--urllib3库详解1

    Python--urllib3库详解1 Urllib3是一个功能强大,条理清晰,用于HTTP客户端的Python库,许多Python的原生系统已经开始使用urllib3.Urllib3提供了很多pyt ...

  3. Struts标签库详解【3】

    struts2标签库详解 要在jsp中使用Struts2的标志,先要指明标志的引入.通过jsp的代码的顶部加入以下的代码: <%@taglib prefix="s" uri= ...

  4. STM32固件库详解

    STM32固件库详解   emouse原创文章,转载请注明出处http://www.cnblogs.com/emouse/ 应部分网友要求,最新加入固件库以及开发环境使用入门视频教程,同时提供例程模板 ...

  5. MySQL5.6的4个自带库详解

    MySQL5.6的4个自带库详解 1.information_schema详细介绍: information_schema数据库是MySQL自带的,它提供了访问数据库元数据的方式.什么是元数据呢?元数 ...

  6. php中的PDO函数库详解

    PHP中的PDO函数库详解 PDO是一个“数据库访问抽象层”,作用是统一各种数据库的访问接口,与mysql和mysqli的函数库相比,PDO让跨数据库的使用更具有亲和力:与ADODB和MDB2相比,P ...

  7. STM32 HAL库详解 及 手动移植

    源: STM32 HAL库详解 及 手动移植

  8. 爬虫入门之urllib库详解(二)

    爬虫入门之urllib库详解(二) 1 urllib模块 urllib模块是一个运用于URL的包 urllib.request用于访问和读取URLS urllib.error包括了所有urllib.r ...

  9. Python爬虫系列-Urllib库详解

    Urllib库详解 Python内置的Http请求库: * urllib.request 请求模块 * urllib.error 异常处理模块 * urllib.parse url解析模块 * url ...

随机推荐

  1. C/C++(基础编码-补码详解)

    两个数的交换 1.引入第三者. 2.求和运算,求差.(这样会产生内存溢出) 3.异或运算 a = a^b; b = a^b; a = a^b; 8b(bit位) = 1B(Byte=字节)//最小单位 ...

  2. terminfo 数据库?

    什么是 terminfo 数据库? UNIX 系统上的 terminfo 数据库用于定义终端和打印机的属性及功能,包括各设备(例如,终端和打印机)的行数和列数以及要发送至该设备的文本的属性.UNIX ...

  3. Python实现的基于ADB的Android远程工具

    本工具为原创,涉及知识: - Python编程 - Tkinter GUI编程 - ADB通信机制 代码已经开源: https://code.csdn.net/codehat/andev/tree/m ...

  4. RelativeLayout-属性大全

    // 相对于给定ID控件 <!--将该控件的底部置于给定ID的控件之上--> android:layout_above <!--将该控件的底部置于给定ID的控件之下--> an ...

  5. 体验SUSE (附视频演示)

    操作动画演示 本文出自 "李晨光原创技术博客" 博客,谢绝转载!

  6. HTML5手机应用的最大优势就是可以在网页上直接调试和修改

    HTML5手机应用的最大优势就是可以在网页上直接调试和修改

  7. BZOJ2329: [HNOI2011]括号修复(Splay)

    解题思路: Replace.Swap.Invert都可以使用Splay完美解决(只需要解决一下标记冲突就好了). 最后只需要统计左右括号冲突就好了. 相当于动态统计最大前缀合和最小后缀和. 因为支持翻 ...

  8. BZOJ2119: 股市的预测(后缀数组)

    Description 墨墨的妈妈热爱炒股,她要求墨墨为她编写一个软件,预测某只股票未来的走势.股票折线图是研究股票的必备工 具,它通过一张时间与股票的价位的函数图像清晰地展示了股票的走势情况.经过长 ...

  9. 【2017百度之星程序设计大赛 - 复赛】Valley Numer

    [链接]http://acm.hdu.edu.cn/showproblem.php?pid=6148 [题意] 在这里写题意 [题解] 先把1..N里面的山峰数字个数算出来->x 然后用N减去这 ...

  10. COGS——T 2057. [ZLXOI2015]殉国

    http://cogs.pro/cogs/problem/problem.php?pid=2057 ★☆   输入文件:BlackHawk.in   输出文件:BlackHawk.out   评测插件 ...