blob: 7e8168a20a43ff93f3a4f8386e069c5282e2c315 [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{
Benny Prijonoca4cff22006-07-02 14:18:47 +0000569 unsigned i, options;
Benny Prijono60b980e2006-04-03 22:41:26 +0000570 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
Benny Prijonoca4cff22006-07-02 14:18:47 +0000590 /* Verify that we can handle the request. */
591 options = 0;
592 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
593 app.sip_endpt, &tdata);
594 if (status != PJ_SUCCESS) {
595
596 /*
597 * No we can't handle the incoming INVITE request.
598 */
599
600 if (tdata) {
601 pjsip_response_addr res_addr;
602
603 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
604 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
605 NULL, NULL);
606
607 } else {
608
609 /* Respond with 500 (Internal Server Error) */
610 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
611 NULL, NULL);
612 }
613
614 return;
615 }
616
Benny Prijono60b980e2006-04-03 22:41:26 +0000617 call = &app.call[i];
618
619 /* Create UAS dialog */
620 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
621 &app.local_contact, &dlg);
622 if (status != PJ_SUCCESS) {
623 const pj_str_t reason = pj_str("Unable to create dialog");
624 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
625 500, &reason,
626 NULL, NULL);
627 return;
628 }
629
630 /* Create SDP */
631 create_sdp( dlg->pool, call, &sdp);
632
633 /* Create UAS invite session */
634 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
635 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000636 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
637 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000638 return;
639 }
640
Benny Prijono4adcb912006-04-04 13:12:38 +0000641
Benny Prijono60b980e2006-04-03 22:41:26 +0000642 /* Attach call data to invite session */
643 call->inv->mod_data[mod_siprtp.id] = call;
644
Benny Prijono4adcb912006-04-04 13:12:38 +0000645 /* Mark start of call */
646 pj_gettimeofday(&call->start_time);
647
648
649
Benny Prijono60b980e2006-04-03 22:41:26 +0000650 /* Create 200 response .*/
651 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
652 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000653 if (status != PJ_SUCCESS) {
654 status = pjsip_inv_initial_answer(call->inv, rdata,
655 PJSIP_SC_NOT_ACCEPTABLE,
656 NULL, NULL, &tdata);
657 if (status == PJ_SUCCESS)
658 pjsip_inv_send_msg(call->inv, tdata);
659 else
660 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
661 return;
662 }
663
Benny Prijono60b980e2006-04-03 22:41:26 +0000664
665 /* Send the 200 response. */
666 status = pjsip_inv_send_msg(call->inv, tdata);
667 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
668
669
670 /* Done */
671}
672
673
674/* Callback to be called when dialog has forked: */
675static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
676{
677 PJ_UNUSED_ARG(inv);
678 PJ_UNUSED_ARG(e);
679
680 PJ_TODO( HANDLE_FORKING );
681}
682
683
684/* Callback to be called to handle incoming requests outside dialogs: */
685static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
686{
Benny Prijono4adcb912006-04-04 13:12:38 +0000687 /* Ignore strandled ACKs (must not send respone */
688 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
689 return PJ_FALSE;
690
Benny Prijono60b980e2006-04-03 22:41:26 +0000691 /* Respond (statelessly) any non-INVITE requests with 500 */
692 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
693 pj_str_t reason = pj_str("Unsupported Operation");
694 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
695 500, &reason,
696 NULL, NULL);
697 return PJ_TRUE;
698 }
699
700 /* Handle incoming INVITE */
701 process_incoming_call(rdata);
702
703 /* Done */
704 return PJ_TRUE;
705}
706
707
Benny Prijono410fbae2006-05-03 18:16:06 +0000708/* Callback timer to disconnect call (limiting call duration) */
709static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
710 struct pj_timer_entry *entry)
711{
712 struct call *call = entry->user_data;
713
714 PJ_UNUSED_ARG(timer_heap);
715
716 entry->id = 0;
717 hangup_call(call->index);
718}
719
720
Benny Prijono60b980e2006-04-03 22:41:26 +0000721/* Callback to be called when invite session's state has changed: */
722static void call_on_state_changed( pjsip_inv_session *inv,
723 pjsip_event *e)
724{
Benny Prijono4adcb912006-04-04 13:12:38 +0000725 struct call *call = inv->mod_data[mod_siprtp.id];
726
Benny Prijono60b980e2006-04-03 22:41:26 +0000727 PJ_UNUSED_ARG(e);
728
Benny Prijono4adcb912006-04-04 13:12:38 +0000729 if (!call)
730 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000731
Benny Prijono4adcb912006-04-04 13:12:38 +0000732 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
733
734 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000735
Benny Prijono410fbae2006-05-03 18:16:06 +0000736 if (call->d_timer.id != 0) {
737 pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
738 call->d_timer.id = 0;
739 }
740
Benny Prijonod7a13f12006-04-05 19:08:16 +0000741 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%s",
742 call->index,
743 pjsip_get_status_text(inv->cause)->ptr));
744 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
745 print_call(call->index);
746
747
Benny Prijono60b980e2006-04-03 22:41:26 +0000748 call->inv = NULL;
749 inv->mod_data[mod_siprtp.id] = NULL;
750
751 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000752
753 call->start_time = null_time;
754 call->response_time = null_time;
755 call->connect_time = null_time;
756
Benny Prijono410fbae2006-05-03 18:16:06 +0000757 ++app.uac_calls;
Benny Prijono4adcb912006-04-04 13:12:38 +0000758
759 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
760
761 pj_time_val t;
762
763 pj_gettimeofday(&call->connect_time);
764 if (call->response_time.sec == 0)
765 call->response_time = call->connect_time;
766
767 t = call->connect_time;
768 PJ_TIME_VAL_SUB(t, call->start_time);
769
770 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
771 PJ_TIME_VAL_MSEC(t)));
772
Benny Prijono410fbae2006-05-03 18:16:06 +0000773 if (app.duration != 0) {
774 call->d_timer.id = 1;
775 call->d_timer.user_data = call;
776 call->d_timer.cb = &timer_disconnect_call;
777
778 t.sec = app.duration;
779 t.msec = 0;
780
781 pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
782 }
783
Benny Prijono4adcb912006-04-04 13:12:38 +0000784 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
785 inv->state == PJSIP_INV_STATE_CONNECTING) {
786
787 if (call->response_time.sec == 0)
788 pj_gettimeofday(&call->response_time);
789
Benny Prijono60b980e2006-04-03 22:41:26 +0000790 }
791}
792
793
794/* Utility */
795static void app_perror(const char *sender, const char *title,
796 pj_status_t status)
797{
798 char errmsg[PJ_ERR_MSG_SIZE];
799
800 pj_strerror(status, errmsg, sizeof(errmsg));
801 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
802}
803
804
Benny Prijono4d3aa922006-06-22 22:31:48 +0000805#if defined(PJ_WIN32) && PJ_WIN32 != 0
806#include <windows.h>
807static void boost_priority(void)
808{
809 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
810 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
811}
812
813#else
814# define boost_priority()
815#endif
816
817
Benny Prijonodeb31962006-06-22 18:51:03 +0000818/* Worker thread for SIP */
819static int sip_worker_thread(void *arg)
Benny Prijono60b980e2006-04-03 22:41:26 +0000820{
821 PJ_UNUSED_ARG(arg);
822
Benny Prijono4d3aa922006-06-22 22:31:48 +0000823 boost_priority();
824
Benny Prijono60b980e2006-04-03 22:41:26 +0000825 while (!app.thread_quit) {
Benny Prijono4d3aa922006-06-22 22:31:48 +0000826 pj_time_val timeout = {0, 1};
Benny Prijono60b980e2006-04-03 22:41:26 +0000827 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
828 }
829
830 return 0;
831}
832
833
Benny Prijono60b980e2006-04-03 22:41:26 +0000834/* Init application options */
835static pj_status_t init_options(int argc, char *argv[])
836{
837 static char ip_addr[32];
838 static char local_uri[64];
839
Benny Prijono4adcb912006-04-04 13:12:38 +0000840 enum { OPT_START,
841 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
Benny Prijonofcb36722006-05-18 18:34:21 +0000842 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
843 OPT_REPORT_FILE };
Benny Prijono4adcb912006-04-04 13:12:38 +0000844
Benny Prijono60b980e2006-04-03 22:41:26 +0000845 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000846 { "count", 1, 0, 'c' },
Benny Prijono410fbae2006-05-03 18:16:06 +0000847 { "duration", 1, 0, 'd' },
848 { "auto-quit", 0, 0, 'q' },
Benny Prijono4adcb912006-04-04 13:12:38 +0000849 { "local-port", 1, 0, 'p' },
850 { "rtp-port", 1, 0, 'r' },
851 { "ip-addr", 1, 0, 'i' },
852
853 { "log-level", 1, 0, 'l' },
854 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
855 { "log-file", 1, 0, OPT_LOG_FILE },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000856
Benny Prijonofcb36722006-05-18 18:34:21 +0000857 { "report-file", 1, 0, OPT_REPORT_FILE },
858
Benny Prijono4d7fd202006-05-14 20:57:20 +0000859 /* Don't support this anymore, see comments in USAGE above.
Benny Prijono4adcb912006-04-04 13:12:38 +0000860 { "a-pt", 1, 0, OPT_A_PT },
861 { "a-name", 1, 0, OPT_A_NAME },
862 { "a-clock", 1, 0, OPT_A_CLOCK },
863 { "a-bitrate", 1, 0, OPT_A_BITRATE },
864 { "a-ptime", 1, 0, OPT_A_PTIME },
Benny Prijono4d7fd202006-05-14 20:57:20 +0000865 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000866
Benny Prijono60b980e2006-04-03 22:41:26 +0000867 { NULL, 0, 0, 0 },
868 };
869 int c;
870 int option_index;
871
872 /* Get local IP address for the default IP address */
873 {
874 const pj_str_t *hostname;
875 pj_sockaddr_in tmp_addr;
876 char *addr;
877
878 hostname = pj_gethostname();
879 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
880 addr = pj_inet_ntoa(tmp_addr.sin_addr);
881 pj_ansi_strcpy(ip_addr, addr);
882 }
883
Benny Prijono4adcb912006-04-04 13:12:38 +0000884 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000885 app.max_calls = 1;
886 app.thread_count = 1;
887 app.sip_port = 5060;
Benny Prijono6647d822006-05-20 13:01:07 +0000888 app.rtp_start_port = RTP_START_PORT;
Benny Prijonodeb31962006-06-22 18:51:03 +0000889 app.local_addr = pj_str(ip_addr);
Benny Prijono4adcb912006-04-04 13:12:38 +0000890 app.log_level = 5;
891 app.app_log_level = 3;
892 app.log_filename = NULL;
893
894 /* Default codecs: */
895 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000896
897 /* Parse options */
898 pj_optind = 0;
Benny Prijono410fbae2006-05-03 18:16:06 +0000899 while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:q",
Benny Prijono60b980e2006-04-03 22:41:26 +0000900 long_options, &option_index))!=-1)
901 {
902 switch (c) {
903 case 'c':
904 app.max_calls = atoi(pj_optarg);
905 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
906 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
907 return 1;
908 }
909 break;
Benny Prijono410fbae2006-05-03 18:16:06 +0000910 case 'd':
911 app.duration = atoi(pj_optarg);
912 break;
913 case 'q':
914 app.auto_quit = 1;
915 break;
916
Benny Prijono60b980e2006-04-03 22:41:26 +0000917 case 'p':
918 app.sip_port = atoi(pj_optarg);
919 break;
920 case 'r':
921 app.rtp_start_port = atoi(pj_optarg);
922 break;
923 case 'i':
Benny Prijonodeb31962006-06-22 18:51:03 +0000924 app.local_addr = pj_str(pj_optarg);
Benny Prijono60b980e2006-04-03 22:41:26 +0000925 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000926
927 case 'l':
928 app.log_level = atoi(pj_optarg);
929 break;
930 case OPT_APP_LOG_LEVEL:
931 app.app_log_level = atoi(pj_optarg);
932 break;
933 case OPT_LOG_FILE:
934 app.log_filename = pj_optarg;
935 break;
936
937 case OPT_A_PT:
938 app.audio_codec.pt = atoi(pj_optarg);
939 break;
940 case OPT_A_NAME:
941 app.audio_codec.name = pj_optarg;
942 break;
943 case OPT_A_CLOCK:
944 app.audio_codec.clock_rate = atoi(pj_optarg);
945 break;
946 case OPT_A_BITRATE:
947 app.audio_codec.bit_rate = atoi(pj_optarg);
948 break;
949 case OPT_A_PTIME:
950 app.audio_codec.ptime = atoi(pj_optarg);
951 break;
Benny Prijonofcb36722006-05-18 18:34:21 +0000952 case OPT_REPORT_FILE:
953 app.report_filename = pj_optarg;
954 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000955
Benny Prijono60b980e2006-04-03 22:41:26 +0000956 default:
957 puts(USAGE);
958 return 1;
959 }
960 }
961
962 /* Check if URL is specified */
963 if (pj_optind < argc)
964 app.uri_to_call = pj_str(argv[pj_optind]);
965
966 /* Build local URI and contact */
Benny Prijonodeb31962006-06-22 18:51:03 +0000967 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
Benny Prijono60b980e2006-04-03 22:41:26 +0000968 app.local_uri = pj_str(local_uri);
969 app.local_contact = app.local_uri;
970
971
972 return PJ_SUCCESS;
973}
974
975
Benny Prijono4adcb912006-04-04 13:12:38 +0000976/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000977 * MEDIA STUFFS
978 */
979
980/*
981 * Create SDP session for a call.
982 */
983static pj_status_t create_sdp( pj_pool_t *pool,
984 struct call *call,
985 pjmedia_sdp_session **p_sdp)
986{
987 pj_time_val tv;
988 pjmedia_sdp_session *sdp;
989 pjmedia_sdp_media *m;
990 pjmedia_sdp_attr *attr;
Benny Prijonodeb31962006-06-22 18:51:03 +0000991 pjmedia_transport_udp_info tpinfo;
Benny Prijono60b980e2006-04-03 22:41:26 +0000992 struct media_stream *audio = &call->media[0];
993
994 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
995
996
Benny Prijonodeb31962006-06-22 18:51:03 +0000997 /* Get transport info */
998 pjmedia_transport_udp_get_info(audio->transport, &tpinfo);
999
Benny Prijono60b980e2006-04-03 22:41:26 +00001000 /* Create and initialize basic SDP session */
1001 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
1002
1003 pj_gettimeofday(&tv);
1004 sdp->origin.user = pj_str("pjsip-siprtp");
1005 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
1006 sdp->origin.net_type = pj_str("IN");
1007 sdp->origin.addr_type = pj_str("IP4");
1008 sdp->origin.addr = *pj_gethostname();
1009 sdp->name = pj_str("pjsip");
1010
1011 /* Since we only support one media stream at present, put the
1012 * SDP connection line in the session level.
1013 */
1014 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
1015 sdp->conn->net_type = pj_str("IN");
1016 sdp->conn->addr_type = pj_str("IP4");
Benny Prijonodeb31962006-06-22 18:51:03 +00001017 sdp->conn->addr = app.local_addr;
Benny Prijono60b980e2006-04-03 22:41:26 +00001018
1019
1020 /* SDP time and attributes. */
1021 sdp->time.start = sdp->time.stop = 0;
1022 sdp->attr_count = 0;
1023
1024 /* Create media stream 0: */
1025
1026 sdp->media_count = 1;
1027 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
1028 sdp->media[0] = m;
1029
1030 /* Standard media info: */
1031 m->desc.media = pj_str("audio");
Benny Prijonodeb31962006-06-22 18:51:03 +00001032 m->desc.port = pj_ntohs(tpinfo.skinfo.rtp_addr_name.sin_port);
Benny Prijono60b980e2006-04-03 22:41:26 +00001033 m->desc.port_count = 1;
1034 m->desc.transport = pj_str("RTP/AVP");
1035
1036 /* Add format and rtpmap for each codec. */
1037 m->desc.fmt_count = 1;
1038 m->attr_count = 0;
1039
1040 {
1041 pjmedia_sdp_rtpmap rtpmap;
1042 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +00001043 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +00001044
Benny Prijono4adcb912006-04-04 13:12:38 +00001045 sprintf(ptstr, "%d", app.audio_codec.pt);
1046 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1047 rtpmap.pt = m->desc.fmt[0];
1048 rtpmap.clock_rate = app.audio_codec.clock_rate;
1049 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +00001050 rtpmap.param.slen = 0;
1051
1052 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1053 m->attr[m->attr_count++] = attr;
1054 }
1055
1056 /* Add sendrecv attribute. */
1057 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1058 attr->name = pj_str("sendrecv");
1059 m->attr[m->attr_count++] = attr;
1060
1061#if 1
1062 /*
1063 * Add support telephony event
1064 */
Benny Prijono4adcb912006-04-04 13:12:38 +00001065 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +00001066 /* Add rtpmap. */
1067 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1068 attr->name = pj_str("rtpmap");
Benny Prijono4adcb912006-04-04 13:12:38 +00001069 attr->value = pj_str(":121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +00001070 m->attr[m->attr_count++] = attr;
1071 /* Add fmtp */
1072 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1073 attr->name = pj_str("fmtp");
Benny Prijono4adcb912006-04-04 13:12:38 +00001074 attr->value = pj_str(":121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +00001075 m->attr[m->attr_count++] = attr;
1076#endif
1077
1078 /* Done */
1079 *p_sdp = sdp;
1080
1081 return PJ_SUCCESS;
1082}
1083
1084
Benny Prijonodeb31962006-06-22 18:51:03 +00001085/*
1086 * This callback is called by media transport on receipt of RTP packet.
1087 */
1088static void on_rx_rtp(void *user_data, const void *pkt, pj_ssize_t size)
1089{
1090 struct media_stream *strm;
1091 pj_status_t status;
1092 const pjmedia_rtp_hdr *hdr;
1093 const void *payload;
1094 unsigned payload_len;
1095
1096 strm = user_data;
1097
1098 /* Discard packet if media is inactive */
1099 if (!strm->active)
1100 return;
1101
1102 /* Check for errors */
1103 if (size < 0) {
1104 app_perror(THIS_FILE, "RTP recv() error", -size);
1105 return;
1106 }
1107
1108 /* Decode RTP packet. */
1109 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1110 pkt, size,
1111 &hdr, &payload, &payload_len);
1112 if (status != PJ_SUCCESS) {
1113 app_perror(THIS_FILE, "RTP decode error", status);
1114 return;
1115 }
1116
1117 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1118
1119 /* Update the RTCP session. */
1120 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1121 pj_ntohl(hdr->ts), payload_len);
1122
1123 /* Update RTP session */
1124 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1125
1126}
1127
Benny Prijono4d3aa922006-06-22 22:31:48 +00001128/* This callback is called when it's time to send RTP packet */
1129static void on_tx_rtp( pj_timer_heap_t *timer_heap,
1130 struct pj_timer_entry *entry)
1131{
1132 pj_status_t status;
1133 const pjmedia_rtp_hdr *hdr;
1134 pj_ssize_t size;
1135 int hdrlen;
1136 pj_time_val interval;
1137 char packet[512];
1138 struct media_stream *strm = entry->user_data;
1139
1140 PJ_UNUSED_ARG(timer_heap);
1141
1142 if (!strm->active)
1143 return;
1144
1145 /* Format RTP header */
1146 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1147 0, /* marker bit */
1148 strm->bytes_per_frame,
1149 strm->samples_per_frame,
1150 (const void**)&hdr, &hdrlen);
1151 if (status == PJ_SUCCESS) {
1152
1153 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
1154
1155 /* Copy RTP header to packet */
1156 pj_memcpy(packet, hdr, hdrlen);
1157
1158 /* Zero the payload */
1159 pj_memset(packet+hdrlen, 0, strm->bytes_per_frame);
1160
1161 /* Send RTP packet */
1162 size = hdrlen + strm->bytes_per_frame;
1163 status = pjmedia_transport_send_rtp(strm->transport,
1164 packet, size);
1165 if (status != PJ_SUCCESS)
1166 app_perror(THIS_FILE, "Error sending RTP packet", status);
1167
1168 } else {
1169 pj_assert(!"RTP encode() error");
1170 }
1171
1172 /* Update RTCP SR */
1173 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1174
1175 /* Schedule next send */
1176 interval.sec = 0;
1177 interval.msec = strm->samples_per_frame * 1000 / strm->clock_rate;
1178 pj_time_val_normalize(&interval);
1179
1180 pjsip_endpt_schedule_timer(app.sip_endpt, &strm->rtp_timer, &interval);
1181}
1182
1183
Benny Prijonodeb31962006-06-22 18:51:03 +00001184/*
1185 * This callback is called by media transport on receipt of RTCP packet.
1186 */
1187static void on_rx_rtcp(void *user_data, const void *pkt, pj_ssize_t size)
1188{
1189 struct media_stream *strm;
1190
1191 strm = user_data;
1192
1193 /* Discard packet if media is inactive */
1194 if (!strm->active)
1195 return;
1196
1197 /* Check for errors */
1198 if (size < 0) {
1199 app_perror(THIS_FILE, "Error receiving RTCP packet", -size);
1200 return;
1201 }
1202
1203 /* Update RTCP session */
1204 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1205}
1206
1207
Benny Prijono4d3aa922006-06-22 22:31:48 +00001208/* This callback is called when it's time to send RTCP packet. */
1209static void on_tx_rtcp(pj_timer_heap_t *timer_heap,
1210 struct pj_timer_entry *entry)
Benny Prijono60b980e2006-04-03 22:41:26 +00001211{
Benny Prijono4d3aa922006-06-22 22:31:48 +00001212 pjmedia_rtcp_pkt *rtcp_pkt;
1213 int rtcp_len;
1214 pj_ssize_t size;
1215 pj_status_t status;
1216 pj_time_val interval;
1217 struct media_stream *strm = entry->user_data;
Benny Prijono60b980e2006-04-03 22:41:26 +00001218
Benny Prijono4d3aa922006-06-22 22:31:48 +00001219 PJ_UNUSED_ARG(timer_heap);
Benny Prijono6647d822006-05-20 13:01:07 +00001220
Benny Prijono4d3aa922006-06-22 22:31:48 +00001221 if (!strm->active)
1222 return;
Benny Prijono6647d822006-05-20 13:01:07 +00001223
Benny Prijono4d3aa922006-06-22 22:31:48 +00001224 /* Build RTCP packet */
1225 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
Benny Prijono6647d822006-05-20 13:01:07 +00001226
Benny Prijono4d3aa922006-06-22 22:31:48 +00001227 /* Send packet */
1228 size = rtcp_len;
1229 status = pjmedia_transport_send_rtcp(strm->transport,
1230 rtcp_pkt, size);
1231 if (status != PJ_SUCCESS) {
1232 app_perror(THIS_FILE, "Error sending RTCP packet", status);
Benny Prijono60b980e2006-04-03 22:41:26 +00001233 }
Benny Prijono4d3aa922006-06-22 22:31:48 +00001234
1235 /* Schedule next send */
1236 interval.sec = 5;
1237 interval.msec = (pj_rand() % 500);
1238 pjsip_endpt_schedule_timer(app.sip_endpt, &strm->rtcp_timer, &interval);
Benny Prijono60b980e2006-04-03 22:41:26 +00001239
Benny Prijono60b980e2006-04-03 22:41:26 +00001240}
1241
Benny Prijono60b980e2006-04-03 22:41:26 +00001242/* Callback to be called when SDP negotiation is done in the call: */
1243static void call_on_media_update( pjsip_inv_session *inv,
1244 pj_status_t status)
1245{
1246 struct call *call;
1247 pj_pool_t *pool;
1248 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001249 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001250 struct codec *codec_desc = NULL;
Benny Prijono4d3aa922006-06-22 22:31:48 +00001251 pj_time_val interval;
Benny Prijono4adcb912006-04-04 13:12:38 +00001252 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001253
1254 call = inv->mod_data[mod_siprtp.id];
1255 pool = inv->dlg->pool;
1256 audio = &call->media[0];
1257
1258 /* If this is a mid-call media update, then destroy existing media */
Benny Prijono4d3aa922006-06-22 22:31:48 +00001259 if (audio->active)
Benny Prijono60b980e2006-04-03 22:41:26 +00001260 destroy_call_media(call->index);
1261
1262
1263 /* Do nothing if media negotiation has failed */
1264 if (status != PJ_SUCCESS) {
1265 app_perror(THIS_FILE, "SDP negotiation failed", status);
1266 return;
1267 }
1268
1269
1270 /* Capture stream definition from the SDP */
1271 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1272 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1273
1274 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
Benny Prijonob04c9e02006-05-17 17:17:39 +00001275 local_sdp, remote_sdp, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001276 if (status != PJ_SUCCESS) {
1277 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1278 return;
1279 }
1280
Benny Prijono4adcb912006-04-04 13:12:38 +00001281 /* Get the remainder of codec information from codec descriptor */
1282 if (audio->si.fmt.pt == app.audio_codec.pt)
1283 codec_desc = &app.audio_codec;
1284 else {
1285 /* Find the codec description in codec array */
1286 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1287 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1288 codec_desc = &audio_codecs[i];
1289 break;
1290 }
1291 }
1292
1293 if (codec_desc == NULL) {
1294 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1295 return;
1296 }
1297 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001298
Benny Prijono15953012006-04-27 22:37:08 +00001299 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001300 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1301 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001302
1303
1304 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001305 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001306 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001307 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001308 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001309
Benny Prijono4adcb912006-04-04 13:12:38 +00001310
Benny Prijonodeb31962006-06-22 18:51:03 +00001311 /* Attach media to transport */
1312 status = pjmedia_transport_attach(audio->transport, audio,
1313 &audio->si.rem_addr,
Benny Prijonob12106f2006-06-29 14:45:17 +00001314 &audio->si.rem_rtcp,
Benny Prijonodeb31962006-06-22 18:51:03 +00001315 sizeof(pj_sockaddr_in),
1316 &on_rx_rtp,
1317 &on_rx_rtcp);
1318 if (status != PJ_SUCCESS) {
1319 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1320 return;
1321 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001322
Benny Prijonodeb31962006-06-22 18:51:03 +00001323 /* Set the media as active */
1324 audio->active = PJ_TRUE;
Benny Prijono4d3aa922006-06-22 22:31:48 +00001325
1326 /* Immediately schedule to send the first RTP packet. */
1327 audio->rtp_timer.id = 1;
1328 interval.sec = interval.msec = 0;
1329 pjsip_endpt_schedule_timer(app.sip_endpt, &audio->rtp_timer, &interval);
1330
1331 /* And schedule the first RTCP packet */
1332 audio->rtcp_timer.id = 1;
1333 interval.sec = 4;
1334 interval.msec = (pj_rand() % 1000);
1335 pjsip_endpt_schedule_timer(app.sip_endpt, &audio->rtcp_timer, &interval);
Benny Prijono60b980e2006-04-03 22:41:26 +00001336}
1337
1338
1339
1340/* Destroy call's media */
1341static void destroy_call_media(unsigned call_index)
1342{
1343 struct media_stream *audio = &app.call[call_index].media[0];
1344
Benny Prijono4d3aa922006-06-22 22:31:48 +00001345 if (audio->active) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001346
1347 audio->active = PJ_FALSE;
1348
Benny Prijono4d3aa922006-06-22 22:31:48 +00001349 if (audio->rtp_timer.id) {
1350 audio->rtp_timer.id = 0;
1351 pjsip_endpt_cancel_timer(app.sip_endpt, &audio->rtp_timer);
1352 }
1353
1354 if (audio->rtcp_timer.id) {
1355 audio->rtcp_timer.id = 0;
1356 pjsip_endpt_cancel_timer(app.sip_endpt, &audio->rtcp_timer);
1357 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001358
Benny Prijonodeb31962006-06-22 18:51:03 +00001359 pjmedia_transport_detach(audio->transport, audio);
Benny Prijono60b980e2006-04-03 22:41:26 +00001360 }
1361}
1362
1363
Benny Prijono4adcb912006-04-04 13:12:38 +00001364/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001365 * USER INTERFACE STUFFS
1366 */
Benny Prijono16a6b0e2006-05-12 10:20:03 +00001367#include "siprtp_report.c"
Benny Prijono60b980e2006-04-03 22:41:26 +00001368
1369
1370static void list_calls()
1371{
1372 unsigned i;
1373 puts("List all calls:");
1374 for (i=0; i<app.max_calls; ++i) {
1375 if (!app.call[i].inv)
1376 continue;
1377 print_call(i);
1378 }
1379}
1380
1381static void hangup_call(unsigned index)
1382{
1383 pjsip_tx_data *tdata;
1384 pj_status_t status;
1385
1386 if (app.call[index].inv == NULL)
1387 return;
1388
1389 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1390 if (status==PJ_SUCCESS && tdata!=NULL)
1391 pjsip_inv_send_msg(app.call[index].inv, tdata);
1392}
1393
1394static void hangup_all_calls()
1395{
1396 unsigned i;
1397 for (i=0; i<app.max_calls; ++i) {
1398 if (!app.call[i].inv)
1399 continue;
1400 hangup_call(i);
1401 }
Benny Prijonodeb31962006-06-22 18:51:03 +00001402
1403 /* Wait until all calls are terminated */
1404 for (i=0; i<app.max_calls; ++i) {
1405 while (app.call[i].inv)
1406 pj_thread_sleep(10);
1407 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001408}
1409
1410static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1411{
1412 char *p;
1413
1414 printf("%s (empty to cancel): ", title); fflush(stdout);
1415 fgets(buf, len, stdin);
1416
1417 /* Remove trailing newlines. */
1418 for (p=buf; ; ++p) {
1419 if (*p=='\r' || *p=='\n') *p='\0';
1420 else if (!*p) break;
1421 }
1422
1423 if (!*buf)
1424 return PJ_FALSE;
1425
1426 return PJ_TRUE;
1427}
1428
1429
1430static const char *MENU =
1431"\n"
1432"Enter menu character:\n"
1433" l List all calls\n"
1434" h Hangup a call\n"
1435" H Hangup all calls\n"
1436" q Quit\n"
1437"\n";
1438
1439
1440/* Main screen menu */
1441static void console_main()
1442{
1443 char input1[10];
1444 unsigned i;
1445
Benny Prijono4adcb912006-04-04 13:12:38 +00001446 printf("%s", MENU);
1447
Benny Prijono60b980e2006-04-03 22:41:26 +00001448 for (;;) {
1449 printf(">>> "); fflush(stdout);
1450 fgets(input1, sizeof(input1), stdin);
1451
1452 switch (input1[0]) {
1453 case 'l':
1454 list_calls();
1455 break;
1456
1457 case 'h':
1458 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1459 break;
1460
1461 i = atoi(input1);
1462 hangup_call(i);
1463 break;
1464
1465 case 'H':
1466 hangup_all_calls();
1467 break;
1468
1469 case 'q':
1470 goto on_exit;
1471
1472 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001473 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001474 printf("%s", MENU);
1475 break;
1476 }
1477
1478 fflush(stdout);
1479 }
1480
1481on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001482 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001483}
1484
1485
Benny Prijono4adcb912006-04-04 13:12:38 +00001486/*****************************************************************************
1487 * Below is a simple module to log all incoming and outgoing SIP messages
1488 */
1489
1490
Benny Prijono60b980e2006-04-03 22:41:26 +00001491/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001492static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001493{
1494 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1495 "%s\n"
1496 "--end msg--",
1497 rdata->msg_info.len,
1498 pjsip_rx_data_get_info(rdata),
1499 rdata->pkt_info.src_name,
1500 rdata->pkt_info.src_port,
1501 rdata->msg_info.msg_buf));
1502
1503 /* Always return false, otherwise messages will not get processed! */
1504 return PJ_FALSE;
1505}
1506
1507/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001508static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001509{
1510
1511 /* Important note:
1512 * tp_info field is only valid after outgoing messages has passed
1513 * transport layer. So don't try to access tp_info when the module
1514 * has lower priority than transport layer.
1515 */
1516
1517 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1518 "%s\n"
1519 "--end msg--",
1520 (tdata->buf.cur - tdata->buf.start),
1521 pjsip_tx_data_get_info(tdata),
1522 tdata->tp_info.dst_name,
1523 tdata->tp_info.dst_port,
1524 tdata->buf.start));
1525
1526 /* Always return success, otherwise message will not get sent! */
1527 return PJ_SUCCESS;
1528}
1529
1530/* The module instance. */
1531static pjsip_module msg_logger =
1532{
1533 NULL, NULL, /* prev, next. */
1534 { "mod-siprtp-log", 14 }, /* Name. */
1535 -1, /* Id */
1536 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1537 NULL, /* load() */
1538 NULL, /* start() */
1539 NULL, /* stop() */
1540 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001541 &logger_on_rx_msg, /* on_rx_request() */
1542 &logger_on_rx_msg, /* on_rx_response() */
1543 &logger_on_tx_msg, /* on_tx_request. */
1544 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00001545 NULL, /* on_tsx_state() */
1546
1547};
1548
1549
1550
Benny Prijono4adcb912006-04-04 13:12:38 +00001551/*****************************************************************************
1552 * Console application custom logging:
1553 */
1554
1555
1556static FILE *log_file;
1557
1558
1559static void app_log_writer(int level, const char *buffer, int len)
1560{
1561 /* Write to both stdout and file. */
1562
1563 if (level <= app.app_log_level)
1564 pj_log_write(level, buffer, len);
1565
1566 if (log_file) {
1567 fwrite(buffer, len, 1, log_file);
1568 fflush(log_file);
1569 }
1570}
1571
1572
1573pj_status_t app_logging_init(void)
1574{
1575 /* Redirect log function to ours */
1576
1577 pj_log_set_log_func( &app_log_writer );
1578
1579 /* If output log file is desired, create the file: */
1580
1581 if (app.log_filename) {
1582 log_file = fopen(app.log_filename, "wt");
1583 if (log_file == NULL) {
1584 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
1585 app.log_filename));
1586 return -1;
1587 }
1588 }
1589
1590 return PJ_SUCCESS;
1591}
1592
1593
1594void app_logging_shutdown(void)
1595{
1596 /* Close logging file, if any: */
1597
1598 if (log_file) {
1599 fclose(log_file);
1600 log_file = NULL;
1601 }
1602}
1603
Benny Prijono60b980e2006-04-03 22:41:26 +00001604
1605/*
1606 * main()
1607 */
1608int main(int argc, char *argv[])
1609{
Benny Prijono4adcb912006-04-04 13:12:38 +00001610 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001611 pj_status_t status;
1612
Benny Prijono4adcb912006-04-04 13:12:38 +00001613 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00001614 status = pj_init();
1615 if (status != PJ_SUCCESS)
1616 return 1;
1617
Benny Prijono4adcb912006-04-04 13:12:38 +00001618 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00001619 status = init_options(argc, argv);
1620 if (status != PJ_SUCCESS)
1621 return 1;
1622
Benny Prijono410fbae2006-05-03 18:16:06 +00001623 /* Verify options: */
1624
1625 /* Auto-quit can not be specified for UAS */
1626 if (app.auto_quit && app.uri_to_call.slen == 0) {
1627 printf("Error: --auto-quit option only valid for outgoing "
1628 "mode (UAC) only\n");
1629 return 1;
1630 }
1631
Benny Prijono4adcb912006-04-04 13:12:38 +00001632 /* Init logging */
1633 status = app_logging_init();
1634 if (status != PJ_SUCCESS)
1635 return 1;
1636
1637 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00001638 status = init_sip();
1639 if (status != PJ_SUCCESS) {
1640 app_perror(THIS_FILE, "Initialization has failed", status);
1641 destroy_sip();
1642 return 1;
1643 }
1644
Benny Prijono4adcb912006-04-04 13:12:38 +00001645 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00001646 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
1647
Benny Prijono4adcb912006-04-04 13:12:38 +00001648 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00001649 status = init_media();
1650 if (status != PJ_SUCCESS) {
1651 app_perror(THIS_FILE, "Media initialization failed", status);
1652 destroy_sip();
1653 return 1;
1654 }
1655
Benny Prijono9a0eab52006-04-04 19:43:24 +00001656 /* Start worker threads */
1657 for (i=0; i<app.thread_count; ++i) {
Benny Prijonodeb31962006-06-22 18:51:03 +00001658 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
1659 0, 0, &app.sip_thread[i]);
Benny Prijono9a0eab52006-04-04 19:43:24 +00001660 }
1661
Benny Prijono4adcb912006-04-04 13:12:38 +00001662 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00001663 if (app.uri_to_call.slen) {
1664 unsigned i;
1665
Benny Prijono4adcb912006-04-04 13:12:38 +00001666 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
1667 app.uri_to_call.ptr));
1668
Benny Prijono60b980e2006-04-03 22:41:26 +00001669 for (i=0; i<app.max_calls; ++i) {
1670 status = make_call(&app.uri_to_call);
1671 if (status != PJ_SUCCESS) {
1672 app_perror(THIS_FILE, "Error making call", status);
1673 break;
1674 }
1675 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001676
Benny Prijono410fbae2006-05-03 18:16:06 +00001677 if (app.auto_quit) {
1678 /* Wait for calls to complete */
1679 while (app.uac_calls < app.max_calls)
1680 pj_thread_sleep(100);
1681 pj_thread_sleep(200);
1682 } else {
1683 /* Start user interface loop */
1684 console_main();
1685 }
1686
Benny Prijono4adcb912006-04-04 13:12:38 +00001687 } else {
1688
1689 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
1690 app.max_calls));
Benny Prijono4adcb912006-04-04 13:12:38 +00001691
Benny Prijono410fbae2006-05-03 18:16:06 +00001692 /* Start user interface loop */
1693 console_main();
1694
1695 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001696
Benny Prijono4adcb912006-04-04 13:12:38 +00001697
1698 /* Shutting down... */
Benny Prijono6647d822006-05-20 13:01:07 +00001699 destroy_media();
Benny Prijono4d3aa922006-06-22 22:31:48 +00001700 destroy_sip();
Benny Prijono6647d822006-05-20 13:01:07 +00001701
1702 if (app.pool) {
1703 pj_pool_release(app.pool);
1704 app.pool = NULL;
1705 pj_caching_pool_destroy(&app.cp);
1706 }
1707
Benny Prijono4adcb912006-04-04 13:12:38 +00001708 app_logging_shutdown();
1709
Benny Prijono60b980e2006-04-03 22:41:26 +00001710
1711 return 0;
1712}
1713