blob: 292cf474081bca605f1934a16edda5f5c707002a [file] [log] [blame]
Benny Prijono60b980e2006-04-03 22:41:26 +00001/* $Id$ */
2/*
Benny Prijonoa771a512007-02-19 01:13:53 +00003 * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
Benny Prijono60b980e2006-04-03 22:41:26 +00004 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20
Benny Prijonobf13fee2006-04-20 11:13:32 +000021
22
Benny Prijono1ec70b32006-06-20 15:39:07 +000023
Benny Prijonobf13fee2006-04-20 11:13:32 +000024/* Usage */
25static const char *USAGE =
26" PURPOSE: \n"
27" This program establishes SIP INVITE session and media, and calculate \n"
28" the media quality (packet lost, jitter, rtt, etc.). Unlike normal \n"
29" pjmedia applications, this program bypasses all pjmedia stream \n"
30" framework and transmit encoded RTP packets manually using own thread. \n"
31"\n"
32" USAGE:\n"
33" siprtp [options] => to start in server mode\n"
34" siprtp [options] URL => to start in client mode\n"
35"\n"
36" Program options:\n"
37" --count=N, -c Set number of calls to create (default:1) \n"
Benny Prijono410fbae2006-05-03 18:16:06 +000038" --duration=SEC, -d Set maximum call duration (default:unlimited) \n"
39" --auto-quit, -q Quit when calls have been completed (default:no)\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000040"\n"
41" Address and ports options:\n"
42" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
43" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
44" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
45" try to determine local IP address from hostname)\n"
46"\n"
47" Logging Options:\n"
48" --log-level=N, -l Set log verbosity level (default=5)\n"
49" --app-log-level=N Set app screen log verbosity (default=3)\n"
50" --log-file=FILE Write log to file FILE\n"
Benny Prijonofcb36722006-05-18 18:34:21 +000051" --report-file=FILE Write report to file FILE\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000052"\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000053/* Don't support this anymore, because codec is properly examined in
54 pjmedia_session_info_from_sdp() function.
55
Benny Prijonobf13fee2006-04-20 11:13:32 +000056" Codec Options:\n"
57" --a-pt=PT Set audio payload type to PT (default=0)\n"
58" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
59" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
60" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
61" --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000062*/
Benny Prijonobf13fee2006-04-20 11:13:32 +000063;
64
65
Benny Prijono60b980e2006-04-03 22:41:26 +000066/* Include all headers. */
67#include <pjsip.h>
68#include <pjmedia.h>
69#include <pjmedia-codec.h>
70#include <pjsip_ua.h>
71#include <pjsip_simple.h>
72#include <pjlib-util.h>
73#include <pjlib.h>
74
75#include <stdlib.h>
76
Benny Prijono6869dc92007-06-03 00:37:30 +000077/* Uncomment these to disable threads.
78 * NOTE:
79 * when threading is disabled, siprtp won't transmit any
80 * RTP packets.
81 */
82/*
83#undef PJ_HAS_THREADS
84#define PJ_HAS_THREADS 0
85*/
86
Benny Prijono9a0eab52006-04-04 19:43:24 +000087
88#if PJ_HAS_HIGH_RES_TIMER==0
89# error "High resolution timer is needed for this sample"
90#endif
91
Benny Prijono60b980e2006-04-03 22:41:26 +000092#define THIS_FILE "siprtp.c"
93#define MAX_CALLS 1024
Benny Prijono6647d822006-05-20 13:01:07 +000094#define RTP_START_PORT 4000
Benny Prijono60b980e2006-04-03 22:41:26 +000095
96
Benny Prijono4adcb912006-04-04 13:12:38 +000097/* Codec descriptor: */
98struct codec
99{
100 unsigned pt;
101 char* name;
102 unsigned clock_rate;
103 unsigned bit_rate;
104 unsigned ptime;
105 char* description;
106};
107
108
Benny Prijonodeb31962006-06-22 18:51:03 +0000109/* A bidirectional media stream created when the call is active. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000110struct media_stream
111{
112 /* Static: */
Benny Prijonodeb31962006-06-22 18:51:03 +0000113 unsigned call_index; /* Call owner. */
114 unsigned media_index; /* Media index in call. */
115 pjmedia_transport *transport; /* To send/recv RTP/RTCP */
116
117 /* Active? */
118 pj_bool_t active; /* Non-zero if is in call. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000119
120 /* Current stream info: */
121 pjmedia_stream_info si; /* Current stream info. */
122
123 /* More info: */
124 unsigned clock_rate; /* clock rate */
125 unsigned samples_per_frame; /* samples per frame */
126 unsigned bytes_per_frame; /* frame size. */
127
Benny Prijono60b980e2006-04-03 22:41:26 +0000128 /* RTP session: */
129 pjmedia_rtp_session out_sess; /* outgoing RTP session */
130 pjmedia_rtp_session in_sess; /* incoming RTP session */
131
132 /* RTCP stats: */
133 pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
Benny Prijono4adcb912006-04-04 13:12:38 +0000134
Benny Prijono513795f2006-07-18 21:12:24 +0000135 /* Thread: */
136 pj_bool_t thread_quit_flag; /* Stop media thread. */
137 pj_thread_t *thread; /* Media thread. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000138};
139
140
Benny Prijonodeb31962006-06-22 18:51:03 +0000141/* This is a call structure that is created when the application starts
142 * and only destroyed when the application quits.
143 */
Benny Prijono60b980e2006-04-03 22:41:26 +0000144struct call
145{
146 unsigned index;
147 pjsip_inv_session *inv;
148 unsigned media_count;
Benny Prijonodeb31962006-06-22 18:51:03 +0000149 struct media_stream media[1];
Benny Prijono4adcb912006-04-04 13:12:38 +0000150 pj_time_val start_time;
151 pj_time_val response_time;
152 pj_time_val connect_time;
Benny Prijono410fbae2006-05-03 18:16:06 +0000153
154 pj_timer_entry d_timer; /**< Disconnect timer. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000155};
156
157
Benny Prijonodeb31962006-06-22 18:51:03 +0000158/* Application's global variables */
Benny Prijono60b980e2006-04-03 22:41:26 +0000159static struct app
160{
161 unsigned max_calls;
Benny Prijono410fbae2006-05-03 18:16:06 +0000162 unsigned uac_calls;
163 unsigned duration;
164 pj_bool_t auto_quit;
Benny Prijono60b980e2006-04-03 22:41:26 +0000165 unsigned thread_count;
166 int sip_port;
167 int rtp_start_port;
Benny Prijonodeb31962006-06-22 18:51:03 +0000168 pj_str_t local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +0000169 pj_str_t local_uri;
170 pj_str_t local_contact;
Benny Prijono4adcb912006-04-04 13:12:38 +0000171
172 int app_log_level;
173 int log_level;
174 char *log_filename;
Benny Prijonofcb36722006-05-18 18:34:21 +0000175 char *report_filename;
Benny Prijono4adcb912006-04-04 13:12:38 +0000176
177 struct codec audio_codec;
Benny Prijono60b980e2006-04-03 22:41:26 +0000178
179 pj_str_t uri_to_call;
180
181 pj_caching_pool cp;
182 pj_pool_t *pool;
183
184 pjsip_endpoint *sip_endpt;
185 pj_bool_t thread_quit;
Benny Prijonodeb31962006-06-22 18:51:03 +0000186 pj_thread_t *sip_thread[1];
Benny Prijono60b980e2006-04-03 22:41:26 +0000187
188 pjmedia_endpt *med_endpt;
189 struct call call[MAX_CALLS];
190} app;
191
192
193
194/*
195 * Prototypes:
196 */
197
198/* Callback to be called when SDP negotiation is done in the call: */
199static void call_on_media_update( pjsip_inv_session *inv,
200 pj_status_t status);
201
202/* Callback to be called when invite session's state has changed: */
203static void call_on_state_changed( pjsip_inv_session *inv,
204 pjsip_event *e);
205
206/* Callback to be called when dialog has forked: */
207static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
208
209/* Callback to be called to handle incoming requests outside dialogs: */
210static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
211
212/* Worker thread prototype */
Benny Prijonodeb31962006-06-22 18:51:03 +0000213static int sip_worker_thread(void *arg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000214
215/* Create SDP for call */
216static pj_status_t create_sdp( pj_pool_t *pool,
217 struct call *call,
218 pjmedia_sdp_session **p_sdp);
219
Benny Prijono410fbae2006-05-03 18:16:06 +0000220/* Hangup call */
221static void hangup_call(unsigned index);
222
Benny Prijono60b980e2006-04-03 22:41:26 +0000223/* Destroy the call's media */
224static void destroy_call_media(unsigned call_index);
225
Benny Prijonodeb31962006-06-22 18:51:03 +0000226/* Destroy media. */
227static void destroy_media();
228
229/* This callback is called by media transport on receipt of RTP packet. */
230static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size);
231
232/* This callback is called by media transport on receipt of RTCP packet. */
233static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size);
234
Benny Prijono60b980e2006-04-03 22:41:26 +0000235/* Display error */
236static void app_perror(const char *sender, const char *title,
237 pj_status_t status);
238
Benny Prijonod7a13f12006-04-05 19:08:16 +0000239/* Print call */
240static void print_call(int call_index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000241
242
Benny Prijono60b980e2006-04-03 22:41:26 +0000243/* This is a PJSIP module to be registered by application to handle
244 * incoming requests outside any dialogs/transactions. The main purpose
245 * here is to handle incoming INVITE request message, where we will
246 * create a dialog and INVITE session for it.
247 */
248static pjsip_module mod_siprtp =
249{
250 NULL, NULL, /* prev, next. */
251 { "mod-siprtpapp", 13 }, /* Name. */
252 -1, /* Id */
253 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
254 NULL, /* load() */
255 NULL, /* start() */
256 NULL, /* stop() */
257 NULL, /* unload() */
258 &on_rx_request, /* on_rx_request() */
259 NULL, /* on_rx_response() */
260 NULL, /* on_tx_request. */
261 NULL, /* on_tx_response() */
262 NULL, /* on_tsx_state() */
263};
264
265
Benny Prijono4adcb912006-04-04 13:12:38 +0000266/* Codec constants */
267struct codec audio_codecs[] =
268{
Benny Prijono97f2a372006-06-07 21:21:57 +0000269 { 0, "PCMU", 8000, 64000, 20, "G.711 ULaw" },
270 { 3, "GSM", 8000, 13200, 20, "GSM" },
271 { 4, "G723", 8000, 6400, 30, "G.723.1" },
272 { 8, "PCMA", 8000, 64000, 20, "G.711 ALaw" },
Benny Prijonodeb31962006-06-22 18:51:03 +0000273 { 18, "G729", 8000, 8000, 20, "G.729" },
Benny Prijono4adcb912006-04-04 13:12:38 +0000274};
275
276
Benny Prijono60b980e2006-04-03 22:41:26 +0000277/*
278 * Init SIP stack
279 */
280static pj_status_t init_sip()
281{
Benny Prijonodeb31962006-06-22 18:51:03 +0000282 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000283 pj_status_t status;
284
285 /* init PJLIB-UTIL: */
286 status = pjlib_util_init();
287 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
288
289 /* Must create a pool factory before we can allocate any memory. */
290 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
291
292 /* Create application pool for misc. */
293 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
294
Benny Prijonodeb31962006-06-22 18:51:03 +0000295 /* Create the endpoint: */
296 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
297 &app.sip_endpt);
298 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000299
300
301 /* Add UDP transport. */
302 {
303 pj_sockaddr_in addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000304 pjsip_host_port addrname;
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000305 pjsip_transport *tp;
Benny Prijono60b980e2006-04-03 22:41:26 +0000306
Benny Prijonoac623b32006-07-03 15:19:31 +0000307 pj_bzero(&addr, sizeof(addr));
Benny Prijono8ab968f2007-07-20 08:08:30 +0000308 addr.sin_family = pj_AF_INET();
Benny Prijono60b980e2006-04-03 22:41:26 +0000309 addr.sin_addr.s_addr = 0;
310 addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
311
Benny Prijonodeb31962006-06-22 18:51:03 +0000312 if (app.local_addr.slen) {
Benny Prijono41c96f32006-10-16 11:39:07 +0000313
Benny Prijonodeb31962006-06-22 18:51:03 +0000314 addrname.host = app.local_addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000315 addrname.port = app.sip_port;
Benny Prijono41c96f32006-10-16 11:39:07 +0000316
317 status = pj_sockaddr_in_init(&addr, &app.local_addr,
318 (pj_uint16_t)app.sip_port);
319 if (status != PJ_SUCCESS) {
320 app_perror(THIS_FILE, "Unable to resolve IP interface", status);
321 return status;
322 }
Benny Prijono49ce9a72006-04-05 16:56:19 +0000323 }
324
325 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
Benny Prijonodeb31962006-06-22 18:51:03 +0000326 (app.local_addr.slen ? &addrname:NULL),
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000327 1, &tp);
Benny Prijono49ce9a72006-04-05 16:56:19 +0000328 if (status != PJ_SUCCESS) {
329 app_perror(THIS_FILE, "Unable to start UDP transport", status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000330 return status;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000331 }
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000332
333 PJ_LOG(3,(THIS_FILE, "SIP UDP listening on %.*s:%d",
334 (int)tp->local_name.host.slen, tp->local_name.host.ptr,
335 tp->local_name.port));
Benny Prijono60b980e2006-04-03 22:41:26 +0000336 }
337
338 /*
339 * Init transaction layer.
340 * This will create/initialize transaction hash tables etc.
341 */
342 status = pjsip_tsx_layer_init_module(app.sip_endpt);
343 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
344
345 /* Initialize UA layer. */
346 status = pjsip_ua_init_module( app.sip_endpt, NULL );
347 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
348
Benny Prijonod6066852008-01-03 17:20:39 +0000349 /* Initialize 100rel support */
350 status = pjsip_100rel_init_module(app.sip_endpt);
351 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
352
Benny Prijono60b980e2006-04-03 22:41:26 +0000353 /* Init invite session module. */
354 {
355 pjsip_inv_callback inv_cb;
356
357 /* Init the callback for INVITE session: */
Benny Prijonoac623b32006-07-03 15:19:31 +0000358 pj_bzero(&inv_cb, sizeof(inv_cb));
Benny Prijono60b980e2006-04-03 22:41:26 +0000359 inv_cb.on_state_changed = &call_on_state_changed;
360 inv_cb.on_new_session = &call_on_forked;
361 inv_cb.on_media_update = &call_on_media_update;
362
363 /* Initialize invite session module: */
364 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
365 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
366 }
367
368 /* Register our module to receive incoming requests. */
369 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
370 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
371
Benny Prijonodeb31962006-06-22 18:51:03 +0000372 /* Init calls */
373 for (i=0; i<app.max_calls; ++i)
374 app.call[i].index = i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000375
Benny Prijono60b980e2006-04-03 22:41:26 +0000376 /* Done */
377 return PJ_SUCCESS;
378}
379
380
381/*
382 * Destroy SIP
383 */
384static void destroy_sip()
385{
386 unsigned i;
387
388 app.thread_quit = 1;
389 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000390 if (app.sip_thread[i]) {
391 pj_thread_join(app.sip_thread[i]);
392 pj_thread_destroy(app.sip_thread[i]);
393 app.sip_thread[i] = NULL;
Benny Prijono60b980e2006-04-03 22:41:26 +0000394 }
395 }
396
397 if (app.sip_endpt) {
398 pjsip_endpt_destroy(app.sip_endpt);
399 app.sip_endpt = NULL;
400 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000401
Benny Prijono60b980e2006-04-03 22:41:26 +0000402}
403
404
405/*
406 * Init media stack.
407 */
408static pj_status_t init_media()
409{
Benny Prijono60b980e2006-04-03 22:41:26 +0000410 unsigned i, count;
411 pj_uint16_t rtp_port;
Benny Prijono60b980e2006-04-03 22:41:26 +0000412 pj_status_t status;
413
414
Benny Prijono60b980e2006-04-03 22:41:26 +0000415 /* Initialize media endpoint so that at least error subsystem is properly
416 * initialized.
417 */
Benny Prijono6869dc92007-06-03 00:37:30 +0000418#if PJ_HAS_THREADS
Benny Prijono513795f2006-07-18 21:12:24 +0000419 status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
Benny Prijono6869dc92007-06-03 00:37:30 +0000420#else
421 status = pjmedia_endpt_create(&app.cp.factory,
422 pjsip_endpt_get_ioqueue(app.sip_endpt),
423 0, &app.med_endpt);
424#endif
Benny Prijono60b980e2006-04-03 22:41:26 +0000425 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
426
427
Benny Prijonodeb31962006-06-22 18:51:03 +0000428 /* Must register codecs to be supported */
Benny Prijonofc24e692007-01-27 18:31:51 +0000429#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
Benny Prijono4d7fd202006-05-14 20:57:20 +0000430 pjmedia_codec_g711_init(app.med_endpt);
Benny Prijonofc24e692007-01-27 18:31:51 +0000431#endif
Benny Prijono4d7fd202006-05-14 20:57:20 +0000432
Benny Prijono60b980e2006-04-03 22:41:26 +0000433 /* RTP port counter */
434 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
435
Benny Prijonodeb31962006-06-22 18:51:03 +0000436 /* Init media transport for all calls. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000437 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
438
Benny Prijonodeb31962006-06-22 18:51:03 +0000439 unsigned j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000440
Benny Prijonodeb31962006-06-22 18:51:03 +0000441 /* Create transport for each media in the call */
442 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
443 /* Repeat binding media socket to next port when fails to bind
444 * to current port number.
445 */
446 int retry;
Benny Prijono60b980e2006-04-03 22:41:26 +0000447
Benny Prijono513795f2006-07-18 21:12:24 +0000448 app.call[i].media[j].call_index = i;
449 app.call[i].media[j].media_index = j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000450
Benny Prijonodeb31962006-06-22 18:51:03 +0000451 status = -1;
452 for (retry=0; retry<100; ++retry,rtp_port+=2) {
453 struct media_stream *m = &app.call[i].media[j];
454
455 status = pjmedia_transport_udp_create2(app.med_endpt,
456 "siprtp",
457 &app.local_addr,
458 rtp_port, 0,
459 &m->transport);
460 if (status == PJ_SUCCESS) {
461 rtp_port += 2;
462 break;
463 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000464 }
Benny Prijono6647d822006-05-20 13:01:07 +0000465 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000466
467 if (status != PJ_SUCCESS)
468 goto on_error;
Benny Prijono60b980e2006-04-03 22:41:26 +0000469 }
470
471 /* Done */
472 return PJ_SUCCESS;
473
474on_error:
Benny Prijonodeb31962006-06-22 18:51:03 +0000475 destroy_media();
Benny Prijono60b980e2006-04-03 22:41:26 +0000476 return status;
477}
478
479
480/*
481 * Destroy media.
482 */
483static void destroy_media()
484{
485 unsigned i;
486
487 for (i=0; i<app.max_calls; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000488 unsigned j;
489 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
490 struct media_stream *m = &app.call[i].media[j];
Benny Prijono60b980e2006-04-03 22:41:26 +0000491
Benny Prijonodeb31962006-06-22 18:51:03 +0000492 if (m->transport) {
493 pjmedia_transport_close(m->transport);
494 m->transport = NULL;
495 }
496 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000497 }
498
499 if (app.med_endpt) {
500 pjmedia_endpt_destroy(app.med_endpt);
501 app.med_endpt = NULL;
502 }
503}
504
505
506/*
507 * Make outgoing call.
508 */
509static pj_status_t make_call(const pj_str_t *dst_uri)
510{
511 unsigned i;
512 struct call *call;
513 pjsip_dialog *dlg;
514 pjmedia_sdp_session *sdp;
515 pjsip_tx_data *tdata;
516 pj_status_t status;
517
518
519 /* Find unused call slot */
520 for (i=0; i<app.max_calls; ++i) {
521 if (app.call[i].inv == NULL)
522 break;
523 }
524
525 if (i == app.max_calls)
526 return PJ_ETOOMANY;
527
528 call = &app.call[i];
529
530 /* Create UAC dialog */
531 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
532 &app.local_uri, /* local URI */
533 &app.local_contact, /* local Contact */
534 dst_uri, /* remote URI */
535 dst_uri, /* remote target */
536 &dlg); /* dialog */
Benny Prijono410fbae2006-05-03 18:16:06 +0000537 if (status != PJ_SUCCESS) {
538 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000539 return status;
Benny Prijono410fbae2006-05-03 18:16:06 +0000540 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000541
542 /* Create SDP */
543 create_sdp( dlg->pool, call, &sdp);
544
545 /* Create the INVITE session. */
546 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
547 if (status != PJ_SUCCESS) {
548 pjsip_dlg_terminate(dlg);
Benny Prijono410fbae2006-05-03 18:16:06 +0000549 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000550 return status;
551 }
552
553
554 /* Attach call data to invite session */
555 call->inv->mod_data[mod_siprtp.id] = call;
556
Benny Prijono4adcb912006-04-04 13:12:38 +0000557 /* Mark start of call */
558 pj_gettimeofday(&call->start_time);
559
Benny Prijono60b980e2006-04-03 22:41:26 +0000560
561 /* Create initial INVITE request.
562 * This INVITE request will contain a perfectly good request and
563 * an SDP body as well.
564 */
565 status = pjsip_inv_invite(call->inv, &tdata);
566 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
567
568
569 /* Send initial INVITE request.
570 * From now on, the invite session's state will be reported to us
571 * via the invite session callbacks.
572 */
573 status = pjsip_inv_send_msg(call->inv, tdata);
574 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
575
576
577 return PJ_SUCCESS;
578}
579
580
581/*
582 * Receive incoming call
583 */
584static void process_incoming_call(pjsip_rx_data *rdata)
585{
Benny Prijonoca4cff22006-07-02 14:18:47 +0000586 unsigned i, options;
Benny Prijono60b980e2006-04-03 22:41:26 +0000587 struct call *call;
588 pjsip_dialog *dlg;
589 pjmedia_sdp_session *sdp;
590 pjsip_tx_data *tdata;
591 pj_status_t status;
592
593 /* Find free call slot */
594 for (i=0; i<app.max_calls; ++i) {
595 if (app.call[i].inv == NULL)
596 break;
597 }
598
599 if (i == app.max_calls) {
600 const pj_str_t reason = pj_str("Too many calls");
601 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
602 500, &reason,
603 NULL, NULL);
604 return;
605 }
606
Benny Prijono513795f2006-07-18 21:12:24 +0000607 call = &app.call[i];
608
Benny Prijonoca4cff22006-07-02 14:18:47 +0000609 /* Verify that we can handle the request. */
610 options = 0;
611 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000612 app.sip_endpt, &tdata);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000613 if (status != PJ_SUCCESS) {
Benny Prijonoca4cff22006-07-02 14:18:47 +0000614 /*
615 * No we can't handle the incoming INVITE request.
616 */
Benny Prijonoca4cff22006-07-02 14:18:47 +0000617 if (tdata) {
618 pjsip_response_addr res_addr;
Benny Prijono513795f2006-07-18 21:12:24 +0000619
Benny Prijonoca4cff22006-07-02 14:18:47 +0000620 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
Benny Prijono513795f2006-07-18 21:12:24 +0000621 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
622 NULL, NULL);
623
Benny Prijonoca4cff22006-07-02 14:18:47 +0000624 } else {
Benny Prijono513795f2006-07-18 21:12:24 +0000625
Benny Prijonoca4cff22006-07-02 14:18:47 +0000626 /* Respond with 500 (Internal Server Error) */
627 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000628 NULL, NULL);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000629 }
Benny Prijono513795f2006-07-18 21:12:24 +0000630
Benny Prijonoca4cff22006-07-02 14:18:47 +0000631 return;
Benny Prijono513795f2006-07-18 21:12:24 +0000632 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000633
634 /* Create UAS dialog */
635 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
636 &app.local_contact, &dlg);
637 if (status != PJ_SUCCESS) {
638 const pj_str_t reason = pj_str("Unable to create dialog");
639 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
640 500, &reason,
641 NULL, NULL);
642 return;
643 }
644
645 /* Create SDP */
646 create_sdp( dlg->pool, call, &sdp);
647
648 /* Create UAS invite session */
649 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
650 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000651 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
652 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000653 return;
654 }
655
Benny Prijono4adcb912006-04-04 13:12:38 +0000656
Benny Prijono60b980e2006-04-03 22:41:26 +0000657 /* Attach call data to invite session */
658 call->inv->mod_data[mod_siprtp.id] = call;
659
Benny Prijono4adcb912006-04-04 13:12:38 +0000660 /* Mark start of call */
661 pj_gettimeofday(&call->start_time);
662
663
664
Benny Prijono60b980e2006-04-03 22:41:26 +0000665 /* Create 200 response .*/
666 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
667 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000668 if (status != PJ_SUCCESS) {
669 status = pjsip_inv_initial_answer(call->inv, rdata,
670 PJSIP_SC_NOT_ACCEPTABLE,
671 NULL, NULL, &tdata);
672 if (status == PJ_SUCCESS)
673 pjsip_inv_send_msg(call->inv, tdata);
674 else
675 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
676 return;
677 }
678
Benny Prijono60b980e2006-04-03 22:41:26 +0000679
680 /* Send the 200 response. */
681 status = pjsip_inv_send_msg(call->inv, tdata);
682 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
683
684
685 /* Done */
686}
687
688
689/* Callback to be called when dialog has forked: */
690static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
691{
692 PJ_UNUSED_ARG(inv);
693 PJ_UNUSED_ARG(e);
694
695 PJ_TODO( HANDLE_FORKING );
696}
697
698
699/* Callback to be called to handle incoming requests outside dialogs: */
700static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
701{
Benny Prijono4adcb912006-04-04 13:12:38 +0000702 /* Ignore strandled ACKs (must not send respone */
703 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
704 return PJ_FALSE;
705
Benny Prijono60b980e2006-04-03 22:41:26 +0000706 /* Respond (statelessly) any non-INVITE requests with 500 */
707 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
708 pj_str_t reason = pj_str("Unsupported Operation");
709 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
710 500, &reason,
711 NULL, NULL);
712 return PJ_TRUE;
713 }
714
715 /* Handle incoming INVITE */
716 process_incoming_call(rdata);
717
718 /* Done */
719 return PJ_TRUE;
720}
721
722
Benny Prijono410fbae2006-05-03 18:16:06 +0000723/* Callback timer to disconnect call (limiting call duration) */
724static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
725 struct pj_timer_entry *entry)
726{
727 struct call *call = entry->user_data;
728
729 PJ_UNUSED_ARG(timer_heap);
730
731 entry->id = 0;
732 hangup_call(call->index);
733}
734
735
Benny Prijono60b980e2006-04-03 22:41:26 +0000736/* Callback to be called when invite session's state has changed: */
737static void call_on_state_changed( pjsip_inv_session *inv,
738 pjsip_event *e)
739{
Benny Prijono4adcb912006-04-04 13:12:38 +0000740 struct call *call = inv->mod_data[mod_siprtp.id];
741
Benny Prijono60b980e2006-04-03 22:41:26 +0000742 PJ_UNUSED_ARG(e);
743
Benny Prijono4adcb912006-04-04 13:12:38 +0000744 if (!call)
745 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000746
Benny Prijono4adcb912006-04-04 13:12:38 +0000747 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
748
749 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000750
Benny Prijono410fbae2006-05-03 18:16:06 +0000751 if (call->d_timer.id != 0) {
752 pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
753 call->d_timer.id = 0;
754 }
755
Benny Prijono258ece92006-07-22 12:53:04 +0000756 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%d (%.*s)",
Benny Prijonod7a13f12006-04-05 19:08:16 +0000757 call->index,
Benny Prijono258ece92006-07-22 12:53:04 +0000758 inv->cause,
759 (int)inv->cause_text.slen,
760 inv->cause_text.ptr));
Benny Prijonod7a13f12006-04-05 19:08:16 +0000761 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
762 print_call(call->index);
763
764
Benny Prijono60b980e2006-04-03 22:41:26 +0000765 call->inv = NULL;
766 inv->mod_data[mod_siprtp.id] = NULL;
767
768 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000769
770 call->start_time = null_time;
771 call->response_time = null_time;
772 call->connect_time = null_time;
773
Benny Prijono410fbae2006-05-03 18:16:06 +0000774 ++app.uac_calls;
Benny Prijono4adcb912006-04-04 13:12:38 +0000775
776 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
777
778 pj_time_val t;
779
780 pj_gettimeofday(&call->connect_time);
781 if (call->response_time.sec == 0)
782 call->response_time = call->connect_time;
783
784 t = call->connect_time;
785 PJ_TIME_VAL_SUB(t, call->start_time);
786
787 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
788 PJ_TIME_VAL_MSEC(t)));
789
Benny Prijono410fbae2006-05-03 18:16:06 +0000790 if (app.duration != 0) {
791 call->d_timer.id = 1;
792 call->d_timer.user_data = call;
793 call->d_timer.cb = &timer_disconnect_call;
794
795 t.sec = app.duration;
796 t.msec = 0;
797
798 pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
799 }
800
Benny Prijono4adcb912006-04-04 13:12:38 +0000801 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
802 inv->state == PJSIP_INV_STATE_CONNECTING) {
803
804 if (call->response_time.sec == 0)
805 pj_gettimeofday(&call->response_time);
806
Benny Prijono60b980e2006-04-03 22:41:26 +0000807 }
808}
809
810
811/* Utility */
812static void app_perror(const char *sender, const char *title,
813 pj_status_t status)
814{
815 char errmsg[PJ_ERR_MSG_SIZE];
816
817 pj_strerror(status, errmsg, sizeof(errmsg));
818 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
819}
820
821
Benny Prijonodeb31962006-06-22 18:51:03 +0000822/* Worker thread for SIP */
823static int sip_worker_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +0000824{
825 PJ_UNUSED_ARG(arg);
826
827 while (!app.thread_quit) {
Benny Prijono513795f2006-07-18 21:12:24 +0000828 pj_time_val timeout = {0, 10};
Benny Prijono60b980e2006-04-03 22:41:26 +0000829 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
830 }
831
832 return 0;
833}
834
835
Benny Prijono60b980e2006-04-03 22:41:26 +0000836/* Init application options */
837static pj_status_t init_options(int argc, char *argv[])
838{
839 static char ip_addr[32];
840 static char local_uri[64];
841
Benny Prijono4adcb912006-04-04 13:12:38 +0000842 enum { OPT_START,
843 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
Benny Prijonofcb36722006-05-18 18:34:21 +0000844 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
845 OPT_REPORT_FILE };
Benny Prijono4adcb912006-04-04 13:12:38 +0000846
Benny Prijono60b980e2006-04-03 22:41:26 +0000847 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000848 { "count", 1, 0, 'c' },
Benny Prijono410fbae2006-05-03 18:16:06 +0000849 { "duration", 1, 0, 'd' },
850 { "auto-quit", 0, 0, 'q' },
Benny Prijono4adcb912006-04-04 13:12:38 +0000851 { "local-port", 1, 0, 'p' },
852 { "rtp-port", 1, 0, 'r' },
853 { "ip-addr", 1, 0, 'i' },
854
855 { "log-level", 1, 0, 'l' },
856 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
857 { "log-file", 1, 0, OPT_LOG_FILE },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000858
Benny Prijonofcb36722006-05-18 18:34:21 +0000859 { "report-file", 1, 0, OPT_REPORT_FILE },
860
Benny Prijono4d7fd202006-05-14 20:57:20 +0000861 /* Don't support this anymore, see comments in USAGE above.
Benny Prijono4adcb912006-04-04 13:12:38 +0000862 { "a-pt", 1, 0, OPT_A_PT },
863 { "a-name", 1, 0, OPT_A_NAME },
864 { "a-clock", 1, 0, OPT_A_CLOCK },
865 { "a-bitrate", 1, 0, OPT_A_BITRATE },
866 { "a-ptime", 1, 0, OPT_A_PTIME },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000867 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000868
Benny Prijono60b980e2006-04-03 22:41:26 +0000869 { NULL, 0, 0, 0 },
870 };
871 int c;
872 int option_index;
873
874 /* Get local IP address for the default IP address */
875 {
876 const pj_str_t *hostname;
877 pj_sockaddr_in tmp_addr;
878 char *addr;
879
880 hostname = pj_gethostname();
881 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
882 addr = pj_inet_ntoa(tmp_addr.sin_addr);
883 pj_ansi_strcpy(ip_addr, addr);
884 }
885
Benny Prijono4adcb912006-04-04 13:12:38 +0000886 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000887 app.max_calls = 1;
888 app.thread_count = 1;
889 app.sip_port = 5060;
Benny Prijono6647d822006-05-20 13:01:07 +0000890 app.rtp_start_port = RTP_START_PORT;
Benny Prijonodeb31962006-06-22 18:51:03 +0000891 app.local_addr = pj_str(ip_addr);
Benny Prijono4adcb912006-04-04 13:12:38 +0000892 app.log_level = 5;
893 app.app_log_level = 3;
894 app.log_filename = NULL;
895
896 /* Default codecs: */
897 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000898
899 /* Parse options */
900 pj_optind = 0;
Benny Prijono410fbae2006-05-03 18:16:06 +0000901 while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:q",
Benny Prijono60b980e2006-04-03 22:41:26 +0000902 long_options, &option_index))!=-1)
903 {
904 switch (c) {
905 case 'c':
906 app.max_calls = atoi(pj_optarg);
907 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
908 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
909 return 1;
910 }
911 break;
Benny Prijono410fbae2006-05-03 18:16:06 +0000912 case 'd':
913 app.duration = atoi(pj_optarg);
914 break;
915 case 'q':
916 app.auto_quit = 1;
917 break;
918
Benny Prijono60b980e2006-04-03 22:41:26 +0000919 case 'p':
920 app.sip_port = atoi(pj_optarg);
921 break;
922 case 'r':
923 app.rtp_start_port = atoi(pj_optarg);
924 break;
925 case 'i':
Benny Prijonodeb31962006-06-22 18:51:03 +0000926 app.local_addr = pj_str(pj_optarg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000927 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000928
929 case 'l':
930 app.log_level = atoi(pj_optarg);
931 break;
932 case OPT_APP_LOG_LEVEL:
933 app.app_log_level = atoi(pj_optarg);
934 break;
935 case OPT_LOG_FILE:
936 app.log_filename = pj_optarg;
937 break;
938
939 case OPT_A_PT:
940 app.audio_codec.pt = atoi(pj_optarg);
941 break;
942 case OPT_A_NAME:
943 app.audio_codec.name = pj_optarg;
944 break;
945 case OPT_A_CLOCK:
946 app.audio_codec.clock_rate = atoi(pj_optarg);
947 break;
948 case OPT_A_BITRATE:
949 app.audio_codec.bit_rate = atoi(pj_optarg);
950 break;
951 case OPT_A_PTIME:
952 app.audio_codec.ptime = atoi(pj_optarg);
953 break;
Benny Prijonofcb36722006-05-18 18:34:21 +0000954 case OPT_REPORT_FILE:
955 app.report_filename = pj_optarg;
956 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000957
Benny Prijono60b980e2006-04-03 22:41:26 +0000958 default:
959 puts(USAGE);
960 return 1;
961 }
962 }
963
964 /* Check if URL is specified */
965 if (pj_optind < argc)
966 app.uri_to_call = pj_str(argv[pj_optind]);
967
968 /* Build local URI and contact */
Benny Prijonodeb31962006-06-22 18:51:03 +0000969 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
Benny Prijono60b980e2006-04-03 22:41:26 +0000970 app.local_uri = pj_str(local_uri);
971 app.local_contact = app.local_uri;
972
973
974 return PJ_SUCCESS;
975}
976
977
Benny Prijono4adcb912006-04-04 13:12:38 +0000978/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000979 * MEDIA STUFFS
980 */
981
982/*
983 * Create SDP session for a call.
984 */
985static pj_status_t create_sdp( pj_pool_t *pool,
986 struct call *call,
987 pjmedia_sdp_session **p_sdp)
988{
989 pj_time_val tv;
990 pjmedia_sdp_session *sdp;
991 pjmedia_sdp_media *m;
992 pjmedia_sdp_attr *attr;
Benny Prijonodeb31962006-06-22 18:51:03 +0000993 pjmedia_transport_udp_info tpinfo;
Benny Prijono60b980e2006-04-03 22:41:26 +0000994 struct media_stream *audio = &call->media[0];
995
996 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
997
998
Benny Prijonodeb31962006-06-22 18:51:03 +0000999 /* Get transport info */
1000 pjmedia_transport_udp_get_info(audio->transport, &tpinfo);
1001
Benny Prijono60b980e2006-04-03 22:41:26 +00001002 /* Create and initialize basic SDP session */
1003 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
1004
1005 pj_gettimeofday(&tv);
1006 sdp->origin.user = pj_str("pjsip-siprtp");
1007 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
1008 sdp->origin.net_type = pj_str("IN");
1009 sdp->origin.addr_type = pj_str("IP4");
1010 sdp->origin.addr = *pj_gethostname();
1011 sdp->name = pj_str("pjsip");
1012
1013 /* Since we only support one media stream at present, put the
1014 * SDP connection line in the session level.
1015 */
1016 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
1017 sdp->conn->net_type = pj_str("IN");
1018 sdp->conn->addr_type = pj_str("IP4");
Benny Prijonodeb31962006-06-22 18:51:03 +00001019 sdp->conn->addr = app.local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +00001020
1021
1022 /* SDP time and attributes. */
1023 sdp->time.start = sdp->time.stop = 0;
1024 sdp->attr_count = 0;
1025
1026 /* Create media stream 0: */
1027
1028 sdp->media_count = 1;
1029 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
1030 sdp->media[0] = m;
1031
1032 /* Standard media info: */
1033 m->desc.media = pj_str("audio");
Benny Prijono5186eae2007-12-03 14:38:25 +00001034 m->desc.port = pj_ntohs(tpinfo.skinfo.rtp_addr_name.ipv4.sin_port);
Benny Prijono60b980e2006-04-03 22:41:26 +00001035 m->desc.port_count = 1;
1036 m->desc.transport = pj_str("RTP/AVP");
1037
1038 /* Add format and rtpmap for each codec. */
1039 m->desc.fmt_count = 1;
1040 m->attr_count = 0;
1041
1042 {
1043 pjmedia_sdp_rtpmap rtpmap;
1044 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +00001045 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +00001046
Benny Prijono4adcb912006-04-04 13:12:38 +00001047 sprintf(ptstr, "%d", app.audio_codec.pt);
1048 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1049 rtpmap.pt = m->desc.fmt[0];
1050 rtpmap.clock_rate = app.audio_codec.clock_rate;
1051 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +00001052 rtpmap.param.slen = 0;
1053
1054 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1055 m->attr[m->attr_count++] = attr;
1056 }
1057
1058 /* Add sendrecv attribute. */
1059 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1060 attr->name = pj_str("sendrecv");
1061 m->attr[m->attr_count++] = attr;
1062
1063#if 1
1064 /*
1065 * Add support telephony event
1066 */
Benny Prijono4adcb912006-04-04 13:12:38 +00001067 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +00001068 /* Add rtpmap. */
1069 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1070 attr->name = pj_str("rtpmap");
Benny Prijonod95abb42006-10-20 10:18:44 +00001071 attr->value = pj_str("121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +00001072 m->attr[m->attr_count++] = attr;
1073 /* Add fmtp */
1074 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1075 attr->name = pj_str("fmtp");
Benny Prijonod95abb42006-10-20 10:18:44 +00001076 attr->value = pj_str("121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +00001077 m->attr[m->attr_count++] = attr;
1078#endif
1079
1080 /* Done */
1081 *p_sdp = sdp;
1082
1083 return PJ_SUCCESS;
1084}
1085
1086
Benny Prijono513795f2006-07-18 21:12:24 +00001087#if defined(PJ_WIN32) && PJ_WIN32 != 0
1088#include <windows.h>
1089static void boost_priority(void)
1090{
1091 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
1092 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
1093}
1094
Benny Prijono7bef0f52006-07-31 18:54:06 +00001095#elif defined(PJ_LINUX) && PJ_LINUX != 0
1096#include <pthread.h>
1097static void boost_priority(void)
1098{
1099#define POLICY SCHED_FIFO
Benny Prijono7bef0f52006-07-31 18:54:06 +00001100 struct sched_param tp;
1101 int max_prio;
1102 int policy;
1103 int rc;
1104
1105 if (sched_get_priority_min(POLICY) < sched_get_priority_max(POLICY))
1106 max_prio = sched_get_priority_max(POLICY)-1;
1107 else
1108 max_prio = sched_get_priority_max(POLICY)+1;
1109
1110 /*
1111 * Adjust process scheduling algorithm and priority
1112 */
1113 rc = sched_getparam(0, &tp);
1114 if (rc != 0) {
1115 app_perror( THIS_FILE, "sched_getparam error",
1116 PJ_RETURN_OS_ERROR(rc));
1117 return;
1118 }
1119 tp.__sched_priority = max_prio;
1120
1121 rc = sched_setscheduler(0, POLICY, &tp);
1122 if (rc != 0) {
1123 app_perror( THIS_FILE, "sched_setscheduler error",
1124 PJ_RETURN_OS_ERROR(rc));
1125 }
1126
1127 PJ_LOG(4, (THIS_FILE, "New process policy=%d, priority=%d",
1128 policy, tp.__sched_priority));
1129
1130 /*
1131 * Adjust thread scheduling algorithm and priority
1132 */
1133 rc = pthread_getschedparam(pthread_self(), &policy, &tp);
1134 if (rc != 0) {
1135 app_perror( THIS_FILE, "pthread_getschedparam error",
1136 PJ_RETURN_OS_ERROR(rc));
1137 return;
1138 }
1139
1140 PJ_LOG(4, (THIS_FILE, "Old thread policy=%d, priority=%d",
1141 policy, tp.__sched_priority));
1142
1143 policy = POLICY;
1144 tp.__sched_priority = max_prio;
1145
1146 rc = pthread_setschedparam(pthread_self(), policy, &tp);
1147 if (rc != 0) {
1148 app_perror( THIS_FILE, "pthread_setschedparam error",
1149 PJ_RETURN_OS_ERROR(rc));
1150 return;
1151 }
1152
1153 PJ_LOG(4, (THIS_FILE, "New thread policy=%d, priority=%d",
1154 policy, tp.__sched_priority));
1155}
1156
Benny Prijono513795f2006-07-18 21:12:24 +00001157#else
1158# define boost_priority()
1159#endif
1160
1161
Benny Prijonodeb31962006-06-22 18:51:03 +00001162/*
1163 * This callback is called by media transport on receipt of RTP packet.
1164 */
1165static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size)
1166{
1167 struct media_stream *strm;
1168 pj_status_t status;
1169 const pjmedia_rtp_hdr *hdr;
1170 const void *payload;
1171 unsigned payload_len;
1172
1173 strm = user_data;
1174
1175 /* Discard packet if media is inactive */
1176 if (!strm->active)
1177 return;
1178
1179 /* Check for errors */
1180 if (size < 0) {
1181 app_perror(THIS_FILE, "RTP recv() error", -size);
1182 return;
1183 }
1184
1185 /* Decode RTP packet. */
1186 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1187 pkt, size,
1188 &hdr, &payload, &payload_len);
1189 if (status != PJ_SUCCESS) {
1190 app_perror(THIS_FILE, "RTP decode error", status);
1191 return;
1192 }
1193
1194 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1195
1196 /* Update the RTCP session. */
1197 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1198 pj_ntohl(hdr->ts), payload_len);
1199
1200 /* Update RTP session */
1201 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1202
1203}
1204
1205/*
1206 * This callback is called by media transport on receipt of RTCP packet.
1207 */
1208static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size)
1209{
1210 struct media_stream *strm;
1211
1212 strm = user_data;
1213
1214 /* Discard packet if media is inactive */
1215 if (!strm->active)
1216 return;
1217
1218 /* Check for errors */
1219 if (size < 0) {
1220 app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
1221 return;
1222 }
1223
1224 /* Update RTCP session */
1225 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1226}
1227
1228
Benny Prijono513795f2006-07-18 21:12:24 +00001229/*
1230 * Media thread
1231 *
1232 * This is the thread to send and receive both RTP and RTCP packets.
1233 */
1234static int media_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +00001235{
Benny Prijono513795f2006-07-18 21:12:24 +00001236 enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
1237 struct media_stream *strm = arg;
1238 char packet[1500];
1239 unsigned msec_interval;
1240 pj_timestamp freq, next_rtp, next_rtcp;
Benny Prijono60b980e2006-04-03 22:41:26 +00001241
Benny Prijono6647d822006-05-20 13:01:07 +00001242
Benny Prijono513795f2006-07-18 21:12:24 +00001243 /* Boost thread priority if necessary */
1244 boost_priority();
Benny Prijono6647d822006-05-20 13:01:07 +00001245
Benny Prijono513795f2006-07-18 21:12:24 +00001246 /* Let things settle */
Benny Prijono258ece92006-07-22 12:53:04 +00001247 pj_thread_sleep(100);
Benny Prijono6647d822006-05-20 13:01:07 +00001248
Benny Prijono513795f2006-07-18 21:12:24 +00001249 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1250 pj_get_timestamp_freq(&freq);
1251
1252 pj_get_timestamp(&next_rtp);
1253 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
1254
1255 next_rtcp = next_rtp;
1256 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
1257
1258
1259 while (!strm->thread_quit_flag) {
1260 pj_timestamp now, lesser;
1261 pj_time_val timeout;
1262 pj_bool_t send_rtp, send_rtcp;
1263
1264 send_rtp = send_rtcp = PJ_FALSE;
1265
1266 /* Determine how long to sleep */
1267 if (next_rtp.u64 < next_rtcp.u64) {
1268 lesser = next_rtp;
1269 send_rtp = PJ_TRUE;
1270 } else {
1271 lesser = next_rtcp;
1272 send_rtcp = PJ_TRUE;
1273 }
1274
1275 pj_get_timestamp(&now);
1276 if (lesser.u64 <= now.u64) {
1277 timeout.sec = timeout.msec = 0;
1278 //printf("immediate "); fflush(stdout);
1279 } else {
1280 pj_uint64_t tick_delay;
1281 tick_delay = lesser.u64 - now.u64;
1282 timeout.sec = 0;
1283 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1284 pj_time_val_normalize(&timeout);
1285
1286 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
1287 }
1288
1289 /* Wait for next interval */
1290 //if (timeout.sec!=0 && timeout.msec!=0) {
1291 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
1292 if (strm->thread_quit_flag)
1293 break;
1294 //}
1295
1296 pj_get_timestamp(&now);
1297
1298 if (send_rtp || next_rtp.u64 <= now.u64) {
1299 /*
1300 * Time to send RTP packet.
1301 */
1302 pj_status_t status;
Benny Prijonoe960bb52007-01-21 17:53:39 +00001303 const void *p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001304 const pjmedia_rtp_hdr *hdr;
1305 pj_ssize_t size;
1306 int hdrlen;
1307
1308 /* Format RTP header */
1309 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1310 0, /* marker bit */
1311 strm->bytes_per_frame,
1312 strm->samples_per_frame,
Benny Prijonoe960bb52007-01-21 17:53:39 +00001313 &p_hdr, &hdrlen);
Benny Prijono513795f2006-07-18 21:12:24 +00001314 if (status == PJ_SUCCESS) {
1315
1316 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
Benny Prijonoe960bb52007-01-21 17:53:39 +00001317
1318 hdr = (const pjmedia_rtp_hdr*) p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001319
1320 /* Copy RTP header to packet */
1321 pj_memcpy(packet, hdr, hdrlen);
1322
1323 /* Zero the payload */
1324 pj_bzero(packet+hdrlen, strm->bytes_per_frame);
1325
1326 /* Send RTP packet */
1327 size = hdrlen + strm->bytes_per_frame;
1328 status = pjmedia_transport_send_rtp(strm->transport,
1329 packet, size);
1330 if (status != PJ_SUCCESS)
1331 app_perror(THIS_FILE, "Error sending RTP packet", status);
1332
1333 } else {
1334 pj_assert(!"RTP encode() error");
1335 }
1336
1337 /* Update RTCP SR */
1338 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1339
1340 /* Schedule next send */
1341 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
1342 }
1343
1344
1345 if (send_rtcp || next_rtcp.u64 <= now.u64) {
1346 /*
1347 * Time to send RTCP packet.
1348 */
Benny Prijonofe81cfa2007-09-20 11:30:59 +00001349 void *rtcp_pkt;
Benny Prijono513795f2006-07-18 21:12:24 +00001350 int rtcp_len;
1351 pj_ssize_t size;
1352 pj_status_t status;
1353
1354 /* Build RTCP packet */
1355 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1356
Benny Prijono4d3aa922006-06-22 22:31:48 +00001357
Benny Prijono513795f2006-07-18 21:12:24 +00001358 /* Send packet */
1359 size = rtcp_len;
1360 status = pjmedia_transport_send_rtcp(strm->transport,
1361 rtcp_pkt, size);
1362 if (status != PJ_SUCCESS) {
1363 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1364 }
1365
1366 /* Schedule next send */
1367 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
1368 1000);
1369 }
1370 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001371
Benny Prijono513795f2006-07-18 21:12:24 +00001372 return 0;
Benny Prijono60b980e2006-04-03 22:41:26 +00001373}
1374
Benny Prijono513795f2006-07-18 21:12:24 +00001375
Benny Prijono60b980e2006-04-03 22:41:26 +00001376/* Callback to be called when SDP negotiation is done in the call: */
1377static void call_on_media_update( pjsip_inv_session *inv,
1378 pj_status_t status)
1379{
1380 struct call *call;
1381 pj_pool_t *pool;
1382 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001383 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001384 struct codec *codec_desc = NULL;
1385 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001386
1387 call = inv->mod_data[mod_siprtp.id];
1388 pool = inv->dlg->pool;
1389 audio = &call->media[0];
1390
1391 /* If this is a mid-call media update, then destroy existing media */
Benny Prijono513795f2006-07-18 21:12:24 +00001392 if (audio->thread != NULL)
Benny Prijono60b980e2006-04-03 22:41:26 +00001393 destroy_call_media(call->index);
1394
1395
1396 /* Do nothing if media negotiation has failed */
1397 if (status != PJ_SUCCESS) {
1398 app_perror(THIS_FILE, "SDP negotiation failed", status);
1399 return;
1400 }
1401
1402
1403 /* Capture stream definition from the SDP */
1404 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1405 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1406
1407 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
Benny Prijonob04c9e02006-05-17 17:17:39 +00001408 local_sdp, remote_sdp, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001409 if (status != PJ_SUCCESS) {
1410 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1411 return;
1412 }
1413
Benny Prijono4adcb912006-04-04 13:12:38 +00001414 /* Get the remainder of codec information from codec descriptor */
1415 if (audio->si.fmt.pt == app.audio_codec.pt)
1416 codec_desc = &app.audio_codec;
1417 else {
1418 /* Find the codec description in codec array */
1419 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1420 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1421 codec_desc = &audio_codecs[i];
1422 break;
1423 }
1424 }
1425
1426 if (codec_desc == NULL) {
1427 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1428 return;
1429 }
1430 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001431
Benny Prijono15953012006-04-27 22:37:08 +00001432 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001433 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1434 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001435
1436
1437 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001438 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001439 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001440 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001441 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001442
Benny Prijono4adcb912006-04-04 13:12:38 +00001443
Benny Prijonodeb31962006-06-22 18:51:03 +00001444 /* Attach media to transport */
1445 status = pjmedia_transport_attach(audio->transport, audio,
1446 &audio->si.rem_addr,
Benny Prijono513795f2006-07-18 21:12:24 +00001447 &audio->si.rem_rtcp,
Benny Prijonodeb31962006-06-22 18:51:03 +00001448 sizeof(pj_sockaddr_in),
1449 &on_rx_rtp,
1450 &on_rx_rtcp);
1451 if (status != PJ_SUCCESS) {
1452 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1453 return;
1454 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001455
Benny Prijono513795f2006-07-18 21:12:24 +00001456 /* Start media thread. */
1457 audio->thread_quit_flag = 0;
Benny Prijono6869dc92007-06-03 00:37:30 +00001458#if PJ_HAS_THREADS
Benny Prijono513795f2006-07-18 21:12:24 +00001459 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1460 0, 0, &audio->thread);
1461 if (status != PJ_SUCCESS) {
1462 app_perror(THIS_FILE, "Error creating media thread", status);
1463 return;
1464 }
Benny Prijono6869dc92007-06-03 00:37:30 +00001465#endif
Benny Prijono513795f2006-07-18 21:12:24 +00001466
Benny Prijonodeb31962006-06-22 18:51:03 +00001467 /* Set the media as active */
1468 audio->active = PJ_TRUE;
Benny Prijono60b980e2006-04-03 22:41:26 +00001469}
1470
1471
1472
1473/* Destroy call's media */
1474static void destroy_call_media(unsigned call_index)
1475{
1476 struct media_stream *audio = &app.call[call_index].media[0];
1477
Benny Prijono5a9e2042006-11-14 13:35:20 +00001478 if (audio) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001479 audio->active = PJ_FALSE;
1480
Benny Prijono5a9e2042006-11-14 13:35:20 +00001481 if (audio->thread) {
1482 audio->thread_quit_flag = 1;
1483 pj_thread_join(audio->thread);
1484 pj_thread_destroy(audio->thread);
1485 audio->thread = NULL;
1486 audio->thread_quit_flag = 0;
1487 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001488
Benny Prijono5b1e14d2006-11-25 08:46:48 +00001489 pjmedia_transport_detach(audio->transport, audio);
Benny Prijono60b980e2006-04-03 22:41:26 +00001490 }
1491}
1492
Benny Prijono513795f2006-07-18 21:12:24 +00001493
Benny Prijono4adcb912006-04-04 13:12:38 +00001494/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001495 * USER INTERFACE STUFFS
1496 */
Benny Prijono258ece92006-07-22 12:53:04 +00001497
1498static void call_get_duration(int call_index, pj_time_val *dur)
1499{
1500 struct call *call = &app.call[call_index];
1501 pjsip_inv_session *inv;
1502
1503 dur->sec = dur->msec = 0;
1504
1505 if (!call)
1506 return;
1507
1508 inv = call->inv;
1509 if (!inv)
1510 return;
1511
1512 if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
1513
1514 pj_gettimeofday(dur);
1515 PJ_TIME_VAL_SUB((*dur), call->connect_time);
1516 }
1517}
1518
1519
1520static const char *good_number(char *buf, pj_int32_t val)
1521{
1522 if (val < 1000) {
1523 pj_ansi_sprintf(buf, "%d", val);
1524 } else if (val < 1000000) {
1525 pj_ansi_sprintf(buf, "%d.%02dK",
1526 val / 1000,
1527 (val % 1000) / 100);
1528 } else {
1529 pj_ansi_sprintf(buf, "%d.%02dM",
1530 val / 1000000,
1531 (val % 1000000) / 10000);
1532 }
1533
1534 return buf;
1535}
1536
1537
1538
1539static void print_avg_stat(void)
1540{
1541#define MIN_(var,val) if ((int)val < (int)var) var = val
1542#define MAX_(var,val) if ((int)val > (int)var) var = val
1543#define AVG_(var,val) var = ( ((var * count) + val) / (count+1) )
1544#define BIGVAL 0x7FFFFFFFL
1545 struct stat_entry
1546 {
1547 int min, avg, max;
1548 };
1549
1550 struct stat_entry call_dur, call_pdd;
1551 pjmedia_rtcp_stat min_stat, avg_stat, max_stat;
1552
1553 char srx_min[16], srx_avg[16], srx_max[16];
1554 char brx_min[16], brx_avg[16], brx_max[16];
1555 char stx_min[16], stx_avg[16], stx_max[16];
1556 char btx_min[16], btx_avg[16], btx_max[16];
1557
1558
1559 unsigned i, count;
1560
1561 pj_bzero(&call_dur, sizeof(call_dur));
1562 call_dur.min = BIGVAL;
1563
1564 pj_bzero(&call_pdd, sizeof(call_pdd));
1565 call_pdd.min = BIGVAL;
1566
1567 pj_bzero(&min_stat, sizeof(min_stat));
1568 min_stat.rx.pkt = min_stat.tx.pkt = BIGVAL;
1569 min_stat.rx.bytes = min_stat.tx.bytes = BIGVAL;
1570 min_stat.rx.loss = min_stat.tx.loss = BIGVAL;
1571 min_stat.rx.dup = min_stat.tx.dup = BIGVAL;
1572 min_stat.rx.reorder = min_stat.tx.reorder = BIGVAL;
1573 min_stat.rx.jitter.min = min_stat.tx.jitter.min = BIGVAL;
1574 min_stat.rtt.min = BIGVAL;
1575
1576 pj_bzero(&avg_stat, sizeof(avg_stat));
1577 pj_bzero(&max_stat, sizeof(max_stat));
1578
1579
1580 for (i=0, count=0; i<app.max_calls; ++i) {
1581
1582 struct call *call = &app.call[i];
1583 struct media_stream *audio = &call->media[0];
1584 pj_time_val dur;
1585 unsigned msec_dur;
1586
1587 if (call->inv == NULL ||
1588 call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
1589 call->connect_time.sec == 0)
1590 {
1591 continue;
1592 }
1593
1594 /* Duration */
1595 call_get_duration(i, &dur);
1596 msec_dur = PJ_TIME_VAL_MSEC(dur);
1597
1598 MIN_(call_dur.min, msec_dur);
1599 MAX_(call_dur.max, msec_dur);
1600 AVG_(call_dur.avg, msec_dur);
1601
1602 /* Connect delay */
1603 if (call->connect_time.sec) {
1604 pj_time_val t = call->connect_time;
1605 PJ_TIME_VAL_SUB(t, call->start_time);
1606 msec_dur = PJ_TIME_VAL_MSEC(t);
1607 } else {
1608 msec_dur = 10;
1609 }
1610
1611 MIN_(call_pdd.min, msec_dur);
1612 MAX_(call_pdd.max, msec_dur);
1613 AVG_(call_pdd.avg, msec_dur);
1614
1615 /* RX Statistisc: */
1616
1617 /* Packets */
1618 MIN_(min_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1619 MAX_(max_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1620 AVG_(avg_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1621
1622 /* Bytes */
1623 MIN_(min_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1624 MAX_(max_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1625 AVG_(avg_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1626
1627
1628 /* Packet loss */
1629 MIN_(min_stat.rx.loss, audio->rtcp.stat.rx.loss);
1630 MAX_(max_stat.rx.loss, audio->rtcp.stat.rx.loss);
1631 AVG_(avg_stat.rx.loss, audio->rtcp.stat.rx.loss);
1632
1633 /* Packet dup */
1634 MIN_(min_stat.rx.dup, audio->rtcp.stat.rx.dup);
1635 MAX_(max_stat.rx.dup, audio->rtcp.stat.rx.dup);
1636 AVG_(avg_stat.rx.dup, audio->rtcp.stat.rx.dup);
1637
1638 /* Packet reorder */
1639 MIN_(min_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1640 MAX_(max_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1641 AVG_(avg_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1642
1643 /* Jitter */
1644 MIN_(min_stat.rx.jitter.min, audio->rtcp.stat.rx.jitter.min);
1645 MAX_(max_stat.rx.jitter.max, audio->rtcp.stat.rx.jitter.max);
1646 AVG_(avg_stat.rx.jitter.avg, audio->rtcp.stat.rx.jitter.avg);
1647
1648
1649 /* TX Statistisc: */
1650
1651 /* Packets */
1652 MIN_(min_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1653 MAX_(max_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1654 AVG_(avg_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1655
1656 /* Bytes */
1657 MIN_(min_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1658 MAX_(max_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1659 AVG_(avg_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1660
1661 /* Packet loss */
1662 MIN_(min_stat.tx.loss, audio->rtcp.stat.tx.loss);
1663 MAX_(max_stat.tx.loss, audio->rtcp.stat.tx.loss);
1664 AVG_(avg_stat.tx.loss, audio->rtcp.stat.tx.loss);
1665
1666 /* Packet dup */
1667 MIN_(min_stat.tx.dup, audio->rtcp.stat.tx.dup);
1668 MAX_(max_stat.tx.dup, audio->rtcp.stat.tx.dup);
1669 AVG_(avg_stat.tx.dup, audio->rtcp.stat.tx.dup);
1670
1671 /* Packet reorder */
1672 MIN_(min_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1673 MAX_(max_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1674 AVG_(avg_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1675
1676 /* Jitter */
1677 MIN_(min_stat.tx.jitter.min, audio->rtcp.stat.tx.jitter.min);
1678 MAX_(max_stat.tx.jitter.max, audio->rtcp.stat.tx.jitter.max);
1679 AVG_(avg_stat.tx.jitter.avg, audio->rtcp.stat.tx.jitter.avg);
1680
1681
1682 /* RTT */
1683 MIN_(min_stat.rtt.min, audio->rtcp.stat.rtt.min);
1684 MAX_(max_stat.rtt.max, audio->rtcp.stat.rtt.max);
1685 AVG_(avg_stat.rtt.avg, audio->rtcp.stat.rtt.avg);
1686
1687 ++count;
1688 }
1689
1690 if (count == 0) {
1691 puts("No active calls");
1692 return;
1693 }
1694
1695 printf("Total %d call(s) active.\n"
1696 " Average Statistics\n"
1697 " min avg max \n"
1698 " -----------------------\n"
1699 " call duration: %7d %7d %7d %s\n"
1700 " connect delay: %7d %7d %7d %s\n"
1701 " RX stat:\n"
1702 " packets: %7s %7s %7s %s\n"
1703 " payload: %7s %7s %7s %s\n"
1704 " loss: %7d %7d %7d %s\n"
1705 " percent loss: %7.3f %7.3f %7.3f %s\n"
1706 " dup: %7d %7d %7d %s\n"
1707 " reorder: %7d %7d %7d %s\n"
1708 " jitter: %7.3f %7.3f %7.3f %s\n"
1709 " TX stat:\n"
1710 " packets: %7s %7s %7s %s\n"
1711 " payload: %7s %7s %7s %s\n"
1712 " loss: %7d %7d %7d %s\n"
1713 " percent loss: %7.3f %7.3f %7.3f %s\n"
1714 " dup: %7d %7d %7d %s\n"
1715 " reorder: %7d %7d %7d %s\n"
1716 " jitter: %7.3f %7.3f %7.3f %s\n"
1717 " RTT : %7.3f %7.3f %7.3f %s\n"
1718 ,
1719 count,
1720 call_dur.min/1000, call_dur.avg/1000, call_dur.max/1000,
1721 "seconds",
1722
1723 call_pdd.min, call_pdd.avg, call_pdd.max,
1724 "ms",
1725
1726 /* rx */
1727
1728 good_number(srx_min, min_stat.rx.pkt),
1729 good_number(srx_avg, avg_stat.rx.pkt),
1730 good_number(srx_max, max_stat.rx.pkt),
1731 "packets",
1732
1733 good_number(brx_min, min_stat.rx.bytes),
1734 good_number(brx_avg, avg_stat.rx.bytes),
1735 good_number(brx_max, max_stat.rx.bytes),
1736 "bytes",
1737
1738 min_stat.rx.loss, avg_stat.rx.loss, max_stat.rx.loss,
1739 "packets",
1740
1741 min_stat.rx.loss*100.0/(min_stat.rx.pkt+min_stat.rx.loss),
1742 avg_stat.rx.loss*100.0/(avg_stat.rx.pkt+avg_stat.rx.loss),
1743 max_stat.rx.loss*100.0/(max_stat.rx.pkt+max_stat.rx.loss),
1744 "%",
1745
1746
1747 min_stat.rx.dup, avg_stat.rx.dup, max_stat.rx.dup,
1748 "packets",
1749
1750 min_stat.rx.reorder, avg_stat.rx.reorder, max_stat.rx.reorder,
1751 "packets",
1752
1753 min_stat.rx.jitter.min/1000.0,
1754 avg_stat.rx.jitter.avg/1000.0,
1755 max_stat.rx.jitter.max/1000.0,
1756 "ms",
1757
1758 /* tx */
1759
1760 good_number(stx_min, min_stat.tx.pkt),
1761 good_number(stx_avg, avg_stat.tx.pkt),
1762 good_number(stx_max, max_stat.tx.pkt),
1763 "packets",
1764
1765 good_number(btx_min, min_stat.tx.bytes),
1766 good_number(btx_avg, avg_stat.tx.bytes),
1767 good_number(btx_max, max_stat.tx.bytes),
1768 "bytes",
1769
1770 min_stat.tx.loss, avg_stat.tx.loss, max_stat.tx.loss,
1771 "packets",
1772
Benny Prijono05784a52006-07-25 11:54:15 +00001773 min_stat.tx.loss*100.0/(min_stat.tx.pkt+min_stat.tx.loss),
1774 avg_stat.tx.loss*100.0/(avg_stat.tx.pkt+avg_stat.tx.loss),
1775 max_stat.tx.loss*100.0/(max_stat.tx.pkt+max_stat.tx.loss),
Benny Prijono258ece92006-07-22 12:53:04 +00001776 "%",
1777
1778 min_stat.tx.dup, avg_stat.tx.dup, max_stat.tx.dup,
1779 "packets",
1780
1781 min_stat.tx.reorder, avg_stat.tx.reorder, max_stat.tx.reorder,
1782 "packets",
1783
1784 min_stat.tx.jitter.min/1000.0,
1785 avg_stat.tx.jitter.avg/1000.0,
1786 max_stat.tx.jitter.max/1000.0,
1787 "ms",
1788
1789 /* rtt */
1790 min_stat.rtt.min/1000.0,
1791 avg_stat.rtt.avg/1000.0,
1792 max_stat.rtt.max/1000.0,
1793 "ms"
1794 );
1795
1796}
1797
1798
Benny Prijono16a6b0e2006-05-12 10:20:03 +00001799#include "siprtp_report.c"
Benny Prijono60b980e2006-04-03 22:41:26 +00001800
1801
1802static void list_calls()
1803{
1804 unsigned i;
1805 puts("List all calls:");
1806 for (i=0; i<app.max_calls; ++i) {
1807 if (!app.call[i].inv)
1808 continue;
1809 print_call(i);
1810 }
1811}
1812
1813static void hangup_call(unsigned index)
1814{
1815 pjsip_tx_data *tdata;
1816 pj_status_t status;
1817
1818 if (app.call[index].inv == NULL)
1819 return;
1820
1821 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1822 if (status==PJ_SUCCESS && tdata!=NULL)
1823 pjsip_inv_send_msg(app.call[index].inv, tdata);
1824}
1825
1826static void hangup_all_calls()
1827{
1828 unsigned i;
1829 for (i=0; i<app.max_calls; ++i) {
1830 if (!app.call[i].inv)
1831 continue;
1832 hangup_call(i);
1833 }
Benny Prijonodeb31962006-06-22 18:51:03 +00001834
1835 /* Wait until all calls are terminated */
1836 for (i=0; i<app.max_calls; ++i) {
1837 while (app.call[i].inv)
1838 pj_thread_sleep(10);
1839 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001840}
1841
1842static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1843{
1844 char *p;
1845
1846 printf("%s (empty to cancel): ", title); fflush(stdout);
1847 fgets(buf, len, stdin);
1848
1849 /* Remove trailing newlines. */
1850 for (p=buf; ; ++p) {
1851 if (*p=='\r' || *p=='\n') *p='\0';
1852 else if (!*p) break;
1853 }
1854
1855 if (!*buf)
1856 return PJ_FALSE;
1857
1858 return PJ_TRUE;
1859}
1860
1861
1862static const char *MENU =
1863"\n"
1864"Enter menu character:\n"
Benny Prijono258ece92006-07-22 12:53:04 +00001865" s Summary\n"
Benny Prijono60b980e2006-04-03 22:41:26 +00001866" l List all calls\n"
1867" h Hangup a call\n"
1868" H Hangup all calls\n"
1869" q Quit\n"
1870"\n";
1871
1872
1873/* Main screen menu */
1874static void console_main()
1875{
1876 char input1[10];
1877 unsigned i;
1878
Benny Prijono4adcb912006-04-04 13:12:38 +00001879 printf("%s", MENU);
1880
Benny Prijono60b980e2006-04-03 22:41:26 +00001881 for (;;) {
1882 printf(">>> "); fflush(stdout);
1883 fgets(input1, sizeof(input1), stdin);
1884
1885 switch (input1[0]) {
Benny Prijono258ece92006-07-22 12:53:04 +00001886
1887 case 's':
1888 print_avg_stat();
1889 break;
1890
Benny Prijono60b980e2006-04-03 22:41:26 +00001891 case 'l':
1892 list_calls();
1893 break;
1894
1895 case 'h':
1896 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1897 break;
1898
1899 i = atoi(input1);
1900 hangup_call(i);
1901 break;
1902
1903 case 'H':
1904 hangup_all_calls();
1905 break;
1906
1907 case 'q':
1908 goto on_exit;
1909
1910 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001911 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001912 printf("%s", MENU);
1913 break;
1914 }
1915
1916 fflush(stdout);
1917 }
1918
1919on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001920 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001921}
1922
1923
Benny Prijono4adcb912006-04-04 13:12:38 +00001924/*****************************************************************************
1925 * Below is a simple module to log all incoming and outgoing SIP messages
1926 */
1927
1928
Benny Prijono60b980e2006-04-03 22:41:26 +00001929/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001930static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001931{
1932 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1933 "%s\n"
1934 "--end msg--",
1935 rdata->msg_info.len,
1936 pjsip_rx_data_get_info(rdata),
1937 rdata->pkt_info.src_name,
1938 rdata->pkt_info.src_port,
1939 rdata->msg_info.msg_buf));
1940
1941 /* Always return false, otherwise messages will not get processed! */
1942 return PJ_FALSE;
1943}
1944
1945/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001946static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001947{
1948
1949 /* Important note:
1950 * tp_info field is only valid after outgoing messages has passed
1951 * transport layer. So don't try to access tp_info when the module
1952 * has lower priority than transport layer.
1953 */
1954
1955 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1956 "%s\n"
1957 "--end msg--",
1958 (tdata->buf.cur - tdata->buf.start),
1959 pjsip_tx_data_get_info(tdata),
1960 tdata->tp_info.dst_name,
1961 tdata->tp_info.dst_port,
1962 tdata->buf.start));
1963
1964 /* Always return success, otherwise message will not get sent! */
1965 return PJ_SUCCESS;
1966}
1967
1968/* The module instance. */
1969static pjsip_module msg_logger =
1970{
1971 NULL, NULL, /* prev, next. */
1972 { "mod-siprtp-log", 14 }, /* Name. */
1973 -1, /* Id */
1974 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1975 NULL, /* load() */
1976 NULL, /* start() */
1977 NULL, /* stop() */
1978 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001979 &logger_on_rx_msg, /* on_rx_request() */
1980 &logger_on_rx_msg, /* on_rx_response() */
1981 &logger_on_tx_msg, /* on_tx_request. */
1982 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00001983 NULL, /* on_tsx_state() */
1984
1985};
1986
1987
1988
Benny Prijono4adcb912006-04-04 13:12:38 +00001989/*****************************************************************************
1990 * Console application custom logging:
1991 */
1992
1993
1994static FILE *log_file;
1995
1996
1997static void app_log_writer(int level, const char *buffer, int len)
1998{
1999 /* Write to both stdout and file. */
2000
2001 if (level <= app.app_log_level)
2002 pj_log_write(level, buffer, len);
2003
2004 if (log_file) {
2005 fwrite(buffer, len, 1, log_file);
2006 fflush(log_file);
2007 }
2008}
2009
2010
2011pj_status_t app_logging_init(void)
2012{
2013 /* Redirect log function to ours */
2014
2015 pj_log_set_log_func( &app_log_writer );
2016
2017 /* If output log file is desired, create the file: */
2018
2019 if (app.log_filename) {
2020 log_file = fopen(app.log_filename, "wt");
2021 if (log_file == NULL) {
2022 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
2023 app.log_filename));
2024 return -1;
2025 }
2026 }
2027
2028 return PJ_SUCCESS;
2029}
2030
2031
2032void app_logging_shutdown(void)
2033{
2034 /* Close logging file, if any: */
2035
2036 if (log_file) {
2037 fclose(log_file);
2038 log_file = NULL;
2039 }
2040}
2041
Benny Prijono60b980e2006-04-03 22:41:26 +00002042
2043/*
2044 * main()
2045 */
2046int main(int argc, char *argv[])
2047{
Benny Prijono4adcb912006-04-04 13:12:38 +00002048 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00002049 pj_status_t status;
2050
Benny Prijono4adcb912006-04-04 13:12:38 +00002051 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00002052 status = pj_init();
2053 if (status != PJ_SUCCESS)
2054 return 1;
2055
Benny Prijono4adcb912006-04-04 13:12:38 +00002056 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00002057 status = init_options(argc, argv);
2058 if (status != PJ_SUCCESS)
2059 return 1;
2060
Benny Prijono410fbae2006-05-03 18:16:06 +00002061 /* Verify options: */
2062
2063 /* Auto-quit can not be specified for UAS */
2064 if (app.auto_quit && app.uri_to_call.slen == 0) {
2065 printf("Error: --auto-quit option only valid for outgoing "
2066 "mode (UAC) only\n");
2067 return 1;
2068 }
2069
Benny Prijono4adcb912006-04-04 13:12:38 +00002070 /* Init logging */
2071 status = app_logging_init();
2072 if (status != PJ_SUCCESS)
2073 return 1;
2074
2075 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00002076 status = init_sip();
2077 if (status != PJ_SUCCESS) {
2078 app_perror(THIS_FILE, "Initialization has failed", status);
2079 destroy_sip();
2080 return 1;
2081 }
2082
Benny Prijono4adcb912006-04-04 13:12:38 +00002083 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00002084 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
2085
Benny Prijono4adcb912006-04-04 13:12:38 +00002086 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00002087 status = init_media();
2088 if (status != PJ_SUCCESS) {
2089 app_perror(THIS_FILE, "Media initialization failed", status);
2090 destroy_sip();
2091 return 1;
2092 }
2093
Benny Prijono9a0eab52006-04-04 19:43:24 +00002094 /* Start worker threads */
Benny Prijono6869dc92007-06-03 00:37:30 +00002095#if PJ_HAS_THREADS
Benny Prijono9a0eab52006-04-04 19:43:24 +00002096 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +00002097 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
2098 0, 0, &app.sip_thread[i]);
Benny Prijono9a0eab52006-04-04 19:43:24 +00002099 }
Benny Prijono6869dc92007-06-03 00:37:30 +00002100#endif
Benny Prijono9a0eab52006-04-04 19:43:24 +00002101
Benny Prijono4adcb912006-04-04 13:12:38 +00002102 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00002103 if (app.uri_to_call.slen) {
2104 unsigned i;
2105
Benny Prijono4adcb912006-04-04 13:12:38 +00002106 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
2107 app.uri_to_call.ptr));
2108
Benny Prijono60b980e2006-04-03 22:41:26 +00002109 for (i=0; i<app.max_calls; ++i) {
2110 status = make_call(&app.uri_to_call);
2111 if (status != PJ_SUCCESS) {
2112 app_perror(THIS_FILE, "Error making call", status);
2113 break;
2114 }
2115 }
Benny Prijono4adcb912006-04-04 13:12:38 +00002116
Benny Prijono410fbae2006-05-03 18:16:06 +00002117 if (app.auto_quit) {
2118 /* Wait for calls to complete */
2119 while (app.uac_calls < app.max_calls)
2120 pj_thread_sleep(100);
2121 pj_thread_sleep(200);
2122 } else {
Benny Prijono6869dc92007-06-03 00:37:30 +00002123#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002124 /* Start user interface loop */
2125 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002126#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002127 }
2128
Benny Prijono4adcb912006-04-04 13:12:38 +00002129 } else {
2130
2131 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
2132 app.max_calls));
Benny Prijono4adcb912006-04-04 13:12:38 +00002133
Benny Prijono6869dc92007-06-03 00:37:30 +00002134#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002135 /* Start user interface loop */
2136 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002137#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002138 }
Benny Prijono60b980e2006-04-03 22:41:26 +00002139
Benny Prijono6869dc92007-06-03 00:37:30 +00002140#if !PJ_HAS_THREADS
2141 PJ_LOG(3,(THIS_FILE, "Press Ctrl-C to quit"));
2142 for (;;) {
2143 pj_time_val t = {0, 10};
2144 pjsip_endpt_handle_events(app.sip_endpt, &t);
2145 }
2146#endif
Benny Prijono4adcb912006-04-04 13:12:38 +00002147
2148 /* Shutting down... */
Benny Prijono4d3aa922006-06-22 22:31:48 +00002149 destroy_sip();
Benny Prijono513795f2006-07-18 21:12:24 +00002150 destroy_media();
Benny Prijono6647d822006-05-20 13:01:07 +00002151
2152 if (app.pool) {
2153 pj_pool_release(app.pool);
2154 app.pool = NULL;
2155 pj_caching_pool_destroy(&app.cp);
2156 }
2157
Benny Prijono4adcb912006-04-04 13:12:38 +00002158 app_logging_shutdown();
2159
Benny Prijono5b1e14d2006-11-25 08:46:48 +00002160 /* Shutdown PJLIB */
2161 pj_shutdown();
Benny Prijono60b980e2006-04-03 22:41:26 +00002162
2163 return 0;
2164}
2165