blob: 2fe135cb4ef5933f3a1183cb831c5b27f858f307 [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
349 /* Init invite session module. */
350 {
351 pjsip_inv_callback inv_cb;
352
353 /* Init the callback for INVITE session: */
Benny Prijonoac623b32006-07-03 15:19:31 +0000354 pj_bzero(&inv_cb, sizeof(inv_cb));
Benny Prijono60b980e2006-04-03 22:41:26 +0000355 inv_cb.on_state_changed = &call_on_state_changed;
356 inv_cb.on_new_session = &call_on_forked;
357 inv_cb.on_media_update = &call_on_media_update;
358
359 /* Initialize invite session module: */
360 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
361 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
362 }
363
364 /* Register our module to receive incoming requests. */
365 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
366 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
367
Benny Prijonodeb31962006-06-22 18:51:03 +0000368 /* Init calls */
369 for (i=0; i<app.max_calls; ++i)
370 app.call[i].index = i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000371
Benny Prijono60b980e2006-04-03 22:41:26 +0000372 /* Done */
373 return PJ_SUCCESS;
374}
375
376
377/*
378 * Destroy SIP
379 */
380static void destroy_sip()
381{
382 unsigned i;
383
384 app.thread_quit = 1;
385 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000386 if (app.sip_thread[i]) {
387 pj_thread_join(app.sip_thread[i]);
388 pj_thread_destroy(app.sip_thread[i]);
389 app.sip_thread[i] = NULL;
Benny Prijono60b980e2006-04-03 22:41:26 +0000390 }
391 }
392
393 if (app.sip_endpt) {
394 pjsip_endpt_destroy(app.sip_endpt);
395 app.sip_endpt = NULL;
396 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000397
Benny Prijono60b980e2006-04-03 22:41:26 +0000398}
399
400
401/*
402 * Init media stack.
403 */
404static pj_status_t init_media()
405{
Benny Prijono60b980e2006-04-03 22:41:26 +0000406 unsigned i, count;
407 pj_uint16_t rtp_port;
Benny Prijono60b980e2006-04-03 22:41:26 +0000408 pj_status_t status;
409
410
Benny Prijono60b980e2006-04-03 22:41:26 +0000411 /* Initialize media endpoint so that at least error subsystem is properly
412 * initialized.
413 */
Benny Prijono6869dc92007-06-03 00:37:30 +0000414#if PJ_HAS_THREADS
Benny Prijono513795f2006-07-18 21:12:24 +0000415 status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
Benny Prijono6869dc92007-06-03 00:37:30 +0000416#else
417 status = pjmedia_endpt_create(&app.cp.factory,
418 pjsip_endpt_get_ioqueue(app.sip_endpt),
419 0, &app.med_endpt);
420#endif
Benny Prijono60b980e2006-04-03 22:41:26 +0000421 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
422
423
Benny Prijonodeb31962006-06-22 18:51:03 +0000424 /* Must register codecs to be supported */
Benny Prijonofc24e692007-01-27 18:31:51 +0000425#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
Benny Prijono4d7fd202006-05-14 20:57:20 +0000426 pjmedia_codec_g711_init(app.med_endpt);
Benny Prijonofc24e692007-01-27 18:31:51 +0000427#endif
Benny Prijono4d7fd202006-05-14 20:57:20 +0000428
Benny Prijono60b980e2006-04-03 22:41:26 +0000429 /* RTP port counter */
430 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
431
Benny Prijonodeb31962006-06-22 18:51:03 +0000432 /* Init media transport for all calls. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000433 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
434
Benny Prijonodeb31962006-06-22 18:51:03 +0000435 unsigned j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000436
Benny Prijonodeb31962006-06-22 18:51:03 +0000437 /* Create transport for each media in the call */
438 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
439 /* Repeat binding media socket to next port when fails to bind
440 * to current port number.
441 */
442 int retry;
Benny Prijono60b980e2006-04-03 22:41:26 +0000443
Benny Prijono513795f2006-07-18 21:12:24 +0000444 app.call[i].media[j].call_index = i;
445 app.call[i].media[j].media_index = j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000446
Benny Prijonodeb31962006-06-22 18:51:03 +0000447 status = -1;
448 for (retry=0; retry<100; ++retry,rtp_port+=2) {
449 struct media_stream *m = &app.call[i].media[j];
450
451 status = pjmedia_transport_udp_create2(app.med_endpt,
452 "siprtp",
453 &app.local_addr,
454 rtp_port, 0,
455 &m->transport);
456 if (status == PJ_SUCCESS) {
457 rtp_port += 2;
458 break;
459 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000460 }
Benny Prijono6647d822006-05-20 13:01:07 +0000461 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000462
463 if (status != PJ_SUCCESS)
464 goto on_error;
Benny Prijono60b980e2006-04-03 22:41:26 +0000465 }
466
467 /* Done */
468 return PJ_SUCCESS;
469
470on_error:
Benny Prijonodeb31962006-06-22 18:51:03 +0000471 destroy_media();
Benny Prijono60b980e2006-04-03 22:41:26 +0000472 return status;
473}
474
475
476/*
477 * Destroy media.
478 */
479static void destroy_media()
480{
481 unsigned i;
482
483 for (i=0; i<app.max_calls; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000484 unsigned j;
485 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
486 struct media_stream *m = &app.call[i].media[j];
Benny Prijono60b980e2006-04-03 22:41:26 +0000487
Benny Prijonodeb31962006-06-22 18:51:03 +0000488 if (m->transport) {
489 pjmedia_transport_close(m->transport);
490 m->transport = NULL;
491 }
492 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000493 }
494
495 if (app.med_endpt) {
496 pjmedia_endpt_destroy(app.med_endpt);
497 app.med_endpt = NULL;
498 }
499}
500
501
502/*
503 * Make outgoing call.
504 */
505static pj_status_t make_call(const pj_str_t *dst_uri)
506{
507 unsigned i;
508 struct call *call;
509 pjsip_dialog *dlg;
510 pjmedia_sdp_session *sdp;
511 pjsip_tx_data *tdata;
512 pj_status_t status;
513
514
515 /* Find unused call slot */
516 for (i=0; i<app.max_calls; ++i) {
517 if (app.call[i].inv == NULL)
518 break;
519 }
520
521 if (i == app.max_calls)
522 return PJ_ETOOMANY;
523
524 call = &app.call[i];
525
526 /* Create UAC dialog */
527 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
528 &app.local_uri, /* local URI */
529 &app.local_contact, /* local Contact */
530 dst_uri, /* remote URI */
531 dst_uri, /* remote target */
532 &dlg); /* dialog */
Benny Prijono410fbae2006-05-03 18:16:06 +0000533 if (status != PJ_SUCCESS) {
534 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000535 return status;
Benny Prijono410fbae2006-05-03 18:16:06 +0000536 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000537
538 /* Create SDP */
539 create_sdp( dlg->pool, call, &sdp);
540
541 /* Create the INVITE session. */
542 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
543 if (status != PJ_SUCCESS) {
544 pjsip_dlg_terminate(dlg);
Benny Prijono410fbae2006-05-03 18:16:06 +0000545 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000546 return status;
547 }
548
549
550 /* Attach call data to invite session */
551 call->inv->mod_data[mod_siprtp.id] = call;
552
Benny Prijono4adcb912006-04-04 13:12:38 +0000553 /* Mark start of call */
554 pj_gettimeofday(&call->start_time);
555
Benny Prijono60b980e2006-04-03 22:41:26 +0000556
557 /* Create initial INVITE request.
558 * This INVITE request will contain a perfectly good request and
559 * an SDP body as well.
560 */
561 status = pjsip_inv_invite(call->inv, &tdata);
562 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
563
564
565 /* Send initial INVITE request.
566 * From now on, the invite session's state will be reported to us
567 * via the invite session callbacks.
568 */
569 status = pjsip_inv_send_msg(call->inv, tdata);
570 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
571
572
573 return PJ_SUCCESS;
574}
575
576
577/*
578 * Receive incoming call
579 */
580static void process_incoming_call(pjsip_rx_data *rdata)
581{
Benny Prijonoca4cff22006-07-02 14:18:47 +0000582 unsigned i, options;
Benny Prijono60b980e2006-04-03 22:41:26 +0000583 struct call *call;
584 pjsip_dialog *dlg;
585 pjmedia_sdp_session *sdp;
586 pjsip_tx_data *tdata;
587 pj_status_t status;
588
589 /* Find free call slot */
590 for (i=0; i<app.max_calls; ++i) {
591 if (app.call[i].inv == NULL)
592 break;
593 }
594
595 if (i == app.max_calls) {
596 const pj_str_t reason = pj_str("Too many calls");
597 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
598 500, &reason,
599 NULL, NULL);
600 return;
601 }
602
Benny Prijono513795f2006-07-18 21:12:24 +0000603 call = &app.call[i];
604
Benny Prijonoca4cff22006-07-02 14:18:47 +0000605 /* Verify that we can handle the request. */
606 options = 0;
607 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000608 app.sip_endpt, &tdata);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000609 if (status != PJ_SUCCESS) {
Benny Prijonoca4cff22006-07-02 14:18:47 +0000610 /*
611 * No we can't handle the incoming INVITE request.
612 */
Benny Prijonoca4cff22006-07-02 14:18:47 +0000613 if (tdata) {
614 pjsip_response_addr res_addr;
Benny Prijono513795f2006-07-18 21:12:24 +0000615
Benny Prijonoca4cff22006-07-02 14:18:47 +0000616 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
Benny Prijono513795f2006-07-18 21:12:24 +0000617 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
618 NULL, NULL);
619
Benny Prijonoca4cff22006-07-02 14:18:47 +0000620 } else {
Benny Prijono513795f2006-07-18 21:12:24 +0000621
Benny Prijonoca4cff22006-07-02 14:18:47 +0000622 /* Respond with 500 (Internal Server Error) */
623 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000624 NULL, NULL);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000625 }
Benny Prijono513795f2006-07-18 21:12:24 +0000626
Benny Prijonoca4cff22006-07-02 14:18:47 +0000627 return;
Benny Prijono513795f2006-07-18 21:12:24 +0000628 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000629
630 /* Create UAS dialog */
631 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
632 &app.local_contact, &dlg);
633 if (status != PJ_SUCCESS) {
634 const pj_str_t reason = pj_str("Unable to create dialog");
635 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
636 500, &reason,
637 NULL, NULL);
638 return;
639 }
640
641 /* Create SDP */
642 create_sdp( dlg->pool, call, &sdp);
643
644 /* Create UAS invite session */
645 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
646 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000647 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
648 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000649 return;
650 }
651
Benny Prijono4adcb912006-04-04 13:12:38 +0000652
Benny Prijono60b980e2006-04-03 22:41:26 +0000653 /* Attach call data to invite session */
654 call->inv->mod_data[mod_siprtp.id] = call;
655
Benny Prijono4adcb912006-04-04 13:12:38 +0000656 /* Mark start of call */
657 pj_gettimeofday(&call->start_time);
658
659
660
Benny Prijono60b980e2006-04-03 22:41:26 +0000661 /* Create 200 response .*/
662 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
663 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000664 if (status != PJ_SUCCESS) {
665 status = pjsip_inv_initial_answer(call->inv, rdata,
666 PJSIP_SC_NOT_ACCEPTABLE,
667 NULL, NULL, &tdata);
668 if (status == PJ_SUCCESS)
669 pjsip_inv_send_msg(call->inv, tdata);
670 else
671 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
672 return;
673 }
674
Benny Prijono60b980e2006-04-03 22:41:26 +0000675
676 /* Send the 200 response. */
677 status = pjsip_inv_send_msg(call->inv, tdata);
678 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
679
680
681 /* Done */
682}
683
684
685/* Callback to be called when dialog has forked: */
686static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
687{
688 PJ_UNUSED_ARG(inv);
689 PJ_UNUSED_ARG(e);
690
691 PJ_TODO( HANDLE_FORKING );
692}
693
694
695/* Callback to be called to handle incoming requests outside dialogs: */
696static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
697{
Benny Prijono4adcb912006-04-04 13:12:38 +0000698 /* Ignore strandled ACKs (must not send respone */
699 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
700 return PJ_FALSE;
701
Benny Prijono60b980e2006-04-03 22:41:26 +0000702 /* Respond (statelessly) any non-INVITE requests with 500 */
703 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
704 pj_str_t reason = pj_str("Unsupported Operation");
705 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
706 500, &reason,
707 NULL, NULL);
708 return PJ_TRUE;
709 }
710
711 /* Handle incoming INVITE */
712 process_incoming_call(rdata);
713
714 /* Done */
715 return PJ_TRUE;
716}
717
718
Benny Prijono410fbae2006-05-03 18:16:06 +0000719/* Callback timer to disconnect call (limiting call duration) */
720static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
721 struct pj_timer_entry *entry)
722{
723 struct call *call = entry->user_data;
724
725 PJ_UNUSED_ARG(timer_heap);
726
727 entry->id = 0;
728 hangup_call(call->index);
729}
730
731
Benny Prijono60b980e2006-04-03 22:41:26 +0000732/* Callback to be called when invite session's state has changed: */
733static void call_on_state_changed( pjsip_inv_session *inv,
734 pjsip_event *e)
735{
Benny Prijono4adcb912006-04-04 13:12:38 +0000736 struct call *call = inv->mod_data[mod_siprtp.id];
737
Benny Prijono60b980e2006-04-03 22:41:26 +0000738 PJ_UNUSED_ARG(e);
739
Benny Prijono4adcb912006-04-04 13:12:38 +0000740 if (!call)
741 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000742
Benny Prijono4adcb912006-04-04 13:12:38 +0000743 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
744
745 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000746
Benny Prijono410fbae2006-05-03 18:16:06 +0000747 if (call->d_timer.id != 0) {
748 pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
749 call->d_timer.id = 0;
750 }
751
Benny Prijono258ece92006-07-22 12:53:04 +0000752 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%d (%.*s)",
Benny Prijonod7a13f12006-04-05 19:08:16 +0000753 call->index,
Benny Prijono258ece92006-07-22 12:53:04 +0000754 inv->cause,
755 (int)inv->cause_text.slen,
756 inv->cause_text.ptr));
Benny Prijonod7a13f12006-04-05 19:08:16 +0000757 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
758 print_call(call->index);
759
760
Benny Prijono60b980e2006-04-03 22:41:26 +0000761 call->inv = NULL;
762 inv->mod_data[mod_siprtp.id] = NULL;
763
764 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000765
766 call->start_time = null_time;
767 call->response_time = null_time;
768 call->connect_time = null_time;
769
Benny Prijono410fbae2006-05-03 18:16:06 +0000770 ++app.uac_calls;
Benny Prijono4adcb912006-04-04 13:12:38 +0000771
772 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
773
774 pj_time_val t;
775
776 pj_gettimeofday(&call->connect_time);
777 if (call->response_time.sec == 0)
778 call->response_time = call->connect_time;
779
780 t = call->connect_time;
781 PJ_TIME_VAL_SUB(t, call->start_time);
782
783 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
784 PJ_TIME_VAL_MSEC(t)));
785
Benny Prijono410fbae2006-05-03 18:16:06 +0000786 if (app.duration != 0) {
787 call->d_timer.id = 1;
788 call->d_timer.user_data = call;
789 call->d_timer.cb = &timer_disconnect_call;
790
791 t.sec = app.duration;
792 t.msec = 0;
793
794 pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
795 }
796
Benny Prijono4adcb912006-04-04 13:12:38 +0000797 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
798 inv->state == PJSIP_INV_STATE_CONNECTING) {
799
800 if (call->response_time.sec == 0)
801 pj_gettimeofday(&call->response_time);
802
Benny Prijono60b980e2006-04-03 22:41:26 +0000803 }
804}
805
806
807/* Utility */
808static void app_perror(const char *sender, const char *title,
809 pj_status_t status)
810{
811 char errmsg[PJ_ERR_MSG_SIZE];
812
813 pj_strerror(status, errmsg, sizeof(errmsg));
814 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
815}
816
817
Benny Prijonodeb31962006-06-22 18:51:03 +0000818/* Worker thread for SIP */
819static int sip_worker_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +0000820{
821 PJ_UNUSED_ARG(arg);
822
823 while (!app.thread_quit) {
Benny Prijono513795f2006-07-18 21:12:24 +0000824 pj_time_val timeout = {0, 10};
Benny Prijono60b980e2006-04-03 22:41:26 +0000825 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
826 }
827
828 return 0;
829}
830
831
Benny Prijono60b980e2006-04-03 22:41:26 +0000832/* Init application options */
833static pj_status_t init_options(int argc, char *argv[])
834{
835 static char ip_addr[32];
836 static char local_uri[64];
837
Benny Prijono4adcb912006-04-04 13:12:38 +0000838 enum { OPT_START,
839 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
Benny Prijonofcb36722006-05-18 18:34:21 +0000840 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
841 OPT_REPORT_FILE };
Benny Prijono4adcb912006-04-04 13:12:38 +0000842
Benny Prijono60b980e2006-04-03 22:41:26 +0000843 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000844 { "count", 1, 0, 'c' },
Benny Prijono410fbae2006-05-03 18:16:06 +0000845 { "duration", 1, 0, 'd' },
846 { "auto-quit", 0, 0, 'q' },
Benny Prijono4adcb912006-04-04 13:12:38 +0000847 { "local-port", 1, 0, 'p' },
848 { "rtp-port", 1, 0, 'r' },
849 { "ip-addr", 1, 0, 'i' },
850
851 { "log-level", 1, 0, 'l' },
852 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
853 { "log-file", 1, 0, OPT_LOG_FILE },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000854
Benny Prijonofcb36722006-05-18 18:34:21 +0000855 { "report-file", 1, 0, OPT_REPORT_FILE },
856
Benny Prijono4d7fd202006-05-14 20:57:20 +0000857 /* Don't support this anymore, see comments in USAGE above.
Benny Prijono4adcb912006-04-04 13:12:38 +0000858 { "a-pt", 1, 0, OPT_A_PT },
859 { "a-name", 1, 0, OPT_A_NAME },
860 { "a-clock", 1, 0, OPT_A_CLOCK },
861 { "a-bitrate", 1, 0, OPT_A_BITRATE },
862 { "a-ptime", 1, 0, OPT_A_PTIME },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000863 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000864
Benny Prijono60b980e2006-04-03 22:41:26 +0000865 { NULL, 0, 0, 0 },
866 };
867 int c;
868 int option_index;
869
870 /* Get local IP address for the default IP address */
871 {
872 const pj_str_t *hostname;
873 pj_sockaddr_in tmp_addr;
874 char *addr;
875
876 hostname = pj_gethostname();
877 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
878 addr = pj_inet_ntoa(tmp_addr.sin_addr);
879 pj_ansi_strcpy(ip_addr, addr);
880 }
881
Benny Prijono4adcb912006-04-04 13:12:38 +0000882 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000883 app.max_calls = 1;
884 app.thread_count = 1;
885 app.sip_port = 5060;
Benny Prijono6647d822006-05-20 13:01:07 +0000886 app.rtp_start_port = RTP_START_PORT;
Benny Prijonodeb31962006-06-22 18:51:03 +0000887 app.local_addr = pj_str(ip_addr);
Benny Prijono4adcb912006-04-04 13:12:38 +0000888 app.log_level = 5;
889 app.app_log_level = 3;
890 app.log_filename = NULL;
891
892 /* Default codecs: */
893 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000894
895 /* Parse options */
896 pj_optind = 0;
Benny Prijono410fbae2006-05-03 18:16:06 +0000897 while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:q",
Benny Prijono60b980e2006-04-03 22:41:26 +0000898 long_options, &option_index))!=-1)
899 {
900 switch (c) {
901 case 'c':
902 app.max_calls = atoi(pj_optarg);
903 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
904 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
905 return 1;
906 }
907 break;
Benny Prijono410fbae2006-05-03 18:16:06 +0000908 case 'd':
909 app.duration = atoi(pj_optarg);
910 break;
911 case 'q':
912 app.auto_quit = 1;
913 break;
914
Benny Prijono60b980e2006-04-03 22:41:26 +0000915 case 'p':
916 app.sip_port = atoi(pj_optarg);
917 break;
918 case 'r':
919 app.rtp_start_port = atoi(pj_optarg);
920 break;
921 case 'i':
Benny Prijonodeb31962006-06-22 18:51:03 +0000922 app.local_addr = pj_str(pj_optarg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000923 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000924
925 case 'l':
926 app.log_level = atoi(pj_optarg);
927 break;
928 case OPT_APP_LOG_LEVEL:
929 app.app_log_level = atoi(pj_optarg);
930 break;
931 case OPT_LOG_FILE:
932 app.log_filename = pj_optarg;
933 break;
934
935 case OPT_A_PT:
936 app.audio_codec.pt = atoi(pj_optarg);
937 break;
938 case OPT_A_NAME:
939 app.audio_codec.name = pj_optarg;
940 break;
941 case OPT_A_CLOCK:
942 app.audio_codec.clock_rate = atoi(pj_optarg);
943 break;
944 case OPT_A_BITRATE:
945 app.audio_codec.bit_rate = atoi(pj_optarg);
946 break;
947 case OPT_A_PTIME:
948 app.audio_codec.ptime = atoi(pj_optarg);
949 break;
Benny Prijonofcb36722006-05-18 18:34:21 +0000950 case OPT_REPORT_FILE:
951 app.report_filename = pj_optarg;
952 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000953
Benny Prijono60b980e2006-04-03 22:41:26 +0000954 default:
955 puts(USAGE);
956 return 1;
957 }
958 }
959
960 /* Check if URL is specified */
961 if (pj_optind < argc)
962 app.uri_to_call = pj_str(argv[pj_optind]);
963
964 /* Build local URI and contact */
Benny Prijonodeb31962006-06-22 18:51:03 +0000965 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
Benny Prijono60b980e2006-04-03 22:41:26 +0000966 app.local_uri = pj_str(local_uri);
967 app.local_contact = app.local_uri;
968
969
970 return PJ_SUCCESS;
971}
972
973
Benny Prijono4adcb912006-04-04 13:12:38 +0000974/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000975 * MEDIA STUFFS
976 */
977
978/*
979 * Create SDP session for a call.
980 */
981static pj_status_t create_sdp( pj_pool_t *pool,
982 struct call *call,
983 pjmedia_sdp_session **p_sdp)
984{
985 pj_time_val tv;
986 pjmedia_sdp_session *sdp;
987 pjmedia_sdp_media *m;
988 pjmedia_sdp_attr *attr;
Benny Prijonodeb31962006-06-22 18:51:03 +0000989 pjmedia_transport_udp_info tpinfo;
Benny Prijono60b980e2006-04-03 22:41:26 +0000990 struct media_stream *audio = &call->media[0];
991
992 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
993
994
Benny Prijonodeb31962006-06-22 18:51:03 +0000995 /* Get transport info */
996 pjmedia_transport_udp_get_info(audio->transport, &tpinfo);
997
Benny Prijono60b980e2006-04-03 22:41:26 +0000998 /* Create and initialize basic SDP session */
999 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
1000
1001 pj_gettimeofday(&tv);
1002 sdp->origin.user = pj_str("pjsip-siprtp");
1003 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
1004 sdp->origin.net_type = pj_str("IN");
1005 sdp->origin.addr_type = pj_str("IP4");
1006 sdp->origin.addr = *pj_gethostname();
1007 sdp->name = pj_str("pjsip");
1008
1009 /* Since we only support one media stream at present, put the
1010 * SDP connection line in the session level.
1011 */
1012 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
1013 sdp->conn->net_type = pj_str("IN");
1014 sdp->conn->addr_type = pj_str("IP4");
Benny Prijonodeb31962006-06-22 18:51:03 +00001015 sdp->conn->addr = app.local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +00001016
1017
1018 /* SDP time and attributes. */
1019 sdp->time.start = sdp->time.stop = 0;
1020 sdp->attr_count = 0;
1021
1022 /* Create media stream 0: */
1023
1024 sdp->media_count = 1;
1025 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
1026 sdp->media[0] = m;
1027
1028 /* Standard media info: */
1029 m->desc.media = pj_str("audio");
Benny Prijono5186eae2007-12-03 14:38:25 +00001030 m->desc.port = pj_ntohs(tpinfo.skinfo.rtp_addr_name.ipv4.sin_port);
Benny Prijono60b980e2006-04-03 22:41:26 +00001031 m->desc.port_count = 1;
1032 m->desc.transport = pj_str("RTP/AVP");
1033
1034 /* Add format and rtpmap for each codec. */
1035 m->desc.fmt_count = 1;
1036 m->attr_count = 0;
1037
1038 {
1039 pjmedia_sdp_rtpmap rtpmap;
1040 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +00001041 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +00001042
Benny Prijono4adcb912006-04-04 13:12:38 +00001043 sprintf(ptstr, "%d", app.audio_codec.pt);
1044 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1045 rtpmap.pt = m->desc.fmt[0];
1046 rtpmap.clock_rate = app.audio_codec.clock_rate;
1047 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +00001048 rtpmap.param.slen = 0;
1049
1050 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1051 m->attr[m->attr_count++] = attr;
1052 }
1053
1054 /* Add sendrecv attribute. */
1055 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1056 attr->name = pj_str("sendrecv");
1057 m->attr[m->attr_count++] = attr;
1058
1059#if 1
1060 /*
1061 * Add support telephony event
1062 */
Benny Prijono4adcb912006-04-04 13:12:38 +00001063 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +00001064 /* Add rtpmap. */
1065 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1066 attr->name = pj_str("rtpmap");
Benny Prijonod95abb42006-10-20 10:18:44 +00001067 attr->value = pj_str("121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +00001068 m->attr[m->attr_count++] = attr;
1069 /* Add fmtp */
1070 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1071 attr->name = pj_str("fmtp");
Benny Prijonod95abb42006-10-20 10:18:44 +00001072 attr->value = pj_str("121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +00001073 m->attr[m->attr_count++] = attr;
1074#endif
1075
1076 /* Done */
1077 *p_sdp = sdp;
1078
1079 return PJ_SUCCESS;
1080}
1081
1082
Benny Prijono513795f2006-07-18 21:12:24 +00001083#if defined(PJ_WIN32) && PJ_WIN32 != 0
1084#include <windows.h>
1085static void boost_priority(void)
1086{
1087 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
1088 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
1089}
1090
Benny Prijono7bef0f52006-07-31 18:54:06 +00001091#elif defined(PJ_LINUX) && PJ_LINUX != 0
1092#include <pthread.h>
1093static void boost_priority(void)
1094{
1095#define POLICY SCHED_FIFO
Benny Prijono7bef0f52006-07-31 18:54:06 +00001096 struct sched_param tp;
1097 int max_prio;
1098 int policy;
1099 int rc;
1100
1101 if (sched_get_priority_min(POLICY) < sched_get_priority_max(POLICY))
1102 max_prio = sched_get_priority_max(POLICY)-1;
1103 else
1104 max_prio = sched_get_priority_max(POLICY)+1;
1105
1106 /*
1107 * Adjust process scheduling algorithm and priority
1108 */
1109 rc = sched_getparam(0, &tp);
1110 if (rc != 0) {
1111 app_perror( THIS_FILE, "sched_getparam error",
1112 PJ_RETURN_OS_ERROR(rc));
1113 return;
1114 }
1115 tp.__sched_priority = max_prio;
1116
1117 rc = sched_setscheduler(0, POLICY, &tp);
1118 if (rc != 0) {
1119 app_perror( THIS_FILE, "sched_setscheduler error",
1120 PJ_RETURN_OS_ERROR(rc));
1121 }
1122
1123 PJ_LOG(4, (THIS_FILE, "New process policy=%d, priority=%d",
1124 policy, tp.__sched_priority));
1125
1126 /*
1127 * Adjust thread scheduling algorithm and priority
1128 */
1129 rc = pthread_getschedparam(pthread_self(), &policy, &tp);
1130 if (rc != 0) {
1131 app_perror( THIS_FILE, "pthread_getschedparam error",
1132 PJ_RETURN_OS_ERROR(rc));
1133 return;
1134 }
1135
1136 PJ_LOG(4, (THIS_FILE, "Old thread policy=%d, priority=%d",
1137 policy, tp.__sched_priority));
1138
1139 policy = POLICY;
1140 tp.__sched_priority = max_prio;
1141
1142 rc = pthread_setschedparam(pthread_self(), policy, &tp);
1143 if (rc != 0) {
1144 app_perror( THIS_FILE, "pthread_setschedparam error",
1145 PJ_RETURN_OS_ERROR(rc));
1146 return;
1147 }
1148
1149 PJ_LOG(4, (THIS_FILE, "New thread policy=%d, priority=%d",
1150 policy, tp.__sched_priority));
1151}
1152
Benny Prijono513795f2006-07-18 21:12:24 +00001153#else
1154# define boost_priority()
1155#endif
1156
1157
Benny Prijonodeb31962006-06-22 18:51:03 +00001158/*
1159 * This callback is called by media transport on receipt of RTP packet.
1160 */
1161static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size)
1162{
1163 struct media_stream *strm;
1164 pj_status_t status;
1165 const pjmedia_rtp_hdr *hdr;
1166 const void *payload;
1167 unsigned payload_len;
1168
1169 strm = user_data;
1170
1171 /* Discard packet if media is inactive */
1172 if (!strm->active)
1173 return;
1174
1175 /* Check for errors */
1176 if (size < 0) {
1177 app_perror(THIS_FILE, "RTP recv() error", -size);
1178 return;
1179 }
1180
1181 /* Decode RTP packet. */
1182 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1183 pkt, size,
1184 &hdr, &payload, &payload_len);
1185 if (status != PJ_SUCCESS) {
1186 app_perror(THIS_FILE, "RTP decode error", status);
1187 return;
1188 }
1189
1190 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1191
1192 /* Update the RTCP session. */
1193 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1194 pj_ntohl(hdr->ts), payload_len);
1195
1196 /* Update RTP session */
1197 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1198
1199}
1200
1201/*
1202 * This callback is called by media transport on receipt of RTCP packet.
1203 */
1204static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size)
1205{
1206 struct media_stream *strm;
1207
1208 strm = user_data;
1209
1210 /* Discard packet if media is inactive */
1211 if (!strm->active)
1212 return;
1213
1214 /* Check for errors */
1215 if (size < 0) {
1216 app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
1217 return;
1218 }
1219
1220 /* Update RTCP session */
1221 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1222}
1223
1224
Benny Prijono513795f2006-07-18 21:12:24 +00001225/*
1226 * Media thread
1227 *
1228 * This is the thread to send and receive both RTP and RTCP packets.
1229 */
1230static int media_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +00001231{
Benny Prijono513795f2006-07-18 21:12:24 +00001232 enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
1233 struct media_stream *strm = arg;
1234 char packet[1500];
1235 unsigned msec_interval;
1236 pj_timestamp freq, next_rtp, next_rtcp;
Benny Prijono60b980e2006-04-03 22:41:26 +00001237
Benny Prijono6647d822006-05-20 13:01:07 +00001238
Benny Prijono513795f2006-07-18 21:12:24 +00001239 /* Boost thread priority if necessary */
1240 boost_priority();
Benny Prijono6647d822006-05-20 13:01:07 +00001241
Benny Prijono513795f2006-07-18 21:12:24 +00001242 /* Let things settle */
Benny Prijono258ece92006-07-22 12:53:04 +00001243 pj_thread_sleep(100);
Benny Prijono6647d822006-05-20 13:01:07 +00001244
Benny Prijono513795f2006-07-18 21:12:24 +00001245 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1246 pj_get_timestamp_freq(&freq);
1247
1248 pj_get_timestamp(&next_rtp);
1249 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
1250
1251 next_rtcp = next_rtp;
1252 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
1253
1254
1255 while (!strm->thread_quit_flag) {
1256 pj_timestamp now, lesser;
1257 pj_time_val timeout;
1258 pj_bool_t send_rtp, send_rtcp;
1259
1260 send_rtp = send_rtcp = PJ_FALSE;
1261
1262 /* Determine how long to sleep */
1263 if (next_rtp.u64 < next_rtcp.u64) {
1264 lesser = next_rtp;
1265 send_rtp = PJ_TRUE;
1266 } else {
1267 lesser = next_rtcp;
1268 send_rtcp = PJ_TRUE;
1269 }
1270
1271 pj_get_timestamp(&now);
1272 if (lesser.u64 <= now.u64) {
1273 timeout.sec = timeout.msec = 0;
1274 //printf("immediate "); fflush(stdout);
1275 } else {
1276 pj_uint64_t tick_delay;
1277 tick_delay = lesser.u64 - now.u64;
1278 timeout.sec = 0;
1279 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1280 pj_time_val_normalize(&timeout);
1281
1282 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
1283 }
1284
1285 /* Wait for next interval */
1286 //if (timeout.sec!=0 && timeout.msec!=0) {
1287 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
1288 if (strm->thread_quit_flag)
1289 break;
1290 //}
1291
1292 pj_get_timestamp(&now);
1293
1294 if (send_rtp || next_rtp.u64 <= now.u64) {
1295 /*
1296 * Time to send RTP packet.
1297 */
1298 pj_status_t status;
Benny Prijonoe960bb52007-01-21 17:53:39 +00001299 const void *p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001300 const pjmedia_rtp_hdr *hdr;
1301 pj_ssize_t size;
1302 int hdrlen;
1303
1304 /* Format RTP header */
1305 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1306 0, /* marker bit */
1307 strm->bytes_per_frame,
1308 strm->samples_per_frame,
Benny Prijonoe960bb52007-01-21 17:53:39 +00001309 &p_hdr, &hdrlen);
Benny Prijono513795f2006-07-18 21:12:24 +00001310 if (status == PJ_SUCCESS) {
1311
1312 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
Benny Prijonoe960bb52007-01-21 17:53:39 +00001313
1314 hdr = (const pjmedia_rtp_hdr*) p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001315
1316 /* Copy RTP header to packet */
1317 pj_memcpy(packet, hdr, hdrlen);
1318
1319 /* Zero the payload */
1320 pj_bzero(packet+hdrlen, strm->bytes_per_frame);
1321
1322 /* Send RTP packet */
1323 size = hdrlen + strm->bytes_per_frame;
1324 status = pjmedia_transport_send_rtp(strm->transport,
1325 packet, size);
1326 if (status != PJ_SUCCESS)
1327 app_perror(THIS_FILE, "Error sending RTP packet", status);
1328
1329 } else {
1330 pj_assert(!"RTP encode() error");
1331 }
1332
1333 /* Update RTCP SR */
1334 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1335
1336 /* Schedule next send */
1337 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
1338 }
1339
1340
1341 if (send_rtcp || next_rtcp.u64 <= now.u64) {
1342 /*
1343 * Time to send RTCP packet.
1344 */
Benny Prijonofe81cfa2007-09-20 11:30:59 +00001345 void *rtcp_pkt;
Benny Prijono513795f2006-07-18 21:12:24 +00001346 int rtcp_len;
1347 pj_ssize_t size;
1348 pj_status_t status;
1349
1350 /* Build RTCP packet */
1351 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1352
Benny Prijono4d3aa922006-06-22 22:31:48 +00001353
Benny Prijono513795f2006-07-18 21:12:24 +00001354 /* Send packet */
1355 size = rtcp_len;
1356 status = pjmedia_transport_send_rtcp(strm->transport,
1357 rtcp_pkt, size);
1358 if (status != PJ_SUCCESS) {
1359 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1360 }
1361
1362 /* Schedule next send */
1363 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
1364 1000);
1365 }
1366 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001367
Benny Prijono513795f2006-07-18 21:12:24 +00001368 return 0;
Benny Prijono60b980e2006-04-03 22:41:26 +00001369}
1370
Benny Prijono513795f2006-07-18 21:12:24 +00001371
Benny Prijono60b980e2006-04-03 22:41:26 +00001372/* Callback to be called when SDP negotiation is done in the call: */
1373static void call_on_media_update( pjsip_inv_session *inv,
1374 pj_status_t status)
1375{
1376 struct call *call;
1377 pj_pool_t *pool;
1378 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001379 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001380 struct codec *codec_desc = NULL;
1381 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001382
1383 call = inv->mod_data[mod_siprtp.id];
1384 pool = inv->dlg->pool;
1385 audio = &call->media[0];
1386
1387 /* If this is a mid-call media update, then destroy existing media */
Benny Prijono513795f2006-07-18 21:12:24 +00001388 if (audio->thread != NULL)
Benny Prijono60b980e2006-04-03 22:41:26 +00001389 destroy_call_media(call->index);
1390
1391
1392 /* Do nothing if media negotiation has failed */
1393 if (status != PJ_SUCCESS) {
1394 app_perror(THIS_FILE, "SDP negotiation failed", status);
1395 return;
1396 }
1397
1398
1399 /* Capture stream definition from the SDP */
1400 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1401 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1402
1403 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
Benny Prijonob04c9e02006-05-17 17:17:39 +00001404 local_sdp, remote_sdp, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001405 if (status != PJ_SUCCESS) {
1406 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1407 return;
1408 }
1409
Benny Prijono4adcb912006-04-04 13:12:38 +00001410 /* Get the remainder of codec information from codec descriptor */
1411 if (audio->si.fmt.pt == app.audio_codec.pt)
1412 codec_desc = &app.audio_codec;
1413 else {
1414 /* Find the codec description in codec array */
1415 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1416 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1417 codec_desc = &audio_codecs[i];
1418 break;
1419 }
1420 }
1421
1422 if (codec_desc == NULL) {
1423 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1424 return;
1425 }
1426 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001427
Benny Prijono15953012006-04-27 22:37:08 +00001428 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001429 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1430 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001431
1432
1433 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001434 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001435 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001436 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001437 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001438
Benny Prijono4adcb912006-04-04 13:12:38 +00001439
Benny Prijonodeb31962006-06-22 18:51:03 +00001440 /* Attach media to transport */
1441 status = pjmedia_transport_attach(audio->transport, audio,
1442 &audio->si.rem_addr,
Benny Prijono513795f2006-07-18 21:12:24 +00001443 &audio->si.rem_rtcp,
Benny Prijonodeb31962006-06-22 18:51:03 +00001444 sizeof(pj_sockaddr_in),
1445 &on_rx_rtp,
1446 &on_rx_rtcp);
1447 if (status != PJ_SUCCESS) {
1448 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1449 return;
1450 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001451
Benny Prijono513795f2006-07-18 21:12:24 +00001452 /* Start media thread. */
1453 audio->thread_quit_flag = 0;
Benny Prijono6869dc92007-06-03 00:37:30 +00001454#if PJ_HAS_THREADS
Benny Prijono513795f2006-07-18 21:12:24 +00001455 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1456 0, 0, &audio->thread);
1457 if (status != PJ_SUCCESS) {
1458 app_perror(THIS_FILE, "Error creating media thread", status);
1459 return;
1460 }
Benny Prijono6869dc92007-06-03 00:37:30 +00001461#endif
Benny Prijono513795f2006-07-18 21:12:24 +00001462
Benny Prijonodeb31962006-06-22 18:51:03 +00001463 /* Set the media as active */
1464 audio->active = PJ_TRUE;
Benny Prijono60b980e2006-04-03 22:41:26 +00001465}
1466
1467
1468
1469/* Destroy call's media */
1470static void destroy_call_media(unsigned call_index)
1471{
1472 struct media_stream *audio = &app.call[call_index].media[0];
1473
Benny Prijono5a9e2042006-11-14 13:35:20 +00001474 if (audio) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001475 audio->active = PJ_FALSE;
1476
Benny Prijono5a9e2042006-11-14 13:35:20 +00001477 if (audio->thread) {
1478 audio->thread_quit_flag = 1;
1479 pj_thread_join(audio->thread);
1480 pj_thread_destroy(audio->thread);
1481 audio->thread = NULL;
1482 audio->thread_quit_flag = 0;
1483 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001484
Benny Prijono5b1e14d2006-11-25 08:46:48 +00001485 pjmedia_transport_detach(audio->transport, audio);
Benny Prijono60b980e2006-04-03 22:41:26 +00001486 }
1487}
1488
Benny Prijono513795f2006-07-18 21:12:24 +00001489
Benny Prijono4adcb912006-04-04 13:12:38 +00001490/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001491 * USER INTERFACE STUFFS
1492 */
Benny Prijono258ece92006-07-22 12:53:04 +00001493
1494static void call_get_duration(int call_index, pj_time_val *dur)
1495{
1496 struct call *call = &app.call[call_index];
1497 pjsip_inv_session *inv;
1498
1499 dur->sec = dur->msec = 0;
1500
1501 if (!call)
1502 return;
1503
1504 inv = call->inv;
1505 if (!inv)
1506 return;
1507
1508 if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
1509
1510 pj_gettimeofday(dur);
1511 PJ_TIME_VAL_SUB((*dur), call->connect_time);
1512 }
1513}
1514
1515
1516static const char *good_number(char *buf, pj_int32_t val)
1517{
1518 if (val < 1000) {
1519 pj_ansi_sprintf(buf, "%d", val);
1520 } else if (val < 1000000) {
1521 pj_ansi_sprintf(buf, "%d.%02dK",
1522 val / 1000,
1523 (val % 1000) / 100);
1524 } else {
1525 pj_ansi_sprintf(buf, "%d.%02dM",
1526 val / 1000000,
1527 (val % 1000000) / 10000);
1528 }
1529
1530 return buf;
1531}
1532
1533
1534
1535static void print_avg_stat(void)
1536{
1537#define MIN_(var,val) if ((int)val < (int)var) var = val
1538#define MAX_(var,val) if ((int)val > (int)var) var = val
1539#define AVG_(var,val) var = ( ((var * count) + val) / (count+1) )
1540#define BIGVAL 0x7FFFFFFFL
1541 struct stat_entry
1542 {
1543 int min, avg, max;
1544 };
1545
1546 struct stat_entry call_dur, call_pdd;
1547 pjmedia_rtcp_stat min_stat, avg_stat, max_stat;
1548
1549 char srx_min[16], srx_avg[16], srx_max[16];
1550 char brx_min[16], brx_avg[16], brx_max[16];
1551 char stx_min[16], stx_avg[16], stx_max[16];
1552 char btx_min[16], btx_avg[16], btx_max[16];
1553
1554
1555 unsigned i, count;
1556
1557 pj_bzero(&call_dur, sizeof(call_dur));
1558 call_dur.min = BIGVAL;
1559
1560 pj_bzero(&call_pdd, sizeof(call_pdd));
1561 call_pdd.min = BIGVAL;
1562
1563 pj_bzero(&min_stat, sizeof(min_stat));
1564 min_stat.rx.pkt = min_stat.tx.pkt = BIGVAL;
1565 min_stat.rx.bytes = min_stat.tx.bytes = BIGVAL;
1566 min_stat.rx.loss = min_stat.tx.loss = BIGVAL;
1567 min_stat.rx.dup = min_stat.tx.dup = BIGVAL;
1568 min_stat.rx.reorder = min_stat.tx.reorder = BIGVAL;
1569 min_stat.rx.jitter.min = min_stat.tx.jitter.min = BIGVAL;
1570 min_stat.rtt.min = BIGVAL;
1571
1572 pj_bzero(&avg_stat, sizeof(avg_stat));
1573 pj_bzero(&max_stat, sizeof(max_stat));
1574
1575
1576 for (i=0, count=0; i<app.max_calls; ++i) {
1577
1578 struct call *call = &app.call[i];
1579 struct media_stream *audio = &call->media[0];
1580 pj_time_val dur;
1581 unsigned msec_dur;
1582
1583 if (call->inv == NULL ||
1584 call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
1585 call->connect_time.sec == 0)
1586 {
1587 continue;
1588 }
1589
1590 /* Duration */
1591 call_get_duration(i, &dur);
1592 msec_dur = PJ_TIME_VAL_MSEC(dur);
1593
1594 MIN_(call_dur.min, msec_dur);
1595 MAX_(call_dur.max, msec_dur);
1596 AVG_(call_dur.avg, msec_dur);
1597
1598 /* Connect delay */
1599 if (call->connect_time.sec) {
1600 pj_time_val t = call->connect_time;
1601 PJ_TIME_VAL_SUB(t, call->start_time);
1602 msec_dur = PJ_TIME_VAL_MSEC(t);
1603 } else {
1604 msec_dur = 10;
1605 }
1606
1607 MIN_(call_pdd.min, msec_dur);
1608 MAX_(call_pdd.max, msec_dur);
1609 AVG_(call_pdd.avg, msec_dur);
1610
1611 /* RX Statistisc: */
1612
1613 /* Packets */
1614 MIN_(min_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1615 MAX_(max_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1616 AVG_(avg_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1617
1618 /* Bytes */
1619 MIN_(min_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1620 MAX_(max_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1621 AVG_(avg_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1622
1623
1624 /* Packet loss */
1625 MIN_(min_stat.rx.loss, audio->rtcp.stat.rx.loss);
1626 MAX_(max_stat.rx.loss, audio->rtcp.stat.rx.loss);
1627 AVG_(avg_stat.rx.loss, audio->rtcp.stat.rx.loss);
1628
1629 /* Packet dup */
1630 MIN_(min_stat.rx.dup, audio->rtcp.stat.rx.dup);
1631 MAX_(max_stat.rx.dup, audio->rtcp.stat.rx.dup);
1632 AVG_(avg_stat.rx.dup, audio->rtcp.stat.rx.dup);
1633
1634 /* Packet reorder */
1635 MIN_(min_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1636 MAX_(max_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1637 AVG_(avg_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1638
1639 /* Jitter */
1640 MIN_(min_stat.rx.jitter.min, audio->rtcp.stat.rx.jitter.min);
1641 MAX_(max_stat.rx.jitter.max, audio->rtcp.stat.rx.jitter.max);
1642 AVG_(avg_stat.rx.jitter.avg, audio->rtcp.stat.rx.jitter.avg);
1643
1644
1645 /* TX Statistisc: */
1646
1647 /* Packets */
1648 MIN_(min_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1649 MAX_(max_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1650 AVG_(avg_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1651
1652 /* Bytes */
1653 MIN_(min_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1654 MAX_(max_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1655 AVG_(avg_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1656
1657 /* Packet loss */
1658 MIN_(min_stat.tx.loss, audio->rtcp.stat.tx.loss);
1659 MAX_(max_stat.tx.loss, audio->rtcp.stat.tx.loss);
1660 AVG_(avg_stat.tx.loss, audio->rtcp.stat.tx.loss);
1661
1662 /* Packet dup */
1663 MIN_(min_stat.tx.dup, audio->rtcp.stat.tx.dup);
1664 MAX_(max_stat.tx.dup, audio->rtcp.stat.tx.dup);
1665 AVG_(avg_stat.tx.dup, audio->rtcp.stat.tx.dup);
1666
1667 /* Packet reorder */
1668 MIN_(min_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1669 MAX_(max_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1670 AVG_(avg_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1671
1672 /* Jitter */
1673 MIN_(min_stat.tx.jitter.min, audio->rtcp.stat.tx.jitter.min);
1674 MAX_(max_stat.tx.jitter.max, audio->rtcp.stat.tx.jitter.max);
1675 AVG_(avg_stat.tx.jitter.avg, audio->rtcp.stat.tx.jitter.avg);
1676
1677
1678 /* RTT */
1679 MIN_(min_stat.rtt.min, audio->rtcp.stat.rtt.min);
1680 MAX_(max_stat.rtt.max, audio->rtcp.stat.rtt.max);
1681 AVG_(avg_stat.rtt.avg, audio->rtcp.stat.rtt.avg);
1682
1683 ++count;
1684 }
1685
1686 if (count == 0) {
1687 puts("No active calls");
1688 return;
1689 }
1690
1691 printf("Total %d call(s) active.\n"
1692 " Average Statistics\n"
1693 " min avg max \n"
1694 " -----------------------\n"
1695 " call duration: %7d %7d %7d %s\n"
1696 " connect delay: %7d %7d %7d %s\n"
1697 " RX stat:\n"
1698 " packets: %7s %7s %7s %s\n"
1699 " payload: %7s %7s %7s %s\n"
1700 " loss: %7d %7d %7d %s\n"
1701 " percent loss: %7.3f %7.3f %7.3f %s\n"
1702 " dup: %7d %7d %7d %s\n"
1703 " reorder: %7d %7d %7d %s\n"
1704 " jitter: %7.3f %7.3f %7.3f %s\n"
1705 " TX stat:\n"
1706 " packets: %7s %7s %7s %s\n"
1707 " payload: %7s %7s %7s %s\n"
1708 " loss: %7d %7d %7d %s\n"
1709 " percent loss: %7.3f %7.3f %7.3f %s\n"
1710 " dup: %7d %7d %7d %s\n"
1711 " reorder: %7d %7d %7d %s\n"
1712 " jitter: %7.3f %7.3f %7.3f %s\n"
1713 " RTT : %7.3f %7.3f %7.3f %s\n"
1714 ,
1715 count,
1716 call_dur.min/1000, call_dur.avg/1000, call_dur.max/1000,
1717 "seconds",
1718
1719 call_pdd.min, call_pdd.avg, call_pdd.max,
1720 "ms",
1721
1722 /* rx */
1723
1724 good_number(srx_min, min_stat.rx.pkt),
1725 good_number(srx_avg, avg_stat.rx.pkt),
1726 good_number(srx_max, max_stat.rx.pkt),
1727 "packets",
1728
1729 good_number(brx_min, min_stat.rx.bytes),
1730 good_number(brx_avg, avg_stat.rx.bytes),
1731 good_number(brx_max, max_stat.rx.bytes),
1732 "bytes",
1733
1734 min_stat.rx.loss, avg_stat.rx.loss, max_stat.rx.loss,
1735 "packets",
1736
1737 min_stat.rx.loss*100.0/(min_stat.rx.pkt+min_stat.rx.loss),
1738 avg_stat.rx.loss*100.0/(avg_stat.rx.pkt+avg_stat.rx.loss),
1739 max_stat.rx.loss*100.0/(max_stat.rx.pkt+max_stat.rx.loss),
1740 "%",
1741
1742
1743 min_stat.rx.dup, avg_stat.rx.dup, max_stat.rx.dup,
1744 "packets",
1745
1746 min_stat.rx.reorder, avg_stat.rx.reorder, max_stat.rx.reorder,
1747 "packets",
1748
1749 min_stat.rx.jitter.min/1000.0,
1750 avg_stat.rx.jitter.avg/1000.0,
1751 max_stat.rx.jitter.max/1000.0,
1752 "ms",
1753
1754 /* tx */
1755
1756 good_number(stx_min, min_stat.tx.pkt),
1757 good_number(stx_avg, avg_stat.tx.pkt),
1758 good_number(stx_max, max_stat.tx.pkt),
1759 "packets",
1760
1761 good_number(btx_min, min_stat.tx.bytes),
1762 good_number(btx_avg, avg_stat.tx.bytes),
1763 good_number(btx_max, max_stat.tx.bytes),
1764 "bytes",
1765
1766 min_stat.tx.loss, avg_stat.tx.loss, max_stat.tx.loss,
1767 "packets",
1768
Benny Prijono05784a52006-07-25 11:54:15 +00001769 min_stat.tx.loss*100.0/(min_stat.tx.pkt+min_stat.tx.loss),
1770 avg_stat.tx.loss*100.0/(avg_stat.tx.pkt+avg_stat.tx.loss),
1771 max_stat.tx.loss*100.0/(max_stat.tx.pkt+max_stat.tx.loss),
Benny Prijono258ece92006-07-22 12:53:04 +00001772 "%",
1773
1774 min_stat.tx.dup, avg_stat.tx.dup, max_stat.tx.dup,
1775 "packets",
1776
1777 min_stat.tx.reorder, avg_stat.tx.reorder, max_stat.tx.reorder,
1778 "packets",
1779
1780 min_stat.tx.jitter.min/1000.0,
1781 avg_stat.tx.jitter.avg/1000.0,
1782 max_stat.tx.jitter.max/1000.0,
1783 "ms",
1784
1785 /* rtt */
1786 min_stat.rtt.min/1000.0,
1787 avg_stat.rtt.avg/1000.0,
1788 max_stat.rtt.max/1000.0,
1789 "ms"
1790 );
1791
1792}
1793
1794
Benny Prijono16a6b0e2006-05-12 10:20:03 +00001795#include "siprtp_report.c"
Benny Prijono60b980e2006-04-03 22:41:26 +00001796
1797
1798static void list_calls()
1799{
1800 unsigned i;
1801 puts("List all calls:");
1802 for (i=0; i<app.max_calls; ++i) {
1803 if (!app.call[i].inv)
1804 continue;
1805 print_call(i);
1806 }
1807}
1808
1809static void hangup_call(unsigned index)
1810{
1811 pjsip_tx_data *tdata;
1812 pj_status_t status;
1813
1814 if (app.call[index].inv == NULL)
1815 return;
1816
1817 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1818 if (status==PJ_SUCCESS && tdata!=NULL)
1819 pjsip_inv_send_msg(app.call[index].inv, tdata);
1820}
1821
1822static void hangup_all_calls()
1823{
1824 unsigned i;
1825 for (i=0; i<app.max_calls; ++i) {
1826 if (!app.call[i].inv)
1827 continue;
1828 hangup_call(i);
1829 }
Benny Prijonodeb31962006-06-22 18:51:03 +00001830
1831 /* Wait until all calls are terminated */
1832 for (i=0; i<app.max_calls; ++i) {
1833 while (app.call[i].inv)
1834 pj_thread_sleep(10);
1835 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001836}
1837
1838static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1839{
1840 char *p;
1841
1842 printf("%s (empty to cancel): ", title); fflush(stdout);
1843 fgets(buf, len, stdin);
1844
1845 /* Remove trailing newlines. */
1846 for (p=buf; ; ++p) {
1847 if (*p=='\r' || *p=='\n') *p='\0';
1848 else if (!*p) break;
1849 }
1850
1851 if (!*buf)
1852 return PJ_FALSE;
1853
1854 return PJ_TRUE;
1855}
1856
1857
1858static const char *MENU =
1859"\n"
1860"Enter menu character:\n"
Benny Prijono258ece92006-07-22 12:53:04 +00001861" s Summary\n"
Benny Prijono60b980e2006-04-03 22:41:26 +00001862" l List all calls\n"
1863" h Hangup a call\n"
1864" H Hangup all calls\n"
1865" q Quit\n"
1866"\n";
1867
1868
1869/* Main screen menu */
1870static void console_main()
1871{
1872 char input1[10];
1873 unsigned i;
1874
Benny Prijono4adcb912006-04-04 13:12:38 +00001875 printf("%s", MENU);
1876
Benny Prijono60b980e2006-04-03 22:41:26 +00001877 for (;;) {
1878 printf(">>> "); fflush(stdout);
1879 fgets(input1, sizeof(input1), stdin);
1880
1881 switch (input1[0]) {
Benny Prijono258ece92006-07-22 12:53:04 +00001882
1883 case 's':
1884 print_avg_stat();
1885 break;
1886
Benny Prijono60b980e2006-04-03 22:41:26 +00001887 case 'l':
1888 list_calls();
1889 break;
1890
1891 case 'h':
1892 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1893 break;
1894
1895 i = atoi(input1);
1896 hangup_call(i);
1897 break;
1898
1899 case 'H':
1900 hangup_all_calls();
1901 break;
1902
1903 case 'q':
1904 goto on_exit;
1905
1906 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001907 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001908 printf("%s", MENU);
1909 break;
1910 }
1911
1912 fflush(stdout);
1913 }
1914
1915on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001916 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001917}
1918
1919
Benny Prijono4adcb912006-04-04 13:12:38 +00001920/*****************************************************************************
1921 * Below is a simple module to log all incoming and outgoing SIP messages
1922 */
1923
1924
Benny Prijono60b980e2006-04-03 22:41:26 +00001925/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001926static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001927{
1928 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1929 "%s\n"
1930 "--end msg--",
1931 rdata->msg_info.len,
1932 pjsip_rx_data_get_info(rdata),
1933 rdata->pkt_info.src_name,
1934 rdata->pkt_info.src_port,
1935 rdata->msg_info.msg_buf));
1936
1937 /* Always return false, otherwise messages will not get processed! */
1938 return PJ_FALSE;
1939}
1940
1941/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001942static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001943{
1944
1945 /* Important note:
1946 * tp_info field is only valid after outgoing messages has passed
1947 * transport layer. So don't try to access tp_info when the module
1948 * has lower priority than transport layer.
1949 */
1950
1951 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1952 "%s\n"
1953 "--end msg--",
1954 (tdata->buf.cur - tdata->buf.start),
1955 pjsip_tx_data_get_info(tdata),
1956 tdata->tp_info.dst_name,
1957 tdata->tp_info.dst_port,
1958 tdata->buf.start));
1959
1960 /* Always return success, otherwise message will not get sent! */
1961 return PJ_SUCCESS;
1962}
1963
1964/* The module instance. */
1965static pjsip_module msg_logger =
1966{
1967 NULL, NULL, /* prev, next. */
1968 { "mod-siprtp-log", 14 }, /* Name. */
1969 -1, /* Id */
1970 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1971 NULL, /* load() */
1972 NULL, /* start() */
1973 NULL, /* stop() */
1974 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001975 &logger_on_rx_msg, /* on_rx_request() */
1976 &logger_on_rx_msg, /* on_rx_response() */
1977 &logger_on_tx_msg, /* on_tx_request. */
1978 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00001979 NULL, /* on_tsx_state() */
1980
1981};
1982
1983
1984
Benny Prijono4adcb912006-04-04 13:12:38 +00001985/*****************************************************************************
1986 * Console application custom logging:
1987 */
1988
1989
1990static FILE *log_file;
1991
1992
1993static void app_log_writer(int level, const char *buffer, int len)
1994{
1995 /* Write to both stdout and file. */
1996
1997 if (level <= app.app_log_level)
1998 pj_log_write(level, buffer, len);
1999
2000 if (log_file) {
2001 fwrite(buffer, len, 1, log_file);
2002 fflush(log_file);
2003 }
2004}
2005
2006
2007pj_status_t app_logging_init(void)
2008{
2009 /* Redirect log function to ours */
2010
2011 pj_log_set_log_func( &app_log_writer );
2012
2013 /* If output log file is desired, create the file: */
2014
2015 if (app.log_filename) {
2016 log_file = fopen(app.log_filename, "wt");
2017 if (log_file == NULL) {
2018 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
2019 app.log_filename));
2020 return -1;
2021 }
2022 }
2023
2024 return PJ_SUCCESS;
2025}
2026
2027
2028void app_logging_shutdown(void)
2029{
2030 /* Close logging file, if any: */
2031
2032 if (log_file) {
2033 fclose(log_file);
2034 log_file = NULL;
2035 }
2036}
2037
Benny Prijono60b980e2006-04-03 22:41:26 +00002038
2039/*
2040 * main()
2041 */
2042int main(int argc, char *argv[])
2043{
Benny Prijono4adcb912006-04-04 13:12:38 +00002044 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00002045 pj_status_t status;
2046
Benny Prijono4adcb912006-04-04 13:12:38 +00002047 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00002048 status = pj_init();
2049 if (status != PJ_SUCCESS)
2050 return 1;
2051
Benny Prijono4adcb912006-04-04 13:12:38 +00002052 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00002053 status = init_options(argc, argv);
2054 if (status != PJ_SUCCESS)
2055 return 1;
2056
Benny Prijono410fbae2006-05-03 18:16:06 +00002057 /* Verify options: */
2058
2059 /* Auto-quit can not be specified for UAS */
2060 if (app.auto_quit && app.uri_to_call.slen == 0) {
2061 printf("Error: --auto-quit option only valid for outgoing "
2062 "mode (UAC) only\n");
2063 return 1;
2064 }
2065
Benny Prijono4adcb912006-04-04 13:12:38 +00002066 /* Init logging */
2067 status = app_logging_init();
2068 if (status != PJ_SUCCESS)
2069 return 1;
2070
2071 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00002072 status = init_sip();
2073 if (status != PJ_SUCCESS) {
2074 app_perror(THIS_FILE, "Initialization has failed", status);
2075 destroy_sip();
2076 return 1;
2077 }
2078
Benny Prijono4adcb912006-04-04 13:12:38 +00002079 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00002080 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
2081
Benny Prijono4adcb912006-04-04 13:12:38 +00002082 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00002083 status = init_media();
2084 if (status != PJ_SUCCESS) {
2085 app_perror(THIS_FILE, "Media initialization failed", status);
2086 destroy_sip();
2087 return 1;
2088 }
2089
Benny Prijono9a0eab52006-04-04 19:43:24 +00002090 /* Start worker threads */
Benny Prijono6869dc92007-06-03 00:37:30 +00002091#if PJ_HAS_THREADS
Benny Prijono9a0eab52006-04-04 19:43:24 +00002092 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +00002093 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
2094 0, 0, &app.sip_thread[i]);
Benny Prijono9a0eab52006-04-04 19:43:24 +00002095 }
Benny Prijono6869dc92007-06-03 00:37:30 +00002096#endif
Benny Prijono9a0eab52006-04-04 19:43:24 +00002097
Benny Prijono4adcb912006-04-04 13:12:38 +00002098 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00002099 if (app.uri_to_call.slen) {
2100 unsigned i;
2101
Benny Prijono4adcb912006-04-04 13:12:38 +00002102 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
2103 app.uri_to_call.ptr));
2104
Benny Prijono60b980e2006-04-03 22:41:26 +00002105 for (i=0; i<app.max_calls; ++i) {
2106 status = make_call(&app.uri_to_call);
2107 if (status != PJ_SUCCESS) {
2108 app_perror(THIS_FILE, "Error making call", status);
2109 break;
2110 }
2111 }
Benny Prijono4adcb912006-04-04 13:12:38 +00002112
Benny Prijono410fbae2006-05-03 18:16:06 +00002113 if (app.auto_quit) {
2114 /* Wait for calls to complete */
2115 while (app.uac_calls < app.max_calls)
2116 pj_thread_sleep(100);
2117 pj_thread_sleep(200);
2118 } else {
Benny Prijono6869dc92007-06-03 00:37:30 +00002119#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002120 /* Start user interface loop */
2121 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002122#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002123 }
2124
Benny Prijono4adcb912006-04-04 13:12:38 +00002125 } else {
2126
2127 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
2128 app.max_calls));
Benny Prijono4adcb912006-04-04 13:12:38 +00002129
Benny Prijono6869dc92007-06-03 00:37:30 +00002130#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002131 /* Start user interface loop */
2132 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002133#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002134 }
Benny Prijono60b980e2006-04-03 22:41:26 +00002135
Benny Prijono6869dc92007-06-03 00:37:30 +00002136#if !PJ_HAS_THREADS
2137 PJ_LOG(3,(THIS_FILE, "Press Ctrl-C to quit"));
2138 for (;;) {
2139 pj_time_val t = {0, 10};
2140 pjsip_endpt_handle_events(app.sip_endpt, &t);
2141 }
2142#endif
Benny Prijono4adcb912006-04-04 13:12:38 +00002143
2144 /* Shutting down... */
Benny Prijono4d3aa922006-06-22 22:31:48 +00002145 destroy_sip();
Benny Prijono513795f2006-07-18 21:12:24 +00002146 destroy_media();
Benny Prijono6647d822006-05-20 13:01:07 +00002147
2148 if (app.pool) {
2149 pj_pool_release(app.pool);
2150 app.pool = NULL;
2151 pj_caching_pool_destroy(&app.cp);
2152 }
2153
Benny Prijono4adcb912006-04-04 13:12:38 +00002154 app_logging_shutdown();
2155
Benny Prijono5b1e14d2006-11-25 08:46:48 +00002156 /* Shutdown PJLIB */
2157 pj_shutdown();
Benny Prijono60b980e2006-04-03 22:41:26 +00002158
2159 return 0;
2160}
2161