PJSIP開源庫詳解


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

 

 
        


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM