blob: 8e761ba282eb53ab633b20a626872719ff18772e [file] [log] [blame]
Benny Prijono60b980e2006-04-03 22:41:26 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20
Benny Prijonobf13fee2006-04-20 11:13:32 +000021
22
Benny Prijono1ec70b32006-06-20 15:39:07 +000023
Benny Prijonobf13fee2006-04-20 11:13:32 +000024/* Usage */
25static const char *USAGE =
26" PURPOSE: \n"
27" This program establishes SIP INVITE session and media, and calculate \n"
28" the media quality (packet lost, jitter, rtt, etc.). Unlike normal \n"
29" pjmedia applications, this program bypasses all pjmedia stream \n"
30" framework and transmit encoded RTP packets manually using own thread. \n"
31"\n"
32" USAGE:\n"
33" siprtp [options] => to start in server mode\n"
34" siprtp [options] URL => to start in client mode\n"
35"\n"
36" Program options:\n"
37" --count=N, -c Set number of calls to create (default:1) \n"
Benny Prijono410fbae2006-05-03 18:16:06 +000038" --duration=SEC, -d Set maximum call duration (default:unlimited) \n"
39" --auto-quit, -q Quit when calls have been completed (default:no)\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000040"\n"
41" Address and ports options:\n"
42" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
43" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
44" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
45" try to determine local IP address from hostname)\n"
46"\n"
47" Logging Options:\n"
48" --log-level=N, -l Set log verbosity level (default=5)\n"
49" --app-log-level=N Set app screen log verbosity (default=3)\n"
50" --log-file=FILE Write log to file FILE\n"
Benny Prijonofcb36722006-05-18 18:34:21 +000051" --report-file=FILE Write report to file FILE\n"
Benny Prijonobf13fee2006-04-20 11:13:32 +000052"\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000053/* Don't support this anymore, because codec is properly examined in
54 pjmedia_session_info_from_sdp() function.
55
Benny Prijonobf13fee2006-04-20 11:13:32 +000056" Codec Options:\n"
57" --a-pt=PT Set audio payload type to PT (default=0)\n"
58" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
59" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
60" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
61" --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
Benny Prijono4d7fd202006-05-14 20:57:20 +000062*/
Benny Prijonobf13fee2006-04-20 11:13:32 +000063;
64
65
Benny Prijono60b980e2006-04-03 22:41:26 +000066/* Include all headers. */
67#include <pjsip.h>
68#include <pjmedia.h>
69#include <pjmedia-codec.h>
70#include <pjsip_ua.h>
71#include <pjsip_simple.h>
72#include <pjlib-util.h>
73#include <pjlib.h>
74
75#include <stdlib.h>
76
Benny Prijono9a0eab52006-04-04 19:43:24 +000077
78#if PJ_HAS_HIGH_RES_TIMER==0
79# error "High resolution timer is needed for this sample"
80#endif
81
Benny Prijono60b980e2006-04-03 22:41:26 +000082#define THIS_FILE "siprtp.c"
83#define MAX_CALLS 1024
Benny Prijono6647d822006-05-20 13:01:07 +000084#define RTP_START_PORT 4000
Benny Prijono60b980e2006-04-03 22:41:26 +000085
86
Benny Prijono4adcb912006-04-04 13:12:38 +000087/* Codec descriptor: */
88struct codec
89{
90 unsigned pt;
91 char* name;
92 unsigned clock_rate;
93 unsigned bit_rate;
94 unsigned ptime;
95 char* description;
96};
97
98
Benny Prijonodeb31962006-06-22 18:51:03 +000099/* A bidirectional media stream created when the call is active. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000100struct media_stream
101{
102 /* Static: */
Benny Prijonodeb31962006-06-22 18:51:03 +0000103 unsigned call_index; /* Call owner. */
104 unsigned media_index; /* Media index in call. */
105 pjmedia_transport *transport; /* To send/recv RTP/RTCP */
106
107 /* Active? */
108 pj_bool_t active; /* Non-zero if is in call. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000109
110 /* Current stream info: */
111 pjmedia_stream_info si; /* Current stream info. */
112
113 /* More info: */
114 unsigned clock_rate; /* clock rate */
115 unsigned samples_per_frame; /* samples per frame */
116 unsigned bytes_per_frame; /* frame size. */
117
Benny Prijono60b980e2006-04-03 22:41:26 +0000118 /* RTP session: */
119 pjmedia_rtp_session out_sess; /* outgoing RTP session */
120 pjmedia_rtp_session in_sess; /* incoming RTP session */
121
122 /* RTCP stats: */
123 pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
Benny Prijono4adcb912006-04-04 13:12:38 +0000124
Benny Prijono4d3aa922006-06-22 22:31:48 +0000125 /* Timer to send RTP and RTCP: */
126 pj_timer_entry rtp_timer; /* timer to send RTP pkt. */
127 pj_timer_entry rtcp_timer; /* timer to send RTCP pkt. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000128};
129
130
Benny Prijonodeb31962006-06-22 18:51:03 +0000131/* This is a call structure that is created when the application starts
132 * and only destroyed when the application quits.
133 */
Benny Prijono60b980e2006-04-03 22:41:26 +0000134struct call
135{
136 unsigned index;
137 pjsip_inv_session *inv;
138 unsigned media_count;
Benny Prijonodeb31962006-06-22 18:51:03 +0000139 struct media_stream media[1];
Benny Prijono4adcb912006-04-04 13:12:38 +0000140 pj_time_val start_time;
141 pj_time_val response_time;
142 pj_time_val connect_time;
Benny Prijono410fbae2006-05-03 18:16:06 +0000143
144 pj_timer_entry d_timer; /**< Disconnect timer. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000145};
146
147
Benny Prijonodeb31962006-06-22 18:51:03 +0000148/* Application's global variables */
Benny Prijono60b980e2006-04-03 22:41:26 +0000149static struct app
150{
151 unsigned max_calls;
Benny Prijono410fbae2006-05-03 18:16:06 +0000152 unsigned uac_calls;
153 unsigned duration;
154 pj_bool_t auto_quit;
Benny Prijono60b980e2006-04-03 22:41:26 +0000155 unsigned thread_count;
156 int sip_port;
157 int rtp_start_port;
Benny Prijonodeb31962006-06-22 18:51:03 +0000158 pj_str_t local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +0000159 pj_str_t local_uri;
160 pj_str_t local_contact;
Benny Prijono4adcb912006-04-04 13:12:38 +0000161
162 int app_log_level;
163 int log_level;
164 char *log_filename;
Benny Prijonofcb36722006-05-18 18:34:21 +0000165 char *report_filename;
Benny Prijono4adcb912006-04-04 13:12:38 +0000166
167 struct codec audio_codec;
Benny Prijono60b980e2006-04-03 22:41:26 +0000168
169 pj_str_t uri_to_call;
170
171 pj_caching_pool cp;
172 pj_pool_t *pool;
173
174 pjsip_endpoint *sip_endpt;
175 pj_bool_t thread_quit;
Benny Prijonodeb31962006-06-22 18:51:03 +0000176 pj_thread_t *sip_thread[1];
Benny Prijono60b980e2006-04-03 22:41:26 +0000177
178 pjmedia_endpt *med_endpt;
179 struct call call[MAX_CALLS];
180} app;
181
182
183
184/*
185 * Prototypes:
186 */
187
188/* Callback to be called when SDP negotiation is done in the call: */
189static void call_on_media_update( pjsip_inv_session *inv,
190 pj_status_t status);
191
192/* Callback to be called when invite session's state has changed: */
193static void call_on_state_changed( pjsip_inv_session *inv,
194 pjsip_event *e);
195
196/* Callback to be called when dialog has forked: */
197static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
198
199/* Callback to be called to handle incoming requests outside dialogs: */
200static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
201
202/* Worker thread prototype */
Benny Prijonodeb31962006-06-22 18:51:03 +0000203static int sip_worker_thread(void *arg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000204
205/* Create SDP for call */
206static pj_status_t create_sdp( pj_pool_t *pool,
207 struct call *call,
208 pjmedia_sdp_session **p_sdp);
209
Benny Prijono410fbae2006-05-03 18:16:06 +0000210/* Hangup call */
211static void hangup_call(unsigned index);
212
Benny Prijono60b980e2006-04-03 22:41:26 +0000213/* Destroy the call's media */
214static void destroy_call_media(unsigned call_index);
215
Benny Prijonodeb31962006-06-22 18:51:03 +0000216/* Destroy media. */
217static void destroy_media();
218
219/* This callback is called by media transport on receipt of RTP packet. */
220static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size);
221
222/* This callback is called by media transport on receipt of RTCP packet. */
223static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size);
224
Benny Prijono4d3aa922006-06-22 22:31:48 +0000225/* This callback is called when it's time to send RTP packet */
226static void on_tx_rtp( pj_timer_heap_t *timer_heap,
227 struct pj_timer_entry *entry);
228
229/* This callback is called when it's time to send RTCP packet. */
230static void on_tx_rtcp(pj_timer_heap_t *timer_heap,
231 struct pj_timer_entry *entry);
232
233
Benny Prijono60b980e2006-04-03 22:41:26 +0000234/* Display error */
235static void app_perror(const char *sender, const char *title,
236 pj_status_t status);
237
Benny Prijonod7a13f12006-04-05 19:08:16 +0000238/* Print call */
239static void print_call(int call_index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000240
241
Benny Prijono60b980e2006-04-03 22:41:26 +0000242/* This is a PJSIP module to be registered by application to handle
243 * incoming requests outside any dialogs/transactions. The main purpose
244 * here is to handle incoming INVITE request message, where we will
245 * create a dialog and INVITE session for it.
246 */
247static pjsip_module mod_siprtp =
248{
249 NULL, NULL, /* prev, next. */
250 { "mod-siprtpapp", 13 }, /* Name. */
251 -1, /* Id */
252 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
253 NULL, /* load() */
254 NULL, /* start() */
255 NULL, /* stop() */
256 NULL, /* unload() */
257 &on_rx_request, /* on_rx_request() */
258 NULL, /* on_rx_response() */
259 NULL, /* on_tx_request. */
260 NULL, /* on_tx_response() */
261 NULL, /* on_tsx_state() */
262};
263
264
Benny Prijono4adcb912006-04-04 13:12:38 +0000265/* Codec constants */
266struct codec audio_codecs[] =
267{
Benny Prijono97f2a372006-06-07 21:21:57 +0000268 { 0, "PCMU", 8000, 64000, 20, "G.711 ULaw" },
269 { 3, "GSM", 8000, 13200, 20, "GSM" },
270 { 4, "G723", 8000, 6400, 30, "G.723.1" },
271 { 8, "PCMA", 8000, 64000, 20, "G.711 ALaw" },
Benny Prijonodeb31962006-06-22 18:51:03 +0000272 { 18, "G729", 8000, 8000, 20, "G.729" },
Benny Prijono4adcb912006-04-04 13:12:38 +0000273};
274
275
Benny Prijono60b980e2006-04-03 22:41:26 +0000276/*
277 * Init SIP stack
278 */
279static pj_status_t init_sip()
280{
Benny Prijonodeb31962006-06-22 18:51:03 +0000281 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000282 pj_status_t status;
283
284 /* init PJLIB-UTIL: */
285 status = pjlib_util_init();
286 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
287
288 /* Must create a pool factory before we can allocate any memory. */
289 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
290
291 /* Create application pool for misc. */
292 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
293
Benny Prijonodeb31962006-06-22 18:51:03 +0000294 /* Create the endpoint: */
295 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
296 &app.sip_endpt);
297 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000298
299
300 /* Add UDP transport. */
301 {
302 pj_sockaddr_in addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000303 pjsip_host_port addrname;
Benny Prijono60b980e2006-04-03 22:41:26 +0000304
Benny Prijono49ce9a72006-04-05 16:56:19 +0000305 pj_memset(&addr, 0, sizeof(addr));
Benny Prijono60b980e2006-04-03 22:41:26 +0000306 addr.sin_family = PJ_AF_INET;
307 addr.sin_addr.s_addr = 0;
308 addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
309
Benny Prijonodeb31962006-06-22 18:51:03 +0000310 if (app.local_addr.slen) {
311 addrname.host = app.local_addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000312 addrname.port = app.sip_port;
313 }
314
315 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
Benny Prijonodeb31962006-06-22 18:51:03 +0000316 (app.local_addr.slen ? &addrname:NULL),
Benny Prijono60b980e2006-04-03 22:41:26 +0000317 1, NULL);
Benny Prijono49ce9a72006-04-05 16:56:19 +0000318 if (status != PJ_SUCCESS) {
319 app_perror(THIS_FILE, "Unable to start UDP transport", status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000320 return status;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000321 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000322 }
323
324 /*
325 * Init transaction layer.
326 * This will create/initialize transaction hash tables etc.
327 */
328 status = pjsip_tsx_layer_init_module(app.sip_endpt);
329 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
330
331 /* Initialize UA layer. */
332 status = pjsip_ua_init_module( app.sip_endpt, NULL );
333 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
334
335 /* Init invite session module. */
336 {
337 pjsip_inv_callback inv_cb;
338
339 /* Init the callback for INVITE session: */
340 pj_memset(&inv_cb, 0, sizeof(inv_cb));
341 inv_cb.on_state_changed = &call_on_state_changed;
342 inv_cb.on_new_session = &call_on_forked;
343 inv_cb.on_media_update = &call_on_media_update;
344
345 /* Initialize invite session module: */
346 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
347 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
348 }
349
350 /* Register our module to receive incoming requests. */
351 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
352 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
353
Benny Prijonodeb31962006-06-22 18:51:03 +0000354 /* Init calls */
355 for (i=0; i<app.max_calls; ++i)
356 app.call[i].index = i;
Benny Prijono60b980e2006-04-03 22:41:26 +0000357
Benny Prijono60b980e2006-04-03 22:41:26 +0000358 /* Done */
359 return PJ_SUCCESS;
360}
361
362
363/*
364 * Destroy SIP
365 */
366static void destroy_sip()
367{
368 unsigned i;
369
370 app.thread_quit = 1;
371 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000372 if (app.sip_thread[i]) {
373 pj_thread_join(app.sip_thread[i]);
374 pj_thread_destroy(app.sip_thread[i]);
375 app.sip_thread[i] = NULL;
Benny Prijono60b980e2006-04-03 22:41:26 +0000376 }
377 }
378
379 if (app.sip_endpt) {
380 pjsip_endpt_destroy(app.sip_endpt);
381 app.sip_endpt = NULL;
382 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000383}
384
385
386/*
387 * Init media stack.
388 */
389static pj_status_t init_media()
390{
Benny Prijono60b980e2006-04-03 22:41:26 +0000391 unsigned i, count;
392 pj_uint16_t rtp_port;
Benny Prijono60b980e2006-04-03 22:41:26 +0000393 pj_status_t status;
394
395
Benny Prijono60b980e2006-04-03 22:41:26 +0000396 /* Initialize media endpoint so that at least error subsystem is properly
397 * initialized.
398 */
Benny Prijono4d3aa922006-06-22 22:31:48 +0000399 status = pjmedia_endpt_create(&app.cp.factory,
Benny Prijonoe6956992006-06-22 22:57:12 +0000400 pjsip_endpt_get_ioqueue(app.sip_endpt), 0,
Benny Prijono4d3aa922006-06-22 22:31:48 +0000401 &app.med_endpt);
Benny Prijono60b980e2006-04-03 22:41:26 +0000402 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
403
404
Benny Prijonodeb31962006-06-22 18:51:03 +0000405 /* Must register codecs to be supported */
Benny Prijono4d7fd202006-05-14 20:57:20 +0000406 pjmedia_codec_g711_init(app.med_endpt);
407
Benny Prijono60b980e2006-04-03 22:41:26 +0000408 /* RTP port counter */
409 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
410
Benny Prijonodeb31962006-06-22 18:51:03 +0000411 /* Init media transport for all calls. */
Benny Prijono60b980e2006-04-03 22:41:26 +0000412 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
413
Benny Prijonodeb31962006-06-22 18:51:03 +0000414 unsigned j;
Benny Prijono60b980e2006-04-03 22:41:26 +0000415
Benny Prijonodeb31962006-06-22 18:51:03 +0000416 /* Create transport for each media in the call */
417 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
418 /* Repeat binding media socket to next port when fails to bind
419 * to current port number.
420 */
Benny Prijono4d3aa922006-06-22 22:31:48 +0000421 struct media_stream *m = &app.call[i].media[j];
Benny Prijonodeb31962006-06-22 18:51:03 +0000422 int retry;
Benny Prijono60b980e2006-04-03 22:41:26 +0000423
Benny Prijono4d3aa922006-06-22 22:31:48 +0000424 m->call_index = i;
425 m->media_index = j;
426
427 m->rtp_timer.user_data = m;
428 m->rtp_timer.cb = &on_tx_rtp;
429
430 m->rtcp_timer.user_data = m;
431 m->rtcp_timer.cb = &on_tx_rtcp;
432
Benny Prijono60b980e2006-04-03 22:41:26 +0000433
Benny Prijonodeb31962006-06-22 18:51:03 +0000434 status = -1;
435 for (retry=0; retry<100; ++retry,rtp_port+=2) {
436 struct media_stream *m = &app.call[i].media[j];
437
438 status = pjmedia_transport_udp_create2(app.med_endpt,
439 "siprtp",
440 &app.local_addr,
441 rtp_port, 0,
442 &m->transport);
443 if (status == PJ_SUCCESS) {
444 rtp_port += 2;
445 break;
446 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000447 }
Benny Prijono6647d822006-05-20 13:01:07 +0000448 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000449
450 if (status != PJ_SUCCESS)
451 goto on_error;
Benny Prijono60b980e2006-04-03 22:41:26 +0000452 }
453
454 /* Done */
455 return PJ_SUCCESS;
456
457on_error:
Benny Prijonodeb31962006-06-22 18:51:03 +0000458 destroy_media();
Benny Prijono60b980e2006-04-03 22:41:26 +0000459 return status;
460}
461
462
463/*
464 * Destroy media.
465 */
466static void destroy_media()
467{
468 unsigned i;
469
470 for (i=0; i<app.max_calls; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +0000471 unsigned j;
472 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
473 struct media_stream *m = &app.call[i].media[j];
Benny Prijono60b980e2006-04-03 22:41:26 +0000474
Benny Prijonodeb31962006-06-22 18:51:03 +0000475 if (m->transport) {
476 pjmedia_transport_close(m->transport);
477 m->transport = NULL;
478 }
479 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000480 }
481
482 if (app.med_endpt) {
483 pjmedia_endpt_destroy(app.med_endpt);
484 app.med_endpt = NULL;
485 }
486}
487
488
489/*
490 * Make outgoing call.
491 */
492static pj_status_t make_call(const pj_str_t *dst_uri)
493{
494 unsigned i;
495 struct call *call;
496 pjsip_dialog *dlg;
497 pjmedia_sdp_session *sdp;
498 pjsip_tx_data *tdata;
499 pj_status_t status;
500
501
502 /* Find unused call slot */
503 for (i=0; i<app.max_calls; ++i) {
504 if (app.call[i].inv == NULL)
505 break;
506 }
507
508 if (i == app.max_calls)
509 return PJ_ETOOMANY;
510
511 call = &app.call[i];
512
513 /* Create UAC dialog */
514 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
515 &app.local_uri, /* local URI */
516 &app.local_contact, /* local Contact */
517 dst_uri, /* remote URI */
518 dst_uri, /* remote target */
519 &dlg); /* dialog */
Benny Prijono410fbae2006-05-03 18:16:06 +0000520 if (status != PJ_SUCCESS) {
521 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000522 return status;
Benny Prijono410fbae2006-05-03 18:16:06 +0000523 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000524
525 /* Create SDP */
526 create_sdp( dlg->pool, call, &sdp);
527
528 /* Create the INVITE session. */
529 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
530 if (status != PJ_SUCCESS) {
531 pjsip_dlg_terminate(dlg);
Benny Prijono410fbae2006-05-03 18:16:06 +0000532 ++app.uac_calls;
Benny Prijono60b980e2006-04-03 22:41:26 +0000533 return status;
534 }
535
536
537 /* Attach call data to invite session */
538 call->inv->mod_data[mod_siprtp.id] = call;
539
Benny Prijono4adcb912006-04-04 13:12:38 +0000540 /* Mark start of call */
541 pj_gettimeofday(&call->start_time);
542
Benny Prijono60b980e2006-04-03 22:41:26 +0000543
544 /* Create initial INVITE request.
545 * This INVITE request will contain a perfectly good request and
546 * an SDP body as well.
547 */
548 status = pjsip_inv_invite(call->inv, &tdata);
549 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
550
551
552 /* Send initial INVITE request.
553 * From now on, the invite session's state will be reported to us
554 * via the invite session callbacks.
555 */
556 status = pjsip_inv_send_msg(call->inv, tdata);
557 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
558
559
560 return PJ_SUCCESS;
561}
562
563
564/*
565 * Receive incoming call
566 */
567static void process_incoming_call(pjsip_rx_data *rdata)
568{
569 unsigned i;
570 struct call *call;
571 pjsip_dialog *dlg;
572 pjmedia_sdp_session *sdp;
573 pjsip_tx_data *tdata;
574 pj_status_t status;
575
576 /* Find free call slot */
577 for (i=0; i<app.max_calls; ++i) {
578 if (app.call[i].inv == NULL)
579 break;
580 }
581
582 if (i == app.max_calls) {
583 const pj_str_t reason = pj_str("Too many calls");
584 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
585 500, &reason,
586 NULL, NULL);
587 return;
588 }
589
590 call = &app.call[i];
591
592 /* Create UAS dialog */
593 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
594 &app.local_contact, &dlg);
595 if (status != PJ_SUCCESS) {
596 const pj_str_t reason = pj_str("Unable to create dialog");
597 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
598 500, &reason,
599 NULL, NULL);
600 return;
601 }
602
603 /* Create SDP */
604 create_sdp( dlg->pool, call, &sdp);
605
606 /* Create UAS invite session */
607 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
608 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000609 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
610 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000611 return;
612 }
613
Benny Prijono4adcb912006-04-04 13:12:38 +0000614
Benny Prijono60b980e2006-04-03 22:41:26 +0000615 /* Attach call data to invite session */
616 call->inv->mod_data[mod_siprtp.id] = call;
617
Benny Prijono4adcb912006-04-04 13:12:38 +0000618 /* Mark start of call */
619 pj_gettimeofday(&call->start_time);
620
621
622
Benny Prijono60b980e2006-04-03 22:41:26 +0000623 /* Create 200 response .*/
624 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
625 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000626 if (status != PJ_SUCCESS) {
627 status = pjsip_inv_initial_answer(call->inv, rdata,
628 PJSIP_SC_NOT_ACCEPTABLE,
629 NULL, NULL, &tdata);
630 if (status == PJ_SUCCESS)
631 pjsip_inv_send_msg(call->inv, tdata);
632 else
633 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
634 return;
635 }
636
Benny Prijono60b980e2006-04-03 22:41:26 +0000637
638 /* Send the 200 response. */
639 status = pjsip_inv_send_msg(call->inv, tdata);
640 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
641
642
643 /* Done */
644}
645
646
647/* Callback to be called when dialog has forked: */
648static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
649{
650 PJ_UNUSED_ARG(inv);
651 PJ_UNUSED_ARG(e);
652
653 PJ_TODO( HANDLE_FORKING );
654}
655
656
657/* Callback to be called to handle incoming requests outside dialogs: */
658static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
659{
Benny Prijono4adcb912006-04-04 13:12:38 +0000660 /* Ignore strandled ACKs (must not send respone */
661 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
662 return PJ_FALSE;
663
Benny Prijono60b980e2006-04-03 22:41:26 +0000664 /* Respond (statelessly) any non-INVITE requests with 500 */
665 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
666 pj_str_t reason = pj_str("Unsupported Operation");
667 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
668 500, &reason,
669 NULL, NULL);
670 return PJ_TRUE;
671 }
672
673 /* Handle incoming INVITE */
674 process_incoming_call(rdata);
675
676 /* Done */
677 return PJ_TRUE;
678}
679
680
Benny Prijono410fbae2006-05-03 18:16:06 +0000681/* Callback timer to disconnect call (limiting call duration) */
682static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
683 struct pj_timer_entry *entry)
684{
685 struct call *call = entry->user_data;
686
687 PJ_UNUSED_ARG(timer_heap);
688
689 entry->id = 0;
690 hangup_call(call->index);
691}
692
693
Benny Prijono60b980e2006-04-03 22:41:26 +0000694/* Callback to be called when invite session's state has changed: */
695static void call_on_state_changed( pjsip_inv_session *inv,
696 pjsip_event *e)
697{
Benny Prijono4adcb912006-04-04 13:12:38 +0000698 struct call *call = inv->mod_data[mod_siprtp.id];
699
Benny Prijono60b980e2006-04-03 22:41:26 +0000700 PJ_UNUSED_ARG(e);
701
Benny Prijono4adcb912006-04-04 13:12:38 +0000702 if (!call)
703 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000704
Benny Prijono4adcb912006-04-04 13:12:38 +0000705 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
706
707 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000708
Benny Prijono410fbae2006-05-03 18:16:06 +0000709 if (call->d_timer.id != 0) {
710 pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
711 call->d_timer.id = 0;
712 }
713
Benny Prijonod7a13f12006-04-05 19:08:16 +0000714 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%s",
715 call->index,
716 pjsip_get_status_text(inv->cause)->ptr));
717 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
718 print_call(call->index);
719
720
Benny Prijono60b980e2006-04-03 22:41:26 +0000721 call->inv = NULL;
722 inv->mod_data[mod_siprtp.id] = NULL;
723
724 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000725
726 call->start_time = null_time;
727 call->response_time = null_time;
728 call->connect_time = null_time;
729
Benny Prijono410fbae2006-05-03 18:16:06 +0000730 ++app.uac_calls;
Benny Prijono4adcb912006-04-04 13:12:38 +0000731
732 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
733
734 pj_time_val t;
735
736 pj_gettimeofday(&call->connect_time);
737 if (call->response_time.sec == 0)
738 call->response_time = call->connect_time;
739
740 t = call->connect_time;
741 PJ_TIME_VAL_SUB(t, call->start_time);
742
743 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
744 PJ_TIME_VAL_MSEC(t)));
745
Benny Prijono410fbae2006-05-03 18:16:06 +0000746 if (app.duration != 0) {
747 call->d_timer.id = 1;
748 call->d_timer.user_data = call;
749 call->d_timer.cb = &timer_disconnect_call;
750
751 t.sec = app.duration;
752 t.msec = 0;
753
754 pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
755 }
756
Benny Prijono4adcb912006-04-04 13:12:38 +0000757 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
758 inv->state == PJSIP_INV_STATE_CONNECTING) {
759
760 if (call->response_time.sec == 0)
761 pj_gettimeofday(&call->response_time);
762
Benny Prijono60b980e2006-04-03 22:41:26 +0000763 }
764}
765
766
767/* Utility */
768static void app_perror(const char *sender, const char *title,
769 pj_status_t status)
770{
771 char errmsg[PJ_ERR_MSG_SIZE];
772
773 pj_strerror(status, errmsg, sizeof(errmsg));
774 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
775}
776
777
Benny Prijono4d3aa922006-06-22 22:31:48 +0000778#if defined(PJ_WIN32) && PJ_WIN32 != 0
779#include <windows.h>
780static void boost_priority(void)
781{
782 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
783 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
784}
785
786#else
787# define boost_priority()
788#endif
789
790
Benny Prijonodeb31962006-06-22 18:51:03 +0000791/* Worker thread for SIP */
792static int sip_worker_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +0000793{
794 PJ_UNUSED_ARG(arg);
795
Benny Prijono4d3aa922006-06-22 22:31:48 +0000796 boost_priority();
797
Benny Prijono60b980e2006-04-03 22:41:26 +0000798 while (!app.thread_quit) {
Benny Prijono4d3aa922006-06-22 22:31:48 +0000799 pj_time_val timeout = {0, 1};
Benny Prijono60b980e2006-04-03 22:41:26 +0000800 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
801 }
802
803 return 0;
804}
805
806
Benny Prijono60b980e2006-04-03 22:41:26 +0000807/* Init application options */
808static pj_status_t init_options(int argc, char *argv[])
809{
810 static char ip_addr[32];
811 static char local_uri[64];
812
Benny Prijono4adcb912006-04-04 13:12:38 +0000813 enum { OPT_START,
814 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
Benny Prijonofcb36722006-05-18 18:34:21 +0000815 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
816 OPT_REPORT_FILE };
Benny Prijono4adcb912006-04-04 13:12:38 +0000817
Benny Prijono60b980e2006-04-03 22:41:26 +0000818 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000819 { "count", 1, 0, 'c' },
Benny Prijono410fbae2006-05-03 18:16:06 +0000820 { "duration", 1, 0, 'd' },
821 { "auto-quit", 0, 0, 'q' },
Benny Prijono4adcb912006-04-04 13:12:38 +0000822 { "local-port", 1, 0, 'p' },
823 { "rtp-port", 1, 0, 'r' },
824 { "ip-addr", 1, 0, 'i' },
825
826 { "log-level", 1, 0, 'l' },
827 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
828 { "log-file", 1, 0, OPT_LOG_FILE },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000829
Benny Prijonofcb36722006-05-18 18:34:21 +0000830 { "report-file", 1, 0, OPT_REPORT_FILE },
831
Benny Prijono4d7fd202006-05-14 20:57:20 +0000832 /* Don't support this anymore, see comments in USAGE above.
Benny Prijono4adcb912006-04-04 13:12:38 +0000833 { "a-pt", 1, 0, OPT_A_PT },
834 { "a-name", 1, 0, OPT_A_NAME },
835 { "a-clock", 1, 0, OPT_A_CLOCK },
836 { "a-bitrate", 1, 0, OPT_A_BITRATE },
837 { "a-ptime", 1, 0, OPT_A_PTIME },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000838 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000839
Benny Prijono60b980e2006-04-03 22:41:26 +0000840 { NULL, 0, 0, 0 },
841 };
842 int c;
843 int option_index;
844
845 /* Get local IP address for the default IP address */
846 {
847 const pj_str_t *hostname;
848 pj_sockaddr_in tmp_addr;
849 char *addr;
850
851 hostname = pj_gethostname();
852 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
853 addr = pj_inet_ntoa(tmp_addr.sin_addr);
854 pj_ansi_strcpy(ip_addr, addr);
855 }
856
Benny Prijono4adcb912006-04-04 13:12:38 +0000857 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000858 app.max_calls = 1;
859 app.thread_count = 1;
860 app.sip_port = 5060;
Benny Prijono6647d822006-05-20 13:01:07 +0000861 app.rtp_start_port = RTP_START_PORT;
Benny Prijonodeb31962006-06-22 18:51:03 +0000862 app.local_addr = pj_str(ip_addr);
Benny Prijono4adcb912006-04-04 13:12:38 +0000863 app.log_level = 5;
864 app.app_log_level = 3;
865 app.log_filename = NULL;
866
867 /* Default codecs: */
868 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000869
870 /* Parse options */
871 pj_optind = 0;
Benny Prijono410fbae2006-05-03 18:16:06 +0000872 while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:q",
Benny Prijono60b980e2006-04-03 22:41:26 +0000873 long_options, &option_index))!=-1)
874 {
875 switch (c) {
876 case 'c':
877 app.max_calls = atoi(pj_optarg);
878 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
879 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
880 return 1;
881 }
882 break;
Benny Prijono410fbae2006-05-03 18:16:06 +0000883 case 'd':
884 app.duration = atoi(pj_optarg);
885 break;
886 case 'q':
887 app.auto_quit = 1;
888 break;
889
Benny Prijono60b980e2006-04-03 22:41:26 +0000890 case 'p':
891 app.sip_port = atoi(pj_optarg);
892 break;
893 case 'r':
894 app.rtp_start_port = atoi(pj_optarg);
895 break;
896 case 'i':
Benny Prijonodeb31962006-06-22 18:51:03 +0000897 app.local_addr = pj_str(pj_optarg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000898 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000899
900 case 'l':
901 app.log_level = atoi(pj_optarg);
902 break;
903 case OPT_APP_LOG_LEVEL:
904 app.app_log_level = atoi(pj_optarg);
905 break;
906 case OPT_LOG_FILE:
907 app.log_filename = pj_optarg;
908 break;
909
910 case OPT_A_PT:
911 app.audio_codec.pt = atoi(pj_optarg);
912 break;
913 case OPT_A_NAME:
914 app.audio_codec.name = pj_optarg;
915 break;
916 case OPT_A_CLOCK:
917 app.audio_codec.clock_rate = atoi(pj_optarg);
918 break;
919 case OPT_A_BITRATE:
920 app.audio_codec.bit_rate = atoi(pj_optarg);
921 break;
922 case OPT_A_PTIME:
923 app.audio_codec.ptime = atoi(pj_optarg);
924 break;
Benny Prijonofcb36722006-05-18 18:34:21 +0000925 case OPT_REPORT_FILE:
926 app.report_filename = pj_optarg;
927 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000928
Benny Prijono60b980e2006-04-03 22:41:26 +0000929 default:
930 puts(USAGE);
931 return 1;
932 }
933 }
934
935 /* Check if URL is specified */
936 if (pj_optind < argc)
937 app.uri_to_call = pj_str(argv[pj_optind]);
938
939 /* Build local URI and contact */
Benny Prijonodeb31962006-06-22 18:51:03 +0000940 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
Benny Prijono60b980e2006-04-03 22:41:26 +0000941 app.local_uri = pj_str(local_uri);
942 app.local_contact = app.local_uri;
943
944
945 return PJ_SUCCESS;
946}
947
948
Benny Prijono4adcb912006-04-04 13:12:38 +0000949/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000950 * MEDIA STUFFS
951 */
952
953/*
954 * Create SDP session for a call.
955 */
956static pj_status_t create_sdp( pj_pool_t *pool,
957 struct call *call,
958 pjmedia_sdp_session **p_sdp)
959{
960 pj_time_val tv;
961 pjmedia_sdp_session *sdp;
962 pjmedia_sdp_media *m;
963 pjmedia_sdp_attr *attr;
Benny Prijonodeb31962006-06-22 18:51:03 +0000964 pjmedia_transport_udp_info tpinfo;
Benny Prijono60b980e2006-04-03 22:41:26 +0000965 struct media_stream *audio = &call->media[0];
966
967 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
968
969
Benny Prijonodeb31962006-06-22 18:51:03 +0000970 /* Get transport info */
971 pjmedia_transport_udp_get_info(audio->transport, &tpinfo);
972
Benny Prijono60b980e2006-04-03 22:41:26 +0000973 /* Create and initialize basic SDP session */
974 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
975
976 pj_gettimeofday(&tv);
977 sdp->origin.user = pj_str("pjsip-siprtp");
978 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
979 sdp->origin.net_type = pj_str("IN");
980 sdp->origin.addr_type = pj_str("IP4");
981 sdp->origin.addr = *pj_gethostname();
982 sdp->name = pj_str("pjsip");
983
984 /* Since we only support one media stream at present, put the
985 * SDP connection line in the session level.
986 */
987 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
988 sdp->conn->net_type = pj_str("IN");
989 sdp->conn->addr_type = pj_str("IP4");
Benny Prijonodeb31962006-06-22 18:51:03 +0000990 sdp->conn->addr = app.local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +0000991
992
993 /* SDP time and attributes. */
994 sdp->time.start = sdp->time.stop = 0;
995 sdp->attr_count = 0;
996
997 /* Create media stream 0: */
998
999 sdp->media_count = 1;
1000 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
1001 sdp->media[0] = m;
1002
1003 /* Standard media info: */
1004 m->desc.media = pj_str("audio");
Benny Prijonodeb31962006-06-22 18:51:03 +00001005 m->desc.port = pj_ntohs(tpinfo.skinfo.rtp_addr_name.sin_port);
Benny Prijono60b980e2006-04-03 22:41:26 +00001006 m->desc.port_count = 1;
1007 m->desc.transport = pj_str("RTP/AVP");
1008
1009 /* Add format and rtpmap for each codec. */
1010 m->desc.fmt_count = 1;
1011 m->attr_count = 0;
1012
1013 {
1014 pjmedia_sdp_rtpmap rtpmap;
1015 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +00001016 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +00001017
Benny Prijono4adcb912006-04-04 13:12:38 +00001018 sprintf(ptstr, "%d", app.audio_codec.pt);
1019 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1020 rtpmap.pt = m->desc.fmt[0];
1021 rtpmap.clock_rate = app.audio_codec.clock_rate;
1022 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +00001023 rtpmap.param.slen = 0;
1024
1025 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1026 m->attr[m->attr_count++] = attr;
1027 }
1028
1029 /* Add sendrecv attribute. */
1030 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1031 attr->name = pj_str("sendrecv");
1032 m->attr[m->attr_count++] = attr;
1033
1034#if 1
1035 /*
1036 * Add support telephony event
1037 */
Benny Prijono4adcb912006-04-04 13:12:38 +00001038 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +00001039 /* Add rtpmap. */
1040 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1041 attr->name = pj_str("rtpmap");
Benny Prijono4adcb912006-04-04 13:12:38 +00001042 attr->value = pj_str(":121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +00001043 m->attr[m->attr_count++] = attr;
1044 /* Add fmtp */
1045 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1046 attr->name = pj_str("fmtp");
Benny Prijono4adcb912006-04-04 13:12:38 +00001047 attr->value = pj_str(":121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +00001048 m->attr[m->attr_count++] = attr;
1049#endif
1050
1051 /* Done */
1052 *p_sdp = sdp;
1053
1054 return PJ_SUCCESS;
1055}
1056
1057
Benny Prijonodeb31962006-06-22 18:51:03 +00001058/*
1059 * This callback is called by media transport on receipt of RTP packet.
1060 */
1061static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size)
1062{
1063 struct media_stream *strm;
1064 pj_status_t status;
1065 const pjmedia_rtp_hdr *hdr;
1066 const void *payload;
1067 unsigned payload_len;
1068
1069 strm = user_data;
1070
1071 /* Discard packet if media is inactive */
1072 if (!strm->active)
1073 return;
1074
1075 /* Check for errors */
1076 if (size < 0) {
1077 app_perror(THIS_FILE, "RTP recv() error", -size);
1078 return;
1079 }
1080
1081 /* Decode RTP packet. */
1082 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1083 pkt, size,
1084 &hdr, &payload, &payload_len);
1085 if (status != PJ_SUCCESS) {
1086 app_perror(THIS_FILE, "RTP decode error", status);
1087 return;
1088 }
1089
1090 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1091
1092 /* Update the RTCP session. */
1093 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1094 pj_ntohl(hdr->ts), payload_len);
1095
1096 /* Update RTP session */
1097 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1098
1099}
1100
Benny Prijono4d3aa922006-06-22 22:31:48 +00001101/* This callback is called when it's time to send RTP packet */
1102static void on_tx_rtp( pj_timer_heap_t *timer_heap,
1103 struct pj_timer_entry *entry)
1104{
1105 pj_status_t status;
1106 const pjmedia_rtp_hdr *hdr;
1107 pj_ssize_t size;
1108 int hdrlen;
1109 pj_time_val interval;
1110 char packet[512];
1111 struct media_stream *strm = entry->user_data;
1112
1113 PJ_UNUSED_ARG(timer_heap);
1114
1115 if (!strm->active)
1116 return;
1117
1118 /* Format RTP header */
1119 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1120 0, /* marker bit */
1121 strm->bytes_per_frame,
1122 strm->samples_per_frame,
1123 (const void**)&hdr, &hdrlen);
1124 if (status == PJ_SUCCESS) {
1125
1126 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
1127
1128 /* Copy RTP header to packet */
1129 pj_memcpy(packet, hdr, hdrlen);
1130
1131 /* Zero the payload */
1132 pj_memset(packet+hdrlen, 0, strm->bytes_per_frame);
1133
1134 /* Send RTP packet */
1135 size = hdrlen + strm->bytes_per_frame;
1136 status = pjmedia_transport_send_rtp(strm->transport,
1137 packet, size);
1138 if (status != PJ_SUCCESS)
1139 app_perror(THIS_FILE, "Error sending RTP packet", status);
1140
1141 } else {
1142 pj_assert(!"RTP encode() error");
1143 }
1144
1145 /* Update RTCP SR */
1146 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1147
1148 /* Schedule next send */
1149 interval.sec = 0;
1150 interval.msec = strm->samples_per_frame * 1000 / strm->clock_rate;
1151 pj_time_val_normalize(&interval);
1152
1153 pjsip_endpt_schedule_timer(app.sip_endpt, &strm->rtp_timer, &interval);
1154}
1155
1156
Benny Prijonodeb31962006-06-22 18:51:03 +00001157/*
1158 * This callback is called by media transport on receipt of RTCP packet.
1159 */
1160static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size)
1161{
1162 struct media_stream *strm;
1163
1164 strm = user_data;
1165
1166 /* Discard packet if media is inactive */
1167 if (!strm->active)
1168 return;
1169
1170 /* Check for errors */
1171 if (size < 0) {
1172 app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
1173 return;
1174 }
1175
1176 /* Update RTCP session */
1177 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1178}
1179
1180
Benny Prijono4d3aa922006-06-22 22:31:48 +00001181/* This callback is called when it's time to send RTCP packet. */
1182static void on_tx_rtcp(pj_timer_heap_t *timer_heap,
1183 struct pj_timer_entry *entry)
Benny Prijono60b980e2006-04-03 22:41:26 +00001184{
Benny Prijono4d3aa922006-06-22 22:31:48 +00001185 pjmedia_rtcp_pkt *rtcp_pkt;
1186 int rtcp_len;
1187 pj_ssize_t size;
1188 pj_status_t status;
1189 pj_time_val interval;
1190 struct media_stream *strm = entry->user_data;
Benny Prijono60b980e2006-04-03 22:41:26 +00001191
Benny Prijono4d3aa922006-06-22 22:31:48 +00001192 PJ_UNUSED_ARG(timer_heap);
Benny Prijono6647d822006-05-20 13:01:07 +00001193
Benny Prijono4d3aa922006-06-22 22:31:48 +00001194 if (!strm->active)
1195 return;
Benny Prijono6647d822006-05-20 13:01:07 +00001196
Benny Prijono4d3aa922006-06-22 22:31:48 +00001197 /* Build RTCP packet */
1198 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
Benny Prijono6647d822006-05-20 13:01:07 +00001199
Benny Prijono4d3aa922006-06-22 22:31:48 +00001200 /* Send packet */
1201 size = rtcp_len;
1202 status = pjmedia_transport_send_rtcp(strm->transport,
1203 rtcp_pkt, size);
1204 if (status != PJ_SUCCESS) {
1205 app_perror(THIS_FILE, "Error sending RTCP packet", status);
Benny Prijono60b980e2006-04-03 22:41:26 +00001206 }
Benny Prijono4d3aa922006-06-22 22:31:48 +00001207
1208 /* Schedule next send */
1209 interval.sec = 5;
1210 interval.msec = (pj_rand() % 500);
1211 pjsip_endpt_schedule_timer(app.sip_endpt, &strm->rtcp_timer, &interval);
Benny Prijono60b980e2006-04-03 22:41:26 +00001212
Benny Prijono60b980e2006-04-03 22:41:26 +00001213}
1214
Benny Prijono60b980e2006-04-03 22:41:26 +00001215/* Callback to be called when SDP negotiation is done in the call: */
1216static void call_on_media_update( pjsip_inv_session *inv,
1217 pj_status_t status)
1218{
1219 struct call *call;
1220 pj_pool_t *pool;
1221 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001222 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001223 struct codec *codec_desc = NULL;
Benny Prijono4d3aa922006-06-22 22:31:48 +00001224 pj_time_val interval;
Benny Prijono4adcb912006-04-04 13:12:38 +00001225 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001226
1227 call = inv->mod_data[mod_siprtp.id];
1228 pool = inv->dlg->pool;
1229 audio = &call->media[0];
1230
1231 /* If this is a mid-call media update, then destroy existing media */
Benny Prijono4d3aa922006-06-22 22:31:48 +00001232 if (audio->active)
Benny Prijono60b980e2006-04-03 22:41:26 +00001233 destroy_call_media(call->index);
1234
1235
1236 /* Do nothing if media negotiation has failed */
1237 if (status != PJ_SUCCESS) {
1238 app_perror(THIS_FILE, "SDP negotiation failed", status);
1239 return;
1240 }
1241
1242
1243 /* Capture stream definition from the SDP */
1244 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1245 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1246
1247 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
Benny Prijonob04c9e02006-05-17 17:17:39 +00001248 local_sdp, remote_sdp, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001249 if (status != PJ_SUCCESS) {
1250 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1251 return;
1252 }
1253
Benny Prijono4adcb912006-04-04 13:12:38 +00001254 /* Get the remainder of codec information from codec descriptor */
1255 if (audio->si.fmt.pt == app.audio_codec.pt)
1256 codec_desc = &app.audio_codec;
1257 else {
1258 /* Find the codec description in codec array */
1259 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1260 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1261 codec_desc = &audio_codecs[i];
1262 break;
1263 }
1264 }
1265
1266 if (codec_desc == NULL) {
1267 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1268 return;
1269 }
1270 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001271
Benny Prijono15953012006-04-27 22:37:08 +00001272 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001273 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1274 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001275
1276
1277 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001278 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001279 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001280 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001281 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001282
Benny Prijono4adcb912006-04-04 13:12:38 +00001283
Benny Prijonodeb31962006-06-22 18:51:03 +00001284 /* Attach media to transport */
1285 status = pjmedia_transport_attach(audio->transport, audio,
1286 &audio->si.rem_addr,
Benny Prijonob12106f2006-06-29 14:45:17 +00001287 &audio->si.rem_rtcp,
Benny Prijonodeb31962006-06-22 18:51:03 +00001288 sizeof(pj_sockaddr_in),
1289 &on_rx_rtp,
1290 &on_rx_rtcp);
1291 if (status != PJ_SUCCESS) {
1292 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1293 return;
1294 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001295
Benny Prijonodeb31962006-06-22 18:51:03 +00001296 /* Set the media as active */
1297 audio->active = PJ_TRUE;
Benny Prijono4d3aa922006-06-22 22:31:48 +00001298
1299 /* Immediately schedule to send the first RTP packet. */
1300 audio->rtp_timer.id = 1;
1301 interval.sec = interval.msec = 0;
1302 pjsip_endpt_schedule_timer(app.sip_endpt, &audio->rtp_timer, &interval);
1303
1304 /* And schedule the first RTCP packet */
1305 audio->rtcp_timer.id = 1;
1306 interval.sec = 4;
1307 interval.msec = (pj_rand() % 1000);
1308 pjsip_endpt_schedule_timer(app.sip_endpt, &audio->rtcp_timer, &interval);
Benny Prijono60b980e2006-04-03 22:41:26 +00001309}
1310
1311
1312
1313/* Destroy call's media */
1314static void destroy_call_media(unsigned call_index)
1315{
1316 struct media_stream *audio = &app.call[call_index].media[0];
1317
Benny Prijono4d3aa922006-06-22 22:31:48 +00001318 if (audio->active) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001319
1320 audio->active = PJ_FALSE;
1321
Benny Prijono4d3aa922006-06-22 22:31:48 +00001322 if (audio->rtp_timer.id) {
1323 audio->rtp_timer.id = 0;
1324 pjsip_endpt_cancel_timer(app.sip_endpt, &audio->rtp_timer);
1325 }
1326
1327 if (audio->rtcp_timer.id) {
1328 audio->rtcp_timer.id = 0;
1329 pjsip_endpt_cancel_timer(app.sip_endpt, &audio->rtcp_timer);
1330 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001331
Benny Prijonodeb31962006-06-22 18:51:03 +00001332 pjmedia_transport_detach(audio->transport, audio);
Benny Prijono60b980e2006-04-03 22:41:26 +00001333 }
1334}
1335
1336
Benny Prijono4adcb912006-04-04 13:12:38 +00001337/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001338 * USER INTERFACE STUFFS
1339 */
Benny Prijono16a6b0e2006-05-12 10:20:03 +00001340#include "siprtp_report.c"
Benny Prijono60b980e2006-04-03 22:41:26 +00001341
1342
1343static void list_calls()
1344{
1345 unsigned i;
1346 puts("List all calls:");
1347 for (i=0; i<app.max_calls; ++i) {
1348 if (!app.call[i].inv)
1349 continue;
1350 print_call(i);
1351 }
1352}
1353
1354static void hangup_call(unsigned index)
1355{
1356 pjsip_tx_data *tdata;
1357 pj_status_t status;
1358
1359 if (app.call[index].inv == NULL)
1360 return;
1361
1362 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1363 if (status==PJ_SUCCESS && tdata!=NULL)
1364 pjsip_inv_send_msg(app.call[index].inv, tdata);
1365}
1366
1367static void hangup_all_calls()
1368{
1369 unsigned i;
1370 for (i=0; i<app.max_calls; ++i) {
1371 if (!app.call[i].inv)
1372 continue;
1373 hangup_call(i);
1374 }
Benny Prijonodeb31962006-06-22 18:51:03 +00001375
1376 /* Wait until all calls are terminated */
1377 for (i=0; i<app.max_calls; ++i) {
1378 while (app.call[i].inv)
1379 pj_thread_sleep(10);
1380 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001381}
1382
1383static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1384{
1385 char *p;
1386
1387 printf("%s (empty to cancel): ", title); fflush(stdout);
1388 fgets(buf, len, stdin);
1389
1390 /* Remove trailing newlines. */
1391 for (p=buf; ; ++p) {
1392 if (*p=='\r' || *p=='\n') *p='\0';
1393 else if (!*p) break;
1394 }
1395
1396 if (!*buf)
1397 return PJ_FALSE;
1398
1399 return PJ_TRUE;
1400}
1401
1402
1403static const char *MENU =
1404"\n"
1405"Enter menu character:\n"
1406" l List all calls\n"
1407" h Hangup a call\n"
1408" H Hangup all calls\n"
1409" q Quit\n"
1410"\n";
1411
1412
1413/* Main screen menu */
1414static void console_main()
1415{
1416 char input1[10];
1417 unsigned i;
1418
Benny Prijono4adcb912006-04-04 13:12:38 +00001419 printf("%s", MENU);
1420
Benny Prijono60b980e2006-04-03 22:41:26 +00001421 for (;;) {
1422 printf(">>> "); fflush(stdout);
1423 fgets(input1, sizeof(input1), stdin);
1424
1425 switch (input1[0]) {
1426 case 'l':
1427 list_calls();
1428 break;
1429
1430 case 'h':
1431 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1432 break;
1433
1434 i = atoi(input1);
1435 hangup_call(i);
1436 break;
1437
1438 case 'H':
1439 hangup_all_calls();
1440 break;
1441
1442 case 'q':
1443 goto on_exit;
1444
1445 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001446 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001447 printf("%s", MENU);
1448 break;
1449 }
1450
1451 fflush(stdout);
1452 }
1453
1454on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001455 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001456}
1457
1458
Benny Prijono4adcb912006-04-04 13:12:38 +00001459/*****************************************************************************
1460 * Below is a simple module to log all incoming and outgoing SIP messages
1461 */
1462
1463
Benny Prijono60b980e2006-04-03 22:41:26 +00001464/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001465static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001466{
1467 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1468 "%s\n"
1469 "--end msg--",
1470 rdata->msg_info.len,
1471 pjsip_rx_data_get_info(rdata),
1472 rdata->pkt_info.src_name,
1473 rdata->pkt_info.src_port,
1474 rdata->msg_info.msg_buf));
1475
1476 /* Always return false, otherwise messages will not get processed! */
1477 return PJ_FALSE;
1478}
1479
1480/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001481static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001482{
1483
1484 /* Important note:
1485 * tp_info field is only valid after outgoing messages has passed
1486 * transport layer. So don't try to access tp_info when the module
1487 * has lower priority than transport layer.
1488 */
1489
1490 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1491 "%s\n"
1492 "--end msg--",
1493 (tdata->buf.cur - tdata->buf.start),
1494 pjsip_tx_data_get_info(tdata),
1495 tdata->tp_info.dst_name,
1496 tdata->tp_info.dst_port,
1497 tdata->buf.start));
1498
1499 /* Always return success, otherwise message will not get sent! */
1500 return PJ_SUCCESS;
1501}
1502
1503/* The module instance. */
1504static pjsip_module msg_logger =
1505{
1506 NULL, NULL, /* prev, next. */
1507 { "mod-siprtp-log", 14 }, /* Name. */
1508 -1, /* Id */
1509 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1510 NULL, /* load() */
1511 NULL, /* start() */
1512 NULL, /* stop() */
1513 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001514 &logger_on_rx_msg, /* on_rx_request() */
1515 &logger_on_rx_msg, /* on_rx_response() */
1516 &logger_on_tx_msg, /* on_tx_request. */
1517 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00001518 NULL, /* on_tsx_state() */
1519
1520};
1521
1522
1523
Benny Prijono4adcb912006-04-04 13:12:38 +00001524/*****************************************************************************
1525 * Console application custom logging:
1526 */
1527
1528
1529static FILE *log_file;
1530
1531
1532static void app_log_writer(int level, const char *buffer, int len)
1533{
1534 /* Write to both stdout and file. */
1535
1536 if (level <= app.app_log_level)
1537 pj_log_write(level, buffer, len);
1538
1539 if (log_file) {
1540 fwrite(buffer, len, 1, log_file);
1541 fflush(log_file);
1542 }
1543}
1544
1545
1546pj_status_t app_logging_init(void)
1547{
1548 /* Redirect log function to ours */
1549
1550 pj_log_set_log_func( &app_log_writer );
1551
1552 /* If output log file is desired, create the file: */
1553
1554 if (app.log_filename) {
1555 log_file = fopen(app.log_filename, "wt");
1556 if (log_file == NULL) {
1557 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
1558 app.log_filename));
1559 return -1;
1560 }
1561 }
1562
1563 return PJ_SUCCESS;
1564}
1565
1566
1567void app_logging_shutdown(void)
1568{
1569 /* Close logging file, if any: */
1570
1571 if (log_file) {
1572 fclose(log_file);
1573 log_file = NULL;
1574 }
1575}
1576
Benny Prijono60b980e2006-04-03 22:41:26 +00001577
1578/*
1579 * main()
1580 */
1581int main(int argc, char *argv[])
1582{
Benny Prijono4adcb912006-04-04 13:12:38 +00001583 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001584 pj_status_t status;
1585
Benny Prijono4adcb912006-04-04 13:12:38 +00001586 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00001587 status = pj_init();
1588 if (status != PJ_SUCCESS)
1589 return 1;
1590
Benny Prijono4adcb912006-04-04 13:12:38 +00001591 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00001592 status = init_options(argc, argv);
1593 if (status != PJ_SUCCESS)
1594 return 1;
1595
Benny Prijono410fbae2006-05-03 18:16:06 +00001596 /* Verify options: */
1597
1598 /* Auto-quit can not be specified for UAS */
1599 if (app.auto_quit && app.uri_to_call.slen == 0) {
1600 printf("Error: --auto-quit option only valid for outgoing "
1601 "mode (UAC) only\n");
1602 return 1;
1603 }
1604
Benny Prijono4adcb912006-04-04 13:12:38 +00001605 /* Init logging */
1606 status = app_logging_init();
1607 if (status != PJ_SUCCESS)
1608 return 1;
1609
1610 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00001611 status = init_sip();
1612 if (status != PJ_SUCCESS) {
1613 app_perror(THIS_FILE, "Initialization has failed", status);
1614 destroy_sip();
1615 return 1;
1616 }
1617
Benny Prijono4adcb912006-04-04 13:12:38 +00001618 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00001619 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
1620
Benny Prijono4adcb912006-04-04 13:12:38 +00001621 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00001622 status = init_media();
1623 if (status != PJ_SUCCESS) {
1624 app_perror(THIS_FILE, "Media initialization failed", status);
1625 destroy_sip();
1626 return 1;
1627 }
1628
Benny Prijono9a0eab52006-04-04 19:43:24 +00001629 /* Start worker threads */
1630 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001631 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
1632 0, 0, &app.sip_thread[i]);
Benny Prijono9a0eab52006-04-04 19:43:24 +00001633 }
1634
Benny Prijono4adcb912006-04-04 13:12:38 +00001635 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00001636 if (app.uri_to_call.slen) {
1637 unsigned i;
1638
Benny Prijono4adcb912006-04-04 13:12:38 +00001639 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
1640 app.uri_to_call.ptr));
1641
Benny Prijono60b980e2006-04-03 22:41:26 +00001642 for (i=0; i<app.max_calls; ++i) {
1643 status = make_call(&app.uri_to_call);
1644 if (status != PJ_SUCCESS) {
1645 app_perror(THIS_FILE, "Error making call", status);
1646 break;
1647 }
1648 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001649
Benny Prijono410fbae2006-05-03 18:16:06 +00001650 if (app.auto_quit) {
1651 /* Wait for calls to complete */
1652 while (app.uac_calls < app.max_calls)
1653 pj_thread_sleep(100);
1654 pj_thread_sleep(200);
1655 } else {
1656 /* Start user interface loop */
1657 console_main();
1658 }
1659
Benny Prijono4adcb912006-04-04 13:12:38 +00001660 } else {
1661
1662 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
1663 app.max_calls));
Benny Prijono4adcb912006-04-04 13:12:38 +00001664
Benny Prijono410fbae2006-05-03 18:16:06 +00001665 /* Start user interface loop */
1666 console_main();
1667
1668 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001669
Benny Prijono4adcb912006-04-04 13:12:38 +00001670
1671 /* Shutting down... */
Benny Prijono6647d822006-05-20 13:01:07 +00001672 destroy_media();
Benny Prijono4d3aa922006-06-22 22:31:48 +00001673 destroy_sip();
Benny Prijono6647d822006-05-20 13:01:07 +00001674
1675 if (app.pool) {
1676 pj_pool_release(app.pool);
1677 app.pool = NULL;
1678 pj_caching_pool_destroy(&app.cp);
1679 }
1680
Benny Prijono4adcb912006-04-04 13:12:38 +00001681 app_logging_shutdown();
1682
Benny Prijono60b980e2006-04-03 22:41:26 +00001683
1684 return 0;
1685}
1686