這是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
//媒體部分完成
}