📄 siprtp.c
字号:
/* $Id: siprtp.c 974 2007-02-19 01:13:53Z bennylp $ */
/*
* Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* Usage */
static const char *USAGE =
" PURPOSE: \n"
" This program establishes SIP INVITE session and media, and calculate \n"
" the media quality (packet lost, jitter, rtt, etc.). Unlike normal \n"
" pjmedia applications, this program bypasses all pjmedia stream \n"
" framework and transmit encoded RTP packets manually using own thread. \n"
"\n"
" USAGE:\n"
" siprtp [options] => to start in server mode\n"
" siprtp [options] URL => to start in client mode\n"
"\n"
" Program options:\n"
" --count=N, -c Set number of calls to create (default:1) \n"
" --duration=SEC, -d Set maximum call duration (default:unlimited) \n"
" --auto-quit, -q Quit when calls have been completed (default:no)\n"
"\n"
" Address and ports options:\n"
" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
" try to determine local IP address from hostname)\n"
"\n"
" Logging Options:\n"
" --log-level=N, -l Set log verbosity level (default=5)\n"
" --app-log-level=N Set app screen log verbosity (default=3)\n"
" --log-file=FILE Write log to file FILE\n"
" --report-file=FILE Write report to file FILE\n"
"\n"
/* Don't support this anymore, because codec is properly examined in
pjmedia_session_info_from_sdp() function.
" Codec Options:\n"
" --a-pt=PT Set audio payload type to PT (default=0)\n"
" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
" --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
*/
;
/* Include all headers. */
#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>
#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
/* Codec descriptor: */
struct codec
{
unsigned pt;
char* name;
unsigned clock_rate;
unsigned bit_rate;
unsigned ptime;
char* description;
};
/* A bidirectional media stream created when the call is active. */
struct media_stream
{
/* Static: */
unsigned call_index; /* Call owner. */
unsigned media_index; /* Media index in call. */
pjmedia_transport *transport; /* To send/recv RTP/RTCP */
/* Active? */
pj_bool_t active; /* Non-zero if is in call. */
/* Current stream info: */
pjmedia_stream_info si; /* Current stream info. */
/* More info: */
unsigned clock_rate; /* clock rate */
unsigned samples_per_frame; /* samples per frame */
unsigned bytes_per_frame; /* frame size. */
/* RTP session: */
pjmedia_rtp_session out_sess; /* outgoing RTP session */
pjmedia_rtp_session in_sess; /* incoming RTP session */
/* RTCP stats: */
pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
/* Thread: */
pj_bool_t thread_quit_flag; /* Stop media thread. */
pj_thread_t *thread; /* Media thread. */
};
/* This is a call structure that is created when the application starts
* and only destroyed when the application quits.
*/
struct call
{
unsigned index;
pjsip_inv_session *inv;
unsigned media_count;
struct media_stream media[1];
pj_time_val start_time;
pj_time_val response_time;
pj_time_val connect_time;
pj_timer_entry d_timer; /**< Disconnect timer. */
};
/* Application's global variables */
static struct app
{
unsigned max_calls;
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;
struct codec 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;
struct call call[MAX_CALLS];
} app;
/*
* Prototypes:
*/
/* Callback to be called when SDP negotiation is done in the call: */
static void call_on_media_update( pjsip_inv_session *inv,
pj_status_t status);
/* Callback to be called when invite session's state has changed: */
static void call_on_state_changed( pjsip_inv_session *inv,
pjsip_event *e);
/* Callback to be called when dialog has forked: */
static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
/* Callback to be called to handle incoming requests outside dialogs: */
static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
/* Worker thread prototype */
static int sip_worker_thread(void *arg);
/* Create SDP for call */
static pj_status_t create_sdp( pj_pool_t *pool,
struct call *call,
pjmedia_sdp_session **p_sdp);
/* Hangup call */
static void hangup_call(unsigned index);
/* Destroy the call's media */
static void destroy_call_media(unsigned call_index);
/* Destroy media. */
static void destroy_media();
/* This callback is called by media transport on receipt of RTP packet. */
static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size);
/* This callback is called by media transport on receipt of RTCP packet. */
static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size);
/* Display error */
static void app_perror(const char *sender, const char *title,
pj_status_t status);
/* Print call */
static void print_call(int call_index);
/* This is a PJSIP module to be registered by application to handle
* incoming requests outside any dialogs/transactions. The main purpose
* here is to handle incoming INVITE request message, where we will
* create a dialog and INVITE session for it.
*/
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() */
};
/* Codec constants */
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" },
};
/*
* Init SIP stack
*/
static pj_status_t init_sip()
{
unsigned i;
pj_status_t status;
/* init PJLIB-UTIL: */
status = pjlib_util_init();
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Must create a pool factory before we can allocate any memory. */
pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
/* Create application pool for misc. */
app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
/* Create the endpoint: */
status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
&app.sip_endpt);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Add UDP transport. */
{
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 IP interface", status);
return status;
}
}
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));
}
/*
* Init transaction layer.
* This will create/initialize transaction hash tables etc.
*/
status = pjsip_tsx_layer_init_module(app.sip_endpt);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Initialize UA layer. */
status = pjsip_ua_init_module( app.sip_endpt, NULL );
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Init invite session module. */
{
pjsip_inv_callback inv_cb;
/* Init the callback for INVITE session: */
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;
/* Initialize invite session module: */
status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
}
/* Register our module to receive incoming requests. */
status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Init calls */
for (i=0; i<app.max_calls; ++i)
app.call[i].index = i;
/* Done */
return PJ_SUCCESS;
}
/*
* Destroy SIP
*/
static void destroy_sip()
{
unsigned i;
app.thread_quit = 1;
for (i=0; i<app.thread_count; ++i) {
if (app.sip_thread[i]) {
pj_thread_join(app.sip_thread[i]);
pj_thread_destroy(app.sip_thread[i]);
app.sip_thread[i] = NULL;
}
}
if (app.sip_endpt) {
pjsip_endpt_destroy(app.sip_endpt);
app.sip_endpt = NULL;
}
}
/*
* Init media stack.
*/
static pj_status_t init_media()
{
unsigned i, count;
pj_uint16_t rtp_port;
pj_status_t status;
/* Initialize media endpoint so that at least error subsystem is properly
* initialized.
*/
status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Must register codecs to be supported */
#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
pjmedia_codec_g711_init(app.med_endpt);
#endif
/* RTP port counter */
rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
/* Init media transport for all calls. */
for (i=0, count=0; i<app.max_calls; ++i, ++count) {
unsigned j;
/* Create transport for each media in the call */
for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
/* Repeat binding media socket to next port when fails to bind
* to current port number.
*/
int retry;
app.call[i].media[j].call_index = i;
app.call[i].media[j].media_index = j;
status = -1;
for (retry=0; retry<100; ++retry,rtp_port+=2) {
struct media_stream *m = &app.call[i].media[j];
status = pjmedia_transport_udp_create2(app.med_endpt,
"siprtp",
&app.local_addr,
rtp_port, 0,
&m->transport);
if (status == PJ_SUCCESS) {
rtp_port += 2;
break;
}
}
}
if (status != PJ_SUCCESS)
goto on_error;
}
/* Done */
return PJ_SUCCESS;
on_error:
destroy_media();
return status;
}
/*
* Destroy media.
*/
static void destroy_media()
{
unsigned i;
for (i=0; i<app.max_calls; ++i) {
unsigned j;
for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
struct media_stream *m = &app.call[i].media[j];
if (m->transport) {
pjmedia_transport_close(m->transport);
m->transport = NULL;
}
}
}
if (app.med_endpt) {
pjmedia_endpt_destroy(app.med_endpt);
app.med_endpt = NULL;
}
}
/*
* Make outgoing call.
*/
static pj_status_t make_call(const pj_str_t *dst_uri)
{
unsigned i;
struct call *call;
pjsip_dialog *dlg;
pjmedia_sdp_session *sdp;
pjsip_tx_data *tdata;
pj_status_t status;
/* Find unused call slot */
for (i=0; i<app.max_calls; ++i) {
if (app.call[i].inv == NULL)
break;
}
if (i == app.max_calls)
return PJ_ETOOMANY;
call = &app.call[i];
/* Create UAC dialog */
status = pjsip_dlg_create_uac( pjsip_ua_instance(),
&app.local_uri, /* local URI */
&app.local_contact, /* local Contact */
dst_uri, /* remote URI */
dst_uri, /* remote target */
&dlg); /* dialog */
if (status != PJ_SUCCESS) {
++app.uac_calls;
return status;
}
/* Create SDP */
create_sdp( dlg->pool, call, &sdp);
/* Create the INVITE session. */
status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
if (status != PJ_SUCCESS) {
pjsip_dlg_terminate(dlg);
++app.uac_calls;
return status;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -