与普通的pj媒体应用程序不同,此程序将绕过pj媒体的流框架,利用自己的线程手工处理RTP包。活动图如下:
//用法
static constchar*USAGE =
" 作用: \n"
" 此程序创建SIP INVITE会话和媒体,并计算媒体质量(丢包、jitter、rtt等) \n"
" 与普通的pj媒体应用程序不同,此程序将绕过pj媒体的流框架 \n"
" 利用自己的线程手工处理RTP包 \n"
"\n"
" 用法:\n"
" siprtp [options] => 以服务模式启动\n"
" siprtp [options] URL => 以客户模式启动\n"
"\n"
" 程序选项:\n"
" --count=N, -c 设置要创建呼叫数目 (缺省:1) \n"
" --gap=N -g 设置呼叫间隙到N毫秒 (缺省:0)\n"
" --duration=SEC, -d 设置最大的呼叫持续时间 (缺省:不限制) \n"
" --auto-quit, -q 当呼叫完成时是否退出(缺省:否)\n"
" --call-report -R 显示呼叫终止报告否 (缺省:是)\n"
"\n"
" 地址和端口选项\n"
" --local-port=PORT,-p 设置本地SIP端口(缺省: 5060)\n"
" --rtp-port=PORT, -r 设置RTP的开始端口 (缺省: 4000)\n"
" --ip-addr=IP, -i Set local IP address to use (otherwise itwill\n"
" try to determinelocal IP address from hostname)\n"
"\n"
" 日志选项:\n"
" --log-level=N, -l 设置日志冗长级别 (缺省=5)\n"
" --app-log-level=N 设置应用日志冗长级别 (缺省=3)\n"
" --log-file=FILE 写日志到文件 FILE\n"
" --report-file=FILE 写报告到文件 FILE\n"
"\n"
/* Don't support this anymore, because codec isproperly examined in
pjmedia_session_info_from_sdp() function.
" Codec Options:\n"
" --a-pt=PT Set audiopayload type to PT (default=0)\n"
" --a-name=NAME Set audiocodec name to NAME (default=pcmu)\n"
" --a-clock=RATE Set audiocodec rate to RATE Hz (default=8000Hz)\n"
" --a-bitrate=BPS Set audiocodec bitrate to BPS (default=64000bps)\n"
" --a-ptime=MS Set audioframe time to MS msec (default=20ms)\n"
*/
;
//包括的头文件
#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>
#include <stdlib.h>
//如果禁用多线程,请解开以下注释
//注意:如果禁用多线程,则siprtp将不能传输RTP包
/*
#undef PJ_HAS_THREADS
#define PJ_HAS_THREADS 0
*/
#if PJ_HAS_HIGH_RES_TIMER==0
# error"High resolution timer is needed for this sample"
#endif
#define THIS_FILE "siprtp.c"
#define MAX_CALLS 1024
#define RTP_START_PORT 4000
// 编解码器描述:
struct codec
{
unsigned pt;
char* name;
unsigned clock_rate;
unsigned bit_rate;
unsigned ptime;
char* description;
};
//当呼叫可用时,创建双向媒体流
struct media_stream
{
//静态
unsigned call_index; //呼叫所有者
unsigned media_index; //呼叫中的媒体索引
pjmedia_transport *transport; //用于发送/接收RTP/RTCP
//是否活动
pj_bool_t active; //如果在呼叫中,则为非零值
//当前流信息
pjmedia_stream_info si; //当前流信息
//更多信息
unsigned clock_rate; //时钟速率
unsigned samples_per_frame; //每帧采样
unsigned bytes_per_frame; //帧大小
//RTP会话
pjmedia_rtp_session out_sess; //呼出 RTP 会话
pjmedia_rtp_session in_sess; //呼入 RTP 会话
//RTCP状态
pjmedia_rtcp_session rtcp; //呼入 RTCP会话
//线程
pj_bool_t thread_quit_flag; //停止媒体线程否
pj_thread_t *thread; //媒体线程
};
//当应用程序启动时,下面的呼叫结构被创建。当应用程序退出时,结构被销毁
struct call
{
unsigned index;
pjsip_inv_session *inv;
unsigned media_count;
structmedia_stream media[1];
pj_time_val start_time;
pj_time_val response_time;
pj_time_val connect_time;
pj_timer_entry d_timer; //断开定时
};
//应用程序用到的全局变量
static structapp
{
unsigned max_calls;
unsigned call_gap;
pj_bool_t call_report;
unsigned uac_calls;
unsigned duration;
pj_bool_t auto_quit;
unsigned thread_count;
int sip_port;
int rtp_start_port;
pj_str_t local_addr;
pj_str_t local_uri;
pj_str_t local_contact;
int app_log_level;
int log_level;
char *log_filename;
char *report_filename;
structcodec audio_codec;
pj_str_t uri_to_call;
pj_caching_pool cp;
pj_pool_t *pool;
pjsip_endpoint *sip_endpt;
pj_bool_t thread_quit;
pj_thread_t *sip_thread[1];
pjmedia_endpt *med_endpt;
structcall call[MAX_CALLS];
} app;
//原型声明:
//当呼叫中的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);
//当在对话外收到呼入请求时,函数被回调
//* Callback to be called to handle incomingrequests outside dialogs: */
static pj_bool_t on_rx_request(pjsip_rx_data *rdata );
//工作线程原型
static int sip_worker_thread(void *arg);
//创建用于呼叫的SDP
static pj_status_t create_sdp(pj_pool_t *pool,
structcall *call,
pjmedia_sdp_session **p_sdp);
//挂断呼叫
static void hangup_call(unsigned index);
//销毁呼叫媒体
static void destroy_call_media(unsigned call_index);
//销毁媒体
static void destroy_media();
//当收到RTP包时,此函数被回调
static void on_rx_rtp(void *user_data, void*pkt, pj_ssize_t size);
//当收到RTCP包时,此函数被回调
static void on_rx_rtcp(void *user_data, void*pkt, pj_ssize_t size);
//显示错误
static void app_perror(const char*sender, const char*title,
pj_status_t status);
//打印呼叫信息
static void print_call(int call_index);
//应用程序使用下面的PJSIP注册模块,控制对话或事务外的呼入请求,
//此处的主要目的是为了控制呼入INVITE请求消息。
static pjsip_module mod_siprtp =
{
NULL, NULL, /* prev, next. */
{ "mod-siprtpapp", 13 }, /* 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() */
};
//编解码器列表
struct codec audio_codecs[] =
{
{ 0, "PCMU",8000, 64000, 20, "G.711 ULaw" },
{ 3, "GSM", 8000, 13200, 20, "GSM"},
{ 4, "G723",8000, 6400, 30, "G.723.1"},
{ 8, "PCMA",8000, 64000, 20, "G.711 ALaw" },
{ 18, "G729", 8000, 8000, 20, "G.729" },
};
//初始化SIP协议栈
static pj_status_t init_sip()
{
unsignedi;
pj_status_t status;
//初始化PJLIB-UTIL:
status =pjlib_util_init();
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
//在分配内存之前,必须创建pool factory!
pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy,0);
//创建应用程序内存池
app.pool= pj_pool_create(&app.cp.factory, "app",1000, 1000, NULL);
//创建SIP终端
status =pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
&app.sip_endpt);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
//添加UDP传输端口
{
pj_sockaddr_in addr;
pjsip_host_port addrname;
pjsip_transport *tp;
pj_bzero(&addr,sizeof(addr));
addr.sin_family= pj_AF_INET();
addr.sin_addr.s_addr= 0;
addr.sin_port= pj_htons((pj_uint16_t)app.sip_port);
if(app.local_addr.slen) {
addrname.host = app.local_addr;
addrname.port = app.sip_port;
status = pj_sockaddr_in_init(&addr,&app.local_addr,
(pj_uint16_t)app.sip_port);
if (status!= PJ_SUCCESS) {
app_perror(THIS_FILE, "Unable to resolve IPinterface", status);
returnstatus;
}
}
//启动UDP侦听
status =pjsip_udp_transport_start( app.sip_endpt, &addr,
(app.local_addr.slen ? &addrname:NULL),
1, &tp);
if(status != PJ_SUCCESS) {
app_perror(THIS_FILE,"Unable to start UDP transport",status);
return status;
}
PJ_LOG(3,(THIS_FILE, "SIP UDP listening on%.*s:%d",
(int)tp->local_name.host.slen,tp->local_name.host.ptr,
tp->local_name.port));
}
//初始化事务层
//将创建/初始化事务hash表。。。
status =pjsip_tsx_layer_init_module(app.sip_endpt);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
//初始化UA层
status =pjsip_ua_init_module( app.sip_endpt, NULL);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
//初始化100rel支持
status =pjsip_100rel_init_module(app.sip_endpt);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
//初始化INVITE会话模块
{
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(app.sip_endpt, &inv_cb);
PJ_ASSERT_RETURN(status== PJ_SUCCESS, 1);
}
//注册模块用于收到呼入请求处理
status =pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
//初始化全部呼叫

这个程序创建SIP INVITE会话并处理RTP包,不同于普通pj媒体应用程序,它绕过流框架,使用自己的线程处理。用户可以设置呼叫数量、间隔、持续时间等参数,程序还包括媒体质量计算、日志记录等功能。
最低0.47元/天 解锁文章
2933

被折叠的 条评论
为什么被折叠?



