blob: 9ab0b1f4c4ce3f1d3b6d50205d1f4564c6b56804 [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. */
234static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size);
235
236/* This callback is called by media transport on receipt of RTCP packet. */
237static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size);
238
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 Prijonod8179652008-01-23 20:39:07 +00001015 pjmedia_transport_get_info(audio->transport, &tpinfo);
Benny Prijonodeb31962006-06-22 18:51:03 +00001016
Benny Prijono60b980e2006-04-03 22:41:26 +00001017 /* Create and initialize basic SDP session */
1018 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
1019
1020 pj_gettimeofday(&tv);
1021 sdp->origin.user = pj_str("pjsip-siprtp");
1022 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
1023 sdp->origin.net_type = pj_str("IN");
1024 sdp->origin.addr_type = pj_str("IP4");
1025 sdp->origin.addr = *pj_gethostname();
1026 sdp->name = pj_str("pjsip");
1027
1028 /* Since we only support one media stream at present, put the
1029 * SDP connection line in the session level.
1030 */
1031 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
1032 sdp->conn->net_type = pj_str("IN");
1033 sdp->conn->addr_type = pj_str("IP4");
Benny Prijonodeb31962006-06-22 18:51:03 +00001034 sdp->conn->addr = app.local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +00001035
1036
1037 /* SDP time and attributes. */
1038 sdp->time.start = sdp->time.stop = 0;
1039 sdp->attr_count = 0;
1040
1041 /* Create media stream 0: */
1042
1043 sdp->media_count = 1;
1044 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
1045 sdp->media[0] = m;
1046
1047 /* Standard media info: */
1048 m->desc.media = pj_str("audio");
Benny Prijonod8179652008-01-23 20:39:07 +00001049 m->desc.port = pj_ntohs(tpinfo.rtp_addr_name.ipv4.sin_port);
Benny Prijono60b980e2006-04-03 22:41:26 +00001050 m->desc.port_count = 1;
1051 m->desc.transport = pj_str("RTP/AVP");
1052
1053 /* Add format and rtpmap for each codec. */
1054 m->desc.fmt_count = 1;
1055 m->attr_count = 0;
1056
1057 {
1058 pjmedia_sdp_rtpmap rtpmap;
1059 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +00001060 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +00001061
Benny Prijono4adcb912006-04-04 13:12:38 +00001062 sprintf(ptstr, "%d", app.audio_codec.pt);
1063 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1064 rtpmap.pt = m->desc.fmt[0];
1065 rtpmap.clock_rate = app.audio_codec.clock_rate;
1066 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +00001067 rtpmap.param.slen = 0;
1068
1069 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1070 m->attr[m->attr_count++] = attr;
1071 }
1072
1073 /* Add sendrecv attribute. */
1074 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1075 attr->name = pj_str("sendrecv");
1076 m->attr[m->attr_count++] = attr;
1077
1078#if 1
1079 /*
1080 * Add support telephony event
1081 */
Benny Prijono4adcb912006-04-04 13:12:38 +00001082 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +00001083 /* Add rtpmap. */
1084 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1085 attr->name = pj_str("rtpmap");
Benny Prijonod95abb42006-10-20 10:18:44 +00001086 attr->value = pj_str("121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +00001087 m->attr[m->attr_count++] = attr;
1088 /* Add fmtp */
1089 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1090 attr->name = pj_str("fmtp");
Benny Prijonod95abb42006-10-20 10:18:44 +00001091 attr->value = pj_str("121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +00001092 m->attr[m->attr_count++] = attr;
1093#endif
1094
1095 /* Done */
1096 *p_sdp = sdp;
1097
1098 return PJ_SUCCESS;
1099}
1100
1101
Benny Prijono513795f2006-07-18 21:12:24 +00001102#if defined(PJ_WIN32) && PJ_WIN32 != 0
1103#include <windows.h>
1104static void boost_priority(void)
1105{
1106 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
1107 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
1108}
1109
Benny Prijono7bef0f52006-07-31 18:54:06 +00001110#elif defined(PJ_LINUX) && PJ_LINUX != 0
1111#include <pthread.h>
1112static void boost_priority(void)
1113{
1114#define POLICY SCHED_FIFO
Benny Prijono7bef0f52006-07-31 18:54:06 +00001115 struct sched_param tp;
1116 int max_prio;
1117 int policy;
1118 int rc;
1119
1120 if (sched_get_priority_min(POLICY) < sched_get_priority_max(POLICY))
1121 max_prio = sched_get_priority_max(POLICY)-1;
1122 else
1123 max_prio = sched_get_priority_max(POLICY)+1;
1124
1125 /*
1126 * Adjust process scheduling algorithm and priority
1127 */
1128 rc = sched_getparam(0, &tp);
1129 if (rc != 0) {
1130 app_perror( THIS_FILE, "sched_getparam error",
1131 PJ_RETURN_OS_ERROR(rc));
1132 return;
1133 }
1134 tp.__sched_priority = max_prio;
1135
1136 rc = sched_setscheduler(0, POLICY, &tp);
1137 if (rc != 0) {
1138 app_perror( THIS_FILE, "sched_setscheduler error",
1139 PJ_RETURN_OS_ERROR(rc));
1140 }
1141
1142 PJ_LOG(4, (THIS_FILE, "New process policy=%d, priority=%d",
1143 policy, tp.__sched_priority));
1144
1145 /*
1146 * Adjust thread scheduling algorithm and priority
1147 */
1148 rc = pthread_getschedparam(pthread_self(), &policy, &tp);
1149 if (rc != 0) {
1150 app_perror( THIS_FILE, "pthread_getschedparam error",
1151 PJ_RETURN_OS_ERROR(rc));
1152 return;
1153 }
1154
1155 PJ_LOG(4, (THIS_FILE, "Old thread policy=%d, priority=%d",
1156 policy, tp.__sched_priority));
1157
1158 policy = POLICY;
1159 tp.__sched_priority = max_prio;
1160
1161 rc = pthread_setschedparam(pthread_self(), policy, &tp);
1162 if (rc != 0) {
1163 app_perror( THIS_FILE, "pthread_setschedparam error",
1164 PJ_RETURN_OS_ERROR(rc));
1165 return;
1166 }
1167
1168 PJ_LOG(4, (THIS_FILE, "New thread policy=%d, priority=%d",
1169 policy, tp.__sched_priority));
1170}
1171
Benny Prijono513795f2006-07-18 21:12:24 +00001172#else
1173# define boost_priority()
1174#endif
1175
1176
Benny Prijonodeb31962006-06-22 18:51:03 +00001177/*
1178 * This callback is called by media transport on receipt of RTP packet.
1179 */
1180static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size)
1181{
1182 struct media_stream *strm;
1183 pj_status_t status;
1184 const pjmedia_rtp_hdr *hdr;
1185 const void *payload;
1186 unsigned payload_len;
1187
1188 strm = user_data;
1189
1190 /* Discard packet if media is inactive */
1191 if (!strm->active)
1192 return;
1193
1194 /* Check for errors */
1195 if (size < 0) {
1196 app_perror(THIS_FILE, "RTP recv() error", -size);
1197 return;
1198 }
1199
1200 /* Decode RTP packet. */
1201 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1202 pkt, size,
1203 &hdr, &payload, &payload_len);
1204 if (status != PJ_SUCCESS) {
1205 app_perror(THIS_FILE, "RTP decode error", status);
1206 return;
1207 }
1208
1209 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1210
1211 /* Update the RTCP session. */
1212 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1213 pj_ntohl(hdr->ts), payload_len);
1214
1215 /* Update RTP session */
1216 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1217
1218}
1219
1220/*
1221 * This callback is called by media transport on receipt of RTCP packet.
1222 */
1223static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size)
1224{
1225 struct media_stream *strm;
1226
1227 strm = user_data;
1228
1229 /* Discard packet if media is inactive */
1230 if (!strm->active)
1231 return;
1232
1233 /* Check for errors */
1234 if (size < 0) {
1235 app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
1236 return;
1237 }
1238
1239 /* Update RTCP session */
1240 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1241}
1242
1243
Benny Prijono513795f2006-07-18 21:12:24 +00001244/*
1245 * Media thread
1246 *
1247 * This is the thread to send and receive both RTP and RTCP packets.
1248 */
1249static int media_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +00001250{
Benny Prijono513795f2006-07-18 21:12:24 +00001251 enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
1252 struct media_stream *strm = arg;
1253 char packet[1500];
1254 unsigned msec_interval;
1255 pj_timestamp freq, next_rtp, next_rtcp;
Benny Prijono60b980e2006-04-03 22:41:26 +00001256
Benny Prijono6647d822006-05-20 13:01:07 +00001257
Benny Prijono513795f2006-07-18 21:12:24 +00001258 /* Boost thread priority if necessary */
1259 boost_priority();
Benny Prijono6647d822006-05-20 13:01:07 +00001260
Benny Prijono513795f2006-07-18 21:12:24 +00001261 /* Let things settle */
Benny Prijono258ece92006-07-22 12:53:04 +00001262 pj_thread_sleep(100);
Benny Prijono6647d822006-05-20 13:01:07 +00001263
Benny Prijono513795f2006-07-18 21:12:24 +00001264 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1265 pj_get_timestamp_freq(&freq);
1266
1267 pj_get_timestamp(&next_rtp);
1268 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
1269
1270 next_rtcp = next_rtp;
1271 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
1272
1273
1274 while (!strm->thread_quit_flag) {
1275 pj_timestamp now, lesser;
1276 pj_time_val timeout;
1277 pj_bool_t send_rtp, send_rtcp;
1278
1279 send_rtp = send_rtcp = PJ_FALSE;
1280
1281 /* Determine how long to sleep */
1282 if (next_rtp.u64 < next_rtcp.u64) {
1283 lesser = next_rtp;
1284 send_rtp = PJ_TRUE;
1285 } else {
1286 lesser = next_rtcp;
1287 send_rtcp = PJ_TRUE;
1288 }
1289
1290 pj_get_timestamp(&now);
1291 if (lesser.u64 <= now.u64) {
1292 timeout.sec = timeout.msec = 0;
1293 //printf("immediate "); fflush(stdout);
1294 } else {
1295 pj_uint64_t tick_delay;
1296 tick_delay = lesser.u64 - now.u64;
1297 timeout.sec = 0;
1298 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1299 pj_time_val_normalize(&timeout);
1300
1301 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
1302 }
1303
1304 /* Wait for next interval */
1305 //if (timeout.sec!=0 && timeout.msec!=0) {
1306 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
1307 if (strm->thread_quit_flag)
1308 break;
1309 //}
1310
1311 pj_get_timestamp(&now);
1312
1313 if (send_rtp || next_rtp.u64 <= now.u64) {
1314 /*
1315 * Time to send RTP packet.
1316 */
1317 pj_status_t status;
Benny Prijonoe960bb52007-01-21 17:53:39 +00001318 const void *p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001319 const pjmedia_rtp_hdr *hdr;
1320 pj_ssize_t size;
1321 int hdrlen;
1322
1323 /* Format RTP header */
1324 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1325 0, /* marker bit */
1326 strm->bytes_per_frame,
1327 strm->samples_per_frame,
Benny Prijonoe960bb52007-01-21 17:53:39 +00001328 &p_hdr, &hdrlen);
Benny Prijono513795f2006-07-18 21:12:24 +00001329 if (status == PJ_SUCCESS) {
1330
1331 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
Benny Prijonoe960bb52007-01-21 17:53:39 +00001332
1333 hdr = (const pjmedia_rtp_hdr*) p_hdr;
Benny Prijono513795f2006-07-18 21:12:24 +00001334
1335 /* Copy RTP header to packet */
1336 pj_memcpy(packet, hdr, hdrlen);
1337
1338 /* Zero the payload */
1339 pj_bzero(packet+hdrlen, strm->bytes_per_frame);
1340
1341 /* Send RTP packet */
1342 size = hdrlen + strm->bytes_per_frame;
1343 status = pjmedia_transport_send_rtp(strm->transport,
1344 packet, size);
1345 if (status != PJ_SUCCESS)
1346 app_perror(THIS_FILE, "Error sending RTP packet", status);
1347
1348 } else {
1349 pj_assert(!"RTP encode() error");
1350 }
1351
1352 /* Update RTCP SR */
1353 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1354
1355 /* Schedule next send */
1356 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
1357 }
1358
1359
1360 if (send_rtcp || next_rtcp.u64 <= now.u64) {
1361 /*
1362 * Time to send RTCP packet.
1363 */
Benny Prijonofe81cfa2007-09-20 11:30:59 +00001364 void *rtcp_pkt;
Benny Prijono513795f2006-07-18 21:12:24 +00001365 int rtcp_len;
1366 pj_ssize_t size;
1367 pj_status_t status;
1368
1369 /* Build RTCP packet */
1370 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1371
Benny Prijono4d3aa922006-06-22 22:31:48 +00001372
Benny Prijono513795f2006-07-18 21:12:24 +00001373 /* Send packet */
1374 size = rtcp_len;
1375 status = pjmedia_transport_send_rtcp(strm->transport,
1376 rtcp_pkt, size);
1377 if (status != PJ_SUCCESS) {
1378 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1379 }
1380
1381 /* Schedule next send */
1382 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
1383 1000);
1384 }
1385 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001386
Benny Prijono513795f2006-07-18 21:12:24 +00001387 return 0;
Benny Prijono60b980e2006-04-03 22:41:26 +00001388}
1389
Benny Prijono513795f2006-07-18 21:12:24 +00001390
Benny Prijono60b980e2006-04-03 22:41:26 +00001391/* Callback to be called when SDP negotiation is done in the call: */
1392static void call_on_media_update( pjsip_inv_session *inv,
1393 pj_status_t status)
1394{
1395 struct call *call;
1396 pj_pool_t *pool;
1397 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001398 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001399 struct codec *codec_desc = NULL;
1400 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001401
1402 call = inv->mod_data[mod_siprtp.id];
1403 pool = inv->dlg->pool;
1404 audio = &call->media[0];
1405
1406 /* If this is a mid-call media update, then destroy existing media */
Benny Prijono513795f2006-07-18 21:12:24 +00001407 if (audio->thread != NULL)
Benny Prijono60b980e2006-04-03 22:41:26 +00001408 destroy_call_media(call->index);
1409
1410
1411 /* Do nothing if media negotiation has failed */
1412 if (status != PJ_SUCCESS) {
1413 app_perror(THIS_FILE, "SDP negotiation failed", status);
1414 return;
1415 }
1416
1417
1418 /* Capture stream definition from the SDP */
1419 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1420 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1421
1422 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
Benny Prijonob04c9e02006-05-17 17:17:39 +00001423 local_sdp, remote_sdp, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001424 if (status != PJ_SUCCESS) {
1425 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1426 return;
1427 }
1428
Benny Prijono4adcb912006-04-04 13:12:38 +00001429 /* Get the remainder of codec information from codec descriptor */
1430 if (audio->si.fmt.pt == app.audio_codec.pt)
1431 codec_desc = &app.audio_codec;
1432 else {
1433 /* Find the codec description in codec array */
1434 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1435 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1436 codec_desc = &audio_codecs[i];
1437 break;
1438 }
1439 }
1440
1441 if (codec_desc == NULL) {
1442 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1443 return;
1444 }
1445 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001446
Benny Prijono15953012006-04-27 22:37:08 +00001447 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001448 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1449 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001450
1451
1452 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001453 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001454 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001455 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001456 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001457
Benny Prijono4adcb912006-04-04 13:12:38 +00001458
Benny Prijonodeb31962006-06-22 18:51:03 +00001459 /* Attach media to transport */
1460 status = pjmedia_transport_attach(audio->transport, audio,
1461 &audio->si.rem_addr,
Benny Prijono513795f2006-07-18 21:12:24 +00001462 &audio->si.rem_rtcp,
Benny Prijonodeb31962006-06-22 18:51:03 +00001463 sizeof(pj_sockaddr_in),
1464 &on_rx_rtp,
1465 &on_rx_rtcp);
1466 if (status != PJ_SUCCESS) {
1467 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1468 return;
1469 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001470
Benny Prijono513795f2006-07-18 21:12:24 +00001471 /* Start media thread. */
1472 audio->thread_quit_flag = 0;
Benny Prijono6869dc92007-06-03 00:37:30 +00001473#if PJ_HAS_THREADS
Benny Prijono513795f2006-07-18 21:12:24 +00001474 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1475 0, 0, &audio->thread);
1476 if (status != PJ_SUCCESS) {
1477 app_perror(THIS_FILE, "Error creating media thread", status);
1478 return;
1479 }
Benny Prijono6869dc92007-06-03 00:37:30 +00001480#endif
Benny Prijono513795f2006-07-18 21:12:24 +00001481
Benny Prijonodeb31962006-06-22 18:51:03 +00001482 /* Set the media as active */
1483 audio->active = PJ_TRUE;
Benny Prijono60b980e2006-04-03 22:41:26 +00001484}
1485
1486
1487
1488/* Destroy call's media */
1489static void destroy_call_media(unsigned call_index)
1490{
1491 struct media_stream *audio = &app.call[call_index].media[0];
1492
Benny Prijono5a9e2042006-11-14 13:35:20 +00001493 if (audio) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001494 audio->active = PJ_FALSE;
1495
Benny Prijono5a9e2042006-11-14 13:35:20 +00001496 if (audio->thread) {
1497 audio->thread_quit_flag = 1;
1498 pj_thread_join(audio->thread);
1499 pj_thread_destroy(audio->thread);
1500 audio->thread = NULL;
1501 audio->thread_quit_flag = 0;
1502 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001503
Benny Prijono5b1e14d2006-11-25 08:46:48 +00001504 pjmedia_transport_detach(audio->transport, audio);
Benny Prijono60b980e2006-04-03 22:41:26 +00001505 }
1506}
1507
Benny Prijono513795f2006-07-18 21:12:24 +00001508
Benny Prijono4adcb912006-04-04 13:12:38 +00001509/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001510 * USER INTERFACE STUFFS
1511 */
Benny Prijono258ece92006-07-22 12:53:04 +00001512
1513static void call_get_duration(int call_index, pj_time_val *dur)
1514{
1515 struct call *call = &app.call[call_index];
1516 pjsip_inv_session *inv;
1517
1518 dur->sec = dur->msec = 0;
1519
1520 if (!call)
1521 return;
1522
1523 inv = call->inv;
1524 if (!inv)
1525 return;
1526
1527 if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
1528
1529 pj_gettimeofday(dur);
1530 PJ_TIME_VAL_SUB((*dur), call->connect_time);
1531 }
1532}
1533
1534
1535static const char *good_number(char *buf, pj_int32_t val)
1536{
1537 if (val < 1000) {
1538 pj_ansi_sprintf(buf, "%d", val);
1539 } else if (val < 1000000) {
1540 pj_ansi_sprintf(buf, "%d.%02dK",
1541 val / 1000,
1542 (val % 1000) / 100);
1543 } else {
1544 pj_ansi_sprintf(buf, "%d.%02dM",
1545 val / 1000000,
1546 (val % 1000000) / 10000);
1547 }
1548
1549 return buf;
1550}
1551
1552
1553
1554static void print_avg_stat(void)
1555{
1556#define MIN_(var,val) if ((int)val < (int)var) var = val
1557#define MAX_(var,val) if ((int)val > (int)var) var = val
1558#define AVG_(var,val) var = ( ((var * count) + val) / (count+1) )
1559#define BIGVAL 0x7FFFFFFFL
1560 struct stat_entry
1561 {
1562 int min, avg, max;
1563 };
1564
1565 struct stat_entry call_dur, call_pdd;
1566 pjmedia_rtcp_stat min_stat, avg_stat, max_stat;
1567
1568 char srx_min[16], srx_avg[16], srx_max[16];
1569 char brx_min[16], brx_avg[16], brx_max[16];
1570 char stx_min[16], stx_avg[16], stx_max[16];
1571 char btx_min[16], btx_avg[16], btx_max[16];
1572
1573
1574 unsigned i, count;
1575
1576 pj_bzero(&call_dur, sizeof(call_dur));
1577 call_dur.min = BIGVAL;
1578
1579 pj_bzero(&call_pdd, sizeof(call_pdd));
1580 call_pdd.min = BIGVAL;
1581
1582 pj_bzero(&min_stat, sizeof(min_stat));
1583 min_stat.rx.pkt = min_stat.tx.pkt = BIGVAL;
1584 min_stat.rx.bytes = min_stat.tx.bytes = BIGVAL;
1585 min_stat.rx.loss = min_stat.tx.loss = BIGVAL;
1586 min_stat.rx.dup = min_stat.tx.dup = BIGVAL;
1587 min_stat.rx.reorder = min_stat.tx.reorder = BIGVAL;
1588 min_stat.rx.jitter.min = min_stat.tx.jitter.min = BIGVAL;
1589 min_stat.rtt.min = BIGVAL;
1590
1591 pj_bzero(&avg_stat, sizeof(avg_stat));
1592 pj_bzero(&max_stat, sizeof(max_stat));
1593
1594
1595 for (i=0, count=0; i<app.max_calls; ++i) {
1596
1597 struct call *call = &app.call[i];
1598 struct media_stream *audio = &call->media[0];
1599 pj_time_val dur;
1600 unsigned msec_dur;
1601
1602 if (call->inv == NULL ||
1603 call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
1604 call->connect_time.sec == 0)
1605 {
1606 continue;
1607 }
1608
1609 /* Duration */
1610 call_get_duration(i, &dur);
1611 msec_dur = PJ_TIME_VAL_MSEC(dur);
1612
1613 MIN_(call_dur.min, msec_dur);
1614 MAX_(call_dur.max, msec_dur);
1615 AVG_(call_dur.avg, msec_dur);
1616
1617 /* Connect delay */
1618 if (call->connect_time.sec) {
1619 pj_time_val t = call->connect_time;
1620 PJ_TIME_VAL_SUB(t, call->start_time);
1621 msec_dur = PJ_TIME_VAL_MSEC(t);
1622 } else {
1623 msec_dur = 10;
1624 }
1625
1626 MIN_(call_pdd.min, msec_dur);
1627 MAX_(call_pdd.max, msec_dur);
1628 AVG_(call_pdd.avg, msec_dur);
1629
1630 /* RX Statistisc: */
1631
1632 /* Packets */
1633 MIN_(min_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1634 MAX_(max_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1635 AVG_(avg_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1636
1637 /* Bytes */
1638 MIN_(min_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1639 MAX_(max_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1640 AVG_(avg_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1641
1642
1643 /* Packet loss */
1644 MIN_(min_stat.rx.loss, audio->rtcp.stat.rx.loss);
1645 MAX_(max_stat.rx.loss, audio->rtcp.stat.rx.loss);
1646 AVG_(avg_stat.rx.loss, audio->rtcp.stat.rx.loss);
1647
1648 /* Packet dup */
1649 MIN_(min_stat.rx.dup, audio->rtcp.stat.rx.dup);
1650 MAX_(max_stat.rx.dup, audio->rtcp.stat.rx.dup);
1651 AVG_(avg_stat.rx.dup, audio->rtcp.stat.rx.dup);
1652
1653 /* Packet reorder */
1654 MIN_(min_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1655 MAX_(max_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1656 AVG_(avg_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1657
1658 /* Jitter */
1659 MIN_(min_stat.rx.jitter.min, audio->rtcp.stat.rx.jitter.min);
1660 MAX_(max_stat.rx.jitter.max, audio->rtcp.stat.rx.jitter.max);
1661 AVG_(avg_stat.rx.jitter.avg, audio->rtcp.stat.rx.jitter.avg);
1662
1663
1664 /* TX Statistisc: */
1665
1666 /* Packets */
1667 MIN_(min_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1668 MAX_(max_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1669 AVG_(avg_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1670
1671 /* Bytes */
1672 MIN_(min_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1673 MAX_(max_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1674 AVG_(avg_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1675
1676 /* Packet loss */
1677 MIN_(min_stat.tx.loss, audio->rtcp.stat.tx.loss);
1678 MAX_(max_stat.tx.loss, audio->rtcp.stat.tx.loss);
1679 AVG_(avg_stat.tx.loss, audio->rtcp.stat.tx.loss);
1680
1681 /* Packet dup */
1682 MIN_(min_stat.tx.dup, audio->rtcp.stat.tx.dup);
1683 MAX_(max_stat.tx.dup, audio->rtcp.stat.tx.dup);
1684 AVG_(avg_stat.tx.dup, audio->rtcp.stat.tx.dup);
1685
1686 /* Packet reorder */
1687 MIN_(min_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1688 MAX_(max_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1689 AVG_(avg_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1690
1691 /* Jitter */
1692 MIN_(min_stat.tx.jitter.min, audio->rtcp.stat.tx.jitter.min);
1693 MAX_(max_stat.tx.jitter.max, audio->rtcp.stat.tx.jitter.max);
1694 AVG_(avg_stat.tx.jitter.avg, audio->rtcp.stat.tx.jitter.avg);
1695
1696
1697 /* RTT */
1698 MIN_(min_stat.rtt.min, audio->rtcp.stat.rtt.min);
1699 MAX_(max_stat.rtt.max, audio->rtcp.stat.rtt.max);
1700 AVG_(avg_stat.rtt.avg, audio->rtcp.stat.rtt.avg);
1701
1702 ++count;
1703 }
1704
1705 if (count == 0) {
1706 puts("No active calls");
1707 return;
1708 }
1709
1710 printf("Total %d call(s) active.\n"
1711 " Average Statistics\n"
1712 " min avg max \n"
1713 " -----------------------\n"
1714 " call duration: %7d %7d %7d %s\n"
1715 " connect delay: %7d %7d %7d %s\n"
1716 " RX stat:\n"
1717 " packets: %7s %7s %7s %s\n"
1718 " payload: %7s %7s %7s %s\n"
1719 " loss: %7d %7d %7d %s\n"
1720 " percent loss: %7.3f %7.3f %7.3f %s\n"
1721 " dup: %7d %7d %7d %s\n"
1722 " reorder: %7d %7d %7d %s\n"
1723 " jitter: %7.3f %7.3f %7.3f %s\n"
1724 " TX stat:\n"
1725 " packets: %7s %7s %7s %s\n"
1726 " payload: %7s %7s %7s %s\n"
1727 " loss: %7d %7d %7d %s\n"
1728 " percent loss: %7.3f %7.3f %7.3f %s\n"
1729 " dup: %7d %7d %7d %s\n"
1730 " reorder: %7d %7d %7d %s\n"
1731 " jitter: %7.3f %7.3f %7.3f %s\n"
1732 " RTT : %7.3f %7.3f %7.3f %s\n"
1733 ,
1734 count,
1735 call_dur.min/1000, call_dur.avg/1000, call_dur.max/1000,
1736 "seconds",
1737
1738 call_pdd.min, call_pdd.avg, call_pdd.max,
1739 "ms",
1740
1741 /* rx */
1742
1743 good_number(srx_min, min_stat.rx.pkt),
1744 good_number(srx_avg, avg_stat.rx.pkt),
1745 good_number(srx_max, max_stat.rx.pkt),
1746 "packets",
1747
1748 good_number(brx_min, min_stat.rx.bytes),
1749 good_number(brx_avg, avg_stat.rx.bytes),
1750 good_number(brx_max, max_stat.rx.bytes),
1751 "bytes",
1752
1753 min_stat.rx.loss, avg_stat.rx.loss, max_stat.rx.loss,
1754 "packets",
1755
1756 min_stat.rx.loss*100.0/(min_stat.rx.pkt+min_stat.rx.loss),
1757 avg_stat.rx.loss*100.0/(avg_stat.rx.pkt+avg_stat.rx.loss),
1758 max_stat.rx.loss*100.0/(max_stat.rx.pkt+max_stat.rx.loss),
1759 "%",
1760
1761
1762 min_stat.rx.dup, avg_stat.rx.dup, max_stat.rx.dup,
1763 "packets",
1764
1765 min_stat.rx.reorder, avg_stat.rx.reorder, max_stat.rx.reorder,
1766 "packets",
1767
1768 min_stat.rx.jitter.min/1000.0,
1769 avg_stat.rx.jitter.avg/1000.0,
1770 max_stat.rx.jitter.max/1000.0,
1771 "ms",
1772
1773 /* tx */
1774
1775 good_number(stx_min, min_stat.tx.pkt),
1776 good_number(stx_avg, avg_stat.tx.pkt),
1777 good_number(stx_max, max_stat.tx.pkt),
1778 "packets",
1779
1780 good_number(btx_min, min_stat.tx.bytes),
1781 good_number(btx_avg, avg_stat.tx.bytes),
1782 good_number(btx_max, max_stat.tx.bytes),
1783 "bytes",
1784
1785 min_stat.tx.loss, avg_stat.tx.loss, max_stat.tx.loss,
1786 "packets",
1787
Benny Prijono05784a52006-07-25 11:54:15 +00001788 min_stat.tx.loss*100.0/(min_stat.tx.pkt+min_stat.tx.loss),
1789 avg_stat.tx.loss*100.0/(avg_stat.tx.pkt+avg_stat.tx.loss),
1790 max_stat.tx.loss*100.0/(max_stat.tx.pkt+max_stat.tx.loss),
Benny Prijono258ece92006-07-22 12:53:04 +00001791 "%",
1792
1793 min_stat.tx.dup, avg_stat.tx.dup, max_stat.tx.dup,
1794 "packets",
1795
1796 min_stat.tx.reorder, avg_stat.tx.reorder, max_stat.tx.reorder,
1797 "packets",
1798
1799 min_stat.tx.jitter.min/1000.0,
1800 avg_stat.tx.jitter.avg/1000.0,
1801 max_stat.tx.jitter.max/1000.0,
1802 "ms",
1803
1804 /* rtt */
1805 min_stat.rtt.min/1000.0,
1806 avg_stat.rtt.avg/1000.0,
1807 max_stat.rtt.max/1000.0,
1808 "ms"
1809 );
1810
1811}
1812
1813
Benny Prijono16a6b0e2006-05-12 10:20:03 +00001814#include "siprtp_report.c"
Benny Prijono60b980e2006-04-03 22:41:26 +00001815
1816
1817static void list_calls()
1818{
1819 unsigned i;
1820 puts("List all calls:");
1821 for (i=0; i<app.max_calls; ++i) {
1822 if (!app.call[i].inv)
1823 continue;
1824 print_call(i);
1825 }
1826}
1827
1828static void hangup_call(unsigned index)
1829{
1830 pjsip_tx_data *tdata;
1831 pj_status_t status;
1832
1833 if (app.call[index].inv == NULL)
1834 return;
1835
1836 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1837 if (status==PJ_SUCCESS && tdata!=NULL)
1838 pjsip_inv_send_msg(app.call[index].inv, tdata);
1839}
1840
1841static void hangup_all_calls()
1842{
1843 unsigned i;
1844 for (i=0; i<app.max_calls; ++i) {
1845 if (!app.call[i].inv)
1846 continue;
1847 hangup_call(i);
Benny Prijono1b543242008-01-08 22:50:06 +00001848 pj_thread_sleep(app.call_gap);
Benny Prijono60b980e2006-04-03 22:41:26 +00001849 }
Benny Prijonodeb31962006-06-22 18:51:03 +00001850
1851 /* Wait until all calls are terminated */
1852 for (i=0; i<app.max_calls; ++i) {
1853 while (app.call[i].inv)
1854 pj_thread_sleep(10);
1855 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001856}
1857
1858static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1859{
1860 char *p;
1861
1862 printf("%s (empty to cancel): ", title); fflush(stdout);
1863 fgets(buf, len, stdin);
1864
1865 /* Remove trailing newlines. */
1866 for (p=buf; ; ++p) {
1867 if (*p=='\r' || *p=='\n') *p='\0';
1868 else if (!*p) break;
1869 }
1870
1871 if (!*buf)
1872 return PJ_FALSE;
1873
1874 return PJ_TRUE;
1875}
1876
1877
1878static const char *MENU =
1879"\n"
1880"Enter menu character:\n"
Benny Prijono258ece92006-07-22 12:53:04 +00001881" s Summary\n"
Benny Prijono60b980e2006-04-03 22:41:26 +00001882" l List all calls\n"
1883" h Hangup a call\n"
1884" H Hangup all calls\n"
1885" q Quit\n"
1886"\n";
1887
1888
1889/* Main screen menu */
1890static void console_main()
1891{
1892 char input1[10];
1893 unsigned i;
1894
Benny Prijono4adcb912006-04-04 13:12:38 +00001895 printf("%s", MENU);
1896
Benny Prijono60b980e2006-04-03 22:41:26 +00001897 for (;;) {
1898 printf(">>> "); fflush(stdout);
1899 fgets(input1, sizeof(input1), stdin);
1900
1901 switch (input1[0]) {
Benny Prijono258ece92006-07-22 12:53:04 +00001902
1903 case 's':
1904 print_avg_stat();
1905 break;
1906
Benny Prijono60b980e2006-04-03 22:41:26 +00001907 case 'l':
1908 list_calls();
1909 break;
1910
1911 case 'h':
1912 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1913 break;
1914
1915 i = atoi(input1);
1916 hangup_call(i);
1917 break;
1918
1919 case 'H':
1920 hangup_all_calls();
1921 break;
1922
1923 case 'q':
1924 goto on_exit;
1925
1926 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001927 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001928 printf("%s", MENU);
1929 break;
1930 }
1931
1932 fflush(stdout);
1933 }
1934
1935on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001936 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001937}
1938
1939
Benny Prijono4adcb912006-04-04 13:12:38 +00001940/*****************************************************************************
1941 * Below is a simple module to log all incoming and outgoing SIP messages
1942 */
1943
1944
Benny Prijono60b980e2006-04-03 22:41:26 +00001945/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001946static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001947{
1948 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1949 "%s\n"
1950 "--end msg--",
1951 rdata->msg_info.len,
1952 pjsip_rx_data_get_info(rdata),
1953 rdata->pkt_info.src_name,
1954 rdata->pkt_info.src_port,
1955 rdata->msg_info.msg_buf));
1956
1957 /* Always return false, otherwise messages will not get processed! */
1958 return PJ_FALSE;
1959}
1960
1961/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001962static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001963{
1964
1965 /* Important note:
1966 * tp_info field is only valid after outgoing messages has passed
1967 * transport layer. So don't try to access tp_info when the module
1968 * has lower priority than transport layer.
1969 */
1970
1971 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1972 "%s\n"
1973 "--end msg--",
1974 (tdata->buf.cur - tdata->buf.start),
1975 pjsip_tx_data_get_info(tdata),
1976 tdata->tp_info.dst_name,
1977 tdata->tp_info.dst_port,
1978 tdata->buf.start));
1979
1980 /* Always return success, otherwise message will not get sent! */
1981 return PJ_SUCCESS;
1982}
1983
1984/* The module instance. */
1985static pjsip_module msg_logger =
1986{
1987 NULL, NULL, /* prev, next. */
1988 { "mod-siprtp-log", 14 }, /* Name. */
1989 -1, /* Id */
1990 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1991 NULL, /* load() */
1992 NULL, /* start() */
1993 NULL, /* stop() */
1994 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001995 &logger_on_rx_msg, /* on_rx_request() */
1996 &logger_on_rx_msg, /* on_rx_response() */
1997 &logger_on_tx_msg, /* on_tx_request. */
1998 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00001999 NULL, /* on_tsx_state() */
2000
2001};
2002
2003
2004
Benny Prijono4adcb912006-04-04 13:12:38 +00002005/*****************************************************************************
2006 * Console application custom logging:
2007 */
2008
2009
2010static FILE *log_file;
2011
2012
2013static void app_log_writer(int level, const char *buffer, int len)
2014{
2015 /* Write to both stdout and file. */
2016
2017 if (level <= app.app_log_level)
2018 pj_log_write(level, buffer, len);
2019
2020 if (log_file) {
2021 fwrite(buffer, len, 1, log_file);
2022 fflush(log_file);
2023 }
2024}
2025
2026
2027pj_status_t app_logging_init(void)
2028{
2029 /* Redirect log function to ours */
2030
2031 pj_log_set_log_func( &app_log_writer );
2032
2033 /* If output log file is desired, create the file: */
2034
2035 if (app.log_filename) {
2036 log_file = fopen(app.log_filename, "wt");
2037 if (log_file == NULL) {
2038 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
2039 app.log_filename));
2040 return -1;
2041 }
2042 }
2043
2044 return PJ_SUCCESS;
2045}
2046
2047
2048void app_logging_shutdown(void)
2049{
2050 /* Close logging file, if any: */
2051
2052 if (log_file) {
2053 fclose(log_file);
2054 log_file = NULL;
2055 }
2056}
2057
Benny Prijono60b980e2006-04-03 22:41:26 +00002058
2059/*
2060 * main()
2061 */
2062int main(int argc, char *argv[])
2063{
Benny Prijono4adcb912006-04-04 13:12:38 +00002064 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00002065 pj_status_t status;
2066
Benny Prijono4adcb912006-04-04 13:12:38 +00002067 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00002068 status = pj_init();
2069 if (status != PJ_SUCCESS)
2070 return 1;
2071
Benny Prijono4adcb912006-04-04 13:12:38 +00002072 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00002073 status = init_options(argc, argv);
2074 if (status != PJ_SUCCESS)
2075 return 1;
2076
Benny Prijono410fbae2006-05-03 18:16:06 +00002077 /* Verify options: */
2078
2079 /* Auto-quit can not be specified for UAS */
2080 if (app.auto_quit && app.uri_to_call.slen == 0) {
2081 printf("Error: --auto-quit option only valid for outgoing "
2082 "mode (UAC) only\n");
2083 return 1;
2084 }
2085
Benny Prijono4adcb912006-04-04 13:12:38 +00002086 /* Init logging */
2087 status = app_logging_init();
2088 if (status != PJ_SUCCESS)
2089 return 1;
2090
2091 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00002092 status = init_sip();
2093 if (status != PJ_SUCCESS) {
2094 app_perror(THIS_FILE, "Initialization has failed", status);
2095 destroy_sip();
2096 return 1;
2097 }
2098
Benny Prijono4adcb912006-04-04 13:12:38 +00002099 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00002100 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
2101
Benny Prijono4adcb912006-04-04 13:12:38 +00002102 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00002103 status = init_media();
2104 if (status != PJ_SUCCESS) {
2105 app_perror(THIS_FILE, "Media initialization failed", status);
2106 destroy_sip();
2107 return 1;
2108 }
2109
Benny Prijono9a0eab52006-04-04 19:43:24 +00002110 /* Start worker threads */
Benny Prijono6869dc92007-06-03 00:37:30 +00002111#if PJ_HAS_THREADS
Benny Prijono9a0eab52006-04-04 19:43:24 +00002112 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +00002113 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
2114 0, 0, &app.sip_thread[i]);
Benny Prijono9a0eab52006-04-04 19:43:24 +00002115 }
Benny Prijono6869dc92007-06-03 00:37:30 +00002116#endif
Benny Prijono9a0eab52006-04-04 19:43:24 +00002117
Benny Prijono4adcb912006-04-04 13:12:38 +00002118 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00002119 if (app.uri_to_call.slen) {
2120 unsigned i;
2121
Benny Prijono4adcb912006-04-04 13:12:38 +00002122 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
2123 app.uri_to_call.ptr));
2124
Benny Prijono60b980e2006-04-03 22:41:26 +00002125 for (i=0; i<app.max_calls; ++i) {
2126 status = make_call(&app.uri_to_call);
2127 if (status != PJ_SUCCESS) {
2128 app_perror(THIS_FILE, "Error making call", status);
2129 break;
2130 }
Benny Prijono1b543242008-01-08 22:50:06 +00002131 pj_thread_sleep(app.call_gap);
Benny Prijono60b980e2006-04-03 22:41:26 +00002132 }
Benny Prijono4adcb912006-04-04 13:12:38 +00002133
Benny Prijono410fbae2006-05-03 18:16:06 +00002134 if (app.auto_quit) {
2135 /* Wait for calls to complete */
2136 while (app.uac_calls < app.max_calls)
2137 pj_thread_sleep(100);
2138 pj_thread_sleep(200);
2139 } else {
Benny Prijono6869dc92007-06-03 00:37:30 +00002140#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002141 /* Start user interface loop */
2142 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002143#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002144 }
2145
Benny Prijono4adcb912006-04-04 13:12:38 +00002146 } else {
2147
2148 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
2149 app.max_calls));
Benny Prijono4adcb912006-04-04 13:12:38 +00002150
Benny Prijono6869dc92007-06-03 00:37:30 +00002151#if PJ_HAS_THREADS
Benny Prijono410fbae2006-05-03 18:16:06 +00002152 /* Start user interface loop */
2153 console_main();
Benny Prijono6869dc92007-06-03 00:37:30 +00002154#endif
Benny Prijono410fbae2006-05-03 18:16:06 +00002155 }
Benny Prijono60b980e2006-04-03 22:41:26 +00002156
Benny Prijono6869dc92007-06-03 00:37:30 +00002157#if !PJ_HAS_THREADS
2158 PJ_LOG(3,(THIS_FILE, "Press Ctrl-C to quit"));
2159 for (;;) {
2160 pj_time_val t = {0, 10};
2161 pjsip_endpt_handle_events(app.sip_endpt, &t);
2162 }
2163#endif
Benny Prijono4adcb912006-04-04 13:12:38 +00002164
2165 /* Shutting down... */
Benny Prijono4d3aa922006-06-22 22:31:48 +00002166 destroy_sip();
Benny Prijono513795f2006-07-18 21:12:24 +00002167 destroy_media();
Benny Prijono6647d822006-05-20 13:01:07 +00002168
2169 if (app.pool) {
2170 pj_pool_release(app.pool);
2171 app.pool = NULL;
2172 pj_caching_pool_destroy(&app.cp);
2173 }
2174
Benny Prijono4adcb912006-04-04 13:12:38 +00002175 app_logging_shutdown();
2176
Benny Prijono5b1e14d2006-11-25 08:46:48 +00002177 /* Shutdown PJLIB */
2178 pj_shutdown();
Benny Prijono60b980e2006-04-03 22:41:26 +00002179
2180 return 0;
2181}
2182