天天看點

pjsua_lib示例之簡單UA

這是pjsip2.5.5的samples工程内提供包括媒體及完整UA功能的簡單應用,檔案位置:pjproject-2.5.5\pjsip-apps\src\samples\simpleua.c,使用者代理(UA)在SDK協商成功後啟動RTP媒體傳輸。

此程式不需要注冊到SIP伺服器,它能夠完成:基本呼叫、在5060端口傳輸UDP、在4000端口傳輸RTP、SDP協商、語音編解碼器隻支援PCMA和PCMU、聲霸卡的語音媒體。

Samples工程編譯完成後,在指令行輸入:

       simpleua 要呼叫的sip位址

要呼叫的sip位址格式: sip:使用者名@對端IP

*呼入的呼叫先以180自動應答,再以200自動應答,此程式單次呼叫後即退出。

#include <pjsip.h>

#include <pjmedia.h>

#include <pjmedia-codec.h>

#include <pjsip_ua.h>

#include <pjsip_simple.h>

#include <pjlib-util.h>

#include <pjlib.h>

#define THIS_FILE  "simpleua.c"

#include "util.h"

//設定

#define AF      pj_AF_INET()// 如果使用IPV6,将此行改為pj_AF_INET6()

                             //必須設定PJ_HAS_IPV6

                             //并且作業系統也需要支援 IPv6.  */

#if 0

#define SIP_PORT    5080             //偵聽的SIP端口

#define RTP_PORT    5000             // RTP端口

#else

#define SIP_PORT    5060            //偵聽的SIP端口

#define RTP_PORT    4000            // RTP端口

#endif

#define MAX_MEDIA_CNT   2       //媒體數量,設定為1将支援音頻,

                              //設定為2,即支援音頻,又支援視訊

//全局靜态變量

static pj_bool_t        g_complete;    //退出狀态

static pjsip_endpoint      *g_endpt;        // SIP終端

static pj_caching_pool       cp;          //全局pool factory

static pjmedia_endpt       *g_med_endpt;   // 媒體終端

staticpjmedia_transport_info g_med_tpinfo[MAX_MEDIA_CNT];

                        //媒體傳輸用套接字資訊

staticpjmedia_transport    *g_med_transport[MAX_MEDIA_CNT];

                        //媒體流傳輸端口

staticpjmedia_sock_info     g_sock_info[MAX_MEDIA_CNT]; 

                        //套接字資訊組

//呼叫部分變量

staticpjsip_inv_session    *g_inv;      //目前invite傳話

static pjmedia_stream       *g_med_stream;  //語音流

static pjmedia_snd_port     *g_snd_port;   //聲霸卡裝置

#if defined(PJMEDIA_HAS_VIDEO)&& (PJMEDIA_HAS_VIDEO != 0)

staticpjmedia_vid_stream   *g_med_vstream; //視訊流

static pjmedia_vid_port    *g_vid_capturer;//視訊捕獲裝置

static pjmedia_vid_port    *g_vid_renderer;//視訊播放裝置

#endif  //PJMEDIA_HAS_VIDEO

//函數聲明

//呼叫過程上,當SDP協商完成後,回調此函數

static void  call_on_media_update( pjsip_inv_session *inv,

                 pj_status_t status);

//當invite會話狀态變化後,回調此函數

static void  call_on_state_changed( pjsip_inv_session *inv,

                   pjsip_event *e);

//當新對話建立後,回調此函數

static void  call_on_forked(pjsip_inv_session *inv, pjsip_event *e);

//對話之外,當收到請求時,回調此函數

static pj_bool_t  on_rx_request( pjsip_rx_data *rdata );

//此PJSIP子產品注冊到應用用于處理對話或事務之外的請求,主要目的是處理INVITE請求消息,在那裡将為它建立新的對話和INVITE會話

static pjsip_module  mod_simpleua =

{

    NULL, NULL,             

    { "mod-simpleua", 12 },     

    -1,                 

    PJSIP_MOD_PRIORITY_APPLICATION,

    NULL,               

    NULL,               

    NULL,               

    NULL,               

    &on_rx_request,         

    NULL,               

    NULL,               

    NULL,               

    NULL,               

};

//呼入消息通知

static pj_bool_t  logging_on_rx_msg(pjsip_rx_data *rdata)

{

    PJ_LOG(4,(THIS_FILE, "RX %d bytes%s from %s %s:%d:\n"

             "%.*s\n"

             "--end msg--",

             rdata->msg_info.len,

             pjsip_rx_data_get_info(rdata),

             rdata->tp_info.transport->type_name,

             rdata->pkt_info.src_name,

             rdata->pkt_info.src_port,

             (int)rdata->msg_info.len,

             rdata->msg_info.msg_buf));

    //此處必須傳回false,否則其它消息将不被處理

    return PJ_FALSE;

}

//呼出消息通知

static pj_status_t  logging_on_tx_msg(pjsip_tx_data *tdata)

{

    PJ_LOG(4,(THIS_FILE, "TX %d bytes%s to %s %s:%d:\n"

             "%.*s\n"

             "--end msg--",

             (tdata->buf.cur - tdata->buf.start),

             pjsip_tx_data_get_info(tdata),

             tdata->tp_info.transport->type_name,

             tdata->tp_info.dst_name,

             tdata->tp_info.dst_port,

             (int)(tdata->buf.cur - tdata->buf.start),

             tdata->buf.start));

    return PJ_SUCCESS;

}

//子產品執行個體

static pjsip_module  msg_logger =

{

    NULL, NULL,             

    { "mod-msg-log", 13 },      

    -1,                 

    PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,

    NULL,               

    NULL,               

    NULL,               

    NULL,               

    &logging_on_rx_msg,         

   &logging_on_rx_msg,         

    &logging_on_tx_msg,         

    &logging_on_tx_msg,         

    NULL,               

};

int main(int argc, char *argv[])

{

    pj_pool_t  *pool = NULL;

    pj_status_t  status;

    unsigned i;

    status = pj_init();

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    pj_log_set_level(5);

    status = pjlib_util_init();

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    pj_caching_pool_init(&cp,&pj_pool_factory_default_policy, 0);

    //建立全局終端

    {

    const  pj_str_t  *hostname;

    const  char  *endpt_name;

    //終端必須配置設定全局唯一名稱,此處簡單地使用主機名實作

    hostname = pj_gethostname();

    endpt_name = hostname->ptr;

    //建立終端

    status = pjsip_endpt_create(&cp.factory,endpt_name,

                    &g_endpt);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    }

//添加UDP傳輸端口

//如果已經存在了UDP套接字時,應用可使用pjsip_udp_transport_attach()函數啟動UDP傳輸端口

    {

    pj_sockaddr  addr;

    pj_sockaddr_init(AF, &addr, NULL, (pj_uint16_t)SIP_PORT);

    if (AF == pj_AF_INET()) {

       status = pjsip_udp_transport_start( g_endpt, &addr.ipv4, NULL,

                        1, NULL);

    } else if (AF == pj_AF_INET6()) {

       status = pjsip_udp_transport_start6(g_endpt, &addr.ipv6, NULL,

                        1, NULL);

    } else {

       status = PJ_EAFNOTSUP;

    }

    if (status != PJ_SUCCESS) {

       app_perror(THIS_FILE, "Unableto start UDP transport", status);

        return 1;

    }

    }

    //初始化事務層,将建立/初始化傳輸hash表

    status =pjsip_tsx_layer_init_module(g_endpt);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

//初始化UA層子產品,将建立/初始化對話hash表

    status = pjsip_ua_init_module( g_endpt, NULL );

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

//初始化invite會話子產品,将初始化會話附加參數,例如與事件相關的回調函數

//on_state_changed與on_new_session是應用必須支援的回調函數

//我們可以讓應用程式在on_media_update()回調函數中,啟動媒體傳輸

    {

    pjsip_inv_callback  inv_cb;

    // 初始化INVITE會話回調

    pj_bzero(&inv_cb, sizeof(inv_cb));

    inv_cb.on_state_changed =&call_on_state_changed;

    inv_cb.on_new_session = &call_on_forked;

    inv_cb.on_media_update =&call_on_media_update;

    //初始化INVITE會話子產品

    status = pjsip_inv_usage_init(g_endpt,&inv_cb);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    }

    //初始化100rel支援

    status = pjsip_100rel_init_module(g_endpt);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS,status);

    //注冊用于接收呼入請求的自己子產品

    status = pjsip_endpt_register_module(g_endpt, &mod_simpleua);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

//注冊消息日志子產品

    status = pjsip_endpt_register_module(g_endpt, &msg_logger);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

//初始化媒體終端,它将隐性地啟動PJMEDIA的相關功能

#if PJ_HAS_THREADS

    status =pjmedia_endpt_create(&cp.factory, NULL, 1, &g_med_endpt);

#else

    status =pjmedia_endpt_create(&cp.factory,

                 pjsip_endpt_get_ioqueue(g_endpt),

                 0, &g_med_endpt);

#endif

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

//給媒體終端增加PCMA/PCMU編解碼器

#if defined(PJMEDIA_HAS_G711_CODEC)&& PJMEDIA_HAS_G711_CODEC!=0

    status = pjmedia_codec_g711_init(g_med_endpt);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

#endif

#if defined(PJMEDIA_HAS_VIDEO)&& (PJMEDIA_HAS_VIDEO != 0)

    //初始化視訊子系統

    pool =pjmedia_endpt_create_pool(g_med_endpt, "Video subsystem", 512, 512);

    status = pjmedia_video_format_mgr_create(pool,64, 0, NULL);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    status = pjmedia_converter_mgr_create(pool,NULL);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    status = pjmedia_vid_codec_mgr_create(pool,NULL);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    status =pjmedia_vid_dev_subsys_init(&cp.factory);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

#   if defined(PJMEDIA_HAS_OPENH264_CODEC) &&PJMEDIA_HAS_OPENH264_CODEC != 0

    status =pjmedia_codec_openh264_vid_init(NULL, &cp.factory);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

#   endif

#  if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) &&PJMEDIA_HAS_FFMPEG_VID_CODEC!=0

    //初始化ffmpeg視訊編解碼器

    status =pjmedia_codec_ffmpeg_vid_init(NULL, &cp.factory);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

#  endif 

#endif 

//建立用于RTP/RTCP套接字發送/接收的媒體傳輸端口

//每個呼叫均需要一個媒體傳輸端口,應用程式可以選擇地複用相同的媒體傳輸端口用于後續呼叫

    for (i = 0; i < PJ_ARRAY_SIZE(g_med_transport); ++i) {

    status = pjmedia_transport_udp_create3(g_med_endpt,AF,NULL, NULL,

                           RTP_PORT + i*2, 0,

                           &g_med_transport[i]);

    if (status != PJ_SUCCESS) {

       app_perror(THIS_FILE, "Unableto create media transport", status);

        return 1;

    }

    //取媒體傳輸端口的套接字資訊(位址,端口),我們需要利用它們建立SDP

    pjmedia_transport_info_init(&g_med_tpinfo[i]);

    pjmedia_transport_get_info(g_med_transport[i],&g_med_tpinfo[i]);

    pj_memcpy(&g_sock_info[i],&g_med_tpinfo[i].sock_info,

          sizeof(pjmedia_sock_info));

    }

//如果提供呼叫的URL,則建立立即建立呼叫

    if (argc > 1) {

    pj_sockaddr hostaddr;

    char hostip[PJ_INET6_ADDRSTRLEN+2];

    char temp[80];

    pj_str_t dst_uri = pj_str(argv[1]);

    pj_str_t local_uri;

    pjsip_dialog *dlg;

    pjmedia_sdp_session *local_sdp;

    pjsip_tx_data *tdata;

    if (pj_gethostip(AF, &hostaddr) !=PJ_SUCCESS) {

       app_perror(THIS_FILE, "Unableto retrieve local host IP", status);

        return 1;

    }

    pj_sockaddr_print(&hostaddr, hostip, sizeof(hostip), 2);

    pj_ansi_sprintf(temp, "<sip:[email protected]%s:%d>",

            hostip, SIP_PORT);

    local_uri = pj_str(temp);

    //建立UAC對話

    status = pjsip_dlg_create_uac(pjsip_ua_instance(),

                       &local_uri, 

                       &local_uri, 

                       &dst_uri,   

                       &dst_uri,   

                      &dlg);       

    if (status != PJ_SUCCESS) {

       app_perror(THIS_FILE, "Unableto create UAC dialog", status);

        return 1;

    }

    //如果怕呼出的INVITE被對方懷疑,我們可以在對話中加入憑證,如下例:

    status = pjmedia_endpt_create_sdp(g_med_endpt,    

                       dlg->pool,      

                       MAX_MEDIA_CNT,  

                       g_sock_info,    

                       &local_sdp);    

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    //建立INVITE傳話,友善地将SDP作為初始參數傳遞給會話

    status = pjsip_inv_create_uac( dlg,local_sdp, 0, &g_inv);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    //建立INVITE初始請求

//此INVITE請求須包含完整的請求和SDP内容

    status = pjsip_inv_invite(g_inv,&tdata);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    //發送INVITE請求

//INVITE傳話狀态會通過回調傳回

    status = pjsip_inv_send_msg(g_inv, tdata);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    } else {

    //沒有外呼的URL

    PJ_LOG(3,(THIS_FILE, "Ready toaccept incoming calls..."));

    }

    //循環,直到某個呼叫完成

    for (;!g_complete;) {

    pj_time_val timeout = {0, 10};

    pjsip_endpt_handle_events(g_endpt,&timeout);

    }

    //退出時,轉儲目前記憶體使用情況

    dump_pool_usage(THIS_FILE, &cp);

//銷毀音頻端口

//因為音頻端口有線程存/取幀資料,故需要在流之前銷毀音頻端口

    if (g_snd_port)

    pjmedia_snd_port_destroy(g_snd_port);

#if defined(PJMEDIA_HAS_VIDEO)&& (PJMEDIA_HAS_VIDEO != 0)

    //銷毀視訊端口

    if (g_vid_capturer)

    pjmedia_vid_port_destroy(g_vid_capturer);

    if (g_vid_renderer)

    pjmedia_vid_port_destroy(g_vid_renderer);

#endif

    //銷毀流

    if (g_med_stream)

    pjmedia_stream_destroy(g_med_stream);

#if defined(PJMEDIA_HAS_VIDEO)&& (PJMEDIA_HAS_VIDEO != 0)

    if (g_med_vstream)

    pjmedia_vid_stream_destroy(g_med_vstream);

    //ffmpeg

#   if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) &&PJMEDIA_HAS_FFMPEG_VID_CODEC!=0

    pjmedia_codec_ffmpeg_vid_deinit();

#   endif

#   if defined(PJMEDIA_HAS_OPENH264_CODEC) &&PJMEDIA_HAS_OPENH264_CODEC != 0

    pjmedia_codec_openh264_vid_deinit();

#   endif

#endif

    //銷毀媒體傳輸端口

    for (i = 0; i < MAX_MEDIA_CNT; ++i) {

    if (g_med_transport[i])

       pjmedia_transport_close(g_med_transport[i]);

    }

    //完結媒體終端

    if (g_med_endpt)

    pjmedia_endpt_destroy(g_med_endpt);

    //完結sip終端

    if (g_endpt)

    pjsip_endpt_destroy(g_endpt);

    //釋放pool

    if (pool)

    pj_pool_release(pool);

    return 0;

}

//當INVITE會話狀态改變時的回調函數。

//此函數是在INVITE傳話子產品初始時注冊,我們通常在此得到INVITE傳話中斷,并退出應用。

static void  call_on_state_changed( pjsip_inv_session *inv,

                   pjsip_event *e)

{

    PJ_UNUSED_ARG(e);

    if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {

    PJ_LOG(3,(THIS_FILE, "CallDISCONNECTED [reason=%d (%s)]",

         inv->cause,

         pjsip_get_status_text(inv->cause)->ptr));

    PJ_LOG(3,(THIS_FILE, "One callcompleted, application quitting..."));

    g_complete = 1;

    } else {

    PJ_LOG(3,(THIS_FILE, "Call statechanged to %s",

         pjsip_inv_state_name(inv->state)));

    }

}

//當會話複制完成後,此函數被回調

static void  call_on_forked(pjsip_inv_session *inv, pjsip_event *e)

{

    PJ_UNUSED_ARG(inv);

    PJ_UNUSED_ARG(e);

}

//當外部呼叫的任意對話或事務到達時,此函數被回調。我們可以控制想接的請求,其他的回應500拒絕

static pj_bool_t  on_rx_request( pjsip_rx_data *rdata )

{

    pj_sockaddr hostaddr;

    char temp[80], hostip[PJ_INET6_ADDRSTRLEN];

    pj_str_t local_uri;

    pjsip_dialog *dlg;

    pjmedia_sdp_session *local_sdp;

    pjsip_tx_data *tdata;

    unsigned options = 0;

    pj_status_t status;

//以500回應(無狀态)任何非INVITE請求

    if (rdata->msg_info.msg->line.req.method.id !=PJSIP_INVITE_METHOD) {

    if (rdata->msg_info.msg->line.req.method.id !=PJSIP_ACK_METHOD) {

       pj_str_t reason = pj_str("Simple UA unable to handle "

                     "this request");

       pjsip_endpt_respond_stateless( g_endpt, rdata,

                       500, &reason,

                       NULL, NULL);

    }

    return PJ_TRUE;

    }

//如果此INVITE會話已經處理,則拒絕

    if (g_inv) {

    pj_str_t reason = pj_str("Another callis in progress");

    pjsip_endpt_respond_stateless( g_endpt,rdata,

                       500, &reason,

                       NULL, NULL);

    return PJ_TRUE;

    }

    //效驗下我們要控制的請求

    status = pjsip_inv_verify_request(rdata,&options, NULL, NULL,

                      g_endpt, NULL);

    if (status != PJ_SUCCESS) {

    pj_str_t reason = pj_str("Sorry SimpleUA can not handle this INVITE");

    pjsip_endpt_respond_stateless( g_endpt,rdata,

                       500, &reason,

                       NULL, NULL);

    return PJ_TRUE;

    }

    //生成聯系URI

    if (pj_gethostip(AF, &hostaddr) !=PJ_SUCCESS) {

    app_perror(THIS_FILE, "Unable toretrieve local host IP", status);

    return PJ_TRUE;

    }

    pj_sockaddr_print(&hostaddr, hostip, sizeof(hostip), 2);

    pj_ansi_sprintf(temp, "<sip:[email protected]%s:%d>",

           hostip, SIP_PORT);

    local_uri = pj_str(temp);

    //建立UAS對話

    status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(),

                        rdata,

                        &local_uri,

                        &dlg);

    if (status != PJ_SUCCESS) {

    pjsip_endpt_respond_stateless(g_endpt,rdata, 500, NULL,

                      NULL, NULL);

    return PJ_TRUE;

    }

//從媒體終端得到媒體能力集

    status = pjmedia_endpt_create_sdp( g_med_endpt,rdata->tp_info.pool,

                       MAX_MEDIA_CNT, g_sock_info,&local_sdp);

    pj_assert(status == PJ_SUCCESS);

    if (status != PJ_SUCCESS) {

    pjsip_dlg_dec_lock(dlg);

    return PJ_TRUE;

    }

//傳遞UAS會話和SDP能力集,建立INVITE會話

    status = pjsip_inv_create_uas( dlg, rdata,local_sdp, 0, &g_inv);

    pj_assert(status == PJ_SUCCESS);

    if (status != PJ_SUCCESS) {

    pjsip_dlg_dec_lock(dlg);

    return PJ_TRUE;

    }

//INVITE傳話已經建立,釋放對話鎖定

    pjsip_dlg_dec_lock(dlg);

//首先發送180回應

//最先發送的回應必須用pjsip_inv_initial_answer()建立,後面相同僚務的回應必須用pjsip_inv_answer()

    status = pjsip_inv_initial_answer(g_inv,rdata,

                      180,

                      NULL, NULL, &tdata);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS,PJ_TRUE);

    //發送180回應 

    status = pjsip_inv_send_msg(g_inv, tdata);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS,PJ_TRUE);

    //現在建立200回應

    status = pjsip_inv_answer( g_inv,

                   200, NULL,   

                   NULL,    

                   &tdata);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS,PJ_TRUE);

    //發送200回應

    status = pjsip_inv_send_msg(g_inv, tdata);

    PJ_ASSERT_RETURN(status == PJ_SUCCESS,PJ_TRUE);

//完成。當呼叫中斷時,将在回調中收到報告

    return PJ_TRUE;

}

//當SDP協商完成時,收到回調。我們要到此回調内快速啟動媒體

static void  call_on_media_update( pjsip_inv_session *inv,

                 pj_status_t status)

{

    pjmedia_stream_info stream_info;

    const pjmedia_sdp_session *local_sdp;

    const pjmedia_sdp_session *remote_sdp;

    pjmedia_port *media_port;

    if (status != PJ_SUCCESS) {

    app_perror(THIS_FILE, "SDPnegotiation has failed", status);

    return;

    }

    //取本地和遠端SDP。我們需要這些SDP建立媒體會話

    status =pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);

    status =pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);

    //SDP中的音頻媒體描述建立流資訊

    status =pjmedia_stream_info_from_sdp(&stream_info, inv->dlg->pool,

                      g_med_endpt,

                      local_sdp, remote_sdp, 0);

    if (status != PJ_SUCCESS) {

    app_perror(THIS_FILE,"Unable tocreate audio stream info",status);

    return;

    }

    //如果需要,在建立流之前,我們可以更改流資訊中的設定,如jitter設定、編解碼器設定等等。

//通過流資訊和媒體套接字,輕松建立新的音頻媒體流

    status = pjmedia_stream_create(g_med_endpt,inv->dlg->pool, &stream_info,

                   g_med_transport[0], NULL, &g_med_stream);

    if (status != PJ_SUCCESS) {

    app_perror( THIS_FILE, "Unable tocreate audio stream", status);

    return;

    }

    //啟動音頻流

    status =pjmedia_stream_start(g_med_stream);

    if (status != PJ_SUCCESS) {

    app_perror( THIS_FILE, "Unable tostart audio stream", status);

    return;

    }

//取音頻流的媒體端口接口。媒體端口接口基本結構包括get_frame()和put_frame()函數,

//我們可以将此媒體端口接口連接配接到會議橋,或直接到一個用于錄音/放音的聲霸卡裝置

    pjmedia_stream_get_port(g_med_stream,&media_port);

    //建立聲霸卡端口

    pjmedia_snd_port_create(inv->pool,

                            PJMEDIA_AUD_DEFAULT_CAPTURE_DEV,

                           PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV,

                           PJMEDIA_PIA_SRATE(&media_port->info),//時間速率

                           PJMEDIA_PIA_CCNT(&media_port->info),//通話數

                           PJMEDIA_PIA_SPF(&media_port->info), //每幀采樣

                           PJMEDIA_PIA_BITS(&media_port->info),//每次采樣bit數

                            0,

                            &g_snd_port);

    if (status != PJ_SUCCESS) {

    app_perror( THIS_FILE, "Unable tocreate sound port", status);

    PJ_LOG(3,(THIS_FILE, "%d %d %d%d",

           PJMEDIA_PIA_SRATE(&media_port->info),

           PJMEDIA_PIA_CCNT(&media_port->info),

           PJMEDIA_PIA_SPF(&media_port->info),

           PJMEDIA_PIA_BITS(&media_port->info)

       ));

    return;

    }

    status =pjmedia_snd_port_connect(g_snd_port, media_port);

//取會話中第2個流的媒體端口接口,視訊流的話,我們可以将此媒體端口接口直接連接配接到渲染/捕獲視訊裝置

#if defined(PJMEDIA_HAS_VIDEO)&& (PJMEDIA_HAS_VIDEO != 0)

    if (local_sdp->media_count > 1) {

    pjmedia_vid_stream_info vstream_info;

    pjmedia_vid_port_param vport_param;

    pjmedia_vid_port_param_default(&vport_param);

    //通過SDP中的視訊媒體描述合建視訊資訊

    status = pjmedia_vid_stream_info_from_sdp(&vstream_info,

                          inv->dlg->pool, g_med_endpt,

                          local_sdp, remote_sdp, 1);

    if (status != PJ_SUCCESS) {

       app_perror(THIS_FILE,"Unable to create video stream info",status);

        return;

    }

    //如果需要,在建立視訊流之前,我們可以更改流資訊中的設定,如jitter設定、編解碼器設定等等。

    //通過流資訊和媒體套接字參數,建立新的視訊媒體流

    status = pjmedia_vid_stream_create(g_med_endpt,NULL, &vstream_info,

                                      g_med_transport[1], NULL,

                                      &g_med_vstream);

    if (status != PJ_SUCCESS) {

       app_perror( THIS_FILE, "Unable to create video stream", status);

        return;

    }

    //啟動視訊流

    status =pjmedia_vid_stream_start(g_med_vstream);

    if (status != PJ_SUCCESS) {

       app_perror( THIS_FILE, "Unable to start video stream", status);

        return;

    }

    if (vstream_info.dir & PJMEDIA_DIR_DECODING) {

       status = pjmedia_vid_dev_default_param(

                inv->pool,PJMEDIA_VID_DEFAULT_RENDER_DEV,

                &vport_param.vidparam);

        if (status !=PJ_SUCCESS) {

        app_perror(THIS_FILE, "Unable to getdefault param of video "

              "rendererdevice",status);

        return;

        }

        //取解碼的視訊流

       pjmedia_vid_stream_get_port(g_med_vstream, PJMEDIA_DIR_DECODING,

                    &media_port);

        //設定格式

       pjmedia_format_copy(&vport_param.vidparam.fmt,

                &media_port->info.fmt);

       vport_param.vidparam.dir = PJMEDIA_DIR_RENDER;

       vport_param.active = PJ_TRUE;

        //建立渲染端口

       status = pjmedia_vid_port_create(inv->pool, &vport_param,

                         &g_vid_renderer);

        if (status != PJ_SUCCESS){

        app_perror(THIS_FILE, "Unable tocreate video renderer device",

              status);

        return;

        }

        //連接配接渲染端口到媒體端口

       status = pjmedia_vid_port_connect(g_vid_renderer, media_port,

                          PJ_FALSE);

        if (status !=PJ_SUCCESS) {

        app_perror(THIS_FILE, "Unable toconnect renderer to stream",

              status);

        return;

        }

    }

    //建立捕獲裝置

    if (vstream_info.dir & PJMEDIA_DIR_ENCODING) {

       status = pjmedia_vid_dev_default_param(

                inv->pool, PJMEDIA_VID_DEFAULT_CAPTURE_DEV,

                &vport_param.vidparam);

        if (status !=PJ_SUCCESS) {

        app_perror(THIS_FILE, "Unable to getdefault param of video "

              "capturedevice",status);

        return;

        }

        //取編碼的視訊流端口

       pjmedia_vid_stream_get_port(g_med_vstream, PJMEDIA_DIR_ENCODING,

                    &media_port);

        //從流資訊中取捕獲格式

       pjmedia_format_copy(&vport_param.vidparam.fmt,

                           &media_port->info.fmt);

       vport_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;

       vport_param.active = PJ_TRUE;

        //建立錄音端口

       status = pjmedia_vid_port_create(inv->pool, &vport_param,

                         &g_vid_capturer);

        if (status !=PJ_SUCCESS) {

        app_perror(THIS_FILE, "Unable tocreate video capture device",

              status);

        return;

        }

        //連接配接錄音媒體端口

       status = pjmedia_vid_port_connect(g_vid_capturer, media_port,

                          PJ_FALSE);

        if (status !=PJ_SUCCESS) {

        app_perror(THIS_FILE, "Unable toconnect capturer to stream",

              status);

        return;

        }

    }

    //啟動流

    if (g_vid_renderer) {

       status = pjmedia_vid_port_start(g_vid_renderer);

        if (status !=PJ_SUCCESS) {

        app_perror(THIS_FILE, "Unable tostart video renderer",

              status);

        return;

        }

    }

    if (g_vid_capturer) {

       status = pjmedia_vid_port_start(g_vid_capturer);

        if (status !=PJ_SUCCESS) {

        app_perror(THIS_FILE, "Unable tostart video capturer",

              status);

        return;

        }

    }

    }

#endif 

    //媒體部分完成

}