blob: 001fd2e38419a32f9f3165466c7347aecc838214 [file] [log] [blame]
Benny Prijono60b980e2006-04-03 22:41:26 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
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 Prijono9a0eab52006-04-04 19:43:24 +000077
78#if PJ_HAS_HIGH_RES_TIMER==0
79# error "High resolution timer is needed for this sample"
80#endif
81
Benny Prijono60b980e2006-04-03 22:41:26 +000082#define THIS_FILE "siprtp.c"
83#define MAX_CALLS 1024
Benny Prijono6647d822006-05-20 13:01:07 +000084#define RTP_START_PORT 4000
Benny Prijono60b980e2006-04-03 22:41:26 +000085
86
Benny Prijono4adcb912006-04-04 13:12:38 +000087/* Codec descriptor: */
88struct codec
89{
90 unsigned pt;
91 char* name;
92 unsigned clock_rate;
93 unsigned bit_rate;
94 unsigned ptime;
95 char* description;
96};
97
98
Benny Prijonodeb31962006-06-22 18:51:03 +000099/* A bidirectional media stream created when the call is active. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000100struct media_stream
101{
102 /* Static: */
Benny Prijonodeb31962006-06-22 18:51:03 +0000103 unsigned call_index; /* Call owner. */
104 unsigned media_index; /* Media index in call. */
105 pjmedia_transport *transport; /* To send/recv RTP/RTCP */
106
107 /* Active? */
108 pj_bool_t active; /* Non-zero if is in call. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000109
110 /* Current stream info: */
111 pjmedia_stream_info si; /* Current stream info. */
112
113 /* More info: */
114 unsigned clock_rate; /* clock rate */
115 unsigned samples_per_frame; /* samples per frame */
116 unsigned bytes_per_frame; /* frame size. */
117
Benny Prijono60b980e2006-04-03 22:41:26 +0000118 /* RTP session: */
119 pjmedia_rtp_session out_sess; /* outgoing RTP session */
120 pjmedia_rtp_session in_sess; /* incoming RTP session */
121
122 /* RTCP stats: */
123 pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
Benny Prijono4adcb912006-04-04 13:12:38 +0000124
Benny Prijono513795f2006-07-18 21:12:24 +0000125 /* Thread: */
126 pj_bool_t thread_quit_flag; /* Stop media thread. */
127 pj_thread_t *thread; /* Media thread. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000128};
129
130
Benny Prijonodeb31962006-06-22 18:51:03 +0000131/* This is a call structure that is created when the application starts
132 * and only destroyed when the application quits.
133 */
Benny Prijono60b980e2006-04-03 22:41:26 +0000134struct call
135{
136 unsigned index;
137 pjsip_inv_session *inv;
138 unsigned media_count;
Benny Prijonodeb31962006-06-22 18:51:03 +0000139 struct media_stream media[1];
Benny Prijono4adcb912006-04-04 13:12:38 +0000140 pj_time_val start_time;
141 pj_time_val response_time;
142 pj_time_val connect_time;
Benny Prijono410fbae2006-05-03 18:16:06 +0000143
144 pj_timer_entry d_timer; /**< Disconnect timer. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000145};
146
147
Benny Prijonodeb31962006-06-22 18:51:03 +0000148/* Application's global variables */
Benny Prijono60b980e2006-04-03 22:41:26 +0000149static struct app
150{
151 unsigned max_calls;
Benny Prijono410fbae2006-05-03 18:16:06 +0000152 unsigned uac_calls;
153 unsigned duration;
154 pj_bool_t auto_quit;
Benny Prijono60b980e2006-04-03 22:41:26 +0000155 unsigned thread_count;
156 int sip_port;
157 int rtp_start_port;
Benny Prijonodeb31962006-06-22 18:51:03 +0000158 pj_str_t local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +0000159 pj_str_t local_uri;
160 pj_str_t local_contact;
Benny Prijono4adcb912006-04-04 13:12:38 +0000161
162 int app_log_level;
163 int log_level;
164 char *log_filename;
Benny Prijonofcb36722006-05-18 18:34:21 +0000165 char *report_filename;
Benny Prijono4adcb912006-04-04 13:12:38 +0000166
167 struct codec audio_codec;
Benny Prijono60b980e2006-04-03 22:41:26 +0000168
169 pj_str_t uri_to_call;
170
171 pj_caching_pool cp;
172 pj_pool_t *pool;
173
174 pjsip_endpoint *sip_endpt;
175 pj_bool_t thread_quit;
Benny Prijonodeb31962006-06-22 18:51:03 +0000176 pj_thread_t *sip_thread[1];
Benny Prijono60b980e2006-04-03 22:41:26 +0000177
178 pjmedia_endpt *med_endpt;
179 struct call call[MAX_CALLS];
180} app;
181
182
183
184/*
185 * Prototypes:
186 */
187
188/* Callback to be called when SDP negotiation is done in the call: */
189static void call_on_media_update( pjsip_inv_session *inv,
190 pj_status_t status);
191
192/* Callback to be called when invite session's state has changed: */
193static void call_on_state_changed( pjsip_inv_session *inv,
194 pjsip_event *e);
195
196/* Callback to be called when dialog has forked: */
197static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
198
199/* Callback to be called to handle incoming requests outside dialogs: */
200static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
201
202/* Worker thread prototype */
Benny Prijonodeb31962006-06-22 18:51:03 +0000203static int sip_worker_thread(void *arg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000204
205/* Create SDP for call */
206static pj_status_t create_sdp( pj_pool_t *pool,
207 struct call *call,
208 pjmedia_sdp_session **p_sdp);
209
Benny Prijono410fbae2006-05-03 18:16:06 +0000210/* Hangup call */
211static void hangup_call(unsigned index);
212
Benny Prijono60b980e2006-04-03 22:41:26 +0000213/* Destroy the call's media */
214static void destroy_call_media(unsigned call_index);
215
Benny Prijonodeb31962006-06-22 18:51:03 +0000216/* Destroy media. */
217static void destroy_media();
218
219/* This callback is called by media transport on receipt of RTP packet. */
220static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size);
221
222/* This callback is called by media transport on receipt of RTCP packet. */
223static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size);
224
Benny Prijono60b980e2006-04-03 22:41:26 +0000225/* Display error */
226static void app_perror(const char *sender, const char *title,
227 pj_status_t status);
228
Benny Prijonod7a13f12006-04-05 19:08:16 +0000229/* Print call */
230static void print_call(int call_index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000231
232
Benny Prijono60b980e2006-04-03 22:41:26 +0000233/* This is a PJSIP module to be registered by application to handle
234 * incoming requests outside any dialogs/transactions. The main purpose
235 * here is to handle incoming INVITE request message, where we will
236 * create a dialog and INVITE session for it.
237 */
238static pjsip_module mod_siprtp =
239{
240 NULL, NULL, /* prev, next. */
241 { "mod-siprtpapp", 13 }, /* Name. */
242 -1, /* Id */
243 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
244 NULL, /* load() */
245 NULL, /* start() */
246 NULL, /* stop() */
247 NULL, /* unload() */
248 &on_rx_request, /* on_rx_request() */
249 NULL, /* on_rx_response() */
250 NULL, /* on_tx_request. */
251 NULL, /* on_tx_response() */
252 NULL, /* on_tsx_state() */
253};
254
255
Benny Prijono4adcb912006-04-04 13:12:38 +0000256/* Codec constants */
257struct codec audio_codecs[] =
258{
Benny Prijono97f2a372006-06-07 21:21:57 +0000259 { 0, "PCMU", 8000, 64000, 20, "G.711 ULaw" },
260 { 3, "GSM", 8000, 13200, 20, "GSM" },
261 { 4, "G723", 8000, 6400, 30, "G.723.1" },
262 { 8, "PCMA", 8000, 64000, 20, "G.711 ALaw" },
Benny Prijonodeb31962006-06-22 18:51:03 +0000263 { 18, "G729", 8000, 8000, 20, "G.729" },
Benny Prijono4adcb912006-04-04 13:12:38 +0000264};
265
266
Benny Prijono60b980e2006-04-03 22:41:26 +0000267/*
268 * Init SIP stack
269 */
270static pj_status_t init_sip()
271{
Benny Prijonodeb31962006-06-22 18:51:03 +0000272 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000273 pj_status_t status;
274
275 /* init PJLIB-UTIL: */
276 status = pjlib_util_init();
277 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
278
279 /* Must create a pool factory before we can allocate any memory. */
280 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
281
282 /* Create application pool for misc. */
283 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
284
Benny Prijonodeb31962006-06-22 18:51:03 +0000285 /* Create the endpoint: */
286 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
287 &app.sip_endpt);
288 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000289
290
291 /* Add UDP transport. */
292 {
293 pj_sockaddr_in addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000294 pjsip_host_port addrname;
Benny Prijono60b980e2006-04-03 22:41:26 +0000295
Benny Prijonoac623b32006-07-03 15:19:31 +0000296 pj_bzero(&addr, sizeof(addr));
Benny Prijono60b980e2006-04-03 22:41:26 +0000297 addr.sin_family = PJ_AF_INET;
298 addr.sin_addr.s_addr = 0;
299 addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
300
Benny Prijonodeb31962006-06-22 18:51:03 +0000301 if (app.local_addr.slen) {
302 addrname.host = app.local_addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000303 addrname.port = app.sip_port;
304 }
305
306 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
Benny Prijonodeb31962006-06-22 18:51:03 +0000307 (app.local_addr.slen ? &addrname:NULL),
Benny Prijono60b980e2006-04-03 22:41:26 +0000308 1, NULL);
Benny Prijono49ce9a72006-04-05 16:56:19 +0000309 if (status != PJ_SUCCESS) {
310 app_perror(THIS_FILE, "Unable to start UDP transport", status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000311 return status;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000312 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000313 }
314
315 /*
316 * Init transaction layer.
317 * This will create/initialize transaction hash tables etc.
318 */
319 status = pjsip_tsx_layer_init_module(app.sip_endpt);
320 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
321
322 /* Initialize UA layer. */
323 status = pjsip_ua_init_module( app.sip_endpt, NULL );
324 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
325
326 /* Init invite session module. */
327 {
328 pjsip_inv_callback inv_cb;
329
330 /* Init the callback for INVITE session: */
Benny Prijonoac623b32006-07-03 15:19:31 +0000331 pj_bzero(&inv_cb, sizeof(inv_cb));
Benny Prijono60b980e2006-04-03 22:41:26 +0000332 inv_cb.on_state_changed = &call_on_state_changed;
333 inv_cb.on_new_session = &call_on_forked;
334 inv_cb.on_media_update = &call_on_media_update;
335
336 /* Initialize invite session module: */
337 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
338 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
339 }
340
341 /* Register our module to receive incoming requests. */
342 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
343 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
344
Benny Prijonodeb31962006-06-22 18:51:03 +0000345 /* Init calls */
346 for (i=0; i<app.max_calls; ++i)
347 app.call[i].index = i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000348
Benny Prijono60b980e2006-04-03 22:41:26 +0000349 /* Done */
350 return PJ_SUCCESS;
351}
352
353
354/*
355 * Destroy SIP
356 */
357static void destroy_sip()
358{
359 unsigned i;
360
361 app.thread_quit = 1;
362 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000363 if (app.sip_thread[i]) {
364 pj_thread_join(app.sip_thread[i]);
365 pj_thread_destroy(app.sip_thread[i]);
366 app.sip_thread[i] = NULL;
Benny Prijono60b980e2006-04-03 22:41:26 +0000367 }
368 }
369
370 if (app.sip_endpt) {
371 pjsip_endpt_destroy(app.sip_endpt);
372 app.sip_endpt = NULL;
373 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000374}
375
376
377/*
378 * Init media stack.
379 */
380static pj_status_t init_media()
381{
Benny Prijono60b980e2006-04-03 22:41:26 +0000382 unsigned i, count;
383 pj_uint16_t rtp_port;
Benny Prijono60b980e2006-04-03 22:41:26 +0000384 pj_status_t status;
385
386
Benny Prijono60b980e2006-04-03 22:41:26 +0000387 /* Initialize media endpoint so that at least error subsystem is properly
388 * initialized.
389 */
Benny Prijono513795f2006-07-18 21:12:24 +0000390 status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
Benny Prijono60b980e2006-04-03 22:41:26 +0000391 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
392
393
Benny Prijonodeb31962006-06-22 18:51:03 +0000394 /* Must register codecs to be supported */
Benny Prijono4d7fd202006-05-14 20:57:20 +0000395 pjmedia_codec_g711_init(app.med_endpt);
396
Benny Prijono60b980e2006-04-03 22:41:26 +0000397 /* RTP port counter */
398 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
399
Benny Prijonodeb31962006-06-22 18:51:03 +0000400 /* Init media transport for all calls. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000401 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
402
Benny Prijonodeb31962006-06-22 18:51:03 +0000403 unsigned j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000404
Benny Prijonodeb31962006-06-22 18:51:03 +0000405 /* Create transport for each media in the call */
406 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
407 /* Repeat binding media socket to next port when fails to bind
408 * to current port number.
409 */
410 int retry;
Benny Prijono60b980e2006-04-03 22:41:26 +0000411
Benny Prijono513795f2006-07-18 21:12:24 +0000412 app.call[i].media[j].call_index = i;
413 app.call[i].media[j].media_index = j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000414
Benny Prijonodeb31962006-06-22 18:51:03 +0000415 status = -1;
416 for (retry=0; retry<100; ++retry,rtp_port+=2) {
417 struct media_stream *m = &app.call[i].media[j];
418
419 status = pjmedia_transport_udp_create2(app.med_endpt,
420 "siprtp",
421 &app.local_addr,
422 rtp_port, 0,
423 &m->transport);
424 if (status == PJ_SUCCESS) {
425 rtp_port += 2;
426 break;
427 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000428 }
Benny Prijono6647d822006-05-20 13:01:07 +0000429 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000430
431 if (status != PJ_SUCCESS)
432 goto on_error;
Benny Prijono60b980e2006-04-03 22:41:26 +0000433 }
434
435 /* Done */
436 return PJ_SUCCESS;
437
438on_error:
Benny Prijonodeb31962006-06-22 18:51:03 +0000439 destroy_media();
Benny Prijono60b980e2006-04-03 22:41:26 +0000440 return status;
441}
442
443
444/*
445 * Destroy media.
446 */
447static void destroy_media()
448{
449 unsigned i;
450
451 for (i=0; i<app.max_calls; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000452 unsigned j;
453 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
454 struct media_stream *m = &app.call[i].media[j];
Benny Prijono60b980e2006-04-03 22:41:26 +0000455
Benny Prijonodeb31962006-06-22 18:51:03 +0000456 if (m->transport) {
457 pjmedia_transport_close(m->transport);
458 m->transport = NULL;
459 }
460 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000461 }
462
463 if (app.med_endpt) {
464 pjmedia_endpt_destroy(app.med_endpt);
465 app.med_endpt = NULL;
466 }
467}
468
469
470/*
471 * Make outgoing call.
472 */
473static pj_status_t make_call(const pj_str_t *dst_uri)
474{
475 unsigned i;
476 struct call *call;
477 pjsip_dialog *dlg;
478 pjmedia_sdp_session *sdp;
479 pjsip_tx_data *tdata;
480 pj_status_t status;
481
482
483 /* Find unused call slot */
484 for (i=0; i<app.max_calls; ++i) {
485 if (app.call[i].inv == NULL)
486 break;
487 }
488
489 if (i == app.max_calls)
490 return PJ_ETOOMANY;
491
492 call = &app.call[i];
493
494 /* Create UAC dialog */
495 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
496 &app.local_uri, /* local URI */
497 &app.local_contact, /* local Contact */
498 dst_uri, /* remote URI */
499 dst_uri, /* remote target */
500 &dlg); /* dialog */
Benny Prijono410fbae2006-05-03 18:16:06 +0000501 if (status != PJ_SUCCESS) {
502 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000503 return status;
Benny Prijono410fbae2006-05-03 18:16:06 +0000504 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000505
506 /* Create SDP */
507 create_sdp( dlg->pool, call, &sdp);
508
509 /* Create the INVITE session. */
510 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
511 if (status != PJ_SUCCESS) {
512 pjsip_dlg_terminate(dlg);
Benny Prijono410fbae2006-05-03 18:16:06 +0000513 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000514 return status;
515 }
516
517
518 /* Attach call data to invite session */
519 call->inv->mod_data[mod_siprtp.id] = call;
520
Benny Prijono4adcb912006-04-04 13:12:38 +0000521 /* Mark start of call */
522 pj_gettimeofday(&call->start_time);
523
Benny Prijono60b980e2006-04-03 22:41:26 +0000524
525 /* Create initial INVITE request.
526 * This INVITE request will contain a perfectly good request and
527 * an SDP body as well.
528 */
529 status = pjsip_inv_invite(call->inv, &tdata);
530 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
531
532
533 /* Send initial INVITE request.
534 * From now on, the invite session's state will be reported to us
535 * via the invite session callbacks.
536 */
537 status = pjsip_inv_send_msg(call->inv, tdata);
538 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
539
540
541 return PJ_SUCCESS;
542}
543
544
545/*
546 * Receive incoming call
547 */
548static void process_incoming_call(pjsip_rx_data *rdata)
549{
Benny Prijonoca4cff22006-07-02 14:18:47 +0000550 unsigned i, options;
Benny Prijono60b980e2006-04-03 22:41:26 +0000551 struct call *call;
552 pjsip_dialog *dlg;
553 pjmedia_sdp_session *sdp;
554 pjsip_tx_data *tdata;
555 pj_status_t status;
556
557 /* Find free call slot */
558 for (i=0; i<app.max_calls; ++i) {
559 if (app.call[i].inv == NULL)
560 break;
561 }
562
563 if (i == app.max_calls) {
564 const pj_str_t reason = pj_str("Too many calls");
565 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
566 500, &reason,
567 NULL, NULL);
568 return;
569 }
570
Benny Prijono513795f2006-07-18 21:12:24 +0000571 call = &app.call[i];
572
Benny Prijonoca4cff22006-07-02 14:18:47 +0000573 /* Verify that we can handle the request. */
574 options = 0;
575 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000576 app.sip_endpt, &tdata);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000577 if (status != PJ_SUCCESS) {
Benny Prijonoca4cff22006-07-02 14:18:47 +0000578 /*
579 * No we can't handle the incoming INVITE request.
580 */
Benny Prijonoca4cff22006-07-02 14:18:47 +0000581 if (tdata) {
582 pjsip_response_addr res_addr;
Benny Prijono513795f2006-07-18 21:12:24 +0000583
Benny Prijonoca4cff22006-07-02 14:18:47 +0000584 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
Benny Prijono513795f2006-07-18 21:12:24 +0000585 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
586 NULL, NULL);
587
Benny Prijonoca4cff22006-07-02 14:18:47 +0000588 } else {
Benny Prijono513795f2006-07-18 21:12:24 +0000589
Benny Prijonoca4cff22006-07-02 14:18:47 +0000590 /* Respond with 500 (Internal Server Error) */
591 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000592 NULL, NULL);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000593 }
Benny Prijono513795f2006-07-18 21:12:24 +0000594
Benny Prijonoca4cff22006-07-02 14:18:47 +0000595 return;
Benny Prijono513795f2006-07-18 21:12:24 +0000596 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000597
598 /* Create UAS dialog */
599 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
600 &app.local_contact, &dlg);
601 if (status != PJ_SUCCESS) {
602 const pj_str_t reason = pj_str("Unable to create dialog");
603 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
604 500, &reason,
605 NULL, NULL);
606 return;
607 }
608
609 /* Create SDP */
610 create_sdp( dlg->pool, call, &sdp);
611
612 /* Create UAS invite session */
613 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
614 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000615 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
616 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000617 return;
618 }
619
Benny Prijono4adcb912006-04-04 13:12:38 +0000620
Benny Prijono60b980e2006-04-03 22:41:26 +0000621 /* Attach call data to invite session */
622 call->inv->mod_data[mod_siprtp.id] = call;
623
Benny Prijono4adcb912006-04-04 13:12:38 +0000624 /* Mark start of call */
625 pj_gettimeofday(&call->start_time);
626
627
628
Benny Prijono60b980e2006-04-03 22:41:26 +0000629 /* Create 200 response .*/
630 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
631 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000632 if (status != PJ_SUCCESS) {
633 status = pjsip_inv_initial_answer(call->inv, rdata,
634 PJSIP_SC_NOT_ACCEPTABLE,
635 NULL, NULL, &tdata);
636 if (status == PJ_SUCCESS)
637 pjsip_inv_send_msg(call->inv, tdata);
638 else
639 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
640 return;
641 }
642
Benny Prijono60b980e2006-04-03 22:41:26 +0000643
644 /* Send the 200 response. */
645 status = pjsip_inv_send_msg(call->inv, tdata);
646 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
647
648
649 /* Done */
650}
651
652
653/* Callback to be called when dialog has forked: */
654static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
655{
656 PJ_UNUSED_ARG(inv);
657 PJ_UNUSED_ARG(e);
658
659 PJ_TODO( HANDLE_FORKING );
660}
661
662
663/* Callback to be called to handle incoming requests outside dialogs: */
664static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
665{
Benny Prijono4adcb912006-04-04 13:12:38 +0000666 /* Ignore strandled ACKs (must not send respone */
667 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
668 return PJ_FALSE;
669
Benny Prijono60b980e2006-04-03 22:41:26 +0000670 /* Respond (statelessly) any non-INVITE requests with 500 */
671 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
672 pj_str_t reason = pj_str("Unsupported Operation");
673 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
674 500, &reason,
675 NULL, NULL);
676 return PJ_TRUE;
677 }
678
679 /* Handle incoming INVITE */
680 process_incoming_call(rdata);
681
682 /* Done */
683 return PJ_TRUE;
684}
685
686
Benny Prijono410fbae2006-05-03 18:16:06 +0000687/* Callback timer to disconnect call (limiting call duration) */
688static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
689 struct pj_timer_entry *entry)
690{
691 struct call *call = entry->user_data;
692
693 PJ_UNUSED_ARG(timer_heap);
694
695 entry->id = 0;
696 hangup_call(call->index);
697}
698
699
Benny Prijono60b980e2006-04-03 22:41:26 +0000700/* Callback to be called when invite session's state has changed: */
701static void call_on_state_changed( pjsip_inv_session *inv,
702 pjsip_event *e)
703{
Benny Prijono4adcb912006-04-04 13:12:38 +0000704 struct call *call = inv->mod_data[mod_siprtp.id];
705
Benny Prijono60b980e2006-04-03 22:41:26 +0000706 PJ_UNUSED_ARG(e);
707
Benny Prijono4adcb912006-04-04 13:12:38 +0000708 if (!call)
709 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000710
Benny Prijono4adcb912006-04-04 13:12:38 +0000711 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
712
713 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000714
Benny Prijono410fbae2006-05-03 18:16:06 +0000715 if (call->d_timer.id != 0) {
716 pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
717 call->d_timer.id = 0;
718 }
719
Benny Prijono258ece92006-07-22 12:53:04 +0000720 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%d (%.*s)",
Benny Prijonod7a13f12006-04-05 19:08:16 +0000721 call->index,
Benny Prijono258ece92006-07-22 12:53:04 +0000722 inv->cause,
723 (int)inv->cause_text.slen,
724 inv->cause_text.ptr));
Benny Prijonod7a13f12006-04-05 19:08:16 +0000725 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
726 print_call(call->index);
727
728
Benny Prijono60b980e2006-04-03 22:41:26 +0000729 call->inv = NULL;
730 inv->mod_data[mod_siprtp.id] = NULL;
731
732 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000733
734 call->start_time = null_time;
735 call->response_time = null_time;
736 call->connect_time = null_time;
737
Benny Prijono410fbae2006-05-03 18:16:06 +0000738 ++app.uac_calls;
Benny Prijono4adcb912006-04-04 13:12:38 +0000739
740 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
741
742 pj_time_val t;
743
744 pj_gettimeofday(&call->connect_time);
745 if (call->response_time.sec == 0)
746 call->response_time = call->connect_time;
747
748 t = call->connect_time;
749 PJ_TIME_VAL_SUB(t, call->start_time);
750
751 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
752 PJ_TIME_VAL_MSEC(t)));
753
Benny Prijono410fbae2006-05-03 18:16:06 +0000754 if (app.duration != 0) {
755 call->d_timer.id = 1;
756 call->d_timer.user_data = call;
757 call->d_timer.cb = &timer_disconnect_call;
758
759 t.sec = app.duration;
760 t.msec = 0;
761
762 pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
763 }
764
Benny Prijono4adcb912006-04-04 13:12:38 +0000765 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
766 inv->state == PJSIP_INV_STATE_CONNECTING) {
767
768 if (call->response_time.sec == 0)
769 pj_gettimeofday(&call->response_time);
770
Benny Prijono60b980e2006-04-03 22:41:26 +0000771 }
772}
773
774
775/* Utility */
776static void app_perror(const char *sender, const char *title,
777 pj_status_t status)
778{
779 char errmsg[PJ_ERR_MSG_SIZE];
780
781 pj_strerror(status, errmsg, sizeof(errmsg));
782 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
783}
784
785
Benny Prijonodeb31962006-06-22 18:51:03 +0000786/* Worker thread for SIP */
787static int sip_worker_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +0000788{
789 PJ_UNUSED_ARG(arg);
790
791 while (!app.thread_quit) {
Benny Prijono513795f2006-07-18 21:12:24 +0000792 pj_time_val timeout = {0, 10};
Benny Prijono60b980e2006-04-03 22:41:26 +0000793 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
794 }
795
796 return 0;
797}
798
799
Benny Prijono60b980e2006-04-03 22:41:26 +0000800/* Init application options */
801static pj_status_t init_options(int argc, char *argv[])
802{
803 static char ip_addr[32];
804 static char local_uri[64];
805
Benny Prijono4adcb912006-04-04 13:12:38 +0000806 enum { OPT_START,
807 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
Benny Prijonofcb36722006-05-18 18:34:21 +0000808 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
809 OPT_REPORT_FILE };
Benny Prijono4adcb912006-04-04 13:12:38 +0000810
Benny Prijono60b980e2006-04-03 22:41:26 +0000811 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000812 { "count", 1, 0, 'c' },
Benny Prijono410fbae2006-05-03 18:16:06 +0000813 { "duration", 1, 0, 'd' },
814 { "auto-quit", 0, 0, 'q' },
Benny Prijono4adcb912006-04-04 13:12:38 +0000815 { "local-port", 1, 0, 'p' },
816 { "rtp-port", 1, 0, 'r' },
817 { "ip-addr", 1, 0, 'i' },
818
819 { "log-level", 1, 0, 'l' },
820 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
821 { "log-file", 1, 0, OPT_LOG_FILE },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000822
Benny Prijonofcb36722006-05-18 18:34:21 +0000823 { "report-file", 1, 0, OPT_REPORT_FILE },
824
Benny Prijono4d7fd202006-05-14 20:57:20 +0000825 /* Don't support this anymore, see comments in USAGE above.
Benny Prijono4adcb912006-04-04 13:12:38 +0000826 { "a-pt", 1, 0, OPT_A_PT },
827 { "a-name", 1, 0, OPT_A_NAME },
828 { "a-clock", 1, 0, OPT_A_CLOCK },
829 { "a-bitrate", 1, 0, OPT_A_BITRATE },
830 { "a-ptime", 1, 0, OPT_A_PTIME },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000831 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000832
Benny Prijono60b980e2006-04-03 22:41:26 +0000833 { NULL, 0, 0, 0 },
834 };
835 int c;
836 int option_index;
837
838 /* Get local IP address for the default IP address */
839 {
840 const pj_str_t *hostname;
841 pj_sockaddr_in tmp_addr;
842 char *addr;
843
844 hostname = pj_gethostname();
845 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
846 addr = pj_inet_ntoa(tmp_addr.sin_addr);
847 pj_ansi_strcpy(ip_addr, addr);
848 }
849
Benny Prijono4adcb912006-04-04 13:12:38 +0000850 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000851 app.max_calls = 1;
852 app.thread_count = 1;
853 app.sip_port = 5060;
Benny Prijono6647d822006-05-20 13:01:07 +0000854 app.rtp_start_port = RTP_START_PORT;
Benny Prijonodeb31962006-06-22 18:51:03 +0000855 app.local_addr = pj_str(ip_addr);
Benny Prijono4adcb912006-04-04 13:12:38 +0000856 app.log_level = 5;
857 app.app_log_level = 3;
858 app.log_filename = NULL;
859
860 /* Default codecs: */
861 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000862
863 /* Parse options */
864 pj_optind = 0;
Benny Prijono410fbae2006-05-03 18:16:06 +0000865 while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:q",
Benny Prijono60b980e2006-04-03 22:41:26 +0000866 long_options, &option_index))!=-1)
867 {
868 switch (c) {
869 case 'c':
870 app.max_calls = atoi(pj_optarg);
871 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
872 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
873 return 1;
874 }
875 break;
Benny Prijono410fbae2006-05-03 18:16:06 +0000876 case 'd':
877 app.duration = atoi(pj_optarg);
878 break;
879 case 'q':
880 app.auto_quit = 1;
881 break;
882
Benny Prijono60b980e2006-04-03 22:41:26 +0000883 case 'p':
884 app.sip_port = atoi(pj_optarg);
885 break;
886 case 'r':
887 app.rtp_start_port = atoi(pj_optarg);
888 break;
889 case 'i':
Benny Prijonodeb31962006-06-22 18:51:03 +0000890 app.local_addr = pj_str(pj_optarg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000891 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000892
893 case 'l':
894 app.log_level = atoi(pj_optarg);
895 break;
896 case OPT_APP_LOG_LEVEL:
897 app.app_log_level = atoi(pj_optarg);
898 break;
899 case OPT_LOG_FILE:
900 app.log_filename = pj_optarg;
901 break;
902
903 case OPT_A_PT:
904 app.audio_codec.pt = atoi(pj_optarg);
905 break;
906 case OPT_A_NAME:
907 app.audio_codec.name = pj_optarg;
908 break;
909 case OPT_A_CLOCK:
910 app.audio_codec.clock_rate = atoi(pj_optarg);
911 break;
912 case OPT_A_BITRATE:
913 app.audio_codec.bit_rate = atoi(pj_optarg);
914 break;
915 case OPT_A_PTIME:
916 app.audio_codec.ptime = atoi(pj_optarg);
917 break;
Benny Prijonofcb36722006-05-18 18:34:21 +0000918 case OPT_REPORT_FILE:
919 app.report_filename = pj_optarg;
920 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000921
Benny Prijono60b980e2006-04-03 22:41:26 +0000922 default:
923 puts(USAGE);
924 return 1;
925 }
926 }
927
928 /* Check if URL is specified */
929 if (pj_optind < argc)
930 app.uri_to_call = pj_str(argv[pj_optind]);
931
932 /* Build local URI and contact */
Benny Prijonodeb31962006-06-22 18:51:03 +0000933 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
Benny Prijono60b980e2006-04-03 22:41:26 +0000934 app.local_uri = pj_str(local_uri);
935 app.local_contact = app.local_uri;
936
937
938 return PJ_SUCCESS;
939}
940
941
Benny Prijono4adcb912006-04-04 13:12:38 +0000942/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000943 * MEDIA STUFFS
944 */
945
946/*
947 * Create SDP session for a call.
948 */
949static pj_status_t create_sdp( pj_pool_t *pool,
950 struct call *call,
951 pjmedia_sdp_session **p_sdp)
952{
953 pj_time_val tv;
954 pjmedia_sdp_session *sdp;
955 pjmedia_sdp_media *m;
956 pjmedia_sdp_attr *attr;
Benny Prijonodeb31962006-06-22 18:51:03 +0000957 pjmedia_transport_udp_info tpinfo;
Benny Prijono60b980e2006-04-03 22:41:26 +0000958 struct media_stream *audio = &call->media[0];
959
960 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
961
962
Benny Prijonodeb31962006-06-22 18:51:03 +0000963 /* Get transport info */
964 pjmedia_transport_udp_get_info(audio->transport, &tpinfo);
965
Benny Prijono60b980e2006-04-03 22:41:26 +0000966 /* Create and initialize basic SDP session */
967 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
968
969 pj_gettimeofday(&tv);
970 sdp->origin.user = pj_str("pjsip-siprtp");
971 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
972 sdp->origin.net_type = pj_str("IN");
973 sdp->origin.addr_type = pj_str("IP4");
974 sdp->origin.addr = *pj_gethostname();
975 sdp->name = pj_str("pjsip");
976
977 /* Since we only support one media stream at present, put the
978 * SDP connection line in the session level.
979 */
980 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
981 sdp->conn->net_type = pj_str("IN");
982 sdp->conn->addr_type = pj_str("IP4");
Benny Prijonodeb31962006-06-22 18:51:03 +0000983 sdp->conn->addr = app.local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +0000984
985
986 /* SDP time and attributes. */
987 sdp->time.start = sdp->time.stop = 0;
988 sdp->attr_count = 0;
989
990 /* Create media stream 0: */
991
992 sdp->media_count = 1;
993 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
994 sdp->media[0] = m;
995
996 /* Standard media info: */
997 m->desc.media = pj_str("audio");
Benny Prijonodeb31962006-06-22 18:51:03 +0000998 m->desc.port = pj_ntohs(tpinfo.skinfo.rtp_addr_name.sin_port);
Benny Prijono60b980e2006-04-03 22:41:26 +0000999 m->desc.port_count = 1;
1000 m->desc.transport = pj_str("RTP/AVP");
1001
1002 /* Add format and rtpmap for each codec. */
1003 m->desc.fmt_count = 1;
1004 m->attr_count = 0;
1005
1006 {
1007 pjmedia_sdp_rtpmap rtpmap;
1008 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +00001009 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +00001010
Benny Prijono4adcb912006-04-04 13:12:38 +00001011 sprintf(ptstr, "%d", app.audio_codec.pt);
1012 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1013 rtpmap.pt = m->desc.fmt[0];
1014 rtpmap.clock_rate = app.audio_codec.clock_rate;
1015 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +00001016 rtpmap.param.slen = 0;
1017
1018 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1019 m->attr[m->attr_count++] = attr;
1020 }
1021
1022 /* Add sendrecv attribute. */
1023 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1024 attr->name = pj_str("sendrecv");
1025 m->attr[m->attr_count++] = attr;
1026
1027#if 1
1028 /*
1029 * Add support telephony event
1030 */
Benny Prijono4adcb912006-04-04 13:12:38 +00001031 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +00001032 /* Add rtpmap. */
1033 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1034 attr->name = pj_str("rtpmap");
Benny Prijono4adcb912006-04-04 13:12:38 +00001035 attr->value = pj_str(":121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +00001036 m->attr[m->attr_count++] = attr;
1037 /* Add fmtp */
1038 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1039 attr->name = pj_str("fmtp");
Benny Prijono4adcb912006-04-04 13:12:38 +00001040 attr->value = pj_str(":121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +00001041 m->attr[m->attr_count++] = attr;
1042#endif
1043
1044 /* Done */
1045 *p_sdp = sdp;
1046
1047 return PJ_SUCCESS;
1048}
1049
1050
Benny Prijono513795f2006-07-18 21:12:24 +00001051#if defined(PJ_WIN32) && PJ_WIN32 != 0
1052#include <windows.h>
1053static void boost_priority(void)
1054{
1055 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
1056 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
1057}
1058
1059#else
1060# define boost_priority()
1061#endif
1062
1063
Benny Prijonodeb31962006-06-22 18:51:03 +00001064/*
1065 * This callback is called by media transport on receipt of RTP packet.
1066 */
1067static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size)
1068{
1069 struct media_stream *strm;
1070 pj_status_t status;
1071 const pjmedia_rtp_hdr *hdr;
1072 const void *payload;
1073 unsigned payload_len;
1074
1075 strm = user_data;
1076
1077 /* Discard packet if media is inactive */
1078 if (!strm->active)
1079 return;
1080
1081 /* Check for errors */
1082 if (size < 0) {
1083 app_perror(THIS_FILE, "RTP recv() error", -size);
1084 return;
1085 }
1086
1087 /* Decode RTP packet. */
1088 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1089 pkt, size,
1090 &hdr, &payload, &payload_len);
1091 if (status != PJ_SUCCESS) {
1092 app_perror(THIS_FILE, "RTP decode error", status);
1093 return;
1094 }
1095
1096 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1097
1098 /* Update the RTCP session. */
1099 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1100 pj_ntohl(hdr->ts), payload_len);
1101
1102 /* Update RTP session */
1103 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1104
1105}
1106
1107/*
1108 * This callback is called by media transport on receipt of RTCP packet.
1109 */
1110static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size)
1111{
1112 struct media_stream *strm;
1113
1114 strm = user_data;
1115
1116 /* Discard packet if media is inactive */
1117 if (!strm->active)
1118 return;
1119
1120 /* Check for errors */
1121 if (size < 0) {
1122 app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
1123 return;
1124 }
1125
1126 /* Update RTCP session */
1127 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1128}
1129
1130
Benny Prijono513795f2006-07-18 21:12:24 +00001131/*
1132 * Media thread
1133 *
1134 * This is the thread to send and receive both RTP and RTCP packets.
1135 */
1136static int media_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +00001137{
Benny Prijono513795f2006-07-18 21:12:24 +00001138 enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
1139 struct media_stream *strm = arg;
1140 char packet[1500];
1141 unsigned msec_interval;
1142 pj_timestamp freq, next_rtp, next_rtcp;
Benny Prijono60b980e2006-04-03 22:41:26 +00001143
Benny Prijono6647d822006-05-20 13:01:07 +00001144
Benny Prijono513795f2006-07-18 21:12:24 +00001145 /* Boost thread priority if necessary */
1146 boost_priority();
Benny Prijono6647d822006-05-20 13:01:07 +00001147
Benny Prijono513795f2006-07-18 21:12:24 +00001148 /* Let things settle */
Benny Prijono258ece92006-07-22 12:53:04 +00001149 pj_thread_sleep(100);
Benny Prijono6647d822006-05-20 13:01:07 +00001150
Benny Prijono513795f2006-07-18 21:12:24 +00001151 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1152 pj_get_timestamp_freq(&freq);
1153
1154 pj_get_timestamp(&next_rtp);
1155 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
1156
1157 next_rtcp = next_rtp;
1158 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
1159
1160
1161 while (!strm->thread_quit_flag) {
1162 pj_timestamp now, lesser;
1163 pj_time_val timeout;
1164 pj_bool_t send_rtp, send_rtcp;
1165
1166 send_rtp = send_rtcp = PJ_FALSE;
1167
1168 /* Determine how long to sleep */
1169 if (next_rtp.u64 < next_rtcp.u64) {
1170 lesser = next_rtp;
1171 send_rtp = PJ_TRUE;
1172 } else {
1173 lesser = next_rtcp;
1174 send_rtcp = PJ_TRUE;
1175 }
1176
1177 pj_get_timestamp(&now);
1178 if (lesser.u64 <= now.u64) {
1179 timeout.sec = timeout.msec = 0;
1180 //printf("immediate "); fflush(stdout);
1181 } else {
1182 pj_uint64_t tick_delay;
1183 tick_delay = lesser.u64 - now.u64;
1184 timeout.sec = 0;
1185 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1186 pj_time_val_normalize(&timeout);
1187
1188 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
1189 }
1190
1191 /* Wait for next interval */
1192 //if (timeout.sec!=0 && timeout.msec!=0) {
1193 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
1194 if (strm->thread_quit_flag)
1195 break;
1196 //}
1197
1198 pj_get_timestamp(&now);
1199
1200 if (send_rtp || next_rtp.u64 <= now.u64) {
1201 /*
1202 * Time to send RTP packet.
1203 */
1204 pj_status_t status;
1205 const pjmedia_rtp_hdr *hdr;
1206 pj_ssize_t size;
1207 int hdrlen;
1208
1209 /* Format RTP header */
1210 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1211 0, /* marker bit */
1212 strm->bytes_per_frame,
1213 strm->samples_per_frame,
1214 (const void**)&hdr, &hdrlen);
1215 if (status == PJ_SUCCESS) {
1216
1217 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
1218
1219 /* Copy RTP header to packet */
1220 pj_memcpy(packet, hdr, hdrlen);
1221
1222 /* Zero the payload */
1223 pj_bzero(packet+hdrlen, strm->bytes_per_frame);
1224
1225 /* Send RTP packet */
1226 size = hdrlen + strm->bytes_per_frame;
1227 status = pjmedia_transport_send_rtp(strm->transport,
1228 packet, size);
1229 if (status != PJ_SUCCESS)
1230 app_perror(THIS_FILE, "Error sending RTP packet", status);
1231
1232 } else {
1233 pj_assert(!"RTP encode() error");
1234 }
1235
1236 /* Update RTCP SR */
1237 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1238
1239 /* Schedule next send */
1240 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
1241 }
1242
1243
1244 if (send_rtcp || next_rtcp.u64 <= now.u64) {
1245 /*
1246 * Time to send RTCP packet.
1247 */
1248 pjmedia_rtcp_pkt *rtcp_pkt;
1249 int rtcp_len;
1250 pj_ssize_t size;
1251 pj_status_t status;
1252
1253 /* Build RTCP packet */
1254 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1255
Benny Prijono4d3aa922006-06-22 22:31:48 +00001256
Benny Prijono513795f2006-07-18 21:12:24 +00001257 /* Send packet */
1258 size = rtcp_len;
1259 status = pjmedia_transport_send_rtcp(strm->transport,
1260 rtcp_pkt, size);
1261 if (status != PJ_SUCCESS) {
1262 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1263 }
1264
1265 /* Schedule next send */
1266 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
1267 1000);
1268 }
1269 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001270
Benny Prijono513795f2006-07-18 21:12:24 +00001271 return 0;
Benny Prijono60b980e2006-04-03 22:41:26 +00001272}
1273
Benny Prijono513795f2006-07-18 21:12:24 +00001274
Benny Prijono60b980e2006-04-03 22:41:26 +00001275/* Callback to be called when SDP negotiation is done in the call: */
1276static void call_on_media_update( pjsip_inv_session *inv,
1277 pj_status_t status)
1278{
1279 struct call *call;
1280 pj_pool_t *pool;
1281 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001282 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001283 struct codec *codec_desc = NULL;
1284 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001285
1286 call = inv->mod_data[mod_siprtp.id];
1287 pool = inv->dlg->pool;
1288 audio = &call->media[0];
1289
1290 /* If this is a mid-call media update, then destroy existing media */
Benny Prijono513795f2006-07-18 21:12:24 +00001291 if (audio->thread != NULL)
Benny Prijono60b980e2006-04-03 22:41:26 +00001292 destroy_call_media(call->index);
1293
1294
1295 /* Do nothing if media negotiation has failed */
1296 if (status != PJ_SUCCESS) {
1297 app_perror(THIS_FILE, "SDP negotiation failed", status);
1298 return;
1299 }
1300
1301
1302 /* Capture stream definition from the SDP */
1303 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1304 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1305
1306 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
Benny Prijonob04c9e02006-05-17 17:17:39 +00001307 local_sdp, remote_sdp, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001308 if (status != PJ_SUCCESS) {
1309 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1310 return;
1311 }
1312
Benny Prijono4adcb912006-04-04 13:12:38 +00001313 /* Get the remainder of codec information from codec descriptor */
1314 if (audio->si.fmt.pt == app.audio_codec.pt)
1315 codec_desc = &app.audio_codec;
1316 else {
1317 /* Find the codec description in codec array */
1318 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1319 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1320 codec_desc = &audio_codecs[i];
1321 break;
1322 }
1323 }
1324
1325 if (codec_desc == NULL) {
1326 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1327 return;
1328 }
1329 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001330
Benny Prijono15953012006-04-27 22:37:08 +00001331 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001332 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1333 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001334
1335
1336 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001337 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001338 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001339 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001340 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001341
Benny Prijono4adcb912006-04-04 13:12:38 +00001342
Benny Prijonodeb31962006-06-22 18:51:03 +00001343 /* Attach media to transport */
1344 status = pjmedia_transport_attach(audio->transport, audio,
1345 &audio->si.rem_addr,
Benny Prijono513795f2006-07-18 21:12:24 +00001346 &audio->si.rem_rtcp,
Benny Prijonodeb31962006-06-22 18:51:03 +00001347 sizeof(pj_sockaddr_in),
1348 &on_rx_rtp,
1349 &on_rx_rtcp);
1350 if (status != PJ_SUCCESS) {
1351 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1352 return;
1353 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001354
Benny Prijono513795f2006-07-18 21:12:24 +00001355 /* Start media thread. */
1356 audio->thread_quit_flag = 0;
1357 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1358 0, 0, &audio->thread);
1359 if (status != PJ_SUCCESS) {
1360 app_perror(THIS_FILE, "Error creating media thread", status);
1361 return;
1362 }
1363
Benny Prijonodeb31962006-06-22 18:51:03 +00001364 /* Set the media as active */
1365 audio->active = PJ_TRUE;
Benny Prijono60b980e2006-04-03 22:41:26 +00001366}
1367
1368
1369
1370/* Destroy call's media */
1371static void destroy_call_media(unsigned call_index)
1372{
1373 struct media_stream *audio = &app.call[call_index].media[0];
1374
Benny Prijono513795f2006-07-18 21:12:24 +00001375 if (audio->thread) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001376
1377 audio->active = PJ_FALSE;
1378
Benny Prijono513795f2006-07-18 21:12:24 +00001379 audio->thread_quit_flag = 1;
1380 pj_thread_join(audio->thread);
1381 pj_thread_destroy(audio->thread);
1382 audio->thread = NULL;
1383 audio->thread_quit_flag = 0;
Benny Prijono4adcb912006-04-04 13:12:38 +00001384
Benny Prijonodeb31962006-06-22 18:51:03 +00001385 pjmedia_transport_detach(audio->transport, audio);
Benny Prijono60b980e2006-04-03 22:41:26 +00001386 }
1387}
1388
Benny Prijono513795f2006-07-18 21:12:24 +00001389
Benny Prijono4adcb912006-04-04 13:12:38 +00001390/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001391 * USER INTERFACE STUFFS
1392 */
Benny Prijono258ece92006-07-22 12:53:04 +00001393
1394static void call_get_duration(int call_index, pj_time_val *dur)
1395{
1396 struct call *call = &app.call[call_index];
1397 pjsip_inv_session *inv;
1398
1399 dur->sec = dur->msec = 0;
1400
1401 if (!call)
1402 return;
1403
1404 inv = call->inv;
1405 if (!inv)
1406 return;
1407
1408 if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
1409
1410 pj_gettimeofday(dur);
1411 PJ_TIME_VAL_SUB((*dur), call->connect_time);
1412 }
1413}
1414
1415
1416static const char *good_number(char *buf, pj_int32_t val)
1417{
1418 if (val < 1000) {
1419 pj_ansi_sprintf(buf, "%d", val);
1420 } else if (val < 1000000) {
1421 pj_ansi_sprintf(buf, "%d.%02dK",
1422 val / 1000,
1423 (val % 1000) / 100);
1424 } else {
1425 pj_ansi_sprintf(buf, "%d.%02dM",
1426 val / 1000000,
1427 (val % 1000000) / 10000);
1428 }
1429
1430 return buf;
1431}
1432
1433
1434
1435static void print_avg_stat(void)
1436{
1437#define MIN_(var,val) if ((int)val < (int)var) var = val
1438#define MAX_(var,val) if ((int)val > (int)var) var = val
1439#define AVG_(var,val) var = ( ((var * count) + val) / (count+1) )
1440#define BIGVAL 0x7FFFFFFFL
1441 struct stat_entry
1442 {
1443 int min, avg, max;
1444 };
1445
1446 struct stat_entry call_dur, call_pdd;
1447 pjmedia_rtcp_stat min_stat, avg_stat, max_stat;
1448
1449 char srx_min[16], srx_avg[16], srx_max[16];
1450 char brx_min[16], brx_avg[16], brx_max[16];
1451 char stx_min[16], stx_avg[16], stx_max[16];
1452 char btx_min[16], btx_avg[16], btx_max[16];
1453
1454
1455 unsigned i, count;
1456
1457 pj_bzero(&call_dur, sizeof(call_dur));
1458 call_dur.min = BIGVAL;
1459
1460 pj_bzero(&call_pdd, sizeof(call_pdd));
1461 call_pdd.min = BIGVAL;
1462
1463 pj_bzero(&min_stat, sizeof(min_stat));
1464 min_stat.rx.pkt = min_stat.tx.pkt = BIGVAL;
1465 min_stat.rx.bytes = min_stat.tx.bytes = BIGVAL;
1466 min_stat.rx.loss = min_stat.tx.loss = BIGVAL;
1467 min_stat.rx.dup = min_stat.tx.dup = BIGVAL;
1468 min_stat.rx.reorder = min_stat.tx.reorder = BIGVAL;
1469 min_stat.rx.jitter.min = min_stat.tx.jitter.min = BIGVAL;
1470 min_stat.rtt.min = BIGVAL;
1471
1472 pj_bzero(&avg_stat, sizeof(avg_stat));
1473 pj_bzero(&max_stat, sizeof(max_stat));
1474
1475
1476 for (i=0, count=0; i<app.max_calls; ++i) {
1477
1478 struct call *call = &app.call[i];
1479 struct media_stream *audio = &call->media[0];
1480 pj_time_val dur;
1481 unsigned msec_dur;
1482
1483 if (call->inv == NULL ||
1484 call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
1485 call->connect_time.sec == 0)
1486 {
1487 continue;
1488 }
1489
1490 /* Duration */
1491 call_get_duration(i, &dur);
1492 msec_dur = PJ_TIME_VAL_MSEC(dur);
1493
1494 MIN_(call_dur.min, msec_dur);
1495 MAX_(call_dur.max, msec_dur);
1496 AVG_(call_dur.avg, msec_dur);
1497
1498 /* Connect delay */
1499 if (call->connect_time.sec) {
1500 pj_time_val t = call->connect_time;
1501 PJ_TIME_VAL_SUB(t, call->start_time);
1502 msec_dur = PJ_TIME_VAL_MSEC(t);
1503 } else {
1504 msec_dur = 10;
1505 }
1506
1507 MIN_(call_pdd.min, msec_dur);
1508 MAX_(call_pdd.max, msec_dur);
1509 AVG_(call_pdd.avg, msec_dur);
1510
1511 /* RX Statistisc: */
1512
1513 /* Packets */
1514 MIN_(min_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1515 MAX_(max_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1516 AVG_(avg_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1517
1518 /* Bytes */
1519 MIN_(min_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1520 MAX_(max_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1521 AVG_(avg_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1522
1523
1524 /* Packet loss */
1525 MIN_(min_stat.rx.loss, audio->rtcp.stat.rx.loss);
1526 MAX_(max_stat.rx.loss, audio->rtcp.stat.rx.loss);
1527 AVG_(avg_stat.rx.loss, audio->rtcp.stat.rx.loss);
1528
1529 /* Packet dup */
1530 MIN_(min_stat.rx.dup, audio->rtcp.stat.rx.dup);
1531 MAX_(max_stat.rx.dup, audio->rtcp.stat.rx.dup);
1532 AVG_(avg_stat.rx.dup, audio->rtcp.stat.rx.dup);
1533
1534 /* Packet reorder */
1535 MIN_(min_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1536 MAX_(max_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1537 AVG_(avg_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1538
1539 /* Jitter */
1540 MIN_(min_stat.rx.jitter.min, audio->rtcp.stat.rx.jitter.min);
1541 MAX_(max_stat.rx.jitter.max, audio->rtcp.stat.rx.jitter.max);
1542 AVG_(avg_stat.rx.jitter.avg, audio->rtcp.stat.rx.jitter.avg);
1543
1544
1545 /* TX Statistisc: */
1546
1547 /* Packets */
1548 MIN_(min_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1549 MAX_(max_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1550 AVG_(avg_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1551
1552 /* Bytes */
1553 MIN_(min_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1554 MAX_(max_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1555 AVG_(avg_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1556
1557 /* Packet loss */
1558 MIN_(min_stat.tx.loss, audio->rtcp.stat.tx.loss);
1559 MAX_(max_stat.tx.loss, audio->rtcp.stat.tx.loss);
1560 AVG_(avg_stat.tx.loss, audio->rtcp.stat.tx.loss);
1561
1562 /* Packet dup */
1563 MIN_(min_stat.tx.dup, audio->rtcp.stat.tx.dup);
1564 MAX_(max_stat.tx.dup, audio->rtcp.stat.tx.dup);
1565 AVG_(avg_stat.tx.dup, audio->rtcp.stat.tx.dup);
1566
1567 /* Packet reorder */
1568 MIN_(min_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1569 MAX_(max_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1570 AVG_(avg_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1571
1572 /* Jitter */
1573 MIN_(min_stat.tx.jitter.min, audio->rtcp.stat.tx.jitter.min);
1574 MAX_(max_stat.tx.jitter.max, audio->rtcp.stat.tx.jitter.max);
1575 AVG_(avg_stat.tx.jitter.avg, audio->rtcp.stat.tx.jitter.avg);
1576
1577
1578 /* RTT */
1579 MIN_(min_stat.rtt.min, audio->rtcp.stat.rtt.min);
1580 MAX_(max_stat.rtt.max, audio->rtcp.stat.rtt.max);
1581 AVG_(avg_stat.rtt.avg, audio->rtcp.stat.rtt.avg);
1582
1583 ++count;
1584 }
1585
1586 if (count == 0) {
1587 puts("No active calls");
1588 return;
1589 }
1590
1591 printf("Total %d call(s) active.\n"
1592 " Average Statistics\n"
1593 " min avg max \n"
1594 " -----------------------\n"
1595 " call duration: %7d %7d %7d %s\n"
1596 " connect delay: %7d %7d %7d %s\n"
1597 " RX stat:\n"
1598 " packets: %7s %7s %7s %s\n"
1599 " payload: %7s %7s %7s %s\n"
1600 " loss: %7d %7d %7d %s\n"
1601 " percent loss: %7.3f %7.3f %7.3f %s\n"
1602 " dup: %7d %7d %7d %s\n"
1603 " reorder: %7d %7d %7d %s\n"
1604 " jitter: %7.3f %7.3f %7.3f %s\n"
1605 " TX stat:\n"
1606 " packets: %7s %7s %7s %s\n"
1607 " payload: %7s %7s %7s %s\n"
1608 " loss: %7d %7d %7d %s\n"
1609 " percent loss: %7.3f %7.3f %7.3f %s\n"
1610 " dup: %7d %7d %7d %s\n"
1611 " reorder: %7d %7d %7d %s\n"
1612 " jitter: %7.3f %7.3f %7.3f %s\n"
1613 " RTT : %7.3f %7.3f %7.3f %s\n"
1614 ,
1615 count,
1616 call_dur.min/1000, call_dur.avg/1000, call_dur.max/1000,
1617 "seconds",
1618
1619 call_pdd.min, call_pdd.avg, call_pdd.max,
1620 "ms",
1621
1622 /* rx */
1623
1624 good_number(srx_min, min_stat.rx.pkt),
1625 good_number(srx_avg, avg_stat.rx.pkt),
1626 good_number(srx_max, max_stat.rx.pkt),
1627 "packets",
1628
1629 good_number(brx_min, min_stat.rx.bytes),
1630 good_number(brx_avg, avg_stat.rx.bytes),
1631 good_number(brx_max, max_stat.rx.bytes),
1632 "bytes",
1633
1634 min_stat.rx.loss, avg_stat.rx.loss, max_stat.rx.loss,
1635 "packets",
1636
1637 min_stat.rx.loss*100.0/(min_stat.rx.pkt+min_stat.rx.loss),
1638 avg_stat.rx.loss*100.0/(avg_stat.rx.pkt+avg_stat.rx.loss),
1639 max_stat.rx.loss*100.0/(max_stat.rx.pkt+max_stat.rx.loss),
1640 "%",
1641
1642
1643 min_stat.rx.dup, avg_stat.rx.dup, max_stat.rx.dup,
1644 "packets",
1645
1646 min_stat.rx.reorder, avg_stat.rx.reorder, max_stat.rx.reorder,
1647 "packets",
1648
1649 min_stat.rx.jitter.min/1000.0,
1650 avg_stat.rx.jitter.avg/1000.0,
1651 max_stat.rx.jitter.max/1000.0,
1652 "ms",
1653
1654 /* tx */
1655
1656 good_number(stx_min, min_stat.tx.pkt),
1657 good_number(stx_avg, avg_stat.tx.pkt),
1658 good_number(stx_max, max_stat.tx.pkt),
1659 "packets",
1660
1661 good_number(btx_min, min_stat.tx.bytes),
1662 good_number(btx_avg, avg_stat.tx.bytes),
1663 good_number(btx_max, max_stat.tx.bytes),
1664 "bytes",
1665
1666 min_stat.tx.loss, avg_stat.tx.loss, max_stat.tx.loss,
1667 "packets",
1668
Benny Prijono05784a52006-07-25 11:54:15 +00001669 min_stat.tx.loss*100.0/(min_stat.tx.pkt+min_stat.tx.loss),
1670 avg_stat.tx.loss*100.0/(avg_stat.tx.pkt+avg_stat.tx.loss),
1671 max_stat.tx.loss*100.0/(max_stat.tx.pkt+max_stat.tx.loss),
Benny Prijono258ece92006-07-22 12:53:04 +00001672 "%",
1673
1674 min_stat.tx.dup, avg_stat.tx.dup, max_stat.tx.dup,
1675 "packets",
1676
1677 min_stat.tx.reorder, avg_stat.tx.reorder, max_stat.tx.reorder,
1678 "packets",
1679
1680 min_stat.tx.jitter.min/1000.0,
1681 avg_stat.tx.jitter.avg/1000.0,
1682 max_stat.tx.jitter.max/1000.0,
1683 "ms",
1684
1685 /* rtt */
1686 min_stat.rtt.min/1000.0,
1687 avg_stat.rtt.avg/1000.0,
1688 max_stat.rtt.max/1000.0,
1689 "ms"
1690 );
1691
1692}
1693
1694
Benny Prijono16a6b0e2006-05-12 10:20:03 +00001695#include "siprtp_report.c"
Benny Prijono60b980e2006-04-03 22:41:26 +00001696
1697
1698static void list_calls()
1699{
1700 unsigned i;
1701 puts("List all calls:");
1702 for (i=0; i<app.max_calls; ++i) {
1703 if (!app.call[i].inv)
1704 continue;
1705 print_call(i);
1706 }
1707}
1708
1709static void hangup_call(unsigned index)
1710{
1711 pjsip_tx_data *tdata;
1712 pj_status_t status;
1713
1714 if (app.call[index].inv == NULL)
1715 return;
1716
1717 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1718 if (status==PJ_SUCCESS && tdata!=NULL)
1719 pjsip_inv_send_msg(app.call[index].inv, tdata);
1720}
1721
1722static void hangup_all_calls()
1723{
1724 unsigned i;
1725 for (i=0; i<app.max_calls; ++i) {
1726 if (!app.call[i].inv)
1727 continue;
1728 hangup_call(i);
1729 }
Benny Prijonodeb31962006-06-22 18:51:03 +00001730
1731 /* Wait until all calls are terminated */
1732 for (i=0; i<app.max_calls; ++i) {
1733 while (app.call[i].inv)
1734 pj_thread_sleep(10);
1735 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001736}
1737
1738static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1739{
1740 char *p;
1741
1742 printf("%s (empty to cancel): ", title); fflush(stdout);
1743 fgets(buf, len, stdin);
1744
1745 /* Remove trailing newlines. */
1746 for (p=buf; ; ++p) {
1747 if (*p=='\r' || *p=='\n') *p='\0';
1748 else if (!*p) break;
1749 }
1750
1751 if (!*buf)
1752 return PJ_FALSE;
1753
1754 return PJ_TRUE;
1755}
1756
1757
1758static const char *MENU =
1759"\n"
1760"Enter menu character:\n"
Benny Prijono258ece92006-07-22 12:53:04 +00001761" s Summary\n"
Benny Prijono60b980e2006-04-03 22:41:26 +00001762" l List all calls\n"
1763" h Hangup a call\n"
1764" H Hangup all calls\n"
1765" q Quit\n"
1766"\n";
1767
1768
1769/* Main screen menu */
1770static void console_main()
1771{
1772 char input1[10];
1773 unsigned i;
1774
Benny Prijono4adcb912006-04-04 13:12:38 +00001775 printf("%s", MENU);
1776
Benny Prijono60b980e2006-04-03 22:41:26 +00001777 for (;;) {
1778 printf(">>> "); fflush(stdout);
1779 fgets(input1, sizeof(input1), stdin);
1780
1781 switch (input1[0]) {
Benny Prijono258ece92006-07-22 12:53:04 +00001782
1783 case 's':
1784 print_avg_stat();
1785 break;
1786
Benny Prijono60b980e2006-04-03 22:41:26 +00001787 case 'l':
1788 list_calls();
1789 break;
1790
1791 case 'h':
1792 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1793 break;
1794
1795 i = atoi(input1);
1796 hangup_call(i);
1797 break;
1798
1799 case 'H':
1800 hangup_all_calls();
1801 break;
1802
1803 case 'q':
1804 goto on_exit;
1805
1806 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001807 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001808 printf("%s", MENU);
1809 break;
1810 }
1811
1812 fflush(stdout);
1813 }
1814
1815on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001816 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001817}
1818
1819
Benny Prijono4adcb912006-04-04 13:12:38 +00001820/*****************************************************************************
1821 * Below is a simple module to log all incoming and outgoing SIP messages
1822 */
1823
1824
Benny Prijono60b980e2006-04-03 22:41:26 +00001825/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001826static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001827{
1828 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1829 "%s\n"
1830 "--end msg--",
1831 rdata->msg_info.len,
1832 pjsip_rx_data_get_info(rdata),
1833 rdata->pkt_info.src_name,
1834 rdata->pkt_info.src_port,
1835 rdata->msg_info.msg_buf));
1836
1837 /* Always return false, otherwise messages will not get processed! */
1838 return PJ_FALSE;
1839}
1840
1841/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001842static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001843{
1844
1845 /* Important note:
1846 * tp_info field is only valid after outgoing messages has passed
1847 * transport layer. So don't try to access tp_info when the module
1848 * has lower priority than transport layer.
1849 */
1850
1851 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1852 "%s\n"
1853 "--end msg--",
1854 (tdata->buf.cur - tdata->buf.start),
1855 pjsip_tx_data_get_info(tdata),
1856 tdata->tp_info.dst_name,
1857 tdata->tp_info.dst_port,
1858 tdata->buf.start));
1859
1860 /* Always return success, otherwise message will not get sent! */
1861 return PJ_SUCCESS;
1862}
1863
1864/* The module instance. */
1865static pjsip_module msg_logger =
1866{
1867 NULL, NULL, /* prev, next. */
1868 { "mod-siprtp-log", 14 }, /* Name. */
1869 -1, /* Id */
1870 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1871 NULL, /* load() */
1872 NULL, /* start() */
1873 NULL, /* stop() */
1874 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001875 &logger_on_rx_msg, /* on_rx_request() */
1876 &logger_on_rx_msg, /* on_rx_response() */
1877 &logger_on_tx_msg, /* on_tx_request. */
1878 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00001879 NULL, /* on_tsx_state() */
1880
1881};
1882
1883
1884
Benny Prijono4adcb912006-04-04 13:12:38 +00001885/*****************************************************************************
1886 * Console application custom logging:
1887 */
1888
1889
1890static FILE *log_file;
1891
1892
1893static void app_log_writer(int level, const char *buffer, int len)
1894{
1895 /* Write to both stdout and file. */
1896
1897 if (level <= app.app_log_level)
1898 pj_log_write(level, buffer, len);
1899
1900 if (log_file) {
1901 fwrite(buffer, len, 1, log_file);
1902 fflush(log_file);
1903 }
1904}
1905
1906
1907pj_status_t app_logging_init(void)
1908{
1909 /* Redirect log function to ours */
1910
1911 pj_log_set_log_func( &app_log_writer );
1912
1913 /* If output log file is desired, create the file: */
1914
1915 if (app.log_filename) {
1916 log_file = fopen(app.log_filename, "wt");
1917 if (log_file == NULL) {
1918 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
1919 app.log_filename));
1920 return -1;
1921 }
1922 }
1923
1924 return PJ_SUCCESS;
1925}
1926
1927
1928void app_logging_shutdown(void)
1929{
1930 /* Close logging file, if any: */
1931
1932 if (log_file) {
1933 fclose(log_file);
1934 log_file = NULL;
1935 }
1936}
1937
Benny Prijono60b980e2006-04-03 22:41:26 +00001938
1939/*
1940 * main()
1941 */
1942int main(int argc, char *argv[])
1943{
Benny Prijono4adcb912006-04-04 13:12:38 +00001944 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001945 pj_status_t status;
1946
Benny Prijono4adcb912006-04-04 13:12:38 +00001947 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00001948 status = pj_init();
1949 if (status != PJ_SUCCESS)
1950 return 1;
1951
Benny Prijono4adcb912006-04-04 13:12:38 +00001952 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00001953 status = init_options(argc, argv);
1954 if (status != PJ_SUCCESS)
1955 return 1;
1956
Benny Prijono410fbae2006-05-03 18:16:06 +00001957 /* Verify options: */
1958
1959 /* Auto-quit can not be specified for UAS */
1960 if (app.auto_quit && app.uri_to_call.slen == 0) {
1961 printf("Error: --auto-quit option only valid for outgoing "
1962 "mode (UAC) only\n");
1963 return 1;
1964 }
1965
Benny Prijono4adcb912006-04-04 13:12:38 +00001966 /* Init logging */
1967 status = app_logging_init();
1968 if (status != PJ_SUCCESS)
1969 return 1;
1970
1971 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00001972 status = init_sip();
1973 if (status != PJ_SUCCESS) {
1974 app_perror(THIS_FILE, "Initialization has failed", status);
1975 destroy_sip();
1976 return 1;
1977 }
1978
Benny Prijono4adcb912006-04-04 13:12:38 +00001979 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00001980 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
1981
Benny Prijono4adcb912006-04-04 13:12:38 +00001982 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00001983 status = init_media();
1984 if (status != PJ_SUCCESS) {
1985 app_perror(THIS_FILE, "Media initialization failed", status);
1986 destroy_sip();
1987 return 1;
1988 }
1989
Benny Prijono9a0eab52006-04-04 19:43:24 +00001990 /* Start worker threads */
1991 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001992 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
1993 0, 0, &app.sip_thread[i]);
Benny Prijono9a0eab52006-04-04 19:43:24 +00001994 }
1995
Benny Prijono4adcb912006-04-04 13:12:38 +00001996 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00001997 if (app.uri_to_call.slen) {
1998 unsigned i;
1999
Benny Prijono4adcb912006-04-04 13:12:38 +00002000 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
2001 app.uri_to_call.ptr));
2002
Benny Prijono60b980e2006-04-03 22:41:26 +00002003 for (i=0; i<app.max_calls; ++i) {
2004 status = make_call(&app.uri_to_call);
2005 if (status != PJ_SUCCESS) {
2006 app_perror(THIS_FILE, "Error making call", status);
2007 break;
2008 }
2009 }
Benny Prijono4adcb912006-04-04 13:12:38 +00002010
Benny Prijono410fbae2006-05-03 18:16:06 +00002011 if (app.auto_quit) {
2012 /* Wait for calls to complete */
2013 while (app.uac_calls < app.max_calls)
2014 pj_thread_sleep(100);
2015 pj_thread_sleep(200);
2016 } else {
2017 /* Start user interface loop */
2018 console_main();
2019 }
2020
Benny Prijono4adcb912006-04-04 13:12:38 +00002021 } else {
2022
2023 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
2024 app.max_calls));
Benny Prijono4adcb912006-04-04 13:12:38 +00002025
Benny Prijono410fbae2006-05-03 18:16:06 +00002026 /* Start user interface loop */
2027 console_main();
2028
2029 }
Benny Prijono60b980e2006-04-03 22:41:26 +00002030
Benny Prijono4adcb912006-04-04 13:12:38 +00002031
2032 /* Shutting down... */
Benny Prijono4d3aa922006-06-22 22:31:48 +00002033 destroy_sip();
Benny Prijono513795f2006-07-18 21:12:24 +00002034 destroy_media();
Benny Prijono6647d822006-05-20 13:01:07 +00002035
2036 if (app.pool) {
2037 pj_pool_release(app.pool);
2038 app.pool = NULL;
2039 pj_caching_pool_destroy(&app.cp);
2040 }
2041
Benny Prijono4adcb912006-04-04 13:12:38 +00002042 app_logging_shutdown();
2043
Benny Prijono60b980e2006-04-03 22:41:26 +00002044
2045 return 0;
2046}
2047