blob: b3e3d7d4faef83b60bb986a8ed3b59dc1841da70 [file] [log] [blame]
Benny Prijono60b980e2006-04-03 22:41:26 +00001/* $Id$ */
2/*
Benny Prijono844653c2008-12-23 17:27:53 +00003 * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
Benny Prijono32177c02008-06-20 22:44:47 +00004 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
Benny Prijono60b980e2006-04-03 22:41:26 +00005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21
Benny Prijonobf13fee2006-04-20 11:13:32 +000022
23
Benny Prijono1ec70b32006-06-20 15:39:07 +000024
Benny Prijonobf13fee2006-04-20 11:13:32 +000025/* Usage */
26static const char *USAGE =
27" PURPOSE: \n"
28" This program establishes SIP INVITE session and media, and calculate \n"
29" the media quality (packet lost, jitter, rtt, etc.). Unlike normal \n"
30" pjmedia applications, this program bypasses all pjmedia stream \n"
31" framework and transmit encoded RTP packets manually using own thread. \n"
32"\n"
33" USAGE:\n"
34" siprtp [options] => to start in server mode\n"
35" siprtp [options] URL => to start in client mode\n"
36"\n"
37" Program options:\n"
38" --count=N, -c Set number of calls to create (default:1) \n"
Benny Prijono1b543242008-01-08 22:50:06 +000039" --gap=N -g Set call gapping to N msec (default:0)\n"
Benny Prijono410fbae2006-05-03 18:16:06 +000040" --duration=SEC, -d Set maximum call duration (default:unlimited) \n"
41" --auto-quit, -q Quit when calls have been completed (default:no)\n"
Benny Prijono1b543242008-01-08 22:50:06 +000042" --call-report -R Display report on call termination (default:yes)\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000043"\n"
44" Address and ports options:\n"
45" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
46" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
47" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
48" try to determine local IP address from hostname)\n"
49"\n"
50" Logging Options:\n"
51" --log-level=N, -l Set log verbosity level (default=5)\n"
52" --app-log-level=N Set app screen log verbosity (default=3)\n"
53" --log-file=FILE Write log to file FILE\n"
Benny Prijonofcb36722006-05-18 18:34:21 +000054" --report-file=FILE Write report to file FILE\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000055"\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000056/* Don't support this anymore, because codec is properly examined in
57 pjmedia_session_info_from_sdp() function.
58
Benny Prijonobf13fee2006-04-20 11:13:32 +000059" Codec Options:\n"
60" --a-pt=PT Set audio payload type to PT (default=0)\n"
61" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
62" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
63" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
64" --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000065*/
Benny Prijonobf13fee2006-04-20 11:13:32 +000066;
67
68
Benny Prijono60b980e2006-04-03 22:41:26 +000069/* Include all headers. */
70#include <pjsip.h>
71#include <pjmedia.h>
72#include <pjmedia-codec.h>
73#include <pjsip_ua.h>
74#include <pjsip_simple.h>
75#include <pjlib-util.h>
76#include <pjlib.h>
77
78#include <stdlib.h>
79
Benny Prijono6869dc92007-06-03 00:37:30 +000080/* Uncomment these to disable threads.
81 * NOTE:
82 * when threading is disabled, siprtp won't transmit any
83 * RTP packets.
84 */
85/*
86#undef PJ_HAS_THREADS
87#define PJ_HAS_THREADS 0
88*/
89
Benny Prijono9a0eab52006-04-04 19:43:24 +000090
91#if PJ_HAS_HIGH_RES_TIMER==0
92# error "High resolution timer is needed for this sample"
93#endif
94
Benny Prijono60b980e2006-04-03 22:41:26 +000095#define THIS_FILE "siprtp.c"
96#define MAX_CALLS 1024
Benny Prijono6647d822006-05-20 13:01:07 +000097#define RTP_START_PORT 4000
Benny Prijono60b980e2006-04-03 22:41:26 +000098
99
Benny Prijono4adcb912006-04-04 13:12:38 +0000100/* Codec descriptor: */
101struct codec
102{
103 unsigned pt;
104 char* name;
105 unsigned clock_rate;
106 unsigned bit_rate;
107 unsigned ptime;
108 char* description;
109};
110
111
Benny Prijonodeb31962006-06-22 18:51:03 +0000112/* A bidirectional media stream created when the call is active. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000113struct media_stream
114{
115 /* Static: */
Benny Prijonodeb31962006-06-22 18:51:03 +0000116 unsigned call_index; /* Call owner. */
117 unsigned media_index; /* Media index in call. */
118 pjmedia_transport *transport; /* To send/recv RTP/RTCP */
119
120 /* Active? */
121 pj_bool_t active; /* Non-zero if is in call. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000122
123 /* Current stream info: */
124 pjmedia_stream_info si; /* Current stream info. */
125
126 /* More info: */
127 unsigned clock_rate; /* clock rate */
128 unsigned samples_per_frame; /* samples per frame */
129 unsigned bytes_per_frame; /* frame size. */
130
Benny Prijono60b980e2006-04-03 22:41:26 +0000131 /* RTP session: */
132 pjmedia_rtp_session out_sess; /* outgoing RTP session */
133 pjmedia_rtp_session in_sess; /* incoming RTP session */
134
135 /* RTCP stats: */
136 pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
Benny Prijono4adcb912006-04-04 13:12:38 +0000137
Benny Prijono513795f2006-07-18 21:12:24 +0000138 /* Thread: */
139 pj_bool_t thread_quit_flag; /* Stop media thread. */
140 pj_thread_t *thread; /* Media thread. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000141};
142
143
Benny Prijonodeb31962006-06-22 18:51:03 +0000144/* This is a call structure that is created when the application starts
145 * and only destroyed when the application quits.
146 */
Benny Prijono60b980e2006-04-03 22:41:26 +0000147struct call
148{
149 unsigned index;
150 pjsip_inv_session *inv;
151 unsigned media_count;
Benny Prijonodeb31962006-06-22 18:51:03 +0000152 struct media_stream media[1];
Benny Prijono4adcb912006-04-04 13:12:38 +0000153 pj_time_val start_time;
154 pj_time_val response_time;
155 pj_time_val connect_time;
Benny Prijono410fbae2006-05-03 18:16:06 +0000156
157 pj_timer_entry d_timer; /**< Disconnect timer. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000158};
159
160
Benny Prijonodeb31962006-06-22 18:51:03 +0000161/* Application's global variables */
Benny Prijono60b980e2006-04-03 22:41:26 +0000162static struct app
163{
164 unsigned max_calls;
Benny Prijono1b543242008-01-08 22:50:06 +0000165 unsigned call_gap;
166 pj_bool_t call_report;
Benny Prijono410fbae2006-05-03 18:16:06 +0000167 unsigned uac_calls;
168 unsigned duration;
169 pj_bool_t auto_quit;
Benny Prijono60b980e2006-04-03 22:41:26 +0000170 unsigned thread_count;
171 int sip_port;
172 int rtp_start_port;
Benny Prijonodeb31962006-06-22 18:51:03 +0000173 pj_str_t local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +0000174 pj_str_t local_uri;
175 pj_str_t local_contact;
Benny Prijono4adcb912006-04-04 13:12:38 +0000176
177 int app_log_level;
178 int log_level;
179 char *log_filename;
Benny Prijonofcb36722006-05-18 18:34:21 +0000180 char *report_filename;
Benny Prijono4adcb912006-04-04 13:12:38 +0000181
182 struct codec audio_codec;
Benny Prijono60b980e2006-04-03 22:41:26 +0000183
184 pj_str_t uri_to_call;
185
186 pj_caching_pool cp;
187 pj_pool_t *pool;
188
189 pjsip_endpoint *sip_endpt;
190 pj_bool_t thread_quit;
Benny Prijonodeb31962006-06-22 18:51:03 +0000191 pj_thread_t *sip_thread[1];
Benny Prijono60b980e2006-04-03 22:41:26 +0000192
193 pjmedia_endpt *med_endpt;
194 struct call call[MAX_CALLS];
195} app;
196
197
198
199/*
200 * Prototypes:
201 */
202
203/* Callback to be called when SDP negotiation is done in the call: */
204static void call_on_media_update( pjsip_inv_session *inv,
205 pj_status_t status);
206
207/* Callback to be called when invite session's state has changed: */
208static void call_on_state_changed( pjsip_inv_session *inv,
209 pjsip_event *e);
210
211/* Callback to be called when dialog has forked: */
212static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
213
214/* Callback to be called to handle incoming requests outside dialogs: */
215static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
216
217/* Worker thread prototype */
Benny Prijonodeb31962006-06-22 18:51:03 +0000218static int sip_worker_thread(void *arg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000219
220/* Create SDP for call */
221static pj_status_t create_sdp( pj_pool_t *pool,
222 struct call *call,
223 pjmedia_sdp_session **p_sdp);
224
Benny Prijono410fbae2006-05-03 18:16:06 +0000225/* Hangup call */
226static void hangup_call(unsigned index);
227
Benny Prijono60b980e2006-04-03 22:41:26 +0000228/* Destroy the call's media */
229static void destroy_call_media(unsigned call_index);
230
Benny Prijonodeb31962006-06-22 18:51:03 +0000231/* Destroy media. */
232static void destroy_media();
233
234/* This callback is called by media transport on receipt of RTP packet. */
Benny Prijono378484d2008-02-07 11:53:47 +0000235static void on_rx_rtp(void *user_data, void *pkt, pj_ssize_t size);
Benny Prijonodeb31962006-06-22 18:51:03 +0000236
237/* This callback is called by media transport on receipt of RTCP packet. */
Benny Prijono378484d2008-02-07 11:53:47 +0000238static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size);
Benny Prijonodeb31962006-06-22 18:51:03 +0000239
Benny Prijono60b980e2006-04-03 22:41:26 +0000240/* Display error */
241static void app_perror(const char *sender, const char *title,
242 pj_status_t status);
243
Benny Prijonod7a13f12006-04-05 19:08:16 +0000244/* Print call */
245static void print_call(int call_index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000246
247
Benny Prijono60b980e2006-04-03 22:41:26 +0000248/* This is a PJSIP module to be registered by application to handle
249 * incoming requests outside any dialogs/transactions. The main purpose
250 * here is to handle incoming INVITE request message, where we will
251 * create a dialog and INVITE session for it.
252 */
253static pjsip_module mod_siprtp =
254{
255 NULL, NULL, /* prev, next. */
256 { "mod-siprtpapp", 13 }, /* Name. */
257 -1, /* Id */
258 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
259 NULL, /* load() */
260 NULL, /* start() */
261 NULL, /* stop() */
262 NULL, /* unload() */
263 &on_rx_request, /* on_rx_request() */
264 NULL, /* on_rx_response() */
265 NULL, /* on_tx_request. */
266 NULL, /* on_tx_response() */
267 NULL, /* on_tsx_state() */
268};
269
270
Benny Prijono4adcb912006-04-04 13:12:38 +0000271/* Codec constants */
272struct codec audio_codecs[] =
273{
Benny Prijono97f2a372006-06-07 21:21:57 +0000274 { 0, "PCMU", 8000, 64000, 20, "G.711 ULaw" },
275 { 3, "GSM", 8000, 13200, 20, "GSM" },
276 { 4, "G723", 8000, 6400, 30, "G.723.1" },
277 { 8, "PCMA", 8000, 64000, 20, "G.711 ALaw" },
Benny Prijonodeb31962006-06-22 18:51:03 +0000278 { 18, "G729", 8000, 8000, 20, "G.729" },
Benny Prijono4adcb912006-04-04 13:12:38 +0000279};
280
281
Benny Prijono60b980e2006-04-03 22:41:26 +0000282/*
283 * Init SIP stack
284 */
285static pj_status_t init_sip()
286{
Benny Prijonodeb31962006-06-22 18:51:03 +0000287 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000288 pj_status_t status;
289
290 /* init PJLIB-UTIL: */
291 status = pjlib_util_init();
292 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
293
294 /* Must create a pool factory before we can allocate any memory. */
295 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
296
297 /* Create application pool for misc. */
298 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
299
Benny Prijonodeb31962006-06-22 18:51:03 +0000300 /* Create the endpoint: */
301 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
302 &app.sip_endpt);
303 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000304
305
306 /* Add UDP transport. */
307 {
308 pj_sockaddr_in addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000309 pjsip_host_port addrname;
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000310 pjsip_transport *tp;
Benny Prijono60b980e2006-04-03 22:41:26 +0000311
Benny Prijonoac623b32006-07-03 15:19:31 +0000312 pj_bzero(&addr, sizeof(addr));
Benny Prijono8ab968f2007-07-20 08:08:30 +0000313 addr.sin_family = pj_AF_INET();
Benny Prijono60b980e2006-04-03 22:41:26 +0000314 addr.sin_addr.s_addr = 0;
315 addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
316
Benny Prijonodeb31962006-06-22 18:51:03 +0000317 if (app.local_addr.slen) {
Benny Prijono41c96f32006-10-16 11:39:07 +0000318
Benny Prijonodeb31962006-06-22 18:51:03 +0000319 addrname.host = app.local_addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000320 addrname.port = app.sip_port;
Benny Prijono41c96f32006-10-16 11:39:07 +0000321
322 status = pj_sockaddr_in_init(&addr, &app.local_addr,
323 (pj_uint16_t)app.sip_port);
324 if (status != PJ_SUCCESS) {
325 app_perror(THIS_FILE, "Unable to resolve IP interface", status);
326 return status;
327 }
Benny Prijono49ce9a72006-04-05 16:56:19 +0000328 }
329
330 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
Benny Prijonodeb31962006-06-22 18:51:03 +0000331 (app.local_addr.slen ? &addrname:NULL),
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000332 1, &tp);
Benny Prijono49ce9a72006-04-05 16:56:19 +0000333 if (status != PJ_SUCCESS) {
334 app_perror(THIS_FILE, "Unable to start UDP transport", status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000335 return status;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000336 }
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000337
338 PJ_LOG(3,(THIS_FILE, "SIP UDP listening on %.*s:%d",
339 (int)tp->local_name.host.slen, tp->local_name.host.ptr,
340 tp->local_name.port));
Benny Prijono60b980e2006-04-03 22:41:26 +0000341 }
342
343 /*
344 * Init transaction layer.
345 * This will create/initialize transaction hash tables etc.
346 */
347 status = pjsip_tsx_layer_init_module(app.sip_endpt);
348 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
349
350 /* Initialize UA layer. */
351 status = pjsip_ua_init_module( app.sip_endpt, NULL );
352 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
353
Benny Prijonod6066852008-01-03 17:20:39 +0000354 /* Initialize 100rel support */
355 status = pjsip_100rel_init_module(app.sip_endpt);
356 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
357
Benny Prijono60b980e2006-04-03 22:41:26 +0000358 /* Init invite session module. */
359 {
360 pjsip_inv_callback inv_cb;
361
362 /* Init the callback for INVITE session: */
Benny Prijonoac623b32006-07-03 15:19:31 +0000363 pj_bzero(&inv_cb, sizeof(inv_cb));
Benny Prijono60b980e2006-04-03 22:41:26 +0000364 inv_cb.on_state_changed = &call_on_state_changed;
365 inv_cb.on_new_session = &call_on_forked;
366 inv_cb.on_media_update = &call_on_media_update;
367
368 /* Initialize invite session module: */
369 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
370 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
371 }
372
373 /* Register our module to receive incoming requests. */
374 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
375 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
376
Benny Prijonodeb31962006-06-22 18:51:03 +0000377 /* Init calls */
378 for (i=0; i<app.max_calls; ++i)
379 app.call[i].index = i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000380
Benny Prijono60b980e2006-04-03 22:41:26 +0000381 /* Done */
382 return PJ_SUCCESS;
383}
384
385
386/*
387 * Destroy SIP
388 */
389static void destroy_sip()
390{
391 unsigned i;
392
393 app.thread_quit = 1;
394 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000395 if (app.sip_thread[i]) {
396 pj_thread_join(app.sip_thread[i]);
397 pj_thread_destroy(app.sip_thread[i]);
398 app.sip_thread[i] = NULL;
Benny Prijono60b980e2006-04-03 22:41:26 +0000399 }
400 }
401
402 if (app.sip_endpt) {
403 pjsip_endpt_destroy(app.sip_endpt);
404 app.sip_endpt = NULL;
405 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000406
Benny Prijono60b980e2006-04-03 22:41:26 +0000407}
408
409
410/*
411 * Init media stack.
412 */
413static pj_status_t init_media()
414{
Benny Prijono60b980e2006-04-03 22:41:26 +0000415 unsigned i, count;
416 pj_uint16_t rtp_port;
Benny Prijono60b980e2006-04-03 22:41:26 +0000417 pj_status_t status;
418
419
Benny Prijono60b980e2006-04-03 22:41:26 +0000420 /* Initialize media endpoint so that at least error subsystem is properly
421 * initialized.
422 */
Benny Prijono6869dc92007-06-03 00:37:30 +0000423#if PJ_HAS_THREADS
Benny Prijono513795f2006-07-18 21:12:24 +0000424 status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
Benny Prijono6869dc92007-06-03 00:37:30 +0000425#else
426 status = pjmedia_endpt_create(&app.cp.factory,
427 pjsip_endpt_get_ioqueue(app.sip_endpt),
428 0, &app.med_endpt);
429#endif
Benny Prijono60b980e2006-04-03 22:41:26 +0000430 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
431
432
Benny Prijonodeb31962006-06-22 18:51:03 +0000433 /* Must register codecs to be supported */
Benny Prijonofc24e692007-01-27 18:31:51 +0000434#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
Benny Prijono4d7fd202006-05-14 20:57:20 +0000435 pjmedia_codec_g711_init(app.med_endpt);
Benny Prijonofc24e692007-01-27 18:31:51 +0000436#endif
Benny Prijono4d7fd202006-05-14 20:57:20 +0000437
Benny Prijono60b980e2006-04-03 22:41:26 +0000438 /* RTP port counter */
439 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
440
Benny Prijonodeb31962006-06-22 18:51:03 +0000441 /* Init media transport for all calls. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000442 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
443
Benny Prijonodeb31962006-06-22 18:51:03 +0000444 unsigned j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000445
Benny Prijonodeb31962006-06-22 18:51:03 +0000446 /* Create transport for each media in the call */
447 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
448 /* Repeat binding media socket to next port when fails to bind
449 * to current port number.
450 */
451 int retry;
Benny Prijono60b980e2006-04-03 22:41:26 +0000452
Benny Prijono513795f2006-07-18 21:12:24 +0000453 app.call[i].media[j].call_index = i;
454 app.call[i].media[j].media_index = j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000455
Benny Prijonodeb31962006-06-22 18:51:03 +0000456 status = -1;
457 for (retry=0; retry<100; ++retry,rtp_port+=2) {
458 struct media_stream *m = &app.call[i].media[j];
459
460 status = pjmedia_transport_udp_create2(app.med_endpt,
461 "siprtp",
462 &app.local_addr,
463 rtp_port, 0,
464 &m->transport);
465 if (status == PJ_SUCCESS) {
466 rtp_port += 2;
467 break;
468 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000469 }
Benny Prijono6647d822006-05-20 13:01:07 +0000470 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000471
472 if (status != PJ_SUCCESS)
473 goto on_error;
Benny Prijono60b980e2006-04-03 22:41:26 +0000474 }
475
476 /* Done */
477 return PJ_SUCCESS;
478
479on_error:
Benny Prijonodeb31962006-06-22 18:51:03 +0000480 destroy_media();
Benny Prijono60b980e2006-04-03 22:41:26 +0000481 return status;
482}
483
484
485/*
486 * Destroy media.
487 */
488static void destroy_media()
489{
490 unsigned i;
491
492 for (i=0; i<app.max_calls; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000493 unsigned j;
494 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
495 struct media_stream *m = &app.call[i].media[j];
Benny Prijono60b980e2006-04-03 22:41:26 +0000496
Benny Prijonodeb31962006-06-22 18:51:03 +0000497 if (m->transport) {
498 pjmedia_transport_close(m->transport);
499 m->transport = NULL;
500 }
501 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000502 }
503
504 if (app.med_endpt) {
505 pjmedia_endpt_destroy(app.med_endpt);
506 app.med_endpt = NULL;
507 }
508}
509
510
511/*
512 * Make outgoing call.
513 */
514static pj_status_t make_call(const pj_str_t *dst_uri)
515{
516 unsigned i;
517 struct call *call;
518 pjsip_dialog *dlg;
519 pjmedia_sdp_session *sdp;
520 pjsip_tx_data *tdata;
521 pj_status_t status;
522
523
524 /* Find unused call slot */
525 for (i=0; i<app.max_calls; ++i) {
526 if (app.call[i].inv == NULL)
527 break;
528 }
529
530 if (i == app.max_calls)
531 return PJ_ETOOMANY;
532
533 call = &app.call[i];
534
535 /* Create UAC dialog */
536 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
537 &app.local_uri, /* local URI */
538 &app.local_contact, /* local Contact */
539 dst_uri, /* remote URI */
540 dst_uri, /* remote target */
541 &dlg); /* dialog */
Benny Prijono410fbae2006-05-03 18:16:06 +0000542 if (status != PJ_SUCCESS) {
543 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000544 return status;
Benny Prijono410fbae2006-05-03 18:16:06 +0000545 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000546
547 /* Create SDP */
548 create_sdp( dlg->pool, call, &sdp);
549
550 /* Create the INVITE session. */
551 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
552 if (status != PJ_SUCCESS) {
553 pjsip_dlg_terminate(dlg);
Benny Prijono410fbae2006-05-03 18:16:06 +0000554 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000555 return status;
556 }
557
558
559 /* Attach call data to invite session */
560 call->inv->mod_data[mod_siprtp.id] = call;
561
Benny Prijono4adcb912006-04-04 13:12:38 +0000562 /* Mark start of call */
563 pj_gettimeofday(&call->start_time);
564
Benny Prijono60b980e2006-04-03 22:41:26 +0000565
566 /* Create initial INVITE request.
567 * This INVITE request will contain a perfectly good request and
568 * an SDP body as well.
569 */
570 status = pjsip_inv_invite(call->inv, &tdata);
571 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
572
573
574 /* Send initial INVITE request.
575 * From now on, the invite session's state will be reported to us
576 * via the invite session callbacks.
577 */
578 status = pjsip_inv_send_msg(call->inv, tdata);
579 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
580
581
582 return PJ_SUCCESS;
583}
584
585
586/*
587 * Receive incoming call
588 */
589static void process_incoming_call(pjsip_rx_data *rdata)
590{
Benny Prijonoca4cff22006-07-02 14:18:47 +0000591 unsigned i, options;
Benny Prijono60b980e2006-04-03 22:41:26 +0000592 struct call *call;
593 pjsip_dialog *dlg;
594 pjmedia_sdp_session *sdp;
595 pjsip_tx_data *tdata;
596 pj_status_t status;
597
598 /* Find free call slot */
599 for (i=0; i<app.max_calls; ++i) {
600 if (app.call[i].inv == NULL)
601 break;
602 }
603
604 if (i == app.max_calls) {
605 const pj_str_t reason = pj_str("Too many calls");
606 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
607 500, &reason,
608 NULL, NULL);
609 return;
610 }
611
Benny Prijono513795f2006-07-18 21:12:24 +0000612 call = &app.call[i];
613
Benny Prijonoca4cff22006-07-02 14:18:47 +0000614 /* Verify that we can handle the request. */
615 options = 0;
616 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000617 app.sip_endpt, &tdata);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000618 if (status != PJ_SUCCESS) {
Benny Prijonoca4cff22006-07-02 14:18:47 +0000619 /*
620 * No we can't handle the incoming INVITE request.
621 */
Benny Prijonoca4cff22006-07-02 14:18:47 +0000622 if (tdata) {
623 pjsip_response_addr res_addr;
Benny Prijono513795f2006-07-18 21:12:24 +0000624
Benny Prijonoca4cff22006-07-02 14:18:47 +0000625 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
Benny Prijono513795f2006-07-18 21:12:24 +0000626 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
627 NULL, NULL);
628
Benny Prijonoca4cff22006-07-02 14:18:47 +0000629 } else {
Benny Prijono513795f2006-07-18 21:12:24 +0000630
Benny Prijonoca4cff22006-07-02 14:18:47 +0000631 /* Respond with 500 (Internal Server Error) */
632 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000633 NULL, NULL);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000634 }
Benny Prijono513795f2006-07-18 21:12:24 +0000635
Benny Prijonoca4cff22006-07-02 14:18:47 +0000636 return;
Benny Prijono513795f2006-07-18 21:12:24 +0000637 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000638
639 /* Create UAS dialog */
640 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
641 &app.local_contact, &dlg);
642 if (status != PJ_SUCCESS) {
643 const pj_str_t reason = pj_str("Unable to create dialog");
644 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
645 500, &reason,
646 NULL, NULL);
647 return;
648 }
649
650 /* Create SDP */
651 create_sdp( dlg->pool, call, &sdp);
652
653 /* Create UAS invite session */
654 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
655 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000656 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
657 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000658 return;
659 }
660
Benny Prijono4adcb912006-04-04 13:12:38 +0000661
Benny Prijono60b980e2006-04-03 22:41:26 +0000662 /* Attach call data to invite session */
663 call->inv->mod_data[mod_siprtp.id] = call;
664
Benny Prijono4adcb912006-04-04 13:12:38 +0000665 /* Mark start of call */
666 pj_gettimeofday(&call->start_time);
667
668
669
Benny Prijono60b980e2006-04-03 22:41:26 +0000670 /* Create 200 response .*/
671 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
672 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000673 if (status != PJ_SUCCESS) {
674 status = pjsip_inv_initial_answer(call->inv, rdata,
675 PJSIP_SC_NOT_ACCEPTABLE,
676 NULL, NULL, &tdata);
677 if (status == PJ_SUCCESS)
678 pjsip_inv_send_msg(call->inv, tdata);
679 else
680 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
681 return;
682 }
683
Benny Prijono60b980e2006-04-03 22:41:26 +0000684
685 /* Send the 200 response. */
686 status = pjsip_inv_send_msg(call->inv, tdata);
687 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
688
689
690 /* Done */
691}
692
693
694/* Callback to be called when dialog has forked: */
695static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
696{
697 PJ_UNUSED_ARG(inv);
698 PJ_UNUSED_ARG(e);
699
700 PJ_TODO( HANDLE_FORKING );
701}
702
703
704/* Callback to be called to handle incoming requests outside dialogs: */
705static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
706{
Benny Prijono4adcb912006-04-04 13:12:38 +0000707 /* Ignore strandled ACKs (must not send respone */
708 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
709 return PJ_FALSE;
710
Benny Prijono60b980e2006-04-03 22:41:26 +0000711 /* Respond (statelessly) any non-INVITE requests with 500 */
712 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
713 pj_str_t reason = pj_str("Unsupported Operation");
714 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
715 500, &reason,
716 NULL, NULL);
717 return PJ_TRUE;
718 }
719
720 /* Handle incoming INVITE */
721 process_incoming_call(rdata);
722
723 /* Done */
724 return PJ_TRUE;
725}
726
727
Benny Prijono410fbae2006-05-03 18:16:06 +0000728/* Callback timer to disconnect call (limiting call duration) */
729static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
730 struct pj_timer_entry *entry)
731{
732 struct call *call = entry->user_data;
733
734 PJ_UNUSED_ARG(timer_heap);
735
736 entry->id = 0;
737 hangup_call(call->index);
738}
739
740
Benny Prijono60b980e2006-04-03 22:41:26 +0000741/* Callback to be called when invite session's state has changed: */
742static void call_on_state_changed( pjsip_inv_session *inv,
743 pjsip_event *e)
744{
Benny Prijono4adcb912006-04-04 13:12:38 +0000745 struct call *call = inv->mod_data[mod_siprtp.id];
746
Benny Prijono60b980e2006-04-03 22:41:26 +0000747 PJ_UNUSED_ARG(e);
748
Benny Prijono4adcb912006-04-04 13:12:38 +0000749 if (!call)
750 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000751
Benny Prijono4adcb912006-04-04 13:12:38 +0000752 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
753
754 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000755
Benny Prijono410fbae2006-05-03 18:16:06 +0000756 if (call->d_timer.id != 0) {
757 pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
758 call->d_timer.id = 0;
759 }
760
Benny Prijono258ece92006-07-22 12:53:04 +0000761 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%d (%.*s)",
Benny Prijonod7a13f12006-04-05 19:08:16 +0000762 call->index,
Benny Prijono258ece92006-07-22 12:53:04 +0000763 inv->cause,
764 (int)inv->cause_text.slen,
765 inv->cause_text.ptr));
Benny Prijono1b543242008-01-08 22:50:06 +0000766
767 if (app.call_report) {
768 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
769 print_call(call->index);
770 }
Benny Prijonod7a13f12006-04-05 19:08:16 +0000771
772
Benny Prijono60b980e2006-04-03 22:41:26 +0000773 call->inv = NULL;
774 inv->mod_data[mod_siprtp.id] = NULL;
775
776 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000777
778 call->start_time = null_time;
779 call->response_time = null_time;
780 call->connect_time = null_time;
781
Benny Prijono410fbae2006-05-03 18:16:06 +0000782 ++app.uac_calls;
Benny Prijono4adcb912006-04-04 13:12:38 +0000783
784 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
785
786 pj_time_val t;
787
788 pj_gettimeofday(&call->connect_time);
789 if (call->response_time.sec == 0)
790 call->response_time = call->connect_time;
791
792 t = call->connect_time;
793 PJ_TIME_VAL_SUB(t, call->start_time);
794
795 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
796 PJ_TIME_VAL_MSEC(t)));
797
Benny Prijono410fbae2006-05-03 18:16:06 +0000798 if (app.duration != 0) {
799 call->d_timer.id = 1;
800 call->d_timer.user_data = call;
801 call->d_timer.cb = &timer_disconnect_call;
802
803 t.sec = app.duration;
804 t.msec = 0;
805
806 pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
807 }
808
Benny Prijono4adcb912006-04-04 13:12:38 +0000809 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
810 inv->state == PJSIP_INV_STATE_CONNECTING) {
811
812 if (call->response_time.sec == 0)
813 pj_gettimeofday(&call->response_time);
814
Benny Prijono60b980e2006-04-03 22:41:26 +0000815 }
816}
817
818
819/* Utility */
820static void app_perror(const char *sender, const char *title,
821 pj_status_t status)
822{
823 char errmsg[PJ_ERR_MSG_SIZE];
824
825 pj_strerror(status, errmsg, sizeof(errmsg));
826 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
827}
828
829
Benny Prijonodeb31962006-06-22 18:51:03 +0000830/* Worker thread for SIP */
831static int sip_worker_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +0000832{
833 PJ_UNUSED_ARG(arg);
834
835 while (!app.thread_quit) {
Benny Prijono513795f2006-07-18 21:12:24 +0000836 pj_time_val timeout = {0, 10};
Benny Prijono60b980e2006-04-03 22:41:26 +0000837 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
838 }
839
840 return 0;
841}
842
843
Benny Prijono60b980e2006-04-03 22:41:26 +0000844/* Init application options */
845static pj_status_t init_options(int argc, char *argv[])
846{
847 static char ip_addr[32];
848 static char local_uri[64];
849
Benny Prijono4adcb912006-04-04 13:12:38 +0000850 enum { OPT_START,
851 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
Benny Prijonofcb36722006-05-18 18:34:21 +0000852 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
853 OPT_REPORT_FILE };
Benny Prijono4adcb912006-04-04 13:12:38 +0000854
Benny Prijono60b980e2006-04-03 22:41:26 +0000855 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000856 { "count", 1, 0, 'c' },
Benny Prijono1b543242008-01-08 22:50:06 +0000857 { "gap", 1, 0, 'g' },
858 { "call-report", 0, 0, 'R' },
Benny Prijono410fbae2006-05-03 18:16:06 +0000859 { "duration", 1, 0, 'd' },
860 { "auto-quit", 0, 0, 'q' },
Benny Prijono4adcb912006-04-04 13:12:38 +0000861 { "local-port", 1, 0, 'p' },
862 { "rtp-port", 1, 0, 'r' },
863 { "ip-addr", 1, 0, 'i' },
864
865 { "log-level", 1, 0, 'l' },
866 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
867 { "log-file", 1, 0, OPT_LOG_FILE },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000868
Benny Prijonofcb36722006-05-18 18:34:21 +0000869 { "report-file", 1, 0, OPT_REPORT_FILE },
870
Benny Prijono4d7fd202006-05-14 20:57:20 +0000871 /* Don't support this anymore, see comments in USAGE above.
Benny Prijono4adcb912006-04-04 13:12:38 +0000872 { "a-pt", 1, 0, OPT_A_PT },
873 { "a-name", 1, 0, OPT_A_NAME },
874 { "a-clock", 1, 0, OPT_A_CLOCK },
875 { "a-bitrate", 1, 0, OPT_A_BITRATE },
876 { "a-ptime", 1, 0, OPT_A_PTIME },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000877 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000878
Benny Prijono60b980e2006-04-03 22:41:26 +0000879 { NULL, 0, 0, 0 },
880 };
881 int c;
882 int option_index;
883
884 /* Get local IP address for the default IP address */
885 {
886 const pj_str_t *hostname;
887 pj_sockaddr_in tmp_addr;
888 char *addr;
889
890 hostname = pj_gethostname();
891 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
892 addr = pj_inet_ntoa(tmp_addr.sin_addr);
893 pj_ansi_strcpy(ip_addr, addr);
894 }
895
Benny Prijono4adcb912006-04-04 13:12:38 +0000896 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000897 app.max_calls = 1;
898 app.thread_count = 1;
899 app.sip_port = 5060;
Benny Prijono6647d822006-05-20 13:01:07 +0000900 app.rtp_start_port = RTP_START_PORT;
Benny Prijonodeb31962006-06-22 18:51:03 +0000901 app.local_addr = pj_str(ip_addr);
Benny Prijono4adcb912006-04-04 13:12:38 +0000902 app.log_level = 5;
903 app.app_log_level = 3;
904 app.log_filename = NULL;
905
906 /* Default codecs: */
907 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000908
909 /* Parse options */
910 pj_optind = 0;
Benny Prijono1b543242008-01-08 22:50:06 +0000911 while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:g:qR",
Benny Prijono60b980e2006-04-03 22:41:26 +0000912 long_options, &option_index))!=-1)
913 {
914 switch (c) {
915 case 'c':
916 app.max_calls = atoi(pj_optarg);
917 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
918 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
919 return 1;
920 }
921 break;
Benny Prijono1b543242008-01-08 22:50:06 +0000922 case 'g':
923 app.call_gap = atoi(pj_optarg);
924 break;
925 case 'R':
926 app.call_report = PJ_TRUE;
927 break;
Benny Prijono410fbae2006-05-03 18:16:06 +0000928 case 'd':
929 app.duration = atoi(pj_optarg);
930 break;
931 case 'q':
932 app.auto_quit = 1;
933 break;
934
Benny Prijono60b980e2006-04-03 22:41:26 +0000935 case 'p':
936 app.sip_port = atoi(pj_optarg);
937 break;
938 case 'r':
939 app.rtp_start_port = atoi(pj_optarg);
940 break;
941 case 'i':
Benny Prijonodeb31962006-06-22 18:51:03 +0000942 app.local_addr = pj_str(pj_optarg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000943 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000944
945 case 'l':
946 app.log_level = atoi(pj_optarg);
947 break;
948 case OPT_APP_LOG_LEVEL:
949 app.app_log_level = atoi(pj_optarg);
950 break;
951 case OPT_LOG_FILE:
952 app.log_filename = pj_optarg;
953 break;
954
955 case OPT_A_PT:
956 app.audio_codec.pt = atoi(pj_optarg);
957 break;
958 case OPT_A_NAME:
959 app.audio_codec.name = pj_optarg;
960 break;
961 case OPT_A_CLOCK:
962 app.audio_codec.clock_rate = atoi(pj_optarg);
963 break;
964 case OPT_A_BITRATE:
965 app.audio_codec.bit_rate = atoi(pj_optarg);
966 break;
967 case OPT_A_PTIME:
968 app.audio_codec.ptime = atoi(pj_optarg);
969 break;
Benny Prijonofcb36722006-05-18 18:34:21 +0000970 case OPT_REPORT_FILE:
971 app.report_filename = pj_optarg;
972 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000973
Benny Prijono60b980e2006-04-03 22:41:26 +0000974 default:
975 puts(USAGE);
976 return 1;
977 }
978 }
979
980 /* Check if URL is specified */
981 if (pj_optind < argc)
982 app.uri_to_call = pj_str(argv[pj_optind]);
983
984 /* Build local URI and contact */
Benny Prijonodeb31962006-06-22 18:51:03 +0000985 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
Benny Prijono60b980e2006-04-03 22:41:26 +0000986 app.local_uri = pj_str(local_uri);
987 app.local_contact = app.local_uri;
988
989
990 return PJ_SUCCESS;
991}
992
993
Benny Prijono4adcb912006-04-04 13:12:38 +0000994/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000995 * MEDIA STUFFS
996 */
997
998/*
999 * Create SDP session for a call.
1000 */
1001static pj_status_t create_sdp( pj_pool_t *pool,
1002 struct call *call,
1003 pjmedia_sdp_session **p_sdp)
1004{
1005 pj_time_val tv;
1006 pjmedia_sdp_session *sdp;
1007 pjmedia_sdp_media *m;
1008 pjmedia_sdp_attr *attr;
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001009 pjmedia_transport_info tpinfo;
Benny Prijono60b980e2006-04-03 22:41:26 +00001010 struct media_stream *audio = &call->media[0];
1011
1012 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
1013
1014
Benny Prijonodeb31962006-06-22 18:51:03 +00001015 /* Get transport info */
Benny Prijono734fc2d2008-03-17 16:05:35 +00001016 pjmedia_transport_info_init(&tpinfo);
Benny Prijonod8179652008-01-23 20:39:07 +00001017 pjmedia_transport_get_info(audio->transport, &tpinfo);
Benny Prijonodeb31962006-06-22 18:51:03 +00001018
Benny Prijono60b980e2006-04-03 22:41:26 +00001019 /* Create and initialize basic SDP session */
1020 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
1021
1022 pj_gettimeofday(&tv);
1023 sdp->origin.user = pj_str("pjsip-siprtp");
1024 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
1025 sdp->origin.net_type = pj_str("IN");
1026 sdp->origin.addr_type = pj_str("IP4");
1027 sdp->origin.addr = *pj_gethostname();
1028 sdp->name = pj_str("pjsip");
1029
1030 /* Since we only support one media stream at present, put the
1031 * SDP connection line in the session level.
1032 */
1033 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
1034 sdp->conn->net_type = pj_str("IN");
1035 sdp->conn->addr_type = pj_str("IP4");
Benny Prijonodeb31962006-06-22 18:51:03 +00001036 sdp->conn->addr = app.local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +00001037
1038
1039 /* SDP time and attributes. */
1040 sdp->time.start = sdp->time.stop = 0;
1041 sdp->attr_count = 0;
1042
1043 /* Create media stream 0: */
1044
1045 sdp->media_count = 1;
1046 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
1047 sdp->media[0] = m;
1048
1049 /* Standard media info: */
1050 m->desc.media = pj_str("audio");
Benny Prijonoe1a5a852008-03-11 21:38:05 +00001051 m->desc.port = pj_ntohs(tpinfo.sock_info.rtp_addr_name.ipv4.sin_port);
Benny Prijono60b980e2006-04-03 22:41:26 +00001052 m->desc.port_count = 1;
1053 m->desc.transport = pj_str("RTP/AVP");
1054
1055 /* Add format and rtpmap for each codec. */
1056 m->desc.fmt_count = 1;
1057 m->attr_count = 0;
1058
1059 {
1060 pjmedia_sdp_rtpmap rtpmap;
1061 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +00001062 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +00001063
Benny Prijono4adcb912006-04-04 13:12:38 +00001064 sprintf(ptstr, "%d", app.audio_codec.pt);
1065 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1066 rtpmap.pt = m->desc.fmt[0];
1067 rtpmap.clock_rate = app.audio_codec.clock_rate;
1068 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +00001069 rtpmap.param.slen = 0;
1070
1071 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1072 m->attr[m->attr_count++] = attr;
1073 }
1074
1075 /* Add sendrecv attribute. */
1076 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1077 attr->name = pj_str("sendrecv");
1078 m->attr[m->attr_count++] = attr;
1079
1080#if 1
1081 /*
1082 * Add support telephony event
1083 */
Benny Prijono4adcb912006-04-04 13:12:38 +00001084 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +00001085 /* Add rtpmap. */
1086 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1087 attr->name = pj_str("rtpmap");
Benny Prijonod95abb42006-10-20 10:18:44 +00001088 attr->value = pj_str("121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +00001089 m->attr[m->attr_count++] = attr;
1090 /* Add fmtp */
1091 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1092 attr->name = pj_str("fmtp");
Benny Prijonod95abb42006-10-20 10:18:44 +00001093 attr->value = pj_str("121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +00001094 m->attr[m->attr_count++] = attr;
1095#endif
1096
1097 /* Done */
1098 *p_sdp = sdp;
1099
1100 return PJ_SUCCESS;
1101}
1102
1103
Benny Prijono513795f2006-07-18 21:12:24 +00001104#if defined(PJ_WIN32) && PJ_WIN32 != 0
1105#include <windows.h>
1106static void boost_priority(void)
1107{
1108 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
1109 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
1110}
1111
Benny Prijono7bef0f52006-07-31 18:54:06 +00001112#elif defined(PJ_LINUX) && PJ_LINUX != 0
1113#include <pthread.h>
1114static void boost_priority(void)
1115{
1116#define POLICY SCHED_FIFO
Benny Prijono7bef0f52006-07-31 18:54:06 +00001117 struct sched_param tp;
1118 int max_prio;
1119 int policy;
1120 int rc;
1121
1122 if (sched_get_priority_min(POLICY) < sched_get_priority_max(POLICY))
1123 max_prio = sched_get_priority_max(POLICY)-1;
1124 else
1125 max_prio = sched_get_priority_max(POLICY)+1;
1126
1127 /*
1128 * Adjust process scheduling algorithm and priority
1129 */
1130 rc = sched_getparam(0, &tp);
1131 if (rc != 0) {
1132 app_perror( THIS_FILE, "sched_getparam error",
1133 PJ_RETURN_OS_ERROR(rc));
1134 return;
1135 }
1136 tp.__sched_priority = max_prio;
1137
1138 rc = sched_setscheduler(0, POLICY, &tp);
1139 if (rc != 0) {
1140 app_perror( THIS_FILE, "sched_setscheduler error",
1141 PJ_RETURN_OS_ERROR(rc));
1142 }
1143
1144 PJ_LOG(4, (THIS_FILE, "New process policy=%d, priority=%d",
1145 policy, tp.__sched_priority));
1146
1147 /*
1148 * Adjust thread scheduling algorithm and priority
1149 */
1150 rc = pthread_getschedparam(pthread_self(), &policy, &tp);
1151 if (rc != 0) {
1152 app_perror( THIS_FILE, "pthread_getschedparam error",
1153 PJ_RETURN_OS_ERROR(rc));
1154 return;
1155 }
1156
1157 PJ_LOG(4, (THIS_FILE, "Old thread policy=%d, priority=%d",
1158 policy, tp.__sched_priority));
1159
1160 policy = POLICY;
1161 tp.__sched_priority = max_prio;
1162
1163 rc = pthread_setschedparam(pthread_self(), policy, &tp);
1164 if (rc != 0) {
1165 app_perror( THIS_FILE, "pthread_setschedparam error",
1166 PJ_RETURN_OS_ERROR(rc));
1167 return;
1168 }
1169
1170 PJ_LOG(4, (THIS_FILE, "New thread policy=%d, priority=%d",
1171 policy, tp.__sched_priority));
1172}
1173
Benny Prijono513795f2006-07-18 21:12:24 +00001174#else
1175# define boost_priority()
1176#endif
1177
1178
Benny Prijonodeb31962006-06-22 18:51:03 +00001179/*
1180 * This callback is called by media transport on receipt of RTP packet.
1181 */
Benny Prijono378484d2008-02-07 11:53:47 +00001182static void on_rx_rtp(void *user_data, void *pkt, pj_ssize_t size)
Benny Prijonodeb31962006-06-22 18:51:03 +00001183{
1184 struct media_stream *strm;
1185 pj_status_t status;
1186 const pjmedia_rtp_hdr *hdr;
1187 const void *payload;
1188 unsigned payload_len;
1189
1190 strm = user_data;
1191
1192 /* Discard packet if media is inactive */
1193 if (!strm->active)
1194 return;
1195
1196 /* Check for errors */
1197 if (size < 0) {
1198 app_perror(THIS_FILE, "RTP recv() error", -size);
1199 return;
1200 }
1201
1202 /* Decode RTP packet. */
1203 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1204 pkt, size,
1205 &hdr, &payload, &payload_len);
1206 if (status != PJ_SUCCESS) {
1207 app_perror(THIS_FILE, "RTP decode error", status);
1208 return;
1209 }
1210
1211 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1212
1213 /* Update the RTCP session. */
1214 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1215 pj_ntohl(hdr->ts), payload_len);
1216
1217 /* Update RTP session */
1218 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1219
1220}
1221
1222/*
1223 * This callback is called by media transport on receipt of RTCP packet.
1224 */
Benny Prijono378484d2008-02-07 11:53:47 +00001225static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size)
Benny Prijonodeb31962006-06-22 18:51:03 +00001226{
1227 struct media_stream *strm;
1228
1229 strm = user_data;
1230
1231 /* Discard packet if media is inactive */
1232 if (!strm->active)
1233 return;
1234
1235 /* Check for errors */
1236 if (size < 0) {
1237 app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
1238 return;
1239 }
1240
1241 /* Update RTCP session */
1242 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1243}
1244
1245
Benny Prijono513795f2006-07-18 21:12:24 +00001246/*
1247 * Media thread
1248 *
1249 * This is the thread to send and receive both RTP and RTCP packets.
1250 */
1251static int media_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +00001252{
Benny Prijono513795f2006-07-18 21:12:24 +00001253 enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
1254 struct media_stream *strm = arg;
1255 char packet[1500];
1256 unsigned msec_interval;
1257 pj_timestamp freq, next_rtp, next_rtcp;
Benny Prijono60b980e2006-04-03 22:41:26 +00001258
Benny Prijono6647d822006-05-20 13:01:07 +00001259
Benny Prijono513795f2006-07-18 21:12:24 +00001260 /* Boost thread priority if necessary */
1261 boost_priority();
Benny Prijono6647d822006-05-20 13:01:07 +00001262
Benny Prijono513795f2006-07-18 21:12:24 +00001263 /* Let things settle */
Benny Prijono258ece92006-07-22 12:53:04 +00001264 pj_thread_sleep(100);
Benny Prijono6647d822006-05-20 13:01:07 +00001265
Benny Prijono513795f2006-07-18 21:12:24 +00001266 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1267 pj_get_timestamp_freq(&freq);
1268
1269 pj_get_timestamp(&next_rtp);
1270 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
1271
1272 next_rtcp = next_rtp;
1273 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
1274
1275
1276 while (!strm->thread_quit_flag) {
1277 pj_timestamp now, lesser;
1278 pj_time_val timeout;
1279 pj_bool_t send_rtp, send_rtcp;
1280
1281 send_rtp = send_rtcp = PJ_FALSE;
1282
1283 /* Determine how long to sleep */
1284 if (next_rtp.u64 < next_rtcp.u64) {
1285 lesser = next_rtp;
1286 send_rtp = PJ_TRUE;
1287 } else {
1288 lesser = next_rtcp;
1289 send_rtcp = PJ_TRUE;
1290 }
1291
1292 pj_get_timestamp(&now);
1293 if (lesser.u64 <= now.u64) {
1294 timeout.sec = timeout.msec = 0;
1295 //printf("immediate "); fflush(stdout);
1296 } else {
1297 pj_uint64_t tick_delay;
1298 tick_delay = lesser.u64 - now.u64;
1299 timeout.sec = 0;
1300 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1301 pj_time_val_normalize(&timeout);
1302
1303 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
1304 }
1305
1306 /* Wait for next interval */
1307 //if (timeout.sec!=0 && timeout.msec!=0) {
1308 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
1309 if (strm->thread_quit_flag)
1310 break;
1311 //}
1312
1313 pj_get_timestamp(&now);
1314
1315 if (send_rtp || next_rtp.u64 <= now.u64) {
1316 /*
1317 * Time to send RTP packet.
1318 */
1319 pj_status_t status;
Benny Prijonoe960bb52007-01-21 17:53:39 +00001320 const void *p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001321 const pjmedia_rtp_hdr *hdr;
1322 pj_ssize_t size;
1323 int hdrlen;
1324
1325 /* Format RTP header */
1326 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1327 0, /* marker bit */
1328 strm->bytes_per_frame,
1329 strm->samples_per_frame,
Benny Prijonoe960bb52007-01-21 17:53:39 +00001330 &p_hdr, &hdrlen);
Benny Prijono513795f2006-07-18 21:12:24 +00001331 if (status == PJ_SUCCESS) {
1332
1333 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
Benny Prijonoe960bb52007-01-21 17:53:39 +00001334
1335 hdr = (const pjmedia_rtp_hdr*) p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001336
1337 /* Copy RTP header to packet */
1338 pj_memcpy(packet, hdr, hdrlen);
1339
1340 /* Zero the payload */
1341 pj_bzero(packet+hdrlen, strm->bytes_per_frame);
1342
1343 /* Send RTP packet */
1344 size = hdrlen + strm->bytes_per_frame;
1345 status = pjmedia_transport_send_rtp(strm->transport,
1346 packet, size);
1347 if (status != PJ_SUCCESS)
1348 app_perror(THIS_FILE, "Error sending RTP packet", status);
1349
1350 } else {
1351 pj_assert(!"RTP encode() error");
1352 }
1353
1354 /* Update RTCP SR */
1355 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1356
1357 /* Schedule next send */
1358 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
1359 }
1360
1361
1362 if (send_rtcp || next_rtcp.u64 <= now.u64) {
1363 /*
1364 * Time to send RTCP packet.
1365 */
Benny Prijonofe81cfa2007-09-20 11:30:59 +00001366 void *rtcp_pkt;
Benny Prijono513795f2006-07-18 21:12:24 +00001367 int rtcp_len;
1368 pj_ssize_t size;
1369 pj_status_t status;
1370
1371 /* Build RTCP packet */
1372 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1373
Benny Prijono4d3aa922006-06-22 22:31:48 +00001374
Benny Prijono513795f2006-07-18 21:12:24 +00001375 /* Send packet */
1376 size = rtcp_len;
1377 status = pjmedia_transport_send_rtcp(strm->transport,
1378 rtcp_pkt, size);
1379 if (status != PJ_SUCCESS) {
1380 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1381 }
1382
1383 /* Schedule next send */
1384 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
1385 1000);
1386 }
1387 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001388
Benny Prijono513795f2006-07-18 21:12:24 +00001389 return 0;
Benny Prijono60b980e2006-04-03 22:41:26 +00001390}
1391
Benny Prijono513795f2006-07-18 21:12:24 +00001392
Benny Prijono60b980e2006-04-03 22:41:26 +00001393/* Callback to be called when SDP negotiation is done in the call: */
1394static void call_on_media_update( pjsip_inv_session *inv,
1395 pj_status_t status)
1396{
1397 struct call *call;
1398 pj_pool_t *pool;
1399 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001400 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001401 struct codec *codec_desc = NULL;
1402 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001403
1404 call = inv->mod_data[mod_siprtp.id];
1405 pool = inv->dlg->pool;
1406 audio = &call->media[0];
1407
1408 /* If this is a mid-call media update, then destroy existing media */
Benny Prijono513795f2006-07-18 21:12:24 +00001409 if (audio->thread != NULL)
Benny Prijono60b980e2006-04-03 22:41:26 +00001410 destroy_call_media(call->index);
1411
1412
1413 /* Do nothing if media negotiation has failed */
1414 if (status != PJ_SUCCESS) {
1415 app_perror(THIS_FILE, "SDP negotiation failed", status);
1416 return;
1417 }
1418
1419
1420 /* Capture stream definition from the SDP */
1421 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1422 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1423
1424 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
Benny Prijonob04c9e02006-05-17 17:17:39 +00001425 local_sdp, remote_sdp, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001426 if (status != PJ_SUCCESS) {
1427 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1428 return;
1429 }
1430
Benny Prijono4adcb912006-04-04 13:12:38 +00001431 /* Get the remainder of codec information from codec descriptor */
1432 if (audio->si.fmt.pt == app.audio_codec.pt)
1433 codec_desc = &app.audio_codec;
1434 else {
1435 /* Find the codec description in codec array */
1436 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1437 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1438 codec_desc = &audio_codecs[i];
1439 break;
1440 }
1441 }
1442
1443 if (codec_desc == NULL) {
1444 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1445 return;
1446 }
1447 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001448
Benny Prijono15953012006-04-27 22:37:08 +00001449 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001450 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1451 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001452
1453
1454 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001455 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001456 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001457 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001458 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001459
Benny Prijono4adcb912006-04-04 13:12:38 +00001460
Benny Prijonodeb31962006-06-22 18:51:03 +00001461 /* Attach media to transport */
1462 status = pjmedia_transport_attach(audio->transport, audio,
1463 &audio->si.rem_addr,
Benny Prijono513795f2006-07-18 21:12:24 +00001464 &audio->si.rem_rtcp,
Benny Prijonodeb31962006-06-22 18:51:03 +00001465 sizeof(pj_sockaddr_in),
1466 &on_rx_rtp,
1467 &on_rx_rtcp);
1468 if (status != PJ_SUCCESS) {
1469 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1470 return;
1471 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001472
Benny Prijono513795f2006-07-18 21:12:24 +00001473 /* Start media thread. */
1474 audio->thread_quit_flag = 0;
Benny Prijono6869dc92007-06-03 00:37:30 +00001475#if PJ_HAS_THREADS
Benny Prijono513795f2006-07-18 21:12:24 +00001476 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1477 0, 0, &audio->thread);
1478 if (status != PJ_SUCCESS) {
1479 app_perror(THIS_FILE, "Error creating media thread", status);
1480 return;
1481 }
Benny Prijono6869dc92007-06-03 00:37:30 +00001482#endif
Benny Prijono513795f2006-07-18 21:12:24 +00001483
Benny Prijonodeb31962006-06-22 18:51:03 +00001484 /* Set the media as active */
1485 audio->active = PJ_TRUE;
Benny Prijono60b980e2006-04-03 22:41:26 +00001486}
1487
1488
1489
1490/* Destroy call's media */
1491static void destroy_call_media(unsigned call_index)
1492{
1493 struct media_stream *audio = &app.call[call_index].media[0];
1494
Benny Prijono5a9e2042006-11-14 13:35:20 +00001495 if (audio) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001496 audio->active = PJ_FALSE;
1497
Benny Prijono5a9e2042006-11-14 13:35:20 +00001498 if (audio->thread) {
1499 audio->thread_quit_flag = 1;
1500 pj_thread_join(audio->thread);
1501 pj_thread_destroy(audio->thread);
1502 audio->thread = NULL;
1503 audio->thread_quit_flag = 0;
1504 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001505
Benny Prijono5b1e14d2006-11-25 08:46:48 +00001506 pjmedia_transport_detach(audio->transport, audio);
Benny Prijono60b980e2006-04-03 22:41:26 +00001507 }
1508}
1509
Benny Prijono513795f2006-07-18 21:12:24 +00001510
Benny Prijono4adcb912006-04-04 13:12:38 +00001511/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001512 * USER INTERFACE STUFFS
1513 */
Benny Prijono258ece92006-07-22 12:53:04 +00001514
1515static void call_get_duration(int call_index, pj_time_val *dur)
1516{
1517 struct call *call = &app.call[call_index];
1518 pjsip_inv_session *inv;
1519
1520 dur->sec = dur->msec = 0;
1521
1522 if (!call)
1523 return;
1524
1525 inv = call->inv;
1526 if (!inv)
1527 return;
1528
1529 if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
1530
1531 pj_gettimeofday(dur);
1532 PJ_TIME_VAL_SUB((*dur), call->connect_time);
1533 }
1534}
1535
1536
1537static const char *good_number(char *buf, pj_int32_t val)
1538{
1539 if (val < 1000) {
1540 pj_ansi_sprintf(buf, "%d", val);
1541 } else if (val < 1000000) {
1542 pj_ansi_sprintf(buf, "%d.%02dK",
1543 val / 1000,
1544 (val % 1000) / 100);
1545 } else {
1546 pj_ansi_sprintf(buf, "%d.%02dM",
1547 val / 1000000,
1548 (val % 1000000) / 10000);
1549 }
1550
1551 return buf;
1552}
1553
1554
1555
1556static void print_avg_stat(void)
1557{
1558#define MIN_(var,val) if ((int)val < (int)var) var = val
1559#define MAX_(var,val) if ((int)val > (int)var) var = val
1560#define AVG_(var,val) var = ( ((var * count) + val) / (count+1) )
1561#define BIGVAL 0x7FFFFFFFL
1562 struct stat_entry
1563 {
1564 int min, avg, max;
1565 };
1566
1567 struct stat_entry call_dur, call_pdd;
1568 pjmedia_rtcp_stat min_stat, avg_stat, max_stat;
1569
1570 char srx_min[16], srx_avg[16], srx_max[16];
1571 char brx_min[16], brx_avg[16], brx_max[16];
1572 char stx_min[16], stx_avg[16], stx_max[16];
1573 char btx_min[16], btx_avg[16], btx_max[16];
1574
1575
1576 unsigned i, count;
1577
1578 pj_bzero(&call_dur, sizeof(call_dur));
1579 call_dur.min = BIGVAL;
1580
1581 pj_bzero(&call_pdd, sizeof(call_pdd));
1582 call_pdd.min = BIGVAL;
1583
1584 pj_bzero(&min_stat, sizeof(min_stat));
1585 min_stat.rx.pkt = min_stat.tx.pkt = BIGVAL;
1586 min_stat.rx.bytes = min_stat.tx.bytes = BIGVAL;
1587 min_stat.rx.loss = min_stat.tx.loss = BIGVAL;
1588 min_stat.rx.dup = min_stat.tx.dup = BIGVAL;
1589 min_stat.rx.reorder = min_stat.tx.reorder = BIGVAL;
1590 min_stat.rx.jitter.min = min_stat.tx.jitter.min = BIGVAL;
1591 min_stat.rtt.min = BIGVAL;
1592
1593 pj_bzero(&avg_stat, sizeof(avg_stat));
1594 pj_bzero(&max_stat, sizeof(max_stat));
1595
1596
1597 for (i=0, count=0; i<app.max_calls; ++i) {
1598
1599 struct call *call = &app.call[i];
1600 struct media_stream *audio = &call->media[0];
1601 pj_time_val dur;
1602 unsigned msec_dur;
1603
1604 if (call->inv == NULL ||
1605 call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
1606 call->connect_time.sec == 0)
1607 {
1608 continue;
1609 }
1610
1611 /* Duration */
1612 call_get_duration(i, &dur);
1613 msec_dur = PJ_TIME_VAL_MSEC(dur);
1614
1615 MIN_(call_dur.min, msec_dur);
1616 MAX_(call_dur.max, msec_dur);
1617 AVG_(call_dur.avg, msec_dur);
1618
1619 /* Connect delay */
1620 if (call->connect_time.sec) {
1621 pj_time_val t = call->connect_time;
1622 PJ_TIME_VAL_SUB(t, call->start_time);
1623 msec_dur = PJ_TIME_VAL_MSEC(t);
1624 } else {
1625 msec_dur = 10;
1626 }
1627
1628 MIN_(call_pdd.min, msec_dur);
1629 MAX_(call_pdd.max, msec_dur);
1630 AVG_(call_pdd.avg, msec_dur);
1631
1632 /* RX Statistisc: */
1633
1634 /* Packets */
1635 MIN_(min_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1636 MAX_(max_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1637 AVG_(avg_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1638
1639 /* Bytes */
1640 MIN_(min_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1641 MAX_(max_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1642 AVG_(avg_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1643
1644
1645 /* Packet loss */
1646 MIN_(min_stat.rx.loss, audio->rtcp.stat.rx.loss);
1647 MAX_(max_stat.rx.loss, audio->rtcp.stat.rx.loss);
1648 AVG_(avg_stat.rx.loss, audio->rtcp.stat.rx.loss);
1649
1650 /* Packet dup */
1651 MIN_(min_stat.rx.dup, audio->rtcp.stat.rx.dup);
1652 MAX_(max_stat.rx.dup, audio->rtcp.stat.rx.dup);
1653 AVG_(avg_stat.rx.dup, audio->rtcp.stat.rx.dup);
1654
1655 /* Packet reorder */
1656 MIN_(min_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1657 MAX_(max_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1658 AVG_(avg_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1659
1660 /* Jitter */
1661 MIN_(min_stat.rx.jitter.min, audio->rtcp.stat.rx.jitter.min);
1662 MAX_(max_stat.rx.jitter.max, audio->rtcp.stat.rx.jitter.max);
Nanang Izzuddin2d4ee7d2008-05-17 14:54:18 +00001663 AVG_(avg_stat.rx.jitter.mean, audio->rtcp.stat.rx.jitter.mean);
Benny Prijono258ece92006-07-22 12:53:04 +00001664
1665
1666 /* TX Statistisc: */
1667
1668 /* Packets */
1669 MIN_(min_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1670 MAX_(max_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1671 AVG_(avg_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1672
1673 /* Bytes */
1674 MIN_(min_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1675 MAX_(max_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1676 AVG_(avg_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1677
1678 /* Packet loss */
1679 MIN_(min_stat.tx.loss, audio->rtcp.stat.tx.loss);
1680 MAX_(max_stat.tx.loss, audio->rtcp.stat.tx.loss);
1681 AVG_(avg_stat.tx.loss, audio->rtcp.stat.tx.loss);
1682
1683 /* Packet dup */
1684 MIN_(min_stat.tx.dup, audio->rtcp.stat.tx.dup);
1685 MAX_(max_stat.tx.dup, audio->rtcp.stat.tx.dup);
1686 AVG_(avg_stat.tx.dup, audio->rtcp.stat.tx.dup);
1687
1688 /* Packet reorder */
1689 MIN_(min_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1690 MAX_(max_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1691 AVG_(avg_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1692
1693 /* Jitter */
1694 MIN_(min_stat.tx.jitter.min, audio->rtcp.stat.tx.jitter.min);
1695 MAX_(max_stat.tx.jitter.max, audio->rtcp.stat.tx.jitter.max);
Nanang Izzuddin2d4ee7d2008-05-17 14:54:18 +00001696 AVG_(avg_stat.tx.jitter.mean, audio->rtcp.stat.tx.jitter.mean);
Benny Prijono258ece92006-07-22 12:53:04 +00001697
1698
1699 /* RTT */
1700 MIN_(min_stat.rtt.min, audio->rtcp.stat.rtt.min);
1701 MAX_(max_stat.rtt.max, audio->rtcp.stat.rtt.max);
Nanang Izzuddin2d4ee7d2008-05-17 14:54:18 +00001702 AVG_(avg_stat.rtt.mean, audio->rtcp.stat.rtt.mean);
Benny Prijono258ece92006-07-22 12:53:04 +00001703
1704 ++count;
1705 }
1706
1707 if (count == 0) {
1708 puts("No active calls");
1709 return;
1710 }
1711
1712 printf("Total %d call(s) active.\n"
1713 " Average Statistics\n"
1714 " min avg max \n"
1715 " -----------------------\n"
1716 " call duration: %7d %7d %7d %s\n"
1717 " connect delay: %7d %7d %7d %s\n"
1718 " RX stat:\n"
1719 " packets: %7s %7s %7s %s\n"
1720 " payload: %7s %7s %7s %s\n"
1721 " loss: %7d %7d %7d %s\n"
1722 " percent loss: %7.3f %7.3f %7.3f %s\n"
1723 " dup: %7d %7d %7d %s\n"
1724 " reorder: %7d %7d %7d %s\n"
1725 " jitter: %7.3f %7.3f %7.3f %s\n"
1726 " TX stat:\n"
1727 " packets: %7s %7s %7s %s\n"
1728 " payload: %7s %7s %7s %s\n"
1729 " loss: %7d %7d %7d %s\n"
1730 " percent loss: %7.3f %7.3f %7.3f %s\n"
1731 " dup: %7d %7d %7d %s\n"
1732 " reorder: %7d %7d %7d %s\n"
1733 " jitter: %7.3f %7.3f %7.3f %s\n"
1734 " RTT : %7.3f %7.3f %7.3f %s\n"
1735 ,
1736 count,
1737 call_dur.min/1000, call_dur.avg/1000, call_dur.max/1000,
1738 "seconds",
1739
1740 call_pdd.min, call_pdd.avg, call_pdd.max,
1741 "ms",
1742
1743 /* rx */
1744
1745 good_number(srx_min, min_stat.rx.pkt),
1746 good_number(srx_avg, avg_stat.rx.pkt),
1747 good_number(srx_max, max_stat.rx.pkt),
1748 "packets",
1749
1750 good_number(brx_min, min_stat.rx.bytes),
1751 good_number(brx_avg, avg_stat.rx.bytes),
1752 good_number(brx_max, max_stat.rx.bytes),
1753 "bytes",
1754
1755 min_stat.rx.loss, avg_stat.rx.loss, max_stat.rx.loss,
1756 "packets",
1757
1758 min_stat.rx.loss*100.0/(min_stat.rx.pkt+min_stat.rx.loss),
1759 avg_stat.rx.loss*100.0/(avg_stat.rx.pkt+avg_stat.rx.loss),
1760 max_stat.rx.loss*100.0/(max_stat.rx.pkt+max_stat.rx.loss),
1761 "%",
1762
1763
1764 min_stat.rx.dup, avg_stat.rx.dup, max_stat.rx.dup,
1765 "packets",
1766
1767 min_stat.rx.reorder, avg_stat.rx.reorder, max_stat.rx.reorder,
1768 "packets",
1769
1770 min_stat.rx.jitter.min/1000.0,
Nanang Izzuddin2d4ee7d2008-05-17 14:54:18 +00001771 avg_stat.rx.jitter.mean/1000.0,
Benny Prijono258ece92006-07-22 12:53:04 +00001772 max_stat.rx.jitter.max/1000.0,
1773 "ms",
1774
1775 /* tx */
1776
1777 good_number(stx_min, min_stat.tx.pkt),
1778 good_number(stx_avg, avg_stat.tx.pkt),
1779 good_number(stx_max, max_stat.tx.pkt),
1780 "packets",
1781
1782 good_number(btx_min, min_stat.tx.bytes),
1783 good_number(btx_avg, avg_stat.tx.bytes),
1784 good_number(btx_max, max_stat.tx.bytes),
1785 "bytes",
1786
1787 min_stat.tx.loss, avg_stat.tx.loss, max_stat.tx.loss,
1788 "packets",
1789
Benny Prijono05784a52006-07-25 11:54:15 +00001790 min_stat.tx.loss*100.0/(min_stat.tx.pkt+min_stat.tx.loss),
1791 avg_stat.tx.loss*100.0/(avg_stat.tx.pkt+avg_stat.tx.loss),
1792 max_stat.tx.loss*100.0/(max_stat.tx.pkt+max_stat.tx.loss),
Benny Prijono258ece92006-07-22 12:53:04 +00001793 "%",
1794
1795 min_stat.tx.dup, avg_stat.tx.dup, max_stat.tx.dup,
1796 "packets",
1797
1798 min_stat.tx.reorder, avg_stat.tx.reorder, max_stat.tx.reorder,
1799 "packets",
1800
1801 min_stat.tx.jitter.min/1000.0,
Nanang Izzuddin2d4ee7d2008-05-17 14:54:18 +00001802 avg_stat.tx.jitter.mean/1000.0,
Benny Prijono258ece92006-07-22 12:53:04 +00001803 max_stat.tx.jitter.max/1000.0,
1804 "ms",
1805
1806 /* rtt */
1807 min_stat.rtt.min/1000.0,
Nanang Izzuddin2d4ee7d2008-05-17 14:54:18 +00001808 avg_stat.rtt.mean/1000.0,
Benny Prijono258ece92006-07-22 12:53:04 +00001809 max_stat.rtt.max/1000.0,
1810 "ms"
1811 );
1812
1813}
1814
1815
Benny Prijono16a6b0e2006-05-12 10:20:03 +00001816#include "siprtp_report.c"
Benny Prijono60b980e2006-04-03 22:41:26 +00001817
1818
1819static void list_calls()
1820{
1821 unsigned i;
1822 puts("List all calls:");
1823 for (i=0; i<app.max_calls; ++i) {
1824 if (!app.call[i].inv)
1825 continue;
1826 print_call(i);
1827 }
1828}
1829
1830static void hangup_call(unsigned index)
1831{
1832 pjsip_tx_data *tdata;
1833 pj_status_t status;
1834
1835 if (app.call[index].inv == NULL)
1836 return;
1837
1838 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1839 if (status==PJ_SUCCESS && tdata!=NULL)
1840 pjsip_inv_send_msg(app.call[index].inv, tdata);
1841}
1842
1843static void hangup_all_calls()
1844{
1845 unsigned i;
1846 for (i=0; i<app.max_calls; ++i) {
1847 if (!app.call[i].inv)
1848 continue;
1849 hangup_call(i);
Benny Prijono1b543242008-01-08 22:50:06 +00001850 pj_thread_sleep(app.call_gap);
Benny Prijono60b980e2006-04-03 22:41:26 +00001851 }
Benny Prijonodeb31962006-06-22 18:51:03 +00001852
1853 /* Wait until all calls are terminated */
1854 for (i=0; i<app.max_calls; ++i) {
1855 while (app.call[i].inv)
1856 pj_thread_sleep(10);
1857 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001858}
1859
1860static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1861{
1862 char *p;
1863
1864 printf("%s (empty to cancel): ", title); fflush(stdout);
Benny Prijono32d267b2009-01-01 22:08:21 +00001865 if (fgets(buf, len, stdin) == NULL)
1866 return PJ_FALSE;
Benny Prijono60b980e2006-04-03 22:41:26 +00001867
1868 /* Remove trailing newlines. */
1869 for (p=buf; ; ++p) {
1870 if (*p=='\r' || *p=='\n') *p='\0';
1871 else if (!*p) break;
1872 }
1873
1874 if (!*buf)
1875 return PJ_FALSE;
1876
1877 return PJ_TRUE;
1878}
1879
1880
1881static const char *MENU =
1882"\n"
1883"Enter menu character:\n"
Benny Prijono258ece92006-07-22 12:53:04 +00001884" s Summary\n"
Benny Prijono60b980e2006-04-03 22:41:26 +00001885" l List all calls\n"
1886" h Hangup a call\n"
1887" H Hangup all calls\n"
1888" q Quit\n"
1889"\n";
1890
1891
1892/* Main screen menu */
1893static void console_main()
1894{
1895 char input1[10];
1896 unsigned i;
1897
Benny Prijono4adcb912006-04-04 13:12:38 +00001898 printf("%s", MENU);
1899
Benny Prijono60b980e2006-04-03 22:41:26 +00001900 for (;;) {
1901 printf(">>> "); fflush(stdout);
Benny Prijono32d267b2009-01-01 22:08:21 +00001902 if (fgets(input1, sizeof(input1), stdin) == NULL) {
1903 puts("EOF while reading stdin, will quit now..");
1904 break;
1905 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001906
1907 switch (input1[0]) {
Benny Prijono258ece92006-07-22 12:53:04 +00001908
1909 case 's':
1910 print_avg_stat();
1911 break;
1912
Benny Prijono60b980e2006-04-03 22:41:26 +00001913 case 'l':
1914 list_calls();
1915 break;
1916
1917 case 'h':
1918 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1919 break;
1920
1921 i = atoi(input1);
1922 hangup_call(i);
1923 break;
1924
1925 case 'H':
1926 hangup_all_calls();
1927 break;
1928
1929 case 'q':
1930 goto on_exit;
1931
1932 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001933 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001934 printf("%s", MENU);
1935 break;
1936 }
1937
1938 fflush(stdout);
1939 }
1940
1941on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001942 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001943}
1944
1945
Benny Prijono4adcb912006-04-04 13:12:38 +00001946/*****************************************************************************
1947 * Below is a simple module to log all incoming and outgoing SIP messages
1948 */
1949
1950
Benny Prijono60b980e2006-04-03 22:41:26 +00001951/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001952static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001953{
1954 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1955 "%s\n"
1956 "--end msg--",
1957 rdata->msg_info.len,
1958 pjsip_rx_data_get_info(rdata),
1959 rdata->pkt_info.src_name,
1960 rdata->pkt_info.src_port,
1961 rdata->msg_info.msg_buf));
1962
1963 /* Always return false, otherwise messages will not get processed! */
1964 return PJ_FALSE;
1965}
1966
1967/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001968static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001969{
1970
1971 /* Important note:
1972 * tp_info field is only valid after outgoing messages has passed
1973 * transport layer. So don't try to access tp_info when the module
1974 * has lower priority than transport layer.
1975 */
1976
1977 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1978 "%s\n"
1979 "--end msg--",
1980 (tdata->buf.cur - tdata->buf.start),
1981 pjsip_tx_data_get_info(tdata),
1982 tdata->tp_info.dst_name,
1983 tdata->tp_info.dst_port,
1984 tdata->buf.start));
1985
1986 /* Always return success, otherwise message will not get sent! */
1987 return PJ_SUCCESS;
1988}
1989
1990/* The module instance. */
1991static pjsip_module msg_logger =
1992{
1993 NULL, NULL, /* prev, next. */
1994 { "mod-siprtp-log", 14 }, /* Name. */
1995 -1, /* Id */
1996 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1997 NULL, /* load() */
1998 NULL, /* start() */
1999 NULL, /* stop() */
2000 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00002001 &logger_on_rx_msg, /* on_rx_request() */
2002 &logger_on_rx_msg, /* on_rx_response() */
2003 &logger_on_tx_msg, /* on_tx_request. */
2004 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00002005 NULL, /* on_tsx_state() */
2006
2007};
2008
2009
2010
Benny Prijono4adcb912006-04-04 13:12:38 +00002011/*****************************************************************************
2012 * Console application custom logging:
2013 */
2014
2015
2016static FILE *log_file;
2017
2018
2019static void app_log_writer(int level, const char *buffer, int len)
2020{
2021 /* Write to both stdout and file. */
2022
2023 if (level <= app.app_log_level)
2024 pj_log_write(level, buffer, len);
2025
2026 if (log_file) {
Benny Prijono32d267b2009-01-01 22:08:21 +00002027 int count = fwrite(buffer, len, 1, log_file);
2028 PJ_UNUSED_ARG(count);
Benny Prijono4adcb912006-04-04 13:12:38 +00002029 fflush(log_file);
2030 }
2031}
2032
2033
2034pj_status_t app_logging_init(void)
2035{
2036 /* Redirect log function to ours */
2037
2038 pj_log_set_log_func( &app_log_writer );
2039
2040 /* If output log file is desired, create the file: */
2041
2042 if (app.log_filename) {
2043 log_file = fopen(app.log_filename, "wt");
2044 if (log_file == NULL) {
2045 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
2046 app.log_filename));
2047 return -1;
2048 }
2049 }
2050
2051 return PJ_SUCCESS;
2052}
2053
2054
2055void app_logging_shutdown(void)
2056{
2057 /* Close logging file, if any: */
2058
2059 if (log_file) {
2060 fclose(log_file);
2061 log_file = NULL;
2062 }
2063}
2064
Benny Prijono60b980e2006-04-03 22:41:26 +00002065
2066/*
2067 * main()
2068 */
2069int main(int argc, char *argv[])
2070{
Benny Prijono4adcb912006-04-04 13:12:38 +00002071 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00002072 pj_status_t status;
2073
Benny Prijono4adcb912006-04-04 13:12:38 +00002074 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00002075 status = pj_init();
2076 if (status != PJ_SUCCESS)
2077 return 1;
2078
Benny Prijono4adcb912006-04-04 13:12:38 +00002079 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00002080 status = init_options(argc, argv);
2081 if (status != PJ_SUCCESS)
2082 return 1;
2083
Benny Prijono410fbae2006-05-03 18:16:06 +00002084 /* Verify options: */
2085
2086 /* Auto-quit can not be specified for UAS */
2087 if (app.auto_quit && app.uri_to_call.slen == 0) {
2088 printf("Error: --auto-quit option only valid for outgoing "
2089 "mode (UAC) only\n");
2090 return 1;
2091 }
2092
Benny Prijono4adcb912006-04-04 13:12:38 +00002093 /* Init logging */
2094 status = app_logging_init();
2095 if (status != PJ_SUCCESS)
2096 return 1;
2097
2098 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00002099 status = init_sip();
2100 if (status != PJ_SUCCESS) {
2101 app_perror(THIS_FILE, "Initialization has failed", status);
2102 destroy_sip();
2103 return 1;
2104 }
2105
Benny Prijono4adcb912006-04-04 13:12:38 +00002106 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00002107 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
2108
Benny Prijono4adcb912006-04-04 13:12:38 +00002109 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00002110 status = init_media();
2111 if (status != PJ_SUCCESS) {
2112 app_perror(THIS_FILE, "Media initialization failed", status);
2113 destroy_sip();
2114 return 1;
2115 }
2116
Benny Prijono9a0eab52006-04-04 19:43:24 +00002117 /* Start worker threads */
Benny Prijono6869dc92007-06-03 00:37:30 +00002118#if PJ_HAS_THREADS
Benny Prijono9a0eab52006-04-04 19:43:24 +00002119 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +00002120 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
2121 0, 0, &app.sip_thread[i]);
Benny Prijono9a0eab52006-04-04 19:43:24 +00002122 }
Benny Prijono6869dc92007-06-03 00:37:30 +00002123#endif
Benny Prijono9a0eab52006-04-04 19:43:24 +00002124
Benny Prijono4adcb912006-04-04 13:12:38 +00002125 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00002126 if (app.uri_to_call.slen) {
2127 unsigned i;
2128
Benny Prijono4adcb912006-04-04 13:12:38 +00002129 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
2130 app.uri_to_call.ptr));
2131
Benny Prijono60b980e2006-04-03 22:41:26 +00002132 for (i=0; i<app.max_calls; ++i) {
2133 status = make_call(&app.uri_to_call);
2134 if (status != PJ_SUCCESS) {
2135 app_perror(THIS_FILE, "Error making call", status);
2136 break;
2137 }
Benny Prijono1b543242008-01-08 22:50:06 +00002138 pj_thread_sleep(app.call_gap);
Benny Prijono60b980e2006-04-03 22:41:26 +00002139 }
Benny Prijono4adcb912006-04-04 13:12:38 +00002140
Benny Prijono410fbae2006-05-03 18:16:06 +00002141 if (app.auto_quit) {
2142 /* Wait for calls to complete */
2143 while (app.uac_calls < app.max_calls)
2144 pj_thread_sleep(100);
2145 pj_thread_sleep(200);
2146 } else {
Benny Prijono6869dc92007-06-03 00:37:30 +00002147#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002148 /* Start user interface loop */
2149 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002150#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002151 }
2152
Benny Prijono4adcb912006-04-04 13:12:38 +00002153 } else {
2154
2155 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
2156 app.max_calls));
Benny Prijono4adcb912006-04-04 13:12:38 +00002157
Benny Prijono6869dc92007-06-03 00:37:30 +00002158#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002159 /* Start user interface loop */
2160 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002161#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002162 }
Benny Prijono60b980e2006-04-03 22:41:26 +00002163
Benny Prijono6869dc92007-06-03 00:37:30 +00002164#if !PJ_HAS_THREADS
2165 PJ_LOG(3,(THIS_FILE, "Press Ctrl-C to quit"));
2166 for (;;) {
2167 pj_time_val t = {0, 10};
2168 pjsip_endpt_handle_events(app.sip_endpt, &t);
2169 }
2170#endif
Benny Prijono4adcb912006-04-04 13:12:38 +00002171
2172 /* Shutting down... */
Benny Prijono4d3aa922006-06-22 22:31:48 +00002173 destroy_sip();
Benny Prijono513795f2006-07-18 21:12:24 +00002174 destroy_media();
Benny Prijono6647d822006-05-20 13:01:07 +00002175
2176 if (app.pool) {
2177 pj_pool_release(app.pool);
2178 app.pool = NULL;
2179 pj_caching_pool_destroy(&app.cp);
2180 }
2181
Benny Prijono4adcb912006-04-04 13:12:38 +00002182 app_logging_shutdown();
2183
Benny Prijono5b1e14d2006-11-25 08:46:48 +00002184 /* Shutdown PJLIB */
2185 pj_shutdown();
Benny Prijono60b980e2006-04-03 22:41:26 +00002186
2187 return 0;
2188}
2189