pjsip简介

PJSIP开源库详解

转自: http://www.cnblogs.com/my_life/articles/2175462.html

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 处理。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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.      初始化: (一下代码仅供参考,如有疑问邮件联系[email protected])
    1
    PJLIB-UTIL初始化 ---> 创建 pool factory ---> 创建SIP endpoint ---> 创建SIP transport ---> 初始化transaction layer ---> 初始化UA layer ---> 初始化 100rel module(处理临时响应) ---> 创建invite session module ---> 创建media endpoint ---> 创建media transport
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    /* 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

猜你喜欢

转载自blog.csdn.net/twd_1991/article/details/80509321