blob: 75b89a259ee8a86359a954bc12f5ff8cc50c2ba5 [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
21/* Include all headers. */
22#include <pjsip.h>
23#include <pjmedia.h>
24#include <pjmedia-codec.h>
25#include <pjsip_ua.h>
26#include <pjsip_simple.h>
27#include <pjlib-util.h>
28#include <pjlib.h>
29
30#include <stdlib.h>
31
Benny Prijono9a0eab52006-04-04 19:43:24 +000032
33#if PJ_HAS_HIGH_RES_TIMER==0
34# error "High resolution timer is needed for this sample"
35#endif
36
Benny Prijono60b980e2006-04-03 22:41:26 +000037#define THIS_FILE "siprtp.c"
38#define MAX_CALLS 1024
39#define RTP_START_PORT 44100
40
41
Benny Prijono4adcb912006-04-04 13:12:38 +000042/* Codec descriptor: */
43struct codec
44{
45 unsigned pt;
46 char* name;
47 unsigned clock_rate;
48 unsigned bit_rate;
49 unsigned ptime;
50 char* description;
51};
52
53
54/* Unidirectional media stat: */
55struct stream_stat
56{
57 pj_uint32_t pkt, payload;
58 pj_uint32_t discard, reorder;
59 unsigned loss_min, loss_avg, loss_max;
60 char *loss_type;
Benny Prijono49ce9a72006-04-05 16:56:19 +000061 unsigned jitter_min_us, jitter_avg_us, jitter_max_us;
Benny Prijono4adcb912006-04-04 13:12:38 +000062 unsigned rtcp_cnt;
63};
64
65
Benny Prijono60b980e2006-04-03 22:41:26 +000066/* A bidirectional media stream */
67struct media_stream
68{
69 /* Static: */
70 pj_uint16_t port; /* RTP port (RTCP is +1) */
71
72 /* Current stream info: */
73 pjmedia_stream_info si; /* Current stream info. */
74
75 /* More info: */
76 unsigned clock_rate; /* clock rate */
77 unsigned samples_per_frame; /* samples per frame */
78 unsigned bytes_per_frame; /* frame size. */
79
80 /* Sockets: */
81 pj_sock_t rtp_sock; /* RTP socket. */
82 pj_sock_t rtcp_sock; /* RTCP socket. */
83
84 /* RTP session: */
85 pjmedia_rtp_session out_sess; /* outgoing RTP session */
86 pjmedia_rtp_session in_sess; /* incoming RTP session */
87
88 /* RTCP stats: */
89 pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
90 pjmedia_rtcp_pkt rem_rtcp; /* received RTCP stat. */
91
Benny Prijono4adcb912006-04-04 13:12:38 +000092 /* More stats: */
93 struct stream_stat rx_stat; /* incoming stream stat */
94 struct stream_stat tx_stat; /* outgoing stream stat. */
95
Benny Prijono60b980e2006-04-03 22:41:26 +000096 /* Thread: */
97 pj_bool_t thread_quit_flag; /* worker thread quit flag */
98 pj_thread_t *thread; /* RTP/RTCP worker thread */
99};
100
101
102struct call
103{
104 unsigned index;
105 pjsip_inv_session *inv;
106 unsigned media_count;
107 struct media_stream media[2];
Benny Prijono4adcb912006-04-04 13:12:38 +0000108 pj_time_val start_time;
109 pj_time_val response_time;
110 pj_time_val connect_time;
Benny Prijono60b980e2006-04-03 22:41:26 +0000111};
112
113
114static struct app
115{
116 unsigned max_calls;
117 unsigned thread_count;
118 int sip_port;
119 int rtp_start_port;
120 char *local_addr;
121 pj_str_t local_uri;
122 pj_str_t local_contact;
Benny Prijono4adcb912006-04-04 13:12:38 +0000123
124 int app_log_level;
125 int log_level;
126 char *log_filename;
127
128 struct codec audio_codec;
Benny Prijono60b980e2006-04-03 22:41:26 +0000129
130 pj_str_t uri_to_call;
131
132 pj_caching_pool cp;
133 pj_pool_t *pool;
134
135 pjsip_endpoint *sip_endpt;
136 pj_bool_t thread_quit;
137 pj_thread_t *thread[1];
138
139 pjmedia_endpt *med_endpt;
140 struct call call[MAX_CALLS];
141} app;
142
143
144
145/*
146 * Prototypes:
147 */
148
149/* Callback to be called when SDP negotiation is done in the call: */
150static void call_on_media_update( pjsip_inv_session *inv,
151 pj_status_t status);
152
153/* Callback to be called when invite session's state has changed: */
154static void call_on_state_changed( pjsip_inv_session *inv,
155 pjsip_event *e);
156
157/* Callback to be called when dialog has forked: */
158static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
159
160/* Callback to be called to handle incoming requests outside dialogs: */
161static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
162
163/* Worker thread prototype */
164static int worker_thread(void *arg);
165
166/* Create SDP for call */
167static pj_status_t create_sdp( pj_pool_t *pool,
168 struct call *call,
169 pjmedia_sdp_session **p_sdp);
170
171/* Destroy the call's media */
172static void destroy_call_media(unsigned call_index);
173
174/* Display error */
175static void app_perror(const char *sender, const char *title,
176 pj_status_t status);
177
178
Benny Prijono4adcb912006-04-04 13:12:38 +0000179
180
Benny Prijono60b980e2006-04-03 22:41:26 +0000181/* This is a PJSIP module to be registered by application to handle
182 * incoming requests outside any dialogs/transactions. The main purpose
183 * here is to handle incoming INVITE request message, where we will
184 * create a dialog and INVITE session for it.
185 */
186static pjsip_module mod_siprtp =
187{
188 NULL, NULL, /* prev, next. */
189 { "mod-siprtpapp", 13 }, /* Name. */
190 -1, /* Id */
191 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
192 NULL, /* load() */
193 NULL, /* start() */
194 NULL, /* stop() */
195 NULL, /* unload() */
196 &on_rx_request, /* on_rx_request() */
197 NULL, /* on_rx_response() */
198 NULL, /* on_tx_request. */
199 NULL, /* on_tx_response() */
200 NULL, /* on_tsx_state() */
201};
202
203
Benny Prijono4adcb912006-04-04 13:12:38 +0000204/* Codec constants */
205struct codec audio_codecs[] =
206{
207 { 0, "pcmu", 8000, 64000, 20, "G.711 ULaw" },
208 { 3, "gsm", 8000, 13200, 20, "GSM" },
209 { 4, "g723", 8000, 6400, 30, "G.723.1" },
210 { 8, "pcma", 8000, 64000, 20, "G.711 ALaw" },
211 { 18, "g729", 8000, 8000, 20, "G.729" },
212};
213
214
Benny Prijono60b980e2006-04-03 22:41:26 +0000215/*
216 * Init SIP stack
217 */
218static pj_status_t init_sip()
219{
Benny Prijono60b980e2006-04-03 22:41:26 +0000220 pj_status_t status;
221
222 /* init PJLIB-UTIL: */
223 status = pjlib_util_init();
224 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
225
226 /* Must create a pool factory before we can allocate any memory. */
227 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
228
229 /* Create application pool for misc. */
230 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
231
232 /* Create global endpoint: */
233 {
234 const pj_str_t *hostname;
235 const char *endpt_name;
236
237 /* Endpoint MUST be assigned a globally unique name.
238 * The name will be used as the hostname in Warning header.
239 */
240
241 /* For this implementation, we'll use hostname for simplicity */
242 hostname = pj_gethostname();
243 endpt_name = hostname->ptr;
244
245 /* Create the endpoint: */
246
247 status = pjsip_endpt_create(&app.cp.factory, endpt_name,
248 &app.sip_endpt);
249 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
250 }
251
252
253 /* Add UDP transport. */
254 {
255 pj_sockaddr_in addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000256 pjsip_host_port addrname;
Benny Prijono60b980e2006-04-03 22:41:26 +0000257
Benny Prijono49ce9a72006-04-05 16:56:19 +0000258 pj_memset(&addr, 0, sizeof(addr));
Benny Prijono60b980e2006-04-03 22:41:26 +0000259 addr.sin_family = PJ_AF_INET;
260 addr.sin_addr.s_addr = 0;
261 addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
262
Benny Prijono49ce9a72006-04-05 16:56:19 +0000263 if (app.local_addr) {
264 addrname.host = pj_str(app.local_addr);
265 addrname.port = app.sip_port;
266 }
267
268 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
269 (app.local_addr ? &addrname:NULL),
Benny Prijono60b980e2006-04-03 22:41:26 +0000270 1, NULL);
Benny Prijono49ce9a72006-04-05 16:56:19 +0000271 if (status != PJ_SUCCESS) {
272 app_perror(THIS_FILE, "Unable to start UDP transport", status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000273 return status;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000274 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000275 }
276
277 /*
278 * Init transaction layer.
279 * This will create/initialize transaction hash tables etc.
280 */
281 status = pjsip_tsx_layer_init_module(app.sip_endpt);
282 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
283
284 /* Initialize UA layer. */
285 status = pjsip_ua_init_module( app.sip_endpt, NULL );
286 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
287
288 /* Init invite session module. */
289 {
290 pjsip_inv_callback inv_cb;
291
292 /* Init the callback for INVITE session: */
293 pj_memset(&inv_cb, 0, sizeof(inv_cb));
294 inv_cb.on_state_changed = &call_on_state_changed;
295 inv_cb.on_new_session = &call_on_forked;
296 inv_cb.on_media_update = &call_on_media_update;
297
298 /* Initialize invite session module: */
299 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
300 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
301 }
302
303 /* Register our module to receive incoming requests. */
304 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
305 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
306
307
Benny Prijono60b980e2006-04-03 22:41:26 +0000308 /* Done */
309 return PJ_SUCCESS;
310}
311
312
313/*
314 * Destroy SIP
315 */
316static void destroy_sip()
317{
318 unsigned i;
319
320 app.thread_quit = 1;
321 for (i=0; i<app.thread_count; ++i) {
322 if (app.thread[i]) {
323 pj_thread_join(app.thread[i]);
324 pj_thread_destroy(app.thread[i]);
325 app.thread[i] = NULL;
326 }
327 }
328
329 if (app.sip_endpt) {
330 pjsip_endpt_destroy(app.sip_endpt);
331 app.sip_endpt = NULL;
332 }
333
334 if (app.pool) {
335 pj_pool_release(app.pool);
336 app.pool = NULL;
337 pj_caching_pool_destroy(&app.cp);
338 }
339}
340
341
342/*
343 * Init media stack.
344 */
345static pj_status_t init_media()
346{
347 pj_ioqueue_t *ioqueue;
348 unsigned i, count;
349 pj_uint16_t rtp_port;
350 pj_str_t temp;
351 pj_sockaddr_in addr;
352 pj_status_t status;
353
354
355 /* Get the ioqueue from the SIP endpoint */
356 ioqueue = pjsip_endpt_get_ioqueue(app.sip_endpt);
357
358
359 /* Initialize media endpoint so that at least error subsystem is properly
360 * initialized.
361 */
362 status = pjmedia_endpt_create(&app.cp.factory, ioqueue, 1,
363 &app.med_endpt);
364 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
365
366
367 /* Determine address to bind socket */
368 pj_memset(&addr, 0, sizeof(addr));
369 addr.sin_family = PJ_AF_INET;
370 i = pj_inet_aton(pj_cstr(&temp, app.local_addr), &addr.sin_addr);
371 if (i == 0) {
372 PJ_LOG(3,(THIS_FILE,
373 "Error: invalid local address %s (expecting IP)",
374 app.local_addr));
375 return -1;
376 }
377
Benny Prijono60b980e2006-04-03 22:41:26 +0000378 /* RTP port counter */
379 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
380
381
382 /* Init media sockets. */
383 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
384
385 int retry;
386
387 app.call[i].index = i;
388
389 /* Repeat binding media socket to next port when fails to bind
390 * to current port number.
391 */
392 retry = 0;
393 do {
394 struct media_stream *m = &app.call[i].media[0];
395
396 ++retry;
397 rtp_port += 2;
398 m->port = rtp_port;
399
400 /* Create and bind RTP socket */
401 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0,
402 &m->rtp_sock);
403 if (status != PJ_SUCCESS)
404 goto on_error;
405
406 addr.sin_port = pj_htons(rtp_port);
407 status = pj_sock_bind(m->rtp_sock, &addr, sizeof(addr));
408 if (status != PJ_SUCCESS) {
409 pj_sock_close(m->rtp_sock), m->rtp_sock=0;
410 continue;
411 }
412
413
414 /* Create and bind RTCP socket */
415 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0,
416 &m->rtcp_sock);
417 if (status != PJ_SUCCESS)
418 goto on_error;
419
420 addr.sin_port = pj_htons((pj_uint16_t)(rtp_port+1));
421 status = pj_sock_bind(m->rtcp_sock, &addr, sizeof(addr));
422 if (status != PJ_SUCCESS) {
423 pj_sock_close(m->rtp_sock), m->rtp_sock=0;
424 pj_sock_close(m->rtcp_sock), m->rtcp_sock=0;
425 continue;
426 }
427
428 } while (status != PJ_SUCCESS && retry < 100);
429
430 if (status != PJ_SUCCESS)
431 goto on_error;
432
433 }
434
435 /* Done */
436 return PJ_SUCCESS;
437
438on_error:
439 for (i=0; i<count; ++i) {
440 struct media_stream *m = &app.call[i].media[0];
441
442 pj_sock_close(m->rtp_sock), m->rtp_sock=0;
443 pj_sock_close(m->rtcp_sock), m->rtcp_sock=0;
444 }
445
446 return status;
447}
448
449
450/*
451 * Destroy media.
452 */
453static void destroy_media()
454{
455 unsigned i;
456
457 for (i=0; i<app.max_calls; ++i) {
458 struct media_stream *m = &app.call[i].media[0];
459
460 if (m->rtp_sock)
461 pj_sock_close(m->rtp_sock), m->rtp_sock = 0;
462
463 if (m->rtcp_sock)
464 pj_sock_close(m->rtcp_sock), m->rtcp_sock = 0;
465 }
466
467 if (app.med_endpt) {
468 pjmedia_endpt_destroy(app.med_endpt);
469 app.med_endpt = NULL;
470 }
471}
472
473
474/*
475 * Make outgoing call.
476 */
477static pj_status_t make_call(const pj_str_t *dst_uri)
478{
479 unsigned i;
480 struct call *call;
481 pjsip_dialog *dlg;
482 pjmedia_sdp_session *sdp;
483 pjsip_tx_data *tdata;
484 pj_status_t status;
485
486
487 /* Find unused call slot */
488 for (i=0; i<app.max_calls; ++i) {
489 if (app.call[i].inv == NULL)
490 break;
491 }
492
493 if (i == app.max_calls)
494 return PJ_ETOOMANY;
495
496 call = &app.call[i];
497
498 /* Create UAC dialog */
499 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
500 &app.local_uri, /* local URI */
501 &app.local_contact, /* local Contact */
502 dst_uri, /* remote URI */
503 dst_uri, /* remote target */
504 &dlg); /* dialog */
505 if (status != PJ_SUCCESS)
506 return status;
507
508 /* Create SDP */
509 create_sdp( dlg->pool, call, &sdp);
510
511 /* Create the INVITE session. */
512 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
513 if (status != PJ_SUCCESS) {
514 pjsip_dlg_terminate(dlg);
515 return status;
516 }
517
518
519 /* Attach call data to invite session */
520 call->inv->mod_data[mod_siprtp.id] = call;
521
Benny Prijono4adcb912006-04-04 13:12:38 +0000522 /* Mark start of call */
523 pj_gettimeofday(&call->start_time);
524
Benny Prijono60b980e2006-04-03 22:41:26 +0000525
526 /* Create initial INVITE request.
527 * This INVITE request will contain a perfectly good request and
528 * an SDP body as well.
529 */
530 status = pjsip_inv_invite(call->inv, &tdata);
531 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
532
533
534 /* Send initial INVITE request.
535 * From now on, the invite session's state will be reported to us
536 * via the invite session callbacks.
537 */
538 status = pjsip_inv_send_msg(call->inv, tdata);
539 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
540
541
542 return PJ_SUCCESS;
543}
544
545
546/*
547 * Receive incoming call
548 */
549static void process_incoming_call(pjsip_rx_data *rdata)
550{
551 unsigned i;
552 struct call *call;
553 pjsip_dialog *dlg;
554 pjmedia_sdp_session *sdp;
555 pjsip_tx_data *tdata;
556 pj_status_t status;
557
558 /* Find free call slot */
559 for (i=0; i<app.max_calls; ++i) {
560 if (app.call[i].inv == NULL)
561 break;
562 }
563
564 if (i == app.max_calls) {
565 const pj_str_t reason = pj_str("Too many calls");
566 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
567 500, &reason,
568 NULL, NULL);
569 return;
570 }
571
572 call = &app.call[i];
573
574 /* Create UAS dialog */
575 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
576 &app.local_contact, &dlg);
577 if (status != PJ_SUCCESS) {
578 const pj_str_t reason = pj_str("Unable to create dialog");
579 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
580 500, &reason,
581 NULL, NULL);
582 return;
583 }
584
585 /* Create SDP */
586 create_sdp( dlg->pool, call, &sdp);
587
588 /* Create UAS invite session */
589 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
590 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000591 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
592 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000593 return;
594 }
595
Benny Prijono4adcb912006-04-04 13:12:38 +0000596
Benny Prijono60b980e2006-04-03 22:41:26 +0000597 /* Attach call data to invite session */
598 call->inv->mod_data[mod_siprtp.id] = call;
599
Benny Prijono4adcb912006-04-04 13:12:38 +0000600 /* Mark start of call */
601 pj_gettimeofday(&call->start_time);
602
603
604
Benny Prijono60b980e2006-04-03 22:41:26 +0000605 /* Create 200 response .*/
606 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
607 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000608 if (status != PJ_SUCCESS) {
609 status = pjsip_inv_initial_answer(call->inv, rdata,
610 PJSIP_SC_NOT_ACCEPTABLE,
611 NULL, NULL, &tdata);
612 if (status == PJ_SUCCESS)
613 pjsip_inv_send_msg(call->inv, tdata);
614 else
615 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
616 return;
617 }
618
Benny Prijono60b980e2006-04-03 22:41:26 +0000619
620 /* Send the 200 response. */
621 status = pjsip_inv_send_msg(call->inv, tdata);
622 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
623
624
625 /* Done */
626}
627
628
629/* Callback to be called when dialog has forked: */
630static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
631{
632 PJ_UNUSED_ARG(inv);
633 PJ_UNUSED_ARG(e);
634
635 PJ_TODO( HANDLE_FORKING );
636}
637
638
639/* Callback to be called to handle incoming requests outside dialogs: */
640static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
641{
Benny Prijono4adcb912006-04-04 13:12:38 +0000642 /* Ignore strandled ACKs (must not send respone */
643 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
644 return PJ_FALSE;
645
Benny Prijono60b980e2006-04-03 22:41:26 +0000646 /* Respond (statelessly) any non-INVITE requests with 500 */
647 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
648 pj_str_t reason = pj_str("Unsupported Operation");
649 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
650 500, &reason,
651 NULL, NULL);
652 return PJ_TRUE;
653 }
654
655 /* Handle incoming INVITE */
656 process_incoming_call(rdata);
657
658 /* Done */
659 return PJ_TRUE;
660}
661
662
663/* Callback to be called when invite session's state has changed: */
664static void call_on_state_changed( pjsip_inv_session *inv,
665 pjsip_event *e)
666{
Benny Prijono4adcb912006-04-04 13:12:38 +0000667 struct call *call = inv->mod_data[mod_siprtp.id];
668
Benny Prijono60b980e2006-04-03 22:41:26 +0000669 PJ_UNUSED_ARG(e);
670
Benny Prijono4adcb912006-04-04 13:12:38 +0000671 if (!call)
672 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000673
Benny Prijono4adcb912006-04-04 13:12:38 +0000674 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
675
676 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000677
678 call->inv = NULL;
679 inv->mod_data[mod_siprtp.id] = NULL;
680
681 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000682
683 call->start_time = null_time;
684 call->response_time = null_time;
685 call->connect_time = null_time;
686
687 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%s",
688 call->index,
689 pjsip_get_status_text(inv->cause)->ptr));
690
691 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
692
693 pj_time_val t;
694
695 pj_gettimeofday(&call->connect_time);
696 if (call->response_time.sec == 0)
697 call->response_time = call->connect_time;
698
699 t = call->connect_time;
700 PJ_TIME_VAL_SUB(t, call->start_time);
701
702 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
703 PJ_TIME_VAL_MSEC(t)));
704
705 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
706 inv->state == PJSIP_INV_STATE_CONNECTING) {
707
708 if (call->response_time.sec == 0)
709 pj_gettimeofday(&call->response_time);
710
Benny Prijono60b980e2006-04-03 22:41:26 +0000711 }
712}
713
714
715/* Utility */
716static void app_perror(const char *sender, const char *title,
717 pj_status_t status)
718{
719 char errmsg[PJ_ERR_MSG_SIZE];
720
721 pj_strerror(status, errmsg, sizeof(errmsg));
722 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
723}
724
725
726/* Worker thread */
727static int worker_thread(void *arg)
728{
729 PJ_UNUSED_ARG(arg);
730
731 while (!app.thread_quit) {
732 pj_time_val timeout = {0, 10};
733 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
734 }
735
736 return 0;
737}
738
739
740/* Usage */
741static const char *USAGE =
Benny Prijono4adcb912006-04-04 13:12:38 +0000742"Usage:\n"
743" siprtp [options] => to start in server mode\n"
744" siprtp [options] URL => to start in client mode\n"
Benny Prijono60b980e2006-04-03 22:41:26 +0000745"\n"
Benny Prijono4adcb912006-04-04 13:12:38 +0000746"Program options:\n"
747" --count=N, -c Set number of calls to create (default:1) \n"
748"\n"
749"Address and ports options:\n"
750" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
751" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
752" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
Benny Prijono60b980e2006-04-03 22:41:26 +0000753" try to determine local IP address from hostname)\n"
Benny Prijono4adcb912006-04-04 13:12:38 +0000754"\n"
755"Logging Options:\n"
756" --log-level=N, -l Set log verbosity level (default=5)\n"
757" --app-log-level=N Set app screen log verbosity (default=3)\n"
758" --log-file=FILE Write log to file FILE\n"
759"\n"
760"Codec Options:\n"
761" --a-pt=PT Set audio payload type to PT (default=0)\n"
762" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
763" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000 Hz)\n"
764" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000 bps)\n"
765" --a-ptime=MS Set audio frame time to MS msec (default=20 msec)\n"
Benny Prijono60b980e2006-04-03 22:41:26 +0000766;
767
768
769/* Init application options */
770static pj_status_t init_options(int argc, char *argv[])
771{
772 static char ip_addr[32];
773 static char local_uri[64];
774
Benny Prijono4adcb912006-04-04 13:12:38 +0000775 enum { OPT_START,
776 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
777 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME };
778
Benny Prijono60b980e2006-04-03 22:41:26 +0000779 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000780 { "count", 1, 0, 'c' },
781 { "local-port", 1, 0, 'p' },
782 { "rtp-port", 1, 0, 'r' },
783 { "ip-addr", 1, 0, 'i' },
784
785 { "log-level", 1, 0, 'l' },
786 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
787 { "log-file", 1, 0, OPT_LOG_FILE },
788 { "a-pt", 1, 0, OPT_A_PT },
789 { "a-name", 1, 0, OPT_A_NAME },
790 { "a-clock", 1, 0, OPT_A_CLOCK },
791 { "a-bitrate", 1, 0, OPT_A_BITRATE },
792 { "a-ptime", 1, 0, OPT_A_PTIME },
793
Benny Prijono60b980e2006-04-03 22:41:26 +0000794 { NULL, 0, 0, 0 },
795 };
796 int c;
797 int option_index;
798
799 /* Get local IP address for the default IP address */
800 {
801 const pj_str_t *hostname;
802 pj_sockaddr_in tmp_addr;
803 char *addr;
804
805 hostname = pj_gethostname();
806 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
807 addr = pj_inet_ntoa(tmp_addr.sin_addr);
808 pj_ansi_strcpy(ip_addr, addr);
809 }
810
Benny Prijono4adcb912006-04-04 13:12:38 +0000811 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000812 app.max_calls = 1;
813 app.thread_count = 1;
814 app.sip_port = 5060;
815 app.rtp_start_port = 4000;
816 app.local_addr = ip_addr;
Benny Prijono4adcb912006-04-04 13:12:38 +0000817 app.log_level = 5;
818 app.app_log_level = 3;
819 app.log_filename = NULL;
820
821 /* Default codecs: */
822 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000823
824 /* Parse options */
825 pj_optind = 0;
Benny Prijono4adcb912006-04-04 13:12:38 +0000826 while((c=pj_getopt_long(argc,argv, "c:p:r:i:l:",
Benny Prijono60b980e2006-04-03 22:41:26 +0000827 long_options, &option_index))!=-1)
828 {
829 switch (c) {
830 case 'c':
831 app.max_calls = atoi(pj_optarg);
832 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
833 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
834 return 1;
835 }
836 break;
837 case 'p':
838 app.sip_port = atoi(pj_optarg);
839 break;
840 case 'r':
841 app.rtp_start_port = atoi(pj_optarg);
842 break;
843 case 'i':
844 app.local_addr = pj_optarg;
845 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000846
847 case 'l':
848 app.log_level = atoi(pj_optarg);
849 break;
850 case OPT_APP_LOG_LEVEL:
851 app.app_log_level = atoi(pj_optarg);
852 break;
853 case OPT_LOG_FILE:
854 app.log_filename = pj_optarg;
855 break;
856
857 case OPT_A_PT:
858 app.audio_codec.pt = atoi(pj_optarg);
859 break;
860 case OPT_A_NAME:
861 app.audio_codec.name = pj_optarg;
862 break;
863 case OPT_A_CLOCK:
864 app.audio_codec.clock_rate = atoi(pj_optarg);
865 break;
866 case OPT_A_BITRATE:
867 app.audio_codec.bit_rate = atoi(pj_optarg);
868 break;
869 case OPT_A_PTIME:
870 app.audio_codec.ptime = atoi(pj_optarg);
871 break;
872
Benny Prijono60b980e2006-04-03 22:41:26 +0000873 default:
874 puts(USAGE);
875 return 1;
876 }
877 }
878
879 /* Check if URL is specified */
880 if (pj_optind < argc)
881 app.uri_to_call = pj_str(argv[pj_optind]);
882
883 /* Build local URI and contact */
884 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr, app.sip_port);
885 app.local_uri = pj_str(local_uri);
886 app.local_contact = app.local_uri;
887
888
889 return PJ_SUCCESS;
890}
891
892
Benny Prijono4adcb912006-04-04 13:12:38 +0000893/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000894 * MEDIA STUFFS
895 */
896
897/*
898 * Create SDP session for a call.
899 */
900static pj_status_t create_sdp( pj_pool_t *pool,
901 struct call *call,
902 pjmedia_sdp_session **p_sdp)
903{
904 pj_time_val tv;
905 pjmedia_sdp_session *sdp;
906 pjmedia_sdp_media *m;
907 pjmedia_sdp_attr *attr;
908 struct media_stream *audio = &call->media[0];
909
910 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
911
912
913 /* Create and initialize basic SDP session */
914 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
915
916 pj_gettimeofday(&tv);
917 sdp->origin.user = pj_str("pjsip-siprtp");
918 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
919 sdp->origin.net_type = pj_str("IN");
920 sdp->origin.addr_type = pj_str("IP4");
921 sdp->origin.addr = *pj_gethostname();
922 sdp->name = pj_str("pjsip");
923
924 /* Since we only support one media stream at present, put the
925 * SDP connection line in the session level.
926 */
927 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
928 sdp->conn->net_type = pj_str("IN");
929 sdp->conn->addr_type = pj_str("IP4");
930 sdp->conn->addr = pj_str(app.local_addr);
931
932
933 /* SDP time and attributes. */
934 sdp->time.start = sdp->time.stop = 0;
935 sdp->attr_count = 0;
936
937 /* Create media stream 0: */
938
939 sdp->media_count = 1;
940 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
941 sdp->media[0] = m;
942
943 /* Standard media info: */
944 m->desc.media = pj_str("audio");
945 m->desc.port = audio->port;
946 m->desc.port_count = 1;
947 m->desc.transport = pj_str("RTP/AVP");
948
949 /* Add format and rtpmap for each codec. */
950 m->desc.fmt_count = 1;
951 m->attr_count = 0;
952
953 {
954 pjmedia_sdp_rtpmap rtpmap;
955 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +0000956 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +0000957
Benny Prijono4adcb912006-04-04 13:12:38 +0000958 sprintf(ptstr, "%d", app.audio_codec.pt);
959 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
960 rtpmap.pt = m->desc.fmt[0];
961 rtpmap.clock_rate = app.audio_codec.clock_rate;
962 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +0000963 rtpmap.param.slen = 0;
964
965 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
966 m->attr[m->attr_count++] = attr;
967 }
968
969 /* Add sendrecv attribute. */
970 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
971 attr->name = pj_str("sendrecv");
972 m->attr[m->attr_count++] = attr;
973
974#if 1
975 /*
976 * Add support telephony event
977 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000978 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +0000979 /* Add rtpmap. */
980 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
981 attr->name = pj_str("rtpmap");
Benny Prijono4adcb912006-04-04 13:12:38 +0000982 attr->value = pj_str(":121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +0000983 m->attr[m->attr_count++] = attr;
984 /* Add fmtp */
985 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
986 attr->name = pj_str("fmtp");
Benny Prijono4adcb912006-04-04 13:12:38 +0000987 attr->value = pj_str(":121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +0000988 m->attr[m->attr_count++] = attr;
989#endif
990
991 /* Done */
992 *p_sdp = sdp;
993
994 return PJ_SUCCESS;
995}
996
997
Benny Prijono4adcb912006-04-04 13:12:38 +0000998/*
999 * Media thread
1000 *
1001 * This is the thread to send and receive both RTP and RTCP packets.
1002 */
Benny Prijono60b980e2006-04-03 22:41:26 +00001003static int media_thread(void *arg)
1004{
Benny Prijono9a0eab52006-04-04 19:43:24 +00001005 enum { RTCP_INTERVAL = 5 };
Benny Prijono60b980e2006-04-03 22:41:26 +00001006 struct media_stream *strm = arg;
1007 char packet[1500];
Benny Prijono9a0eab52006-04-04 19:43:24 +00001008 unsigned msec_interval;
1009 pj_timestamp freq, next_rtp, next_rtcp;
Benny Prijono60b980e2006-04-03 22:41:26 +00001010
Benny Prijono9a0eab52006-04-04 19:43:24 +00001011 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1012 pj_get_timestamp_freq(&freq);
1013
1014 pj_get_timestamp(&next_rtp);
1015 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
Benny Prijono60b980e2006-04-03 22:41:26 +00001016
1017 next_rtcp = next_rtp;
Benny Prijono9a0eab52006-04-04 19:43:24 +00001018 next_rtcp.u64 += (freq.u64 * RTCP_INTERVAL);
Benny Prijono60b980e2006-04-03 22:41:26 +00001019
1020
1021 while (!strm->thread_quit_flag) {
1022 pj_fd_set_t set;
Benny Prijono9a0eab52006-04-04 19:43:24 +00001023 pj_timestamp now, lesser;
1024 pj_time_val timeout;
Benny Prijono60b980e2006-04-03 22:41:26 +00001025 int rc;
1026
1027 /* Determine how long to sleep */
Benny Prijono9a0eab52006-04-04 19:43:24 +00001028 if (next_rtp.u64 < next_rtcp.u64)
Benny Prijono60b980e2006-04-03 22:41:26 +00001029 lesser = next_rtp;
1030 else
1031 lesser = next_rtcp;
1032
Benny Prijono9a0eab52006-04-04 19:43:24 +00001033 pj_get_timestamp(&now);
1034 if (lesser.u64 <= now.u64) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001035 timeout.sec = timeout.msec = 0;
Benny Prijono9a0eab52006-04-04 19:43:24 +00001036 //printf("immediate "); fflush(stdout);
1037 } else {
1038 pj_uint64_t tick_delay;
1039 tick_delay = lesser.u64 - now.u64;
1040 timeout.sec = 0;
1041 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1042 pj_time_val_normalize(&timeout);
1043
1044 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
Benny Prijono60b980e2006-04-03 22:41:26 +00001045 }
1046
1047 PJ_FD_ZERO(&set);
1048 PJ_FD_SET(strm->rtp_sock, &set);
1049 PJ_FD_SET(strm->rtcp_sock, &set);
1050
1051 rc = pj_sock_select(FD_SETSIZE, &set, NULL, NULL, &timeout);
1052
Benny Prijono9a0eab52006-04-04 19:43:24 +00001053 if (rc > 0 && PJ_FD_ISSET(strm->rtp_sock, &set)) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001054
1055 /*
1056 * Process incoming RTP packet.
1057 */
1058 pj_status_t status;
1059 pj_ssize_t size;
1060 const pjmedia_rtp_hdr *hdr;
1061 const void *payload;
1062 unsigned payload_len;
1063
1064 size = sizeof(packet);
1065 status = pj_sock_recv(strm->rtp_sock, packet, &size, 0);
1066 if (status != PJ_SUCCESS) {
1067 app_perror(THIS_FILE, "RTP recv() error", status);
1068 continue;
1069 }
1070
Benny Prijono4adcb912006-04-04 13:12:38 +00001071 ++strm->rx_stat.pkt;
1072 strm->rx_stat.payload += (size - 12);
1073
Benny Prijono60b980e2006-04-03 22:41:26 +00001074 /* Decode RTP packet. */
1075 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1076 packet, size,
1077 &hdr,
1078 &payload, &payload_len);
1079 if (status != PJ_SUCCESS) {
1080 app_perror(THIS_FILE, "RTP decode error", status);
Benny Prijono4adcb912006-04-04 13:12:38 +00001081 strm->rx_stat.discard++;
Benny Prijono60b980e2006-04-03 22:41:26 +00001082 continue;
1083 }
1084
1085 /* Update RTP session */
1086 status = pjmedia_rtp_session_update(&strm->in_sess, hdr);
1087 if (status != PJ_SUCCESS &&
1088 status != PJMEDIA_RTP_ESESSPROBATION &&
1089 status != PJMEDIA_RTP_ESESSRESTART)
1090 {
1091 app_perror(THIS_FILE, "RTP update error", status);
1092 PJ_LOG(3,(THIS_FILE,"RTP packet detail: pt=%d, seq=%d",
1093 hdr->pt, pj_ntohs(hdr->seq)));
Benny Prijono4adcb912006-04-04 13:12:38 +00001094 strm->rx_stat.discard++;
Benny Prijono60b980e2006-04-03 22:41:26 +00001095 continue;
1096 }
1097
1098 /* Update the RTCP session. */
1099 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1100 pj_ntohl(hdr->ts));
1101
Benny Prijono9a0eab52006-04-04 19:43:24 +00001102 }
1103
1104 if (rc > 0 && PJ_FD_ISSET(strm->rtcp_sock, &set)) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001105
1106 /*
1107 * Process incoming RTCP
1108 */
1109 pj_status_t status;
1110 pj_ssize_t size;
1111
1112 size = sizeof(packet);
1113 status = pj_sock_recv( strm->rtcp_sock, packet, &size, 0);
1114 if (status != PJ_SUCCESS)
1115 app_perror(THIS_FILE, "Error receiving RTCP packet", status);
1116 else {
Benny Prijono46ca9962006-04-04 21:39:10 +00001117 if (size != sizeof(strm->rem_rtcp)) {
1118 PJ_LOG(3,(THIS_FILE, "Error: RTCP packet size mismatch "
1119 "(recv %d bytes, expecting %d)",
1120 size, sizeof(strm->rem_rtcp)));
Benny Prijono4adcb912006-04-04 13:12:38 +00001121 status = -1;
1122 } else {
Benny Prijono60b980e2006-04-03 22:41:26 +00001123 pj_memcpy(&strm->rem_rtcp, packet, size);
Benny Prijono4adcb912006-04-04 13:12:38 +00001124 status = PJ_SUCCESS;
Benny Prijonoa1d03b42006-04-05 12:53:42 +00001125
1126 /* Report receipt of RTCP to RTCP session */
1127 pjmedia_rtcp_rx_rtcp(&strm->rtcp, packet, size);
Benny Prijono4adcb912006-04-04 13:12:38 +00001128 }
1129 }
1130
1131 if (status == PJ_SUCCESS) {
1132 /* Process RTCP stats */
1133 unsigned jitter;
1134
Benny Prijono49ce9a72006-04-05 16:56:19 +00001135 jitter = (unsigned)(pj_ntohl(strm->rem_rtcp.rr.jitter) *
1136 1000000.0 / strm->clock_rate);
1137 if (jitter < strm->tx_stat.jitter_min_us)
1138 strm->tx_stat.jitter_min_us = jitter;
1139 if (jitter > strm->tx_stat.jitter_max_us)
1140 strm->tx_stat.jitter_max_us = jitter;
1141 strm->tx_stat.jitter_avg_us =
1142 (strm->tx_stat.jitter_avg_us * strm->tx_stat.rtcp_cnt +
1143 jitter) / (strm->tx_stat.rtcp_cnt + 1);
Benny Prijono4adcb912006-04-04 13:12:38 +00001144
1145 strm->tx_stat.rtcp_cnt++;
Benny Prijono60b980e2006-04-03 22:41:26 +00001146 }
1147 }
1148
1149
Benny Prijono9a0eab52006-04-04 19:43:24 +00001150 pj_get_timestamp(&now);
Benny Prijono60b980e2006-04-03 22:41:26 +00001151
Benny Prijono9a0eab52006-04-04 19:43:24 +00001152 if (next_rtp.u64 <= now.u64) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001153 /*
1154 * Time to send RTP packet.
1155 */
1156 pj_status_t status;
1157 const pjmedia_rtp_hdr *hdr;
1158 pj_ssize_t size;
1159 int hdrlen;
1160
1161 /* Format RTP header */
1162 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1163 0, /* marker bit */
1164 strm->bytes_per_frame,
1165 strm->samples_per_frame,
1166 &hdr, &hdrlen);
1167 if (status == PJ_SUCCESS) {
1168
1169 /* Copy RTP header to packet */
1170 pj_memcpy(packet, hdr, hdrlen);
1171
1172 /* Zero the payload */
1173 pj_memset(packet+hdrlen, 0, strm->bytes_per_frame);
1174
1175 /* Send RTP packet */
1176 size = hdrlen + strm->bytes_per_frame;
1177 status = pj_sock_sendto( strm->rtp_sock, packet, &size, 0,
1178 &strm->si.rem_addr,
1179 sizeof(strm->si.rem_addr));
1180
1181 if (status != PJ_SUCCESS)
1182 app_perror(THIS_FILE, "Error sending RTP packet", status);
1183
1184 }
1185
1186 /* Update RTCP SR */
1187 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1188
1189 /* Schedule next send */
Benny Prijono9a0eab52006-04-04 19:43:24 +00001190 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
Benny Prijono4adcb912006-04-04 13:12:38 +00001191
1192 /* Update stats */
1193 strm->tx_stat.pkt++;
1194 strm->tx_stat.payload += strm->bytes_per_frame;
Benny Prijono60b980e2006-04-03 22:41:26 +00001195 }
1196
1197
Benny Prijono9a0eab52006-04-04 19:43:24 +00001198 if (next_rtcp.u64 <= now.u64) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001199 /*
1200 * Time to send RTCP packet.
1201 */
1202 pjmedia_rtcp_pkt *rtcp_pkt;
1203 int rtcp_len;
1204 pj_sockaddr_in rem_addr;
1205 pj_ssize_t size;
1206 int port;
1207 pj_status_t status;
1208
1209 /* Build RTCP packet */
1210 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1211
1212
1213 /* Calculate address based on RTP address */
1214 rem_addr = strm->si.rem_addr;
1215 port = pj_ntohs(strm->si.rem_addr.sin_port) + 1;
1216 rem_addr.sin_port = pj_htons((pj_uint16_t)port);
1217
1218 /* Send packet */
1219 size = rtcp_len;
1220 status = pj_sock_sendto(strm->rtcp_sock, rtcp_pkt, &size, 0,
1221 &rem_addr, sizeof(rem_addr));
1222 if (status != PJ_SUCCESS) {
1223 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1224 }
1225
1226
Benny Prijono4adcb912006-04-04 13:12:38 +00001227 /* Process RTCP stats */
1228 {
1229 unsigned jitter;
1230
Benny Prijono49ce9a72006-04-05 16:56:19 +00001231 jitter = (unsigned) (pj_ntohl(rtcp_pkt->rr.jitter) *
1232 1000000.0 / strm->clock_rate);
1233 if (jitter < strm->rx_stat.jitter_min_us)
1234 strm->rx_stat.jitter_min_us = jitter;
1235 if (jitter > strm->rx_stat.jitter_max_us)
1236 strm->rx_stat.jitter_max_us = jitter;
1237 strm->rx_stat.jitter_avg_us =
1238 (strm->rx_stat.jitter_avg_us * strm->rx_stat.rtcp_cnt +
1239 jitter) / (strm->rx_stat.rtcp_cnt + 1);
Benny Prijono4adcb912006-04-04 13:12:38 +00001240
1241 strm->rx_stat.rtcp_cnt++;
1242 }
1243
Benny Prijono9a0eab52006-04-04 19:43:24 +00001244 next_rtcp.u64 += (freq.u64 * RTCP_INTERVAL);
Benny Prijono60b980e2006-04-03 22:41:26 +00001245 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001246 }
1247
1248 return 0;
1249}
1250
1251
1252/* Callback to be called when SDP negotiation is done in the call: */
1253static void call_on_media_update( pjsip_inv_session *inv,
1254 pj_status_t status)
1255{
1256 struct call *call;
1257 pj_pool_t *pool;
1258 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001259 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001260 struct codec *codec_desc = NULL;
1261 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001262
1263 call = inv->mod_data[mod_siprtp.id];
1264 pool = inv->dlg->pool;
1265 audio = &call->media[0];
1266
1267 /* If this is a mid-call media update, then destroy existing media */
1268 if (audio->thread != NULL)
1269 destroy_call_media(call->index);
1270
1271
1272 /* Do nothing if media negotiation has failed */
1273 if (status != PJ_SUCCESS) {
1274 app_perror(THIS_FILE, "SDP negotiation failed", status);
1275 return;
1276 }
1277
1278
1279 /* Capture stream definition from the SDP */
1280 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1281 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1282
1283 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
1284 local_sdp, remote_sdp, 0);
1285 if (status != PJ_SUCCESS) {
1286 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1287 return;
1288 }
1289
Benny Prijono4adcb912006-04-04 13:12:38 +00001290 /* Get the remainder of codec information from codec descriptor */
1291 if (audio->si.fmt.pt == app.audio_codec.pt)
1292 codec_desc = &app.audio_codec;
1293 else {
1294 /* Find the codec description in codec array */
1295 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1296 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1297 codec_desc = &audio_codecs[i];
1298 break;
1299 }
1300 }
1301
1302 if (codec_desc == NULL) {
1303 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1304 return;
1305 }
1306 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001307
1308 audio->clock_rate = audio->si.fmt.sample_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001309 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1310 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001311
1312
1313 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001314 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001315 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono9a0eab52006-04-04 19:43:24 +00001316 pjmedia_rtcp_init(&audio->rtcp, audio->clock_rate, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001317
Benny Prijono4adcb912006-04-04 13:12:38 +00001318
1319 /* Clear media statistics */
1320 pj_memset(&audio->rx_stat, 0, sizeof(audio->rx_stat));
1321 pj_memset(&audio->tx_stat, 0, sizeof(audio->tx_stat));
1322
1323
Benny Prijono60b980e2006-04-03 22:41:26 +00001324 /* Start media thread. */
1325 audio->thread_quit_flag = 0;
1326 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1327 0, 0, &audio->thread);
1328 if (status != PJ_SUCCESS) {
1329 app_perror(THIS_FILE, "Error creating media thread", status);
1330 }
1331}
1332
1333
1334
1335/* Destroy call's media */
1336static void destroy_call_media(unsigned call_index)
1337{
1338 struct media_stream *audio = &app.call[call_index].media[0];
1339
1340 if (audio->thread) {
1341 audio->thread_quit_flag = 1;
1342 pj_thread_join(audio->thread);
1343 pj_thread_destroy(audio->thread);
1344 audio->thread = NULL;
1345 audio->thread_quit_flag = 0;
Benny Prijono4adcb912006-04-04 13:12:38 +00001346
1347 /* Flush RTP/RTCP packets */
1348 {
1349 pj_fd_set_t set;
1350 pj_time_val timeout = {0, 0};
1351 char packet[1500];
1352 pj_ssize_t size;
1353 pj_status_t status;
1354 int rc;
1355
1356 do {
1357 PJ_FD_ZERO(&set);
1358 PJ_FD_SET(audio->rtp_sock, &set);
1359 PJ_FD_SET(audio->rtcp_sock, &set);
1360
1361 rc = pj_sock_select(FD_SETSIZE, &set, NULL, NULL, &timeout);
1362 if (rc > 0 && PJ_FD_ISSET(audio->rtp_sock, &set)) {
1363 size = sizeof(packet);
1364 status = pj_sock_recv(audio->rtp_sock, packet, &size, 0);
1365
1366 }
1367 if (rc > 0 && PJ_FD_ISSET(audio->rtcp_sock, &set)) {
1368 size = sizeof(packet);
1369 status = pj_sock_recv(audio->rtcp_sock, packet, &size, 0);
1370 }
1371
1372 } while (rc > 0);
1373 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001374 }
1375}
1376
1377
Benny Prijono4adcb912006-04-04 13:12:38 +00001378/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001379 * USER INTERFACE STUFFS
1380 */
1381
1382static const char *good_number(char *buf, pj_int32_t val)
1383{
1384 if (val < 1000) {
1385 pj_ansi_sprintf(buf, "%d", val);
1386 } else if (val < 1000000) {
1387 pj_ansi_sprintf(buf, "%d.%dK",
1388 val / 1000,
1389 (val % 1000) / 100);
1390 } else {
1391 pj_ansi_sprintf(buf, "%d.%02dM",
1392 val / 1000000,
1393 (val % 1000000) / 10000);
1394 }
1395
1396 return buf;
1397}
1398
1399
1400static void print_call(int call_index)
1401{
Benny Prijono4adcb912006-04-04 13:12:38 +00001402 struct call *call = &app.call[call_index];
Benny Prijono60b980e2006-04-03 22:41:26 +00001403 int len;
Benny Prijono4adcb912006-04-04 13:12:38 +00001404 pjsip_inv_session *inv = call->inv;
Benny Prijono60b980e2006-04-03 22:41:26 +00001405 pjsip_dialog *dlg = inv->dlg;
Benny Prijono4adcb912006-04-04 13:12:38 +00001406 struct media_stream *audio = &call->media[0];
Benny Prijono60b980e2006-04-03 22:41:26 +00001407 char userinfo[128];
Benny Prijono4adcb912006-04-04 13:12:38 +00001408 char duration[80];
1409 char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
1410 pj_uint32_t total_loss;
Benny Prijono60b980e2006-04-03 22:41:26 +00001411
Benny Prijono60b980e2006-04-03 22:41:26 +00001412
Benny Prijono4adcb912006-04-04 13:12:38 +00001413 /* Print duration */
1414 if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
1415 pj_time_val now;
1416
1417 pj_gettimeofday(&now);
1418 PJ_TIME_VAL_SUB(now, call->connect_time);
1419
Benny Prijono49ce9a72006-04-05 16:56:19 +00001420 sprintf(duration, " [duration: %02ld:%02ld:%02ld.%03ld]",
Benny Prijono4adcb912006-04-04 13:12:38 +00001421 now.sec / 3600,
1422 (now.sec % 3600) / 60,
1423 (now.sec % 60),
1424 now.msec);
1425
1426 } else {
1427 duration[0] = '\0';
1428 }
1429
1430
1431
1432 /* Call number and state */
1433 printf("Call #%d: %s%s\n", call_index, pjsip_inv_state_name(inv->state),
1434 duration);
1435
1436
1437
1438 /* Call identification */
Benny Prijono60b980e2006-04-03 22:41:26 +00001439 len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
1440 if (len < 1)
1441 pj_ansi_strcpy(userinfo, "<--uri too long-->");
1442 else
1443 userinfo[len] = '\0';
Benny Prijono4adcb912006-04-04 13:12:38 +00001444
Benny Prijono60b980e2006-04-03 22:41:26 +00001445 printf(" %s\n", userinfo);
1446
Benny Prijono4adcb912006-04-04 13:12:38 +00001447
1448 /* Signaling quality */
1449 {
1450 char pdd[64], connectdelay[64];
1451 pj_time_val t;
1452
1453 if (call->response_time.sec) {
1454 t = call->response_time;
1455 PJ_TIME_VAL_SUB(t, call->start_time);
Benny Prijono49ce9a72006-04-05 16:56:19 +00001456 sprintf(pdd, "got 1st response in %ld ms", PJ_TIME_VAL_MSEC(t));
Benny Prijono4adcb912006-04-04 13:12:38 +00001457 } else {
1458 pdd[0] = '\0';
1459 }
1460
1461 if (call->connect_time.sec) {
1462 t = call->connect_time;
1463 PJ_TIME_VAL_SUB(t, call->start_time);
Benny Prijono49ce9a72006-04-05 16:56:19 +00001464 sprintf(connectdelay, ", connected after: %ld ms",
1465 PJ_TIME_VAL_MSEC(t));
Benny Prijono4adcb912006-04-04 13:12:38 +00001466 } else {
1467 connectdelay[0] = '\0';
1468 }
1469
1470 printf(" Signaling quality: %s%s\n", pdd, connectdelay);
1471 }
1472
1473
1474 if (call->media[0].thread == NULL) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001475 return;
1476 }
1477
Benny Prijono49ce9a72006-04-05 16:56:19 +00001478 printf(" Stream #0: audio %.*s@%dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
1479 (int)audio->si.fmt.encoding_name.slen,
1480 audio->si.fmt.encoding_name.ptr,
1481 audio->clock_rate,
1482 audio->samples_per_frame * 1000 / audio->clock_rate,
1483 good_number(bps, audio->bytes_per_frame * audio->clock_rate / audio->samples_per_frame),
1484 good_number(ipbps, (audio->bytes_per_frame+32) * audio->clock_rate / audio->samples_per_frame));
Benny Prijono4adcb912006-04-04 13:12:38 +00001485
1486 total_loss = (audio->rtcp.rtcp_pkt.rr.total_lost_2 << 16) +
1487 (audio->rtcp.rtcp_pkt.rr.total_lost_1 << 8) +
1488 audio->rtcp.rtcp_pkt.rr.total_lost_0;
1489
1490 printf(" RX total %s packets %sB received (%sB +IP hdr)%s\n"
1491 " pkt discards=%d (%3.1f%%), loss=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
Benny Prijono49ce9a72006-04-05 16:56:19 +00001492 " loss period min=%dms, avg=%dms, max=%dms%s\n"
1493 " jitter min=%5.3fms, avg=%5.3fms, max=%5.3fms, curr=%5.3f ms%s\n",
Benny Prijono4adcb912006-04-04 13:12:38 +00001494 good_number(packets, audio->rx_stat.pkt),
1495 good_number(bytes, audio->rx_stat.payload),
1496 good_number(ipbytes, audio->rx_stat.payload + audio->rx_stat.pkt * 32),
1497 "",
1498 audio->rx_stat.discard,
1499 audio->rx_stat.discard * 100.0 / audio->rx_stat.pkt,
1500 total_loss,
1501 total_loss * 100.0 / audio->rx_stat.pkt,
1502 0, 0.0,
1503 "",
1504 -1, -1, -1,
1505 "",
Benny Prijono49ce9a72006-04-05 16:56:19 +00001506 (audio->rx_stat.rtcp_cnt? audio->rx_stat.jitter_min_us/1000.0 : -1.),
1507 (audio->rx_stat.rtcp_cnt? audio->rx_stat.jitter_avg_us/1000.0 : -1.),
1508 (audio->rx_stat.rtcp_cnt? audio->rx_stat.jitter_max_us/1000.0 : -1.),
1509 (audio->rx_stat.rtcp_cnt? pj_ntohl(audio->rtcp.rtcp_pkt.rr.jitter)*1000.0/audio->clock_rate : -1.),
Benny Prijono4adcb912006-04-04 13:12:38 +00001510 ""
1511 );
1512
1513
1514 total_loss = (audio->rem_rtcp.rr.total_lost_2 << 16) +
1515 (audio->rem_rtcp.rr.total_lost_1 << 8) +
1516 audio->rem_rtcp.rr.total_lost_0;
1517
1518 printf(" TX total %s packets %sB sent (%sB +IP hdr)%s\n"
1519 " pkt discards=%d (%3.1f%%), loss=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
Benny Prijono49ce9a72006-04-05 16:56:19 +00001520 " loss period min=%dms, avg=%dms, max=%dms%s\n"
1521 " jitter min=%5.3fms, avg=%5.3fms, max=%5.3fms, curr=%5.3f ms%s\n",
Benny Prijono4adcb912006-04-04 13:12:38 +00001522 good_number(packets, audio->tx_stat.pkt),
1523 good_number(bytes, audio->tx_stat.payload),
1524 good_number(ipbytes, audio->tx_stat.payload + audio->tx_stat.pkt * 32),
1525 "",
1526 audio->tx_stat.discard,
1527 audio->tx_stat.discard * 100.0 / audio->tx_stat.pkt,
1528 total_loss,
1529 total_loss * 100.0 / audio->tx_stat.pkt,
1530 0, 0.0,
1531 "",
1532 -1, -1, -1,
1533 "",
Benny Prijono49ce9a72006-04-05 16:56:19 +00001534 (audio->tx_stat.rtcp_cnt? audio->tx_stat.jitter_min_us/1000.0 : -1.),
1535 (audio->tx_stat.rtcp_cnt? audio->tx_stat.jitter_avg_us/1000.0 : -1.),
1536 (audio->tx_stat.rtcp_cnt? audio->tx_stat.jitter_max_us/1000.0 : -1.),
1537 (audio->tx_stat.rtcp_cnt? pj_ntohl(audio->rem_rtcp.rr.jitter)*1000.0/audio->clock_rate : -1.),
Benny Prijono4adcb912006-04-04 13:12:38 +00001538 ""
1539 );
1540
Benny Prijono49ce9a72006-04-05 16:56:19 +00001541 printf(" End to end delay: %5.3f ms\n",
1542 audio->rtcp.rtt_us / 1000.0);
Benny Prijonoa1d03b42006-04-05 12:53:42 +00001543
Benny Prijono60b980e2006-04-03 22:41:26 +00001544}
1545
1546
1547static void list_calls()
1548{
1549 unsigned i;
1550 puts("List all calls:");
1551 for (i=0; i<app.max_calls; ++i) {
1552 if (!app.call[i].inv)
1553 continue;
1554 print_call(i);
1555 }
1556}
1557
1558static void hangup_call(unsigned index)
1559{
1560 pjsip_tx_data *tdata;
1561 pj_status_t status;
1562
1563 if (app.call[index].inv == NULL)
1564 return;
1565
1566 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1567 if (status==PJ_SUCCESS && tdata!=NULL)
1568 pjsip_inv_send_msg(app.call[index].inv, tdata);
1569}
1570
1571static void hangup_all_calls()
1572{
1573 unsigned i;
1574 for (i=0; i<app.max_calls; ++i) {
1575 if (!app.call[i].inv)
1576 continue;
1577 hangup_call(i);
1578 }
1579}
1580
1581static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1582{
1583 char *p;
1584
1585 printf("%s (empty to cancel): ", title); fflush(stdout);
1586 fgets(buf, len, stdin);
1587
1588 /* Remove trailing newlines. */
1589 for (p=buf; ; ++p) {
1590 if (*p=='\r' || *p=='\n') *p='\0';
1591 else if (!*p) break;
1592 }
1593
1594 if (!*buf)
1595 return PJ_FALSE;
1596
1597 return PJ_TRUE;
1598}
1599
1600
1601static const char *MENU =
1602"\n"
1603"Enter menu character:\n"
1604" l List all calls\n"
1605" h Hangup a call\n"
1606" H Hangup all calls\n"
1607" q Quit\n"
1608"\n";
1609
1610
1611/* Main screen menu */
1612static void console_main()
1613{
1614 char input1[10];
1615 unsigned i;
1616
Benny Prijono4adcb912006-04-04 13:12:38 +00001617 printf("%s", MENU);
1618
Benny Prijono60b980e2006-04-03 22:41:26 +00001619 for (;;) {
1620 printf(">>> "); fflush(stdout);
1621 fgets(input1, sizeof(input1), stdin);
1622
1623 switch (input1[0]) {
1624 case 'l':
1625 list_calls();
1626 break;
1627
1628 case 'h':
1629 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1630 break;
1631
1632 i = atoi(input1);
1633 hangup_call(i);
1634 break;
1635
1636 case 'H':
1637 hangup_all_calls();
1638 break;
1639
1640 case 'q':
1641 goto on_exit;
1642
1643 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001644 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001645 printf("%s", MENU);
1646 break;
1647 }
1648
1649 fflush(stdout);
1650 }
1651
1652on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001653 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001654}
1655
1656
Benny Prijono4adcb912006-04-04 13:12:38 +00001657/*****************************************************************************
1658 * Below is a simple module to log all incoming and outgoing SIP messages
1659 */
1660
1661
Benny Prijono60b980e2006-04-03 22:41:26 +00001662/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001663static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001664{
1665 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1666 "%s\n"
1667 "--end msg--",
1668 rdata->msg_info.len,
1669 pjsip_rx_data_get_info(rdata),
1670 rdata->pkt_info.src_name,
1671 rdata->pkt_info.src_port,
1672 rdata->msg_info.msg_buf));
1673
1674 /* Always return false, otherwise messages will not get processed! */
1675 return PJ_FALSE;
1676}
1677
1678/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001679static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001680{
1681
1682 /* Important note:
1683 * tp_info field is only valid after outgoing messages has passed
1684 * transport layer. So don't try to access tp_info when the module
1685 * has lower priority than transport layer.
1686 */
1687
1688 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1689 "%s\n"
1690 "--end msg--",
1691 (tdata->buf.cur - tdata->buf.start),
1692 pjsip_tx_data_get_info(tdata),
1693 tdata->tp_info.dst_name,
1694 tdata->tp_info.dst_port,
1695 tdata->buf.start));
1696
1697 /* Always return success, otherwise message will not get sent! */
1698 return PJ_SUCCESS;
1699}
1700
1701/* The module instance. */
1702static pjsip_module msg_logger =
1703{
1704 NULL, NULL, /* prev, next. */
1705 { "mod-siprtp-log", 14 }, /* Name. */
1706 -1, /* Id */
1707 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1708 NULL, /* load() */
1709 NULL, /* start() */
1710 NULL, /* stop() */
1711 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001712 &logger_on_rx_msg, /* on_rx_request() */
1713 &logger_on_rx_msg, /* on_rx_response() */
1714 &logger_on_tx_msg, /* on_tx_request. */
1715 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00001716 NULL, /* on_tsx_state() */
1717
1718};
1719
1720
1721
Benny Prijono4adcb912006-04-04 13:12:38 +00001722/*****************************************************************************
1723 * Console application custom logging:
1724 */
1725
1726
1727static FILE *log_file;
1728
1729
1730static void app_log_writer(int level, const char *buffer, int len)
1731{
1732 /* Write to both stdout and file. */
1733
1734 if (level <= app.app_log_level)
1735 pj_log_write(level, buffer, len);
1736
1737 if (log_file) {
1738 fwrite(buffer, len, 1, log_file);
1739 fflush(log_file);
1740 }
1741}
1742
1743
1744pj_status_t app_logging_init(void)
1745{
1746 /* Redirect log function to ours */
1747
1748 pj_log_set_log_func( &app_log_writer );
1749
1750 /* If output log file is desired, create the file: */
1751
1752 if (app.log_filename) {
1753 log_file = fopen(app.log_filename, "wt");
1754 if (log_file == NULL) {
1755 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
1756 app.log_filename));
1757 return -1;
1758 }
1759 }
1760
1761 return PJ_SUCCESS;
1762}
1763
1764
1765void app_logging_shutdown(void)
1766{
1767 /* Close logging file, if any: */
1768
1769 if (log_file) {
1770 fclose(log_file);
1771 log_file = NULL;
1772 }
1773}
1774
Benny Prijono60b980e2006-04-03 22:41:26 +00001775
1776/*
1777 * main()
1778 */
1779int main(int argc, char *argv[])
1780{
Benny Prijono4adcb912006-04-04 13:12:38 +00001781 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001782 pj_status_t status;
1783
Benny Prijono4adcb912006-04-04 13:12:38 +00001784 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00001785 status = pj_init();
1786 if (status != PJ_SUCCESS)
1787 return 1;
1788
Benny Prijono4adcb912006-04-04 13:12:38 +00001789 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00001790 status = init_options(argc, argv);
1791 if (status != PJ_SUCCESS)
1792 return 1;
1793
Benny Prijono4adcb912006-04-04 13:12:38 +00001794 /* Init logging */
1795 status = app_logging_init();
1796 if (status != PJ_SUCCESS)
1797 return 1;
1798
1799 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00001800 status = init_sip();
1801 if (status != PJ_SUCCESS) {
1802 app_perror(THIS_FILE, "Initialization has failed", status);
1803 destroy_sip();
1804 return 1;
1805 }
1806
Benny Prijono4adcb912006-04-04 13:12:38 +00001807 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00001808 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
1809
Benny Prijono4adcb912006-04-04 13:12:38 +00001810 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00001811 status = init_media();
1812 if (status != PJ_SUCCESS) {
1813 app_perror(THIS_FILE, "Media initialization failed", status);
1814 destroy_sip();
1815 return 1;
1816 }
1817
Benny Prijono9a0eab52006-04-04 19:43:24 +00001818 /* Start worker threads */
1819 for (i=0; i<app.thread_count; ++i) {
1820 pj_thread_create( app.pool, "app", &worker_thread, NULL,
1821 0, 0, &app.thread[i]);
1822 }
1823
Benny Prijono4adcb912006-04-04 13:12:38 +00001824 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00001825 if (app.uri_to_call.slen) {
1826 unsigned i;
1827
Benny Prijono4adcb912006-04-04 13:12:38 +00001828 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
1829 app.uri_to_call.ptr));
1830
Benny Prijono60b980e2006-04-03 22:41:26 +00001831 for (i=0; i<app.max_calls; ++i) {
1832 status = make_call(&app.uri_to_call);
1833 if (status != PJ_SUCCESS) {
1834 app_perror(THIS_FILE, "Error making call", status);
1835 break;
1836 }
1837 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001838
1839 } else {
1840
1841 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
1842 app.max_calls));
Benny Prijono60b980e2006-04-03 22:41:26 +00001843 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001844
Benny Prijono4adcb912006-04-04 13:12:38 +00001845 /* Start user interface loop */
Benny Prijono60b980e2006-04-03 22:41:26 +00001846 console_main();
1847
Benny Prijono4adcb912006-04-04 13:12:38 +00001848
1849 /* Shutting down... */
Benny Prijono60b980e2006-04-03 22:41:26 +00001850 destroy_media();
1851 destroy_sip();
Benny Prijono4adcb912006-04-04 13:12:38 +00001852 app_logging_shutdown();
1853
Benny Prijono60b980e2006-04-03 22:41:26 +00001854
1855 return 0;
1856}
1857