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

#include

#include

#include

#include

#include

#include

 

#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,             /*prev, next.       */

    { "mod-simpleua", 12 },      /* 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()        */

};

 

 

//呼入消息通知

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)

{

    

    /* Important note:

     *   tp_infofield is only valid after outgoing messages has passed

     *   transportlayer. So don't try to access tp_info when the module

     *   haslower priority than transport layer.

     */

 

    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));

 

    /* Always return success, otherwise message will notget sent! */

    return PJ_SUCCESS;

}

 

//模块实例

static pjsip_module  msg_logger =

{

    NULL, NULL,              /* prev, next.      */

    { "mod-msg-log", 13 },       /* Name.        */

    -1,                  /* Id           */

    PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority        */

    NULL,                /* load()       */

    NULL,                /* start()      */

    NULL,                /* stop()       */

    NULL,                /* unload()     */

    &logging_on_rx_msg,          /* on_rx_request()  */

   &logging_on_rx_msg,          /* on_rx_response() */

    &logging_on_tx_msg,          /* on_tx_request.   */

    &logging_on_tx_msg,          /* on_tx_response() */

    NULL,                /* on_tsx_state()   */

 

};

 

 

/*

 * main()

 *

 * If called with argument, treat argument asSIP URL to be called.

 * Otherwise wait for incoming calls.

 */

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

{

    pj_pool_t  *pool = NULL;

    pj_status_t  status;

    unsigned i;

 

    /* 必须先初始化PJLIB*/

    status = pj_init();

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

 

    pj_log_set_level(5);

 

    /* 再初始化PJLIB-UTIL*/

    status = pjlib_util_init();

    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

 

 

    /* 在分配内存之前必须创建pool factory*/

    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_changedon_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  /* PJMEDIA_HAS_FFMPEG_VID_CODEC */

 

#endif  /* PJMEDIA_HAS_VIDEO */

   

//创建用于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, "",

            hostip, SIP_PORT);

    local_uri = pj_str(temp);

 

    //创建UAC对话

    status = pjsip_dlg_create_uac(pjsip_ua_instance(),

                       &local_uri,  /* local URI */

                       &local_uri,  /* local Contact */

                       &dst_uri,    /* remote URI */

                       &dst_uri,    /* remote target */

                      &dlg);        /* dialog */

    if (status != PJ_SUCCESS) {

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

        return 1;

    }

 

    //如果怕呼出的INVITE被对方怀疑,我们可以在对话中加入凭证,如下例:

    /*

        {

        pjsip_cred_info cred[1];

 

        cred[0].realm     = pj_str("sip.server.realm");

        cred[0].scheme    = pj_str("digest");

        cred[0].username  = pj_str("theuser");

        cred[0].data_type =PJSIP_CRED_DATA_PLAIN_PASSWD;

        cred[0].data      = pj_str("thepassword");

 

        pjsip_auth_clt_set_credentials(&dlg->auth_sess, 1, cred);

        }

     */

 

 

    /* Get the SDP body to be put in the outgoingINVITE, by asking

     *media endpoint to create one for us.

     */

    status = pjmedia_endpt_create_sdp(g_med_endpt,     /* the media endpt   */

                       dlg->pool,       /* pool.     */

                       MAX_MEDIA_CNT,  /* # of streams  */

                       g_sock_info,     /* RTP sock info */

                       &local_sdp);     /* the SDP result    */

    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);

 

    /* If we want the initial INVITE to travel tospecific SIP proxies,

     *then we should put the initial dialog's route set here. The final

     *route set will be updated once a dialog has been established.

     * Toset the dialog's initial route set, we do it with something

     *like this:

     *

        {

        pjsip_route_hdr route_set;

        pjsip_route_hdr *route;

        const pj_str_t hname = {"Route", 5 };

        char *uri ="sip:proxy.server;lr";

 

        pj_list_init(&route_set);

 

        route = pjsip_parse_hdr( dlg->pool,&hname,

                     uri, strlen(uri),

                     NULL);

        PJ_ASSERT_RETURN(route != NULL, 1);

        pj_list_push_back(&route_set,route);

 

        pjsip_dlg_set_route_set(dlg,&route_set);

        }

     *

     *Note that Route URI SHOULD have an ";lr" parameter!

     */

 

    //创建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)

{

    /* To be done... */

    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, "",

           hostip, SIP_PORT);

    local_uri = pj_str(temp);

 

    //创建UAS对话

    status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(),

                        rdata,

                        &local_uri, /* contact */

                        &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,    /* st_code and st_text */

                   NULL,     /* SDP already specified */

                   &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);

 

    /* Here we should disconnect call if we're not inthe middle

     * ofinitializing an UAS dialog and if this is not a re-INVITE.

     */

    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),/* clock rate       */

           PJMEDIA_PIA_CCNT(&media_port->info),/* channel count    */

           PJMEDIA_PIA_SPF(&media_port->info), /* samples per frame*/

           PJMEDIA_PIA_BITS(&media_port->info) /* bits per sample  */

       ));

    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  /* PJMEDIA_HAS_VIDEO */

 

    //媒体部分完成

}

你可能感兴趣的:(PJSIP媒体,PJSIP)