blob: 5f0a3379f50b126d5ecfa8c6b732ebcb1b1800ed [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 Prijono1b543242008-01-08 22:50:06 +000038" --gap=N -g Set call gapping to N msec (default:0)\n"
Benny Prijono410fbae2006-05-03 18:16:06 +000039" --duration=SEC, -d Set maximum call duration (default:unlimited) \n"
40" --auto-quit, -q Quit when calls have been completed (default:no)\n"
Benny Prijono1b543242008-01-08 22:50:06 +000041" --call-report -R Display report on call termination (default:yes)\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000042"\n"
43" Address and ports options:\n"
44" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
45" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
46" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
47" try to determine local IP address from hostname)\n"
48"\n"
49" Logging Options:\n"
50" --log-level=N, -l Set log verbosity level (default=5)\n"
51" --app-log-level=N Set app screen log verbosity (default=3)\n"
52" --log-file=FILE Write log to file FILE\n"
Benny Prijonofcb36722006-05-18 18:34:21 +000053" --report-file=FILE Write report to file FILE\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000054"\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000055/* Don't support this anymore, because codec is properly examined in
56 pjmedia_session_info_from_sdp() function.
57
Benny Prijonobf13fee2006-04-20 11:13:32 +000058" Codec Options:\n"
59" --a-pt=PT Set audio payload type to PT (default=0)\n"
60" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
61" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
62" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
63" --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000064*/
Benny Prijonobf13fee2006-04-20 11:13:32 +000065;
66
67
Benny Prijono60b980e2006-04-03 22:41:26 +000068/* Include all headers. */
69#include <pjsip.h>
70#include <pjmedia.h>
71#include <pjmedia-codec.h>
72#include <pjsip_ua.h>
73#include <pjsip_simple.h>
74#include <pjlib-util.h>
75#include <pjlib.h>
76
77#include <stdlib.h>
78
Benny Prijono6869dc92007-06-03 00:37:30 +000079/* Uncomment these to disable threads.
80 * NOTE:
81 * when threading is disabled, siprtp won't transmit any
82 * RTP packets.
83 */
84/*
85#undef PJ_HAS_THREADS
86#define PJ_HAS_THREADS 0
87*/
88
Benny Prijono9a0eab52006-04-04 19:43:24 +000089
90#if PJ_HAS_HIGH_RES_TIMER==0
91# error "High resolution timer is needed for this sample"
92#endif
93
Benny Prijono60b980e2006-04-03 22:41:26 +000094#define THIS_FILE "siprtp.c"
95#define MAX_CALLS 1024
Benny Prijono6647d822006-05-20 13:01:07 +000096#define RTP_START_PORT 4000
Benny Prijono60b980e2006-04-03 22:41:26 +000097
98
Benny Prijono4adcb912006-04-04 13:12:38 +000099/* Codec descriptor: */
100struct codec
101{
102 unsigned pt;
103 char* name;
104 unsigned clock_rate;
105 unsigned bit_rate;
106 unsigned ptime;
107 char* description;
108};
109
110
Benny Prijonodeb31962006-06-22 18:51:03 +0000111/* A bidirectional media stream created when the call is active. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000112struct media_stream
113{
114 /* Static: */
Benny Prijonodeb31962006-06-22 18:51:03 +0000115 unsigned call_index; /* Call owner. */
116 unsigned media_index; /* Media index in call. */
117 pjmedia_transport *transport; /* To send/recv RTP/RTCP */
118
119 /* Active? */
120 pj_bool_t active; /* Non-zero if is in call. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000121
122 /* Current stream info: */
123 pjmedia_stream_info si; /* Current stream info. */
124
125 /* More info: */
126 unsigned clock_rate; /* clock rate */
127 unsigned samples_per_frame; /* samples per frame */
128 unsigned bytes_per_frame; /* frame size. */
129
Benny Prijono60b980e2006-04-03 22:41:26 +0000130 /* RTP session: */
131 pjmedia_rtp_session out_sess; /* outgoing RTP session */
132 pjmedia_rtp_session in_sess; /* incoming RTP session */
133
134 /* RTCP stats: */
135 pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
Benny Prijono4adcb912006-04-04 13:12:38 +0000136
Benny Prijono513795f2006-07-18 21:12:24 +0000137 /* Thread: */
138 pj_bool_t thread_quit_flag; /* Stop media thread. */
139 pj_thread_t *thread; /* Media thread. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000140};
141
142
Benny Prijonodeb31962006-06-22 18:51:03 +0000143/* This is a call structure that is created when the application starts
144 * and only destroyed when the application quits.
145 */
Benny Prijono60b980e2006-04-03 22:41:26 +0000146struct call
147{
148 unsigned index;
149 pjsip_inv_session *inv;
150 unsigned media_count;
Benny Prijonodeb31962006-06-22 18:51:03 +0000151 struct media_stream media[1];
Benny Prijono4adcb912006-04-04 13:12:38 +0000152 pj_time_val start_time;
153 pj_time_val response_time;
154 pj_time_val connect_time;
Benny Prijono410fbae2006-05-03 18:16:06 +0000155
156 pj_timer_entry d_timer; /**< Disconnect timer. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000157};
158
159
Benny Prijonodeb31962006-06-22 18:51:03 +0000160/* Application's global variables */
Benny Prijono60b980e2006-04-03 22:41:26 +0000161static struct app
162{
163 unsigned max_calls;
Benny Prijono1b543242008-01-08 22:50:06 +0000164 unsigned call_gap;
165 pj_bool_t call_report;
Benny Prijono410fbae2006-05-03 18:16:06 +0000166 unsigned uac_calls;
167 unsigned duration;
168 pj_bool_t auto_quit;
Benny Prijono60b980e2006-04-03 22:41:26 +0000169 unsigned thread_count;
170 int sip_port;
171 int rtp_start_port;
Benny Prijonodeb31962006-06-22 18:51:03 +0000172 pj_str_t local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +0000173 pj_str_t local_uri;
174 pj_str_t local_contact;
Benny Prijono4adcb912006-04-04 13:12:38 +0000175
176 int app_log_level;
177 int log_level;
178 char *log_filename;
Benny Prijonofcb36722006-05-18 18:34:21 +0000179 char *report_filename;
Benny Prijono4adcb912006-04-04 13:12:38 +0000180
181 struct codec audio_codec;
Benny Prijono60b980e2006-04-03 22:41:26 +0000182
183 pj_str_t uri_to_call;
184
185 pj_caching_pool cp;
186 pj_pool_t *pool;
187
188 pjsip_endpoint *sip_endpt;
189 pj_bool_t thread_quit;
Benny Prijonodeb31962006-06-22 18:51:03 +0000190 pj_thread_t *sip_thread[1];
Benny Prijono60b980e2006-04-03 22:41:26 +0000191
192 pjmedia_endpt *med_endpt;
193 struct call call[MAX_CALLS];
194} app;
195
196
197
198/*
199 * Prototypes:
200 */
201
202/* Callback to be called when SDP negotiation is done in the call: */
203static void call_on_media_update( pjsip_inv_session *inv,
204 pj_status_t status);
205
206/* Callback to be called when invite session's state has changed: */
207static void call_on_state_changed( pjsip_inv_session *inv,
208 pjsip_event *e);
209
210/* Callback to be called when dialog has forked: */
211static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
212
213/* Callback to be called to handle incoming requests outside dialogs: */
214static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
215
216/* Worker thread prototype */
Benny Prijonodeb31962006-06-22 18:51:03 +0000217static int sip_worker_thread(void *arg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000218
219/* Create SDP for call */
220static pj_status_t create_sdp( pj_pool_t *pool,
221 struct call *call,
222 pjmedia_sdp_session **p_sdp);
223
Benny Prijono410fbae2006-05-03 18:16:06 +0000224/* Hangup call */
225static void hangup_call(unsigned index);
226
Benny Prijono60b980e2006-04-03 22:41:26 +0000227/* Destroy the call's media */
228static void destroy_call_media(unsigned call_index);
229
Benny Prijonodeb31962006-06-22 18:51:03 +0000230/* Destroy media. */
231static void destroy_media();
232
233/* This callback is called by media transport on receipt of RTP packet. */
Benny Prijono378484d2008-02-07 11:53:47 +0000234static void on_rx_rtp(void *user_data, void *pkt, pj_ssize_t size);
Benny Prijonodeb31962006-06-22 18:51:03 +0000235
236/* This callback is called by media transport on receipt of RTCP packet. */
Benny Prijono378484d2008-02-07 11:53:47 +0000237static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size);
Benny Prijonodeb31962006-06-22 18:51:03 +0000238
Benny Prijono60b980e2006-04-03 22:41:26 +0000239/* Display error */
240static void app_perror(const char *sender, const char *title,
241 pj_status_t status);
242
Benny Prijonod7a13f12006-04-05 19:08:16 +0000243/* Print call */
244static void print_call(int call_index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000245
246
Benny Prijono60b980e2006-04-03 22:41:26 +0000247/* This is a PJSIP module to be registered by application to handle
248 * incoming requests outside any dialogs/transactions. The main purpose
249 * here is to handle incoming INVITE request message, where we will
250 * create a dialog and INVITE session for it.
251 */
252static pjsip_module mod_siprtp =
253{
254 NULL, NULL, /* prev, next. */
255 { "mod-siprtpapp", 13 }, /* Name. */
256 -1, /* Id */
257 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
258 NULL, /* load() */
259 NULL, /* start() */
260 NULL, /* stop() */
261 NULL, /* unload() */
262 &on_rx_request, /* on_rx_request() */
263 NULL, /* on_rx_response() */
264 NULL, /* on_tx_request. */
265 NULL, /* on_tx_response() */
266 NULL, /* on_tsx_state() */
267};
268
269
Benny Prijono4adcb912006-04-04 13:12:38 +0000270/* Codec constants */
271struct codec audio_codecs[] =
272{
Benny Prijono97f2a372006-06-07 21:21:57 +0000273 { 0, "PCMU", 8000, 64000, 20, "G.711 ULaw" },
274 { 3, "GSM", 8000, 13200, 20, "GSM" },
275 { 4, "G723", 8000, 6400, 30, "G.723.1" },
276 { 8, "PCMA", 8000, 64000, 20, "G.711 ALaw" },
Benny Prijonodeb31962006-06-22 18:51:03 +0000277 { 18, "G729", 8000, 8000, 20, "G.729" },
Benny Prijono4adcb912006-04-04 13:12:38 +0000278};
279
280
Benny Prijono60b980e2006-04-03 22:41:26 +0000281/*
282 * Init SIP stack
283 */
284static pj_status_t init_sip()
285{
Benny Prijonodeb31962006-06-22 18:51:03 +0000286 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000287 pj_status_t status;
288
289 /* init PJLIB-UTIL: */
290 status = pjlib_util_init();
291 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
292
293 /* Must create a pool factory before we can allocate any memory. */
294 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
295
296 /* Create application pool for misc. */
297 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
298
Benny Prijonodeb31962006-06-22 18:51:03 +0000299 /* Create the endpoint: */
300 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
301 &app.sip_endpt);
302 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000303
304
305 /* Add UDP transport. */
306 {
307 pj_sockaddr_in addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000308 pjsip_host_port addrname;
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000309 pjsip_transport *tp;
Benny Prijono60b980e2006-04-03 22:41:26 +0000310
Benny Prijonoac623b32006-07-03 15:19:31 +0000311 pj_bzero(&addr, sizeof(addr));
Benny Prijono8ab968f2007-07-20 08:08:30 +0000312 addr.sin_family = pj_AF_INET();
Benny Prijono60b980e2006-04-03 22:41:26 +0000313 addr.sin_addr.s_addr = 0;
314 addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
315
Benny Prijonodeb31962006-06-22 18:51:03 +0000316 if (app.local_addr.slen) {
Benny Prijono41c96f32006-10-16 11:39:07 +0000317
Benny Prijonodeb31962006-06-22 18:51:03 +0000318 addrname.host = app.local_addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000319 addrname.port = app.sip_port;
Benny Prijono41c96f32006-10-16 11:39:07 +0000320
321 status = pj_sockaddr_in_init(&addr, &app.local_addr,
322 (pj_uint16_t)app.sip_port);
323 if (status != PJ_SUCCESS) {
324 app_perror(THIS_FILE, "Unable to resolve IP interface", status);
325 return status;
326 }
Benny Prijono49ce9a72006-04-05 16:56:19 +0000327 }
328
329 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
Benny Prijonodeb31962006-06-22 18:51:03 +0000330 (app.local_addr.slen ? &addrname:NULL),
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000331 1, &tp);
Benny Prijono49ce9a72006-04-05 16:56:19 +0000332 if (status != PJ_SUCCESS) {
333 app_perror(THIS_FILE, "Unable to start UDP transport", status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000334 return status;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000335 }
Benny Prijono5b1e14d2006-11-25 08:46:48 +0000336
337 PJ_LOG(3,(THIS_FILE, "SIP UDP listening on %.*s:%d",
338 (int)tp->local_name.host.slen, tp->local_name.host.ptr,
339 tp->local_name.port));
Benny Prijono60b980e2006-04-03 22:41:26 +0000340 }
341
342 /*
343 * Init transaction layer.
344 * This will create/initialize transaction hash tables etc.
345 */
346 status = pjsip_tsx_layer_init_module(app.sip_endpt);
347 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
348
349 /* Initialize UA layer. */
350 status = pjsip_ua_init_module( app.sip_endpt, NULL );
351 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
352
Benny Prijonod6066852008-01-03 17:20:39 +0000353 /* Initialize 100rel support */
354 status = pjsip_100rel_init_module(app.sip_endpt);
355 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
356
Benny Prijono60b980e2006-04-03 22:41:26 +0000357 /* Init invite session module. */
358 {
359 pjsip_inv_callback inv_cb;
360
361 /* Init the callback for INVITE session: */
Benny Prijonoac623b32006-07-03 15:19:31 +0000362 pj_bzero(&inv_cb, sizeof(inv_cb));
Benny Prijono60b980e2006-04-03 22:41:26 +0000363 inv_cb.on_state_changed = &call_on_state_changed;
364 inv_cb.on_new_session = &call_on_forked;
365 inv_cb.on_media_update = &call_on_media_update;
366
367 /* Initialize invite session module: */
368 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
369 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
370 }
371
372 /* Register our module to receive incoming requests. */
373 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
374 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
375
Benny Prijonodeb31962006-06-22 18:51:03 +0000376 /* Init calls */
377 for (i=0; i<app.max_calls; ++i)
378 app.call[i].index = i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000379
Benny Prijono60b980e2006-04-03 22:41:26 +0000380 /* Done */
381 return PJ_SUCCESS;
382}
383
384
385/*
386 * Destroy SIP
387 */
388static void destroy_sip()
389{
390 unsigned i;
391
392 app.thread_quit = 1;
393 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000394 if (app.sip_thread[i]) {
395 pj_thread_join(app.sip_thread[i]);
396 pj_thread_destroy(app.sip_thread[i]);
397 app.sip_thread[i] = NULL;
Benny Prijono60b980e2006-04-03 22:41:26 +0000398 }
399 }
400
401 if (app.sip_endpt) {
402 pjsip_endpt_destroy(app.sip_endpt);
403 app.sip_endpt = NULL;
404 }
Benny Prijonoaf1bb1e2006-11-21 12:39:31 +0000405
Benny Prijono60b980e2006-04-03 22:41:26 +0000406}
407
408
409/*
410 * Init media stack.
411 */
412static pj_status_t init_media()
413{
Benny Prijono60b980e2006-04-03 22:41:26 +0000414 unsigned i, count;
415 pj_uint16_t rtp_port;
Benny Prijono60b980e2006-04-03 22:41:26 +0000416 pj_status_t status;
417
418
Benny Prijono60b980e2006-04-03 22:41:26 +0000419 /* Initialize media endpoint so that at least error subsystem is properly
420 * initialized.
421 */
Benny Prijono6869dc92007-06-03 00:37:30 +0000422#if PJ_HAS_THREADS
Benny Prijono513795f2006-07-18 21:12:24 +0000423 status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
Benny Prijono6869dc92007-06-03 00:37:30 +0000424#else
425 status = pjmedia_endpt_create(&app.cp.factory,
426 pjsip_endpt_get_ioqueue(app.sip_endpt),
427 0, &app.med_endpt);
428#endif
Benny Prijono60b980e2006-04-03 22:41:26 +0000429 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
430
431
Benny Prijonodeb31962006-06-22 18:51:03 +0000432 /* Must register codecs to be supported */
Benny Prijonofc24e692007-01-27 18:31:51 +0000433#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
Benny Prijono4d7fd202006-05-14 20:57:20 +0000434 pjmedia_codec_g711_init(app.med_endpt);
Benny Prijonofc24e692007-01-27 18:31:51 +0000435#endif
Benny Prijono4d7fd202006-05-14 20:57:20 +0000436
Benny Prijono60b980e2006-04-03 22:41:26 +0000437 /* RTP port counter */
438 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
439
Benny Prijonodeb31962006-06-22 18:51:03 +0000440 /* Init media transport for all calls. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000441 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
442
Benny Prijonodeb31962006-06-22 18:51:03 +0000443 unsigned j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000444
Benny Prijonodeb31962006-06-22 18:51:03 +0000445 /* Create transport for each media in the call */
446 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
447 /* Repeat binding media socket to next port when fails to bind
448 * to current port number.
449 */
450 int retry;
Benny Prijono60b980e2006-04-03 22:41:26 +0000451
Benny Prijono513795f2006-07-18 21:12:24 +0000452 app.call[i].media[j].call_index = i;
453 app.call[i].media[j].media_index = j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000454
Benny Prijonodeb31962006-06-22 18:51:03 +0000455 status = -1;
456 for (retry=0; retry<100; ++retry,rtp_port+=2) {
457 struct media_stream *m = &app.call[i].media[j];
458
459 status = pjmedia_transport_udp_create2(app.med_endpt,
460 "siprtp",
461 &app.local_addr,
462 rtp_port, 0,
463 &m->transport);
464 if (status == PJ_SUCCESS) {
465 rtp_port += 2;
466 break;
467 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000468 }
Benny Prijono6647d822006-05-20 13:01:07 +0000469 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000470
471 if (status != PJ_SUCCESS)
472 goto on_error;
Benny Prijono60b980e2006-04-03 22:41:26 +0000473 }
474
475 /* Done */
476 return PJ_SUCCESS;
477
478on_error:
Benny Prijonodeb31962006-06-22 18:51:03 +0000479 destroy_media();
Benny Prijono60b980e2006-04-03 22:41:26 +0000480 return status;
481}
482
483
484/*
485 * Destroy media.
486 */
487static void destroy_media()
488{
489 unsigned i;
490
491 for (i=0; i<app.max_calls; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000492 unsigned j;
493 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
494 struct media_stream *m = &app.call[i].media[j];
Benny Prijono60b980e2006-04-03 22:41:26 +0000495
Benny Prijonodeb31962006-06-22 18:51:03 +0000496 if (m->transport) {
497 pjmedia_transport_close(m->transport);
498 m->transport = NULL;
499 }
500 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000501 }
502
503 if (app.med_endpt) {
504 pjmedia_endpt_destroy(app.med_endpt);
505 app.med_endpt = NULL;
506 }
507}
508
509
510/*
511 * Make outgoing call.
512 */
513static pj_status_t make_call(const pj_str_t *dst_uri)
514{
515 unsigned i;
516 struct call *call;
517 pjsip_dialog *dlg;
518 pjmedia_sdp_session *sdp;
519 pjsip_tx_data *tdata;
520 pj_status_t status;
521
522
523 /* Find unused call slot */
524 for (i=0; i<app.max_calls; ++i) {
525 if (app.call[i].inv == NULL)
526 break;
527 }
528
529 if (i == app.max_calls)
530 return PJ_ETOOMANY;
531
532 call = &app.call[i];
533
534 /* Create UAC dialog */
535 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
536 &app.local_uri, /* local URI */
537 &app.local_contact, /* local Contact */
538 dst_uri, /* remote URI */
539 dst_uri, /* remote target */
540 &dlg); /* dialog */
Benny Prijono410fbae2006-05-03 18:16:06 +0000541 if (status != PJ_SUCCESS) {
542 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000543 return status;
Benny Prijono410fbae2006-05-03 18:16:06 +0000544 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000545
546 /* Create SDP */
547 create_sdp( dlg->pool, call, &sdp);
548
549 /* Create the INVITE session. */
550 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
551 if (status != PJ_SUCCESS) {
552 pjsip_dlg_terminate(dlg);
Benny Prijono410fbae2006-05-03 18:16:06 +0000553 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000554 return status;
555 }
556
557
558 /* Attach call data to invite session */
559 call->inv->mod_data[mod_siprtp.id] = call;
560
Benny Prijono4adcb912006-04-04 13:12:38 +0000561 /* Mark start of call */
562 pj_gettimeofday(&call->start_time);
563
Benny Prijono60b980e2006-04-03 22:41:26 +0000564
565 /* Create initial INVITE request.
566 * This INVITE request will contain a perfectly good request and
567 * an SDP body as well.
568 */
569 status = pjsip_inv_invite(call->inv, &tdata);
570 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
571
572
573 /* Send initial INVITE request.
574 * From now on, the invite session's state will be reported to us
575 * via the invite session callbacks.
576 */
577 status = pjsip_inv_send_msg(call->inv, tdata);
578 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
579
580
581 return PJ_SUCCESS;
582}
583
584
585/*
586 * Receive incoming call
587 */
588static void process_incoming_call(pjsip_rx_data *rdata)
589{
Benny Prijonoca4cff22006-07-02 14:18:47 +0000590 unsigned i, options;
Benny Prijono60b980e2006-04-03 22:41:26 +0000591 struct call *call;
592 pjsip_dialog *dlg;
593 pjmedia_sdp_session *sdp;
594 pjsip_tx_data *tdata;
595 pj_status_t status;
596
597 /* Find free call slot */
598 for (i=0; i<app.max_calls; ++i) {
599 if (app.call[i].inv == NULL)
600 break;
601 }
602
603 if (i == app.max_calls) {
604 const pj_str_t reason = pj_str("Too many calls");
605 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
606 500, &reason,
607 NULL, NULL);
608 return;
609 }
610
Benny Prijono513795f2006-07-18 21:12:24 +0000611 call = &app.call[i];
612
Benny Prijonoca4cff22006-07-02 14:18:47 +0000613 /* Verify that we can handle the request. */
614 options = 0;
615 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000616 app.sip_endpt, &tdata);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000617 if (status != PJ_SUCCESS) {
Benny Prijonoca4cff22006-07-02 14:18:47 +0000618 /*
619 * No we can't handle the incoming INVITE request.
620 */
Benny Prijonoca4cff22006-07-02 14:18:47 +0000621 if (tdata) {
622 pjsip_response_addr res_addr;
Benny Prijono513795f2006-07-18 21:12:24 +0000623
Benny Prijonoca4cff22006-07-02 14:18:47 +0000624 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
Benny Prijono513795f2006-07-18 21:12:24 +0000625 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
626 NULL, NULL);
627
Benny Prijonoca4cff22006-07-02 14:18:47 +0000628 } else {
Benny Prijono513795f2006-07-18 21:12:24 +0000629
Benny Prijonoca4cff22006-07-02 14:18:47 +0000630 /* Respond with 500 (Internal Server Error) */
631 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
Benny Prijono513795f2006-07-18 21:12:24 +0000632 NULL, NULL);
Benny Prijonoca4cff22006-07-02 14:18:47 +0000633 }
Benny Prijono513795f2006-07-18 21:12:24 +0000634
Benny Prijonoca4cff22006-07-02 14:18:47 +0000635 return;
Benny Prijono513795f2006-07-18 21:12:24 +0000636 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000637
638 /* Create UAS dialog */
639 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
640 &app.local_contact, &dlg);
641 if (status != PJ_SUCCESS) {
642 const pj_str_t reason = pj_str("Unable to create dialog");
643 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
644 500, &reason,
645 NULL, NULL);
646 return;
647 }
648
649 /* Create SDP */
650 create_sdp( dlg->pool, call, &sdp);
651
652 /* Create UAS invite session */
653 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
654 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000655 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
656 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000657 return;
658 }
659
Benny Prijono4adcb912006-04-04 13:12:38 +0000660
Benny Prijono60b980e2006-04-03 22:41:26 +0000661 /* Attach call data to invite session */
662 call->inv->mod_data[mod_siprtp.id] = call;
663
Benny Prijono4adcb912006-04-04 13:12:38 +0000664 /* Mark start of call */
665 pj_gettimeofday(&call->start_time);
666
667
668
Benny Prijono60b980e2006-04-03 22:41:26 +0000669 /* Create 200 response .*/
670 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
671 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000672 if (status != PJ_SUCCESS) {
673 status = pjsip_inv_initial_answer(call->inv, rdata,
674 PJSIP_SC_NOT_ACCEPTABLE,
675 NULL, NULL, &tdata);
676 if (status == PJ_SUCCESS)
677 pjsip_inv_send_msg(call->inv, tdata);
678 else
679 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
680 return;
681 }
682
Benny Prijono60b980e2006-04-03 22:41:26 +0000683
684 /* Send the 200 response. */
685 status = pjsip_inv_send_msg(call->inv, tdata);
686 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
687
688
689 /* Done */
690}
691
692
693/* Callback to be called when dialog has forked: */
694static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
695{
696 PJ_UNUSED_ARG(inv);
697 PJ_UNUSED_ARG(e);
698
699 PJ_TODO( HANDLE_FORKING );
700}
701
702
703/* Callback to be called to handle incoming requests outside dialogs: */
704static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
705{
Benny Prijono4adcb912006-04-04 13:12:38 +0000706 /* Ignore strandled ACKs (must not send respone */
707 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
708 return PJ_FALSE;
709
Benny Prijono60b980e2006-04-03 22:41:26 +0000710 /* Respond (statelessly) any non-INVITE requests with 500 */
711 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
712 pj_str_t reason = pj_str("Unsupported Operation");
713 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
714 500, &reason,
715 NULL, NULL);
716 return PJ_TRUE;
717 }
718
719 /* Handle incoming INVITE */
720 process_incoming_call(rdata);
721
722 /* Done */
723 return PJ_TRUE;
724}
725
726
Benny Prijono410fbae2006-05-03 18:16:06 +0000727/* Callback timer to disconnect call (limiting call duration) */
728static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
729 struct pj_timer_entry *entry)
730{
731 struct call *call = entry->user_data;
732
733 PJ_UNUSED_ARG(timer_heap);
734
735 entry->id = 0;
736 hangup_call(call->index);
737}
738
739
Benny Prijono60b980e2006-04-03 22:41:26 +0000740/* Callback to be called when invite session's state has changed: */
741static void call_on_state_changed( pjsip_inv_session *inv,
742 pjsip_event *e)
743{
Benny Prijono4adcb912006-04-04 13:12:38 +0000744 struct call *call = inv->mod_data[mod_siprtp.id];
745
Benny Prijono60b980e2006-04-03 22:41:26 +0000746 PJ_UNUSED_ARG(e);
747
Benny Prijono4adcb912006-04-04 13:12:38 +0000748 if (!call)
749 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000750
Benny Prijono4adcb912006-04-04 13:12:38 +0000751 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
752
753 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000754
Benny Prijono410fbae2006-05-03 18:16:06 +0000755 if (call->d_timer.id != 0) {
756 pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
757 call->d_timer.id = 0;
758 }
759
Benny Prijono258ece92006-07-22 12:53:04 +0000760 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%d (%.*s)",
Benny Prijonod7a13f12006-04-05 19:08:16 +0000761 call->index,
Benny Prijono258ece92006-07-22 12:53:04 +0000762 inv->cause,
763 (int)inv->cause_text.slen,
764 inv->cause_text.ptr));
Benny Prijono1b543242008-01-08 22:50:06 +0000765
766 if (app.call_report) {
767 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
768 print_call(call->index);
769 }
Benny Prijonod7a13f12006-04-05 19:08:16 +0000770
771
Benny Prijono60b980e2006-04-03 22:41:26 +0000772 call->inv = NULL;
773 inv->mod_data[mod_siprtp.id] = NULL;
774
775 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000776
777 call->start_time = null_time;
778 call->response_time = null_time;
779 call->connect_time = null_time;
780
Benny Prijono410fbae2006-05-03 18:16:06 +0000781 ++app.uac_calls;
Benny Prijono4adcb912006-04-04 13:12:38 +0000782
783 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
784
785 pj_time_val t;
786
787 pj_gettimeofday(&call->connect_time);
788 if (call->response_time.sec == 0)
789 call->response_time = call->connect_time;
790
791 t = call->connect_time;
792 PJ_TIME_VAL_SUB(t, call->start_time);
793
794 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
795 PJ_TIME_VAL_MSEC(t)));
796
Benny Prijono410fbae2006-05-03 18:16:06 +0000797 if (app.duration != 0) {
798 call->d_timer.id = 1;
799 call->d_timer.user_data = call;
800 call->d_timer.cb = &timer_disconnect_call;
801
802 t.sec = app.duration;
803 t.msec = 0;
804
805 pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
806 }
807
Benny Prijono4adcb912006-04-04 13:12:38 +0000808 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
809 inv->state == PJSIP_INV_STATE_CONNECTING) {
810
811 if (call->response_time.sec == 0)
812 pj_gettimeofday(&call->response_time);
813
Benny Prijono60b980e2006-04-03 22:41:26 +0000814 }
815}
816
817
818/* Utility */
819static void app_perror(const char *sender, const char *title,
820 pj_status_t status)
821{
822 char errmsg[PJ_ERR_MSG_SIZE];
823
824 pj_strerror(status, errmsg, sizeof(errmsg));
825 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
826}
827
828
Benny Prijonodeb31962006-06-22 18:51:03 +0000829/* Worker thread for SIP */
830static int sip_worker_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +0000831{
832 PJ_UNUSED_ARG(arg);
833
834 while (!app.thread_quit) {
Benny Prijono513795f2006-07-18 21:12:24 +0000835 pj_time_val timeout = {0, 10};
Benny Prijono60b980e2006-04-03 22:41:26 +0000836 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
837 }
838
839 return 0;
840}
841
842
Benny Prijono60b980e2006-04-03 22:41:26 +0000843/* Init application options */
844static pj_status_t init_options(int argc, char *argv[])
845{
846 static char ip_addr[32];
847 static char local_uri[64];
848
Benny Prijono4adcb912006-04-04 13:12:38 +0000849 enum { OPT_START,
850 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
Benny Prijonofcb36722006-05-18 18:34:21 +0000851 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
852 OPT_REPORT_FILE };
Benny Prijono4adcb912006-04-04 13:12:38 +0000853
Benny Prijono60b980e2006-04-03 22:41:26 +0000854 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000855 { "count", 1, 0, 'c' },
Benny Prijono1b543242008-01-08 22:50:06 +0000856 { "gap", 1, 0, 'g' },
857 { "call-report", 0, 0, 'R' },
Benny Prijono410fbae2006-05-03 18:16:06 +0000858 { "duration", 1, 0, 'd' },
859 { "auto-quit", 0, 0, 'q' },
Benny Prijono4adcb912006-04-04 13:12:38 +0000860 { "local-port", 1, 0, 'p' },
861 { "rtp-port", 1, 0, 'r' },
862 { "ip-addr", 1, 0, 'i' },
863
864 { "log-level", 1, 0, 'l' },
865 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
866 { "log-file", 1, 0, OPT_LOG_FILE },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000867
Benny Prijonofcb36722006-05-18 18:34:21 +0000868 { "report-file", 1, 0, OPT_REPORT_FILE },
869
Benny Prijono4d7fd202006-05-14 20:57:20 +0000870 /* Don't support this anymore, see comments in USAGE above.
Benny Prijono4adcb912006-04-04 13:12:38 +0000871 { "a-pt", 1, 0, OPT_A_PT },
872 { "a-name", 1, 0, OPT_A_NAME },
873 { "a-clock", 1, 0, OPT_A_CLOCK },
874 { "a-bitrate", 1, 0, OPT_A_BITRATE },
875 { "a-ptime", 1, 0, OPT_A_PTIME },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000876 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000877
Benny Prijono60b980e2006-04-03 22:41:26 +0000878 { NULL, 0, 0, 0 },
879 };
880 int c;
881 int option_index;
882
883 /* Get local IP address for the default IP address */
884 {
885 const pj_str_t *hostname;
886 pj_sockaddr_in tmp_addr;
887 char *addr;
888
889 hostname = pj_gethostname();
890 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
891 addr = pj_inet_ntoa(tmp_addr.sin_addr);
892 pj_ansi_strcpy(ip_addr, addr);
893 }
894
Benny Prijono4adcb912006-04-04 13:12:38 +0000895 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000896 app.max_calls = 1;
897 app.thread_count = 1;
898 app.sip_port = 5060;
Benny Prijono6647d822006-05-20 13:01:07 +0000899 app.rtp_start_port = RTP_START_PORT;
Benny Prijonodeb31962006-06-22 18:51:03 +0000900 app.local_addr = pj_str(ip_addr);
Benny Prijono4adcb912006-04-04 13:12:38 +0000901 app.log_level = 5;
902 app.app_log_level = 3;
903 app.log_filename = NULL;
904
905 /* Default codecs: */
906 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000907
908 /* Parse options */
909 pj_optind = 0;
Benny Prijono1b543242008-01-08 22:50:06 +0000910 while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:g:qR",
Benny Prijono60b980e2006-04-03 22:41:26 +0000911 long_options, &option_index))!=-1)
912 {
913 switch (c) {
914 case 'c':
915 app.max_calls = atoi(pj_optarg);
916 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
917 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
918 return 1;
919 }
920 break;
Benny Prijono1b543242008-01-08 22:50:06 +0000921 case 'g':
922 app.call_gap = atoi(pj_optarg);
923 break;
924 case 'R':
925 app.call_report = PJ_TRUE;
926 break;
Benny Prijono410fbae2006-05-03 18:16:06 +0000927 case 'd':
928 app.duration = atoi(pj_optarg);
929 break;
930 case 'q':
931 app.auto_quit = 1;
932 break;
933
Benny Prijono60b980e2006-04-03 22:41:26 +0000934 case 'p':
935 app.sip_port = atoi(pj_optarg);
936 break;
937 case 'r':
938 app.rtp_start_port = atoi(pj_optarg);
939 break;
940 case 'i':
Benny Prijonodeb31962006-06-22 18:51:03 +0000941 app.local_addr = pj_str(pj_optarg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000942 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000943
944 case 'l':
945 app.log_level = atoi(pj_optarg);
946 break;
947 case OPT_APP_LOG_LEVEL:
948 app.app_log_level = atoi(pj_optarg);
949 break;
950 case OPT_LOG_FILE:
951 app.log_filename = pj_optarg;
952 break;
953
954 case OPT_A_PT:
955 app.audio_codec.pt = atoi(pj_optarg);
956 break;
957 case OPT_A_NAME:
958 app.audio_codec.name = pj_optarg;
959 break;
960 case OPT_A_CLOCK:
961 app.audio_codec.clock_rate = atoi(pj_optarg);
962 break;
963 case OPT_A_BITRATE:
964 app.audio_codec.bit_rate = atoi(pj_optarg);
965 break;
966 case OPT_A_PTIME:
967 app.audio_codec.ptime = atoi(pj_optarg);
968 break;
Benny Prijonofcb36722006-05-18 18:34:21 +0000969 case OPT_REPORT_FILE:
970 app.report_filename = pj_optarg;
971 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000972
Benny Prijono60b980e2006-04-03 22:41:26 +0000973 default:
974 puts(USAGE);
975 return 1;
976 }
977 }
978
979 /* Check if URL is specified */
980 if (pj_optind < argc)
981 app.uri_to_call = pj_str(argv[pj_optind]);
982
983 /* Build local URI and contact */
Benny Prijonodeb31962006-06-22 18:51:03 +0000984 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
Benny Prijono60b980e2006-04-03 22:41:26 +0000985 app.local_uri = pj_str(local_uri);
986 app.local_contact = app.local_uri;
987
988
989 return PJ_SUCCESS;
990}
991
992
Benny Prijono4adcb912006-04-04 13:12:38 +0000993/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000994 * MEDIA STUFFS
995 */
996
997/*
998 * Create SDP session for a call.
999 */
1000static pj_status_t create_sdp( pj_pool_t *pool,
1001 struct call *call,
1002 pjmedia_sdp_session **p_sdp)
1003{
1004 pj_time_val tv;
1005 pjmedia_sdp_session *sdp;
1006 pjmedia_sdp_media *m;
1007 pjmedia_sdp_attr *attr;
Benny Prijonod8179652008-01-23 20:39:07 +00001008 pjmedia_sock_info tpinfo;
Benny Prijono60b980e2006-04-03 22:41:26 +00001009 struct media_stream *audio = &call->media[0];
1010
1011 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
1012
1013
Benny Prijonodeb31962006-06-22 18:51:03 +00001014 /* Get transport info */
Benny Prijono1a68b662008-02-02 09:09:27 +00001015 pj_bzero(&tpinfo, sizeof(tpinfo));
Benny Prijonod8179652008-01-23 20:39:07 +00001016 pjmedia_transport_get_info(audio->transport, &tpinfo);
Benny Prijonodeb31962006-06-22 18:51:03 +00001017
Benny Prijono60b980e2006-04-03 22:41:26 +00001018 /* Create and initialize basic SDP session */
1019 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
1020
1021 pj_gettimeofday(&tv);
1022 sdp->origin.user = pj_str("pjsip-siprtp");
1023 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
1024 sdp->origin.net_type = pj_str("IN");
1025 sdp->origin.addr_type = pj_str("IP4");
1026 sdp->origin.addr = *pj_gethostname();
1027 sdp->name = pj_str("pjsip");
1028
1029 /* Since we only support one media stream at present, put the
1030 * SDP connection line in the session level.
1031 */
1032 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
1033 sdp->conn->net_type = pj_str("IN");
1034 sdp->conn->addr_type = pj_str("IP4");
Benny Prijonodeb31962006-06-22 18:51:03 +00001035 sdp->conn->addr = app.local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +00001036
1037
1038 /* SDP time and attributes. */
1039 sdp->time.start = sdp->time.stop = 0;
1040 sdp->attr_count = 0;
1041
1042 /* Create media stream 0: */
1043
1044 sdp->media_count = 1;
1045 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
1046 sdp->media[0] = m;
1047
1048 /* Standard media info: */
1049 m->desc.media = pj_str("audio");
Benny Prijonod8179652008-01-23 20:39:07 +00001050 m->desc.port = pj_ntohs(tpinfo.rtp_addr_name.ipv4.sin_port);
Benny Prijono60b980e2006-04-03 22:41:26 +00001051 m->desc.port_count = 1;
1052 m->desc.transport = pj_str("RTP/AVP");
1053
1054 /* Add format and rtpmap for each codec. */
1055 m->desc.fmt_count = 1;
1056 m->attr_count = 0;
1057
1058 {
1059 pjmedia_sdp_rtpmap rtpmap;
1060 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +00001061 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +00001062
Benny Prijono4adcb912006-04-04 13:12:38 +00001063 sprintf(ptstr, "%d", app.audio_codec.pt);
1064 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1065 rtpmap.pt = m->desc.fmt[0];
1066 rtpmap.clock_rate = app.audio_codec.clock_rate;
1067 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +00001068 rtpmap.param.slen = 0;
1069
1070 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1071 m->attr[m->attr_count++] = attr;
1072 }
1073
1074 /* Add sendrecv attribute. */
1075 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1076 attr->name = pj_str("sendrecv");
1077 m->attr[m->attr_count++] = attr;
1078
1079#if 1
1080 /*
1081 * Add support telephony event
1082 */
Benny Prijono4adcb912006-04-04 13:12:38 +00001083 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +00001084 /* Add rtpmap. */
1085 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1086 attr->name = pj_str("rtpmap");
Benny Prijonod95abb42006-10-20 10:18:44 +00001087 attr->value = pj_str("121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +00001088 m->attr[m->attr_count++] = attr;
1089 /* Add fmtp */
1090 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1091 attr->name = pj_str("fmtp");
Benny Prijonod95abb42006-10-20 10:18:44 +00001092 attr->value = pj_str("121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +00001093 m->attr[m->attr_count++] = attr;
1094#endif
1095
1096 /* Done */
1097 *p_sdp = sdp;
1098
1099 return PJ_SUCCESS;
1100}
1101
1102
Benny Prijono513795f2006-07-18 21:12:24 +00001103#if defined(PJ_WIN32) && PJ_WIN32 != 0
1104#include <windows.h>
1105static void boost_priority(void)
1106{
1107 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
1108 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
1109}
1110
Benny Prijono7bef0f52006-07-31 18:54:06 +00001111#elif defined(PJ_LINUX) && PJ_LINUX != 0
1112#include <pthread.h>
1113static void boost_priority(void)
1114{
1115#define POLICY SCHED_FIFO
Benny Prijono7bef0f52006-07-31 18:54:06 +00001116 struct sched_param tp;
1117 int max_prio;
1118 int policy;
1119 int rc;
1120
1121 if (sched_get_priority_min(POLICY) < sched_get_priority_max(POLICY))
1122 max_prio = sched_get_priority_max(POLICY)-1;
1123 else
1124 max_prio = sched_get_priority_max(POLICY)+1;
1125
1126 /*
1127 * Adjust process scheduling algorithm and priority
1128 */
1129 rc = sched_getparam(0, &tp);
1130 if (rc != 0) {
1131 app_perror( THIS_FILE, "sched_getparam error",
1132 PJ_RETURN_OS_ERROR(rc));
1133 return;
1134 }
1135 tp.__sched_priority = max_prio;
1136
1137 rc = sched_setscheduler(0, POLICY, &tp);
1138 if (rc != 0) {
1139 app_perror( THIS_FILE, "sched_setscheduler error",
1140 PJ_RETURN_OS_ERROR(rc));
1141 }
1142
1143 PJ_LOG(4, (THIS_FILE, "New process policy=%d, priority=%d",
1144 policy, tp.__sched_priority));
1145
1146 /*
1147 * Adjust thread scheduling algorithm and priority
1148 */
1149 rc = pthread_getschedparam(pthread_self(), &policy, &tp);
1150 if (rc != 0) {
1151 app_perror( THIS_FILE, "pthread_getschedparam error",
1152 PJ_RETURN_OS_ERROR(rc));
1153 return;
1154 }
1155
1156 PJ_LOG(4, (THIS_FILE, "Old thread policy=%d, priority=%d",
1157 policy, tp.__sched_priority));
1158
1159 policy = POLICY;
1160 tp.__sched_priority = max_prio;
1161
1162 rc = pthread_setschedparam(pthread_self(), policy, &tp);
1163 if (rc != 0) {
1164 app_perror( THIS_FILE, "pthread_setschedparam error",
1165 PJ_RETURN_OS_ERROR(rc));
1166 return;
1167 }
1168
1169 PJ_LOG(4, (THIS_FILE, "New thread policy=%d, priority=%d",
1170 policy, tp.__sched_priority));
1171}
1172
Benny Prijono513795f2006-07-18 21:12:24 +00001173#else
1174# define boost_priority()
1175#endif
1176
1177
Benny Prijonodeb31962006-06-22 18:51:03 +00001178/*
1179 * This callback is called by media transport on receipt of RTP packet.
1180 */
Benny Prijono378484d2008-02-07 11:53:47 +00001181static void on_rx_rtp(void *user_data, void *pkt, pj_ssize_t size)
Benny Prijonodeb31962006-06-22 18:51:03 +00001182{
1183 struct media_stream *strm;
1184 pj_status_t status;
1185 const pjmedia_rtp_hdr *hdr;
1186 const void *payload;
1187 unsigned payload_len;
1188
1189 strm = user_data;
1190
1191 /* Discard packet if media is inactive */
1192 if (!strm->active)
1193 return;
1194
1195 /* Check for errors */
1196 if (size < 0) {
1197 app_perror(THIS_FILE, "RTP recv() error", -size);
1198 return;
1199 }
1200
1201 /* Decode RTP packet. */
1202 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1203 pkt, size,
1204 &hdr, &payload, &payload_len);
1205 if (status != PJ_SUCCESS) {
1206 app_perror(THIS_FILE, "RTP decode error", status);
1207 return;
1208 }
1209
1210 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1211
1212 /* Update the RTCP session. */
1213 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1214 pj_ntohl(hdr->ts), payload_len);
1215
1216 /* Update RTP session */
1217 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1218
1219}
1220
1221/*
1222 * This callback is called by media transport on receipt of RTCP packet.
1223 */
Benny Prijono378484d2008-02-07 11:53:47 +00001224static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size)
Benny Prijonodeb31962006-06-22 18:51:03 +00001225{
1226 struct media_stream *strm;
1227
1228 strm = user_data;
1229
1230 /* Discard packet if media is inactive */
1231 if (!strm->active)
1232 return;
1233
1234 /* Check for errors */
1235 if (size < 0) {
1236 app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
1237 return;
1238 }
1239
1240 /* Update RTCP session */
1241 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1242}
1243
1244
Benny Prijono513795f2006-07-18 21:12:24 +00001245/*
1246 * Media thread
1247 *
1248 * This is the thread to send and receive both RTP and RTCP packets.
1249 */
1250static int media_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +00001251{
Benny Prijono513795f2006-07-18 21:12:24 +00001252 enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
1253 struct media_stream *strm = arg;
1254 char packet[1500];
1255 unsigned msec_interval;
1256 pj_timestamp freq, next_rtp, next_rtcp;
Benny Prijono60b980e2006-04-03 22:41:26 +00001257
Benny Prijono6647d822006-05-20 13:01:07 +00001258
Benny Prijono513795f2006-07-18 21:12:24 +00001259 /* Boost thread priority if necessary */
1260 boost_priority();
Benny Prijono6647d822006-05-20 13:01:07 +00001261
Benny Prijono513795f2006-07-18 21:12:24 +00001262 /* Let things settle */
Benny Prijono258ece92006-07-22 12:53:04 +00001263 pj_thread_sleep(100);
Benny Prijono6647d822006-05-20 13:01:07 +00001264
Benny Prijono513795f2006-07-18 21:12:24 +00001265 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1266 pj_get_timestamp_freq(&freq);
1267
1268 pj_get_timestamp(&next_rtp);
1269 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
1270
1271 next_rtcp = next_rtp;
1272 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
1273
1274
1275 while (!strm->thread_quit_flag) {
1276 pj_timestamp now, lesser;
1277 pj_time_val timeout;
1278 pj_bool_t send_rtp, send_rtcp;
1279
1280 send_rtp = send_rtcp = PJ_FALSE;
1281
1282 /* Determine how long to sleep */
1283 if (next_rtp.u64 < next_rtcp.u64) {
1284 lesser = next_rtp;
1285 send_rtp = PJ_TRUE;
1286 } else {
1287 lesser = next_rtcp;
1288 send_rtcp = PJ_TRUE;
1289 }
1290
1291 pj_get_timestamp(&now);
1292 if (lesser.u64 <= now.u64) {
1293 timeout.sec = timeout.msec = 0;
1294 //printf("immediate "); fflush(stdout);
1295 } else {
1296 pj_uint64_t tick_delay;
1297 tick_delay = lesser.u64 - now.u64;
1298 timeout.sec = 0;
1299 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1300 pj_time_val_normalize(&timeout);
1301
1302 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
1303 }
1304
1305 /* Wait for next interval */
1306 //if (timeout.sec!=0 && timeout.msec!=0) {
1307 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
1308 if (strm->thread_quit_flag)
1309 break;
1310 //}
1311
1312 pj_get_timestamp(&now);
1313
1314 if (send_rtp || next_rtp.u64 <= now.u64) {
1315 /*
1316 * Time to send RTP packet.
1317 */
1318 pj_status_t status;
Benny Prijonoe960bb52007-01-21 17:53:39 +00001319 const void *p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001320 const pjmedia_rtp_hdr *hdr;
1321 pj_ssize_t size;
1322 int hdrlen;
1323
1324 /* Format RTP header */
1325 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1326 0, /* marker bit */
1327 strm->bytes_per_frame,
1328 strm->samples_per_frame,
Benny Prijonoe960bb52007-01-21 17:53:39 +00001329 &p_hdr, &hdrlen);
Benny Prijono513795f2006-07-18 21:12:24 +00001330 if (status == PJ_SUCCESS) {
1331
1332 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
Benny Prijonoe960bb52007-01-21 17:53:39 +00001333
1334 hdr = (const pjmedia_rtp_hdr*) p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001335
1336 /* Copy RTP header to packet */
1337 pj_memcpy(packet, hdr, hdrlen);
1338
1339 /* Zero the payload */
1340 pj_bzero(packet+hdrlen, strm->bytes_per_frame);
1341
1342 /* Send RTP packet */
1343 size = hdrlen + strm->bytes_per_frame;
1344 status = pjmedia_transport_send_rtp(strm->transport,
1345 packet, size);
1346 if (status != PJ_SUCCESS)
1347 app_perror(THIS_FILE, "Error sending RTP packet", status);
1348
1349 } else {
1350 pj_assert(!"RTP encode() error");
1351 }
1352
1353 /* Update RTCP SR */
1354 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1355
1356 /* Schedule next send */
1357 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
1358 }
1359
1360
1361 if (send_rtcp || next_rtcp.u64 <= now.u64) {
1362 /*
1363 * Time to send RTCP packet.
1364 */
Benny Prijonofe81cfa2007-09-20 11:30:59 +00001365 void *rtcp_pkt;
Benny Prijono513795f2006-07-18 21:12:24 +00001366 int rtcp_len;
1367 pj_ssize_t size;
1368 pj_status_t status;
1369
1370 /* Build RTCP packet */
1371 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1372
Benny Prijono4d3aa922006-06-22 22:31:48 +00001373
Benny Prijono513795f2006-07-18 21:12:24 +00001374 /* Send packet */
1375 size = rtcp_len;
1376 status = pjmedia_transport_send_rtcp(strm->transport,
1377 rtcp_pkt, size);
1378 if (status != PJ_SUCCESS) {
1379 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1380 }
1381
1382 /* Schedule next send */
1383 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
1384 1000);
1385 }
1386 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001387
Benny Prijono513795f2006-07-18 21:12:24 +00001388 return 0;
Benny Prijono60b980e2006-04-03 22:41:26 +00001389}
1390
Benny Prijono513795f2006-07-18 21:12:24 +00001391
Benny Prijono60b980e2006-04-03 22:41:26 +00001392/* Callback to be called when SDP negotiation is done in the call: */
1393static void call_on_media_update( pjsip_inv_session *inv,
1394 pj_status_t status)
1395{
1396 struct call *call;
1397 pj_pool_t *pool;
1398 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001399 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001400 struct codec *codec_desc = NULL;
1401 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001402
1403 call = inv->mod_data[mod_siprtp.id];
1404 pool = inv->dlg->pool;
1405 audio = &call->media[0];
1406
1407 /* If this is a mid-call media update, then destroy existing media */
Benny Prijono513795f2006-07-18 21:12:24 +00001408 if (audio->thread != NULL)
Benny Prijono60b980e2006-04-03 22:41:26 +00001409 destroy_call_media(call->index);
1410
1411
1412 /* Do nothing if media negotiation has failed */
1413 if (status != PJ_SUCCESS) {
1414 app_perror(THIS_FILE, "SDP negotiation failed", status);
1415 return;
1416 }
1417
1418
1419 /* Capture stream definition from the SDP */
1420 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1421 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1422
1423 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
Benny Prijonob04c9e02006-05-17 17:17:39 +00001424 local_sdp, remote_sdp, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001425 if (status != PJ_SUCCESS) {
1426 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1427 return;
1428 }
1429
Benny Prijono4adcb912006-04-04 13:12:38 +00001430 /* Get the remainder of codec information from codec descriptor */
1431 if (audio->si.fmt.pt == app.audio_codec.pt)
1432 codec_desc = &app.audio_codec;
1433 else {
1434 /* Find the codec description in codec array */
1435 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1436 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1437 codec_desc = &audio_codecs[i];
1438 break;
1439 }
1440 }
1441
1442 if (codec_desc == NULL) {
1443 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1444 return;
1445 }
1446 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001447
Benny Prijono15953012006-04-27 22:37:08 +00001448 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001449 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1450 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001451
1452
1453 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001454 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001455 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001456 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001457 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001458
Benny Prijono4adcb912006-04-04 13:12:38 +00001459
Benny Prijonodeb31962006-06-22 18:51:03 +00001460 /* Attach media to transport */
1461 status = pjmedia_transport_attach(audio->transport, audio,
1462 &audio->si.rem_addr,
Benny Prijono513795f2006-07-18 21:12:24 +00001463 &audio->si.rem_rtcp,
Benny Prijonodeb31962006-06-22 18:51:03 +00001464 sizeof(pj_sockaddr_in),
1465 &on_rx_rtp,
1466 &on_rx_rtcp);
1467 if (status != PJ_SUCCESS) {
1468 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1469 return;
1470 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001471
Benny Prijono513795f2006-07-18 21:12:24 +00001472 /* Start media thread. */
1473 audio->thread_quit_flag = 0;
Benny Prijono6869dc92007-06-03 00:37:30 +00001474#if PJ_HAS_THREADS
Benny Prijono513795f2006-07-18 21:12:24 +00001475 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1476 0, 0, &audio->thread);
1477 if (status != PJ_SUCCESS) {
1478 app_perror(THIS_FILE, "Error creating media thread", status);
1479 return;
1480 }
Benny Prijono6869dc92007-06-03 00:37:30 +00001481#endif
Benny Prijono513795f2006-07-18 21:12:24 +00001482
Benny Prijonodeb31962006-06-22 18:51:03 +00001483 /* Set the media as active */
1484 audio->active = PJ_TRUE;
Benny Prijono60b980e2006-04-03 22:41:26 +00001485}
1486
1487
1488
1489/* Destroy call's media */
1490static void destroy_call_media(unsigned call_index)
1491{
1492 struct media_stream *audio = &app.call[call_index].media[0];
1493
Benny Prijono5a9e2042006-11-14 13:35:20 +00001494 if (audio) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001495 audio->active = PJ_FALSE;
1496
Benny Prijono5a9e2042006-11-14 13:35:20 +00001497 if (audio->thread) {
1498 audio->thread_quit_flag = 1;
1499 pj_thread_join(audio->thread);
1500 pj_thread_destroy(audio->thread);
1501 audio->thread = NULL;
1502 audio->thread_quit_flag = 0;
1503 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001504
Benny Prijono5b1e14d2006-11-25 08:46:48 +00001505 pjmedia_transport_detach(audio->transport, audio);
Benny Prijono60b980e2006-04-03 22:41:26 +00001506 }
1507}
1508
Benny Prijono513795f2006-07-18 21:12:24 +00001509
Benny Prijono4adcb912006-04-04 13:12:38 +00001510/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001511 * USER INTERFACE STUFFS
1512 */
Benny Prijono258ece92006-07-22 12:53:04 +00001513
1514static void call_get_duration(int call_index, pj_time_val *dur)
1515{
1516 struct call *call = &app.call[call_index];
1517 pjsip_inv_session *inv;
1518
1519 dur->sec = dur->msec = 0;
1520
1521 if (!call)
1522 return;
1523
1524 inv = call->inv;
1525 if (!inv)
1526 return;
1527
1528 if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
1529
1530 pj_gettimeofday(dur);
1531 PJ_TIME_VAL_SUB((*dur), call->connect_time);
1532 }
1533}
1534
1535
1536static const char *good_number(char *buf, pj_int32_t val)
1537{
1538 if (val < 1000) {
1539 pj_ansi_sprintf(buf, "%d", val);
1540 } else if (val < 1000000) {
1541 pj_ansi_sprintf(buf, "%d.%02dK",
1542 val / 1000,
1543 (val % 1000) / 100);
1544 } else {
1545 pj_ansi_sprintf(buf, "%d.%02dM",
1546 val / 1000000,
1547 (val % 1000000) / 10000);
1548 }
1549
1550 return buf;
1551}
1552
1553
1554
1555static void print_avg_stat(void)
1556{
1557#define MIN_(var,val) if ((int)val < (int)var) var = val
1558#define MAX_(var,val) if ((int)val > (int)var) var = val
1559#define AVG_(var,val) var = ( ((var * count) + val) / (count+1) )
1560#define BIGVAL 0x7FFFFFFFL
1561 struct stat_entry
1562 {
1563 int min, avg, max;
1564 };
1565
1566 struct stat_entry call_dur, call_pdd;
1567 pjmedia_rtcp_stat min_stat, avg_stat, max_stat;
1568
1569 char srx_min[16], srx_avg[16], srx_max[16];
1570 char brx_min[16], brx_avg[16], brx_max[16];
1571 char stx_min[16], stx_avg[16], stx_max[16];
1572 char btx_min[16], btx_avg[16], btx_max[16];
1573
1574
1575 unsigned i, count;
1576
1577 pj_bzero(&call_dur, sizeof(call_dur));
1578 call_dur.min = BIGVAL;
1579
1580 pj_bzero(&call_pdd, sizeof(call_pdd));
1581 call_pdd.min = BIGVAL;
1582
1583 pj_bzero(&min_stat, sizeof(min_stat));
1584 min_stat.rx.pkt = min_stat.tx.pkt = BIGVAL;
1585 min_stat.rx.bytes = min_stat.tx.bytes = BIGVAL;
1586 min_stat.rx.loss = min_stat.tx.loss = BIGVAL;
1587 min_stat.rx.dup = min_stat.tx.dup = BIGVAL;
1588 min_stat.rx.reorder = min_stat.tx.reorder = BIGVAL;
1589 min_stat.rx.jitter.min = min_stat.tx.jitter.min = BIGVAL;
1590 min_stat.rtt.min = BIGVAL;
1591
1592 pj_bzero(&avg_stat, sizeof(avg_stat));
1593 pj_bzero(&max_stat, sizeof(max_stat));
1594
1595
1596 for (i=0, count=0; i<app.max_calls; ++i) {
1597
1598 struct call *call = &app.call[i];
1599 struct media_stream *audio = &call->media[0];
1600 pj_time_val dur;
1601 unsigned msec_dur;
1602
1603 if (call->inv == NULL ||
1604 call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
1605 call->connect_time.sec == 0)
1606 {
1607 continue;
1608 }
1609
1610 /* Duration */
1611 call_get_duration(i, &dur);
1612 msec_dur = PJ_TIME_VAL_MSEC(dur);
1613
1614 MIN_(call_dur.min, msec_dur);
1615 MAX_(call_dur.max, msec_dur);
1616 AVG_(call_dur.avg, msec_dur);
1617
1618 /* Connect delay */
1619 if (call->connect_time.sec) {
1620 pj_time_val t = call->connect_time;
1621 PJ_TIME_VAL_SUB(t, call->start_time);
1622 msec_dur = PJ_TIME_VAL_MSEC(t);
1623 } else {
1624 msec_dur = 10;
1625 }
1626
1627 MIN_(call_pdd.min, msec_dur);
1628 MAX_(call_pdd.max, msec_dur);
1629 AVG_(call_pdd.avg, msec_dur);
1630
1631 /* RX Statistisc: */
1632
1633 /* Packets */
1634 MIN_(min_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1635 MAX_(max_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1636 AVG_(avg_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1637
1638 /* Bytes */
1639 MIN_(min_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1640 MAX_(max_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1641 AVG_(avg_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1642
1643
1644 /* Packet loss */
1645 MIN_(min_stat.rx.loss, audio->rtcp.stat.rx.loss);
1646 MAX_(max_stat.rx.loss, audio->rtcp.stat.rx.loss);
1647 AVG_(avg_stat.rx.loss, audio->rtcp.stat.rx.loss);
1648
1649 /* Packet dup */
1650 MIN_(min_stat.rx.dup, audio->rtcp.stat.rx.dup);
1651 MAX_(max_stat.rx.dup, audio->rtcp.stat.rx.dup);
1652 AVG_(avg_stat.rx.dup, audio->rtcp.stat.rx.dup);
1653
1654 /* Packet reorder */
1655 MIN_(min_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1656 MAX_(max_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1657 AVG_(avg_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1658
1659 /* Jitter */
1660 MIN_(min_stat.rx.jitter.min, audio->rtcp.stat.rx.jitter.min);
1661 MAX_(max_stat.rx.jitter.max, audio->rtcp.stat.rx.jitter.max);
1662 AVG_(avg_stat.rx.jitter.avg, audio->rtcp.stat.rx.jitter.avg);
1663
1664
1665 /* TX Statistisc: */
1666
1667 /* Packets */
1668 MIN_(min_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1669 MAX_(max_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1670 AVG_(avg_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1671
1672 /* Bytes */
1673 MIN_(min_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1674 MAX_(max_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1675 AVG_(avg_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1676
1677 /* Packet loss */
1678 MIN_(min_stat.tx.loss, audio->rtcp.stat.tx.loss);
1679 MAX_(max_stat.tx.loss, audio->rtcp.stat.tx.loss);
1680 AVG_(avg_stat.tx.loss, audio->rtcp.stat.tx.loss);
1681
1682 /* Packet dup */
1683 MIN_(min_stat.tx.dup, audio->rtcp.stat.tx.dup);
1684 MAX_(max_stat.tx.dup, audio->rtcp.stat.tx.dup);
1685 AVG_(avg_stat.tx.dup, audio->rtcp.stat.tx.dup);
1686
1687 /* Packet reorder */
1688 MIN_(min_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1689 MAX_(max_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1690 AVG_(avg_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1691
1692 /* Jitter */
1693 MIN_(min_stat.tx.jitter.min, audio->rtcp.stat.tx.jitter.min);
1694 MAX_(max_stat.tx.jitter.max, audio->rtcp.stat.tx.jitter.max);
1695 AVG_(avg_stat.tx.jitter.avg, audio->rtcp.stat.tx.jitter.avg);
1696
1697
1698 /* RTT */
1699 MIN_(min_stat.rtt.min, audio->rtcp.stat.rtt.min);
1700 MAX_(max_stat.rtt.max, audio->rtcp.stat.rtt.max);
1701 AVG_(avg_stat.rtt.avg, audio->rtcp.stat.rtt.avg);
1702
1703 ++count;
1704 }
1705
1706 if (count == 0) {
1707 puts("No active calls");
1708 return;
1709 }
1710
1711 printf("Total %d call(s) active.\n"
1712 " Average Statistics\n"
1713 " min avg max \n"
1714 " -----------------------\n"
1715 " call duration: %7d %7d %7d %s\n"
1716 " connect delay: %7d %7d %7d %s\n"
1717 " RX stat:\n"
1718 " packets: %7s %7s %7s %s\n"
1719 " payload: %7s %7s %7s %s\n"
1720 " loss: %7d %7d %7d %s\n"
1721 " percent loss: %7.3f %7.3f %7.3f %s\n"
1722 " dup: %7d %7d %7d %s\n"
1723 " reorder: %7d %7d %7d %s\n"
1724 " jitter: %7.3f %7.3f %7.3f %s\n"
1725 " TX stat:\n"
1726 " packets: %7s %7s %7s %s\n"
1727 " payload: %7s %7s %7s %s\n"
1728 " loss: %7d %7d %7d %s\n"
1729 " percent loss: %7.3f %7.3f %7.3f %s\n"
1730 " dup: %7d %7d %7d %s\n"
1731 " reorder: %7d %7d %7d %s\n"
1732 " jitter: %7.3f %7.3f %7.3f %s\n"
1733 " RTT : %7.3f %7.3f %7.3f %s\n"
1734 ,
1735 count,
1736 call_dur.min/1000, call_dur.avg/1000, call_dur.max/1000,
1737 "seconds",
1738
1739 call_pdd.min, call_pdd.avg, call_pdd.max,
1740 "ms",
1741
1742 /* rx */
1743
1744 good_number(srx_min, min_stat.rx.pkt),
1745 good_number(srx_avg, avg_stat.rx.pkt),
1746 good_number(srx_max, max_stat.rx.pkt),
1747 "packets",
1748
1749 good_number(brx_min, min_stat.rx.bytes),
1750 good_number(brx_avg, avg_stat.rx.bytes),
1751 good_number(brx_max, max_stat.rx.bytes),
1752 "bytes",
1753
1754 min_stat.rx.loss, avg_stat.rx.loss, max_stat.rx.loss,
1755 "packets",
1756
1757 min_stat.rx.loss*100.0/(min_stat.rx.pkt+min_stat.rx.loss),
1758 avg_stat.rx.loss*100.0/(avg_stat.rx.pkt+avg_stat.rx.loss),
1759 max_stat.rx.loss*100.0/(max_stat.rx.pkt+max_stat.rx.loss),
1760 "%",
1761
1762
1763 min_stat.rx.dup, avg_stat.rx.dup, max_stat.rx.dup,
1764 "packets",
1765
1766 min_stat.rx.reorder, avg_stat.rx.reorder, max_stat.rx.reorder,
1767 "packets",
1768
1769 min_stat.rx.jitter.min/1000.0,
1770 avg_stat.rx.jitter.avg/1000.0,
1771 max_stat.rx.jitter.max/1000.0,
1772 "ms",
1773
1774 /* tx */
1775
1776 good_number(stx_min, min_stat.tx.pkt),
1777 good_number(stx_avg, avg_stat.tx.pkt),
1778 good_number(stx_max, max_stat.tx.pkt),
1779 "packets",
1780
1781 good_number(btx_min, min_stat.tx.bytes),
1782 good_number(btx_avg, avg_stat.tx.bytes),
1783 good_number(btx_max, max_stat.tx.bytes),
1784 "bytes",
1785
1786 min_stat.tx.loss, avg_stat.tx.loss, max_stat.tx.loss,
1787 "packets",
1788
Benny Prijono05784a52006-07-25 11:54:15 +00001789 min_stat.tx.loss*100.0/(min_stat.tx.pkt+min_stat.tx.loss),
1790 avg_stat.tx.loss*100.0/(avg_stat.tx.pkt+avg_stat.tx.loss),
1791 max_stat.tx.loss*100.0/(max_stat.tx.pkt+max_stat.tx.loss),
Benny Prijono258ece92006-07-22 12:53:04 +00001792 "%",
1793
1794 min_stat.tx.dup, avg_stat.tx.dup, max_stat.tx.dup,
1795 "packets",
1796
1797 min_stat.tx.reorder, avg_stat.tx.reorder, max_stat.tx.reorder,
1798 "packets",
1799
1800 min_stat.tx.jitter.min/1000.0,
1801 avg_stat.tx.jitter.avg/1000.0,
1802 max_stat.tx.jitter.max/1000.0,
1803 "ms",
1804
1805 /* rtt */
1806 min_stat.rtt.min/1000.0,
1807 avg_stat.rtt.avg/1000.0,
1808 max_stat.rtt.max/1000.0,
1809 "ms"
1810 );
1811
1812}
1813
1814
Benny Prijono16a6b0e2006-05-12 10:20:03 +00001815#include "siprtp_report.c"
Benny Prijono60b980e2006-04-03 22:41:26 +00001816
1817
1818static void list_calls()
1819{
1820 unsigned i;
1821 puts("List all calls:");
1822 for (i=0; i<app.max_calls; ++i) {
1823 if (!app.call[i].inv)
1824 continue;
1825 print_call(i);
1826 }
1827}
1828
1829static void hangup_call(unsigned index)
1830{
1831 pjsip_tx_data *tdata;
1832 pj_status_t status;
1833
1834 if (app.call[index].inv == NULL)
1835 return;
1836
1837 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1838 if (status==PJ_SUCCESS && tdata!=NULL)
1839 pjsip_inv_send_msg(app.call[index].inv, tdata);
1840}
1841
1842static void hangup_all_calls()
1843{
1844 unsigned i;
1845 for (i=0; i<app.max_calls; ++i) {
1846 if (!app.call[i].inv)
1847 continue;
1848 hangup_call(i);
Benny Prijono1b543242008-01-08 22:50:06 +00001849 pj_thread_sleep(app.call_gap);
Benny Prijono60b980e2006-04-03 22:41:26 +00001850 }
Benny Prijonodeb31962006-06-22 18:51:03 +00001851
1852 /* Wait until all calls are terminated */
1853 for (i=0; i<app.max_calls; ++i) {
1854 while (app.call[i].inv)
1855 pj_thread_sleep(10);
1856 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001857}
1858
1859static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1860{
1861 char *p;
1862
1863 printf("%s (empty to cancel): ", title); fflush(stdout);
1864 fgets(buf, len, stdin);
1865
1866 /* Remove trailing newlines. */
1867 for (p=buf; ; ++p) {
1868 if (*p=='\r' || *p=='\n') *p='\0';
1869 else if (!*p) break;
1870 }
1871
1872 if (!*buf)
1873 return PJ_FALSE;
1874
1875 return PJ_TRUE;
1876}
1877
1878
1879static const char *MENU =
1880"\n"
1881"Enter menu character:\n"
Benny Prijono258ece92006-07-22 12:53:04 +00001882" s Summary\n"
Benny Prijono60b980e2006-04-03 22:41:26 +00001883" l List all calls\n"
1884" h Hangup a call\n"
1885" H Hangup all calls\n"
1886" q Quit\n"
1887"\n";
1888
1889
1890/* Main screen menu */
1891static void console_main()
1892{
1893 char input1[10];
1894 unsigned i;
1895
Benny Prijono4adcb912006-04-04 13:12:38 +00001896 printf("%s", MENU);
1897
Benny Prijono60b980e2006-04-03 22:41:26 +00001898 for (;;) {
1899 printf(">>> "); fflush(stdout);
1900 fgets(input1, sizeof(input1), stdin);
1901
1902 switch (input1[0]) {
Benny Prijono258ece92006-07-22 12:53:04 +00001903
1904 case 's':
1905 print_avg_stat();
1906 break;
1907
Benny Prijono60b980e2006-04-03 22:41:26 +00001908 case 'l':
1909 list_calls();
1910 break;
1911
1912 case 'h':
1913 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1914 break;
1915
1916 i = atoi(input1);
1917 hangup_call(i);
1918 break;
1919
1920 case 'H':
1921 hangup_all_calls();
1922 break;
1923
1924 case 'q':
1925 goto on_exit;
1926
1927 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001928 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001929 printf("%s", MENU);
1930 break;
1931 }
1932
1933 fflush(stdout);
1934 }
1935
1936on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001937 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001938}
1939
1940
Benny Prijono4adcb912006-04-04 13:12:38 +00001941/*****************************************************************************
1942 * Below is a simple module to log all incoming and outgoing SIP messages
1943 */
1944
1945
Benny Prijono60b980e2006-04-03 22:41:26 +00001946/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001947static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001948{
1949 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1950 "%s\n"
1951 "--end msg--",
1952 rdata->msg_info.len,
1953 pjsip_rx_data_get_info(rdata),
1954 rdata->pkt_info.src_name,
1955 rdata->pkt_info.src_port,
1956 rdata->msg_info.msg_buf));
1957
1958 /* Always return false, otherwise messages will not get processed! */
1959 return PJ_FALSE;
1960}
1961
1962/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001963static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001964{
1965
1966 /* Important note:
1967 * tp_info field is only valid after outgoing messages has passed
1968 * transport layer. So don't try to access tp_info when the module
1969 * has lower priority than transport layer.
1970 */
1971
1972 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1973 "%s\n"
1974 "--end msg--",
1975 (tdata->buf.cur - tdata->buf.start),
1976 pjsip_tx_data_get_info(tdata),
1977 tdata->tp_info.dst_name,
1978 tdata->tp_info.dst_port,
1979 tdata->buf.start));
1980
1981 /* Always return success, otherwise message will not get sent! */
1982 return PJ_SUCCESS;
1983}
1984
1985/* The module instance. */
1986static pjsip_module msg_logger =
1987{
1988 NULL, NULL, /* prev, next. */
1989 { "mod-siprtp-log", 14 }, /* Name. */
1990 -1, /* Id */
1991 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1992 NULL, /* load() */
1993 NULL, /* start() */
1994 NULL, /* stop() */
1995 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001996 &logger_on_rx_msg, /* on_rx_request() */
1997 &logger_on_rx_msg, /* on_rx_response() */
1998 &logger_on_tx_msg, /* on_tx_request. */
1999 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00002000 NULL, /* on_tsx_state() */
2001
2002};
2003
2004
2005
Benny Prijono4adcb912006-04-04 13:12:38 +00002006/*****************************************************************************
2007 * Console application custom logging:
2008 */
2009
2010
2011static FILE *log_file;
2012
2013
2014static void app_log_writer(int level, const char *buffer, int len)
2015{
2016 /* Write to both stdout and file. */
2017
2018 if (level <= app.app_log_level)
2019 pj_log_write(level, buffer, len);
2020
2021 if (log_file) {
2022 fwrite(buffer, len, 1, log_file);
2023 fflush(log_file);
2024 }
2025}
2026
2027
2028pj_status_t app_logging_init(void)
2029{
2030 /* Redirect log function to ours */
2031
2032 pj_log_set_log_func( &app_log_writer );
2033
2034 /* If output log file is desired, create the file: */
2035
2036 if (app.log_filename) {
2037 log_file = fopen(app.log_filename, "wt");
2038 if (log_file == NULL) {
2039 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
2040 app.log_filename));
2041 return -1;
2042 }
2043 }
2044
2045 return PJ_SUCCESS;
2046}
2047
2048
2049void app_logging_shutdown(void)
2050{
2051 /* Close logging file, if any: */
2052
2053 if (log_file) {
2054 fclose(log_file);
2055 log_file = NULL;
2056 }
2057}
2058
Benny Prijono60b980e2006-04-03 22:41:26 +00002059
2060/*
2061 * main()
2062 */
2063int main(int argc, char *argv[])
2064{
Benny Prijono4adcb912006-04-04 13:12:38 +00002065 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00002066 pj_status_t status;
2067
Benny Prijono4adcb912006-04-04 13:12:38 +00002068 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00002069 status = pj_init();
2070 if (status != PJ_SUCCESS)
2071 return 1;
2072
Benny Prijono4adcb912006-04-04 13:12:38 +00002073 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00002074 status = init_options(argc, argv);
2075 if (status != PJ_SUCCESS)
2076 return 1;
2077
Benny Prijono410fbae2006-05-03 18:16:06 +00002078 /* Verify options: */
2079
2080 /* Auto-quit can not be specified for UAS */
2081 if (app.auto_quit && app.uri_to_call.slen == 0) {
2082 printf("Error: --auto-quit option only valid for outgoing "
2083 "mode (UAC) only\n");
2084 return 1;
2085 }
2086
Benny Prijono4adcb912006-04-04 13:12:38 +00002087 /* Init logging */
2088 status = app_logging_init();
2089 if (status != PJ_SUCCESS)
2090 return 1;
2091
2092 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00002093 status = init_sip();
2094 if (status != PJ_SUCCESS) {
2095 app_perror(THIS_FILE, "Initialization has failed", status);
2096 destroy_sip();
2097 return 1;
2098 }
2099
Benny Prijono4adcb912006-04-04 13:12:38 +00002100 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00002101 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
2102
Benny Prijono4adcb912006-04-04 13:12:38 +00002103 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00002104 status = init_media();
2105 if (status != PJ_SUCCESS) {
2106 app_perror(THIS_FILE, "Media initialization failed", status);
2107 destroy_sip();
2108 return 1;
2109 }
2110
Benny Prijono9a0eab52006-04-04 19:43:24 +00002111 /* Start worker threads */
Benny Prijono6869dc92007-06-03 00:37:30 +00002112#if PJ_HAS_THREADS
Benny Prijono9a0eab52006-04-04 19:43:24 +00002113 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +00002114 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
2115 0, 0, &app.sip_thread[i]);
Benny Prijono9a0eab52006-04-04 19:43:24 +00002116 }
Benny Prijono6869dc92007-06-03 00:37:30 +00002117#endif
Benny Prijono9a0eab52006-04-04 19:43:24 +00002118
Benny Prijono4adcb912006-04-04 13:12:38 +00002119 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00002120 if (app.uri_to_call.slen) {
2121 unsigned i;
2122
Benny Prijono4adcb912006-04-04 13:12:38 +00002123 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
2124 app.uri_to_call.ptr));
2125
Benny Prijono60b980e2006-04-03 22:41:26 +00002126 for (i=0; i<app.max_calls; ++i) {
2127 status = make_call(&app.uri_to_call);
2128 if (status != PJ_SUCCESS) {
2129 app_perror(THIS_FILE, "Error making call", status);
2130 break;
2131 }
Benny Prijono1b543242008-01-08 22:50:06 +00002132 pj_thread_sleep(app.call_gap);
Benny Prijono60b980e2006-04-03 22:41:26 +00002133 }
Benny Prijono4adcb912006-04-04 13:12:38 +00002134
Benny Prijono410fbae2006-05-03 18:16:06 +00002135 if (app.auto_quit) {
2136 /* Wait for calls to complete */
2137 while (app.uac_calls < app.max_calls)
2138 pj_thread_sleep(100);
2139 pj_thread_sleep(200);
2140 } else {
Benny Prijono6869dc92007-06-03 00:37:30 +00002141#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002142 /* Start user interface loop */
2143 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002144#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002145 }
2146
Benny Prijono4adcb912006-04-04 13:12:38 +00002147 } else {
2148
2149 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
2150 app.max_calls));
Benny Prijono4adcb912006-04-04 13:12:38 +00002151
Benny Prijono6869dc92007-06-03 00:37:30 +00002152#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002153 /* Start user interface loop */
2154 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002155#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002156 }
Benny Prijono60b980e2006-04-03 22:41:26 +00002157
Benny Prijono6869dc92007-06-03 00:37:30 +00002158#if !PJ_HAS_THREADS
2159 PJ_LOG(3,(THIS_FILE, "Press Ctrl-C to quit"));
2160 for (;;) {
2161 pj_time_val t = {0, 10};
2162 pjsip_endpt_handle_events(app.sip_endpt, &t);
2163 }
2164#endif
Benny Prijono4adcb912006-04-04 13:12:38 +00002165
2166 /* Shutting down... */
Benny Prijono4d3aa922006-06-22 22:31:48 +00002167 destroy_sip();
Benny Prijono513795f2006-07-18 21:12:24 +00002168 destroy_media();
Benny Prijono6647d822006-05-20 13:01:07 +00002169
2170 if (app.pool) {
2171 pj_pool_release(app.pool);
2172 app.pool = NULL;
2173 pj_caching_pool_destroy(&app.cp);
2174 }
2175
Benny Prijono4adcb912006-04-04 13:12:38 +00002176 app_logging_shutdown();
2177
Benny Prijono5b1e14d2006-11-25 08:46:48 +00002178 /* Shutdown PJLIB */
2179 pj_shutdown();
Benny Prijono60b980e2006-04-03 22:41:26 +00002180
2181 return 0;
2182}
2183