blob: b60473e35bcfb9659e417229da0713860573e90f [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id: siprtp.c 4537 2013-06-19 06:47:43Z riza $ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21
22
23
24
25/* Usage */
26static const char *USAGE =
27" PURPOSE: \n"
28" This program establishes SIP INVITE session and media, and calculate \n"
29" the media quality (packet lost, jitter, rtt, etc.). Unlike normal \n"
30" pjmedia applications, this program bypasses all pjmedia stream \n"
31" framework and transmit encoded RTP packets manually using own thread. \n"
32"\n"
33" USAGE:\n"
34" siprtp [options] => to start in server mode\n"
35" siprtp [options] URL => to start in client mode\n"
36"\n"
37" Program options:\n"
38" --count=N, -c Set number of calls to create (default:1) \n"
39" --gap=N -g Set call gapping to N msec (default:0)\n"
40" --duration=SEC, -d Set maximum call duration (default:unlimited) \n"
41" --auto-quit, -q Quit when calls have been completed (default:no)\n"
42" --call-report -R Display report on call termination (default:yes)\n"
43"\n"
44" Address and ports options:\n"
45" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
46" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
47" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
48" try to determine local IP address from hostname)\n"
49"\n"
50" Logging Options:\n"
51" --log-level=N, -l Set log verbosity level (default=5)\n"
52" --app-log-level=N Set app screen log verbosity (default=3)\n"
53" --log-file=FILE Write log to file FILE\n"
54" --report-file=FILE Write report to file FILE\n"
55"\n"
56/* Don't support this anymore, because codec is properly examined in
57 pjmedia_session_info_from_sdp() function.
58
59" Codec Options:\n"
60" --a-pt=PT Set audio payload type to PT (default=0)\n"
61" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
62" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
63" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
64" --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
65*/
66;
67
68
69/* Include all headers. */
70#include <pjsip.h>
71#include <pjmedia.h>
72#include <pjmedia-codec.h>
73#include <pjsip_ua.h>
74#include <pjsip_simple.h>
75#include <pjlib-util.h>
76#include <pjlib.h>
77
78#include <stdlib.h>
79
80/* Uncomment these to disable threads.
81 * NOTE:
82 * when threading is disabled, siprtp won't transmit any
83 * RTP packets.
84 */
85/*
86#undef PJ_HAS_THREADS
87#define PJ_HAS_THREADS 0
88*/
89
90
91#if PJ_HAS_HIGH_RES_TIMER==0
92# error "High resolution timer is needed for this sample"
93#endif
94
95#define THIS_FILE "siprtp.c"
96#define MAX_CALLS 1024
97#define RTP_START_PORT 4000
98
99
100/* Codec descriptor: */
101struct codec
102{
103 unsigned pt;
104 char* name;
105 unsigned clock_rate;
106 unsigned bit_rate;
107 unsigned ptime;
108 char* description;
109};
110
111
112/* A bidirectional media stream created when the call is active. */
113struct media_stream
114{
115 /* Static: */
116 unsigned call_index; /* Call owner. */
117 unsigned media_index; /* Media index in call. */
118 pjmedia_transport *transport; /* To send/recv RTP/RTCP */
119
120 /* Active? */
121 pj_bool_t active; /* Non-zero if is in call. */
122
123 /* Current stream info: */
124 pjmedia_stream_info si; /* Current stream info. */
125
126 /* More info: */
127 unsigned clock_rate; /* clock rate */
128 unsigned samples_per_frame; /* samples per frame */
129 unsigned bytes_per_frame; /* frame size. */
130
131 /* RTP session: */
132 pjmedia_rtp_session out_sess; /* outgoing RTP session */
133 pjmedia_rtp_session in_sess; /* incoming RTP session */
134
135 /* RTCP stats: */
136 pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
137
138 /* Thread: */
139 pj_bool_t thread_quit_flag; /* Stop media thread. */
140 pj_thread_t *thread; /* Media thread. */
141};
142
143
144/* This is a call structure that is created when the application starts
145 * and only destroyed when the application quits.
146 */
147struct call
148{
149 unsigned index;
150 pjsip_inv_session *inv;
151 unsigned media_count;
152 struct media_stream media[1];
153 pj_time_val start_time;
154 pj_time_val response_time;
155 pj_time_val connect_time;
156
157 pj_timer_entry d_timer; /**< Disconnect timer. */
158};
159
160
161/* Application's global variables */
162static struct app
163{
164 unsigned max_calls;
165 unsigned call_gap;
166 pj_bool_t call_report;
167 unsigned uac_calls;
168 unsigned duration;
169 pj_bool_t auto_quit;
170 unsigned thread_count;
171 int sip_port;
172 int rtp_start_port;
173 pj_str_t local_addr;
174 pj_str_t local_uri;
175 pj_str_t local_contact;
176
177 int app_log_level;
178 int log_level;
179 char *log_filename;
180 char *report_filename;
181
182 struct codec audio_codec;
183
184 pj_str_t uri_to_call;
185
186 pj_caching_pool cp;
187 pj_pool_t *pool;
188
189 pjsip_endpoint *sip_endpt;
190 pj_bool_t thread_quit;
191 pj_thread_t *sip_thread[1];
192
193 pjmedia_endpt *med_endpt;
194 struct call call[MAX_CALLS];
195} app;
196
197
198
199/*
200 * Prototypes:
201 */
202
203/* Callback to be called when SDP negotiation is done in the call: */
204static void call_on_media_update( pjsip_inv_session *inv,
205 pj_status_t status);
206
207/* Callback to be called when invite session's state has changed: */
208static void call_on_state_changed( pjsip_inv_session *inv,
209 pjsip_event *e);
210
211/* Callback to be called when dialog has forked: */
212static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
213
214/* Callback to be called to handle incoming requests outside dialogs: */
215static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
216
217/* Worker thread prototype */
218static int sip_worker_thread(void *arg);
219
220/* Create SDP for call */
221static pj_status_t create_sdp( pj_pool_t *pool,
222 struct call *call,
223 pjmedia_sdp_session **p_sdp);
224
225/* Hangup call */
226static void hangup_call(unsigned index);
227
228/* Destroy the call's media */
229static void destroy_call_media(unsigned call_index);
230
231/* Destroy media. */
232static void destroy_media();
233
234/* This callback is called by media transport on receipt of RTP packet. */
235static void on_rx_rtp(void *user_data, void *pkt, pj_ssize_t size);
236
237/* This callback is called by media transport on receipt of RTCP packet. */
238static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size);
239
240/* Display error */
241static void app_perror(const char *sender, const char *title,
242 pj_status_t status);
243
244/* Print call */
245static void print_call(int call_index);
246
247
248/* This is a PJSIP module to be registered by application to handle
249 * incoming requests outside any dialogs/transactions. The main purpose
250 * here is to handle incoming INVITE request message, where we will
251 * create a dialog and INVITE session for it.
252 */
253static pjsip_module mod_siprtp =
254{
255 NULL, NULL, /* prev, next. */
256 { "mod-siprtpapp", 13 }, /* Name. */
257 -1, /* Id */
258 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
259 NULL, /* load() */
260 NULL, /* start() */
261 NULL, /* stop() */
262 NULL, /* unload() */
263 &on_rx_request, /* on_rx_request() */
264 NULL, /* on_rx_response() */
265 NULL, /* on_tx_request. */
266 NULL, /* on_tx_response() */
267 NULL, /* on_tsx_state() */
268};
269
270
271/* Codec constants */
272struct codec audio_codecs[] =
273{
274 { 0, "PCMU", 8000, 64000, 20, "G.711 ULaw" },
275 { 3, "GSM", 8000, 13200, 20, "GSM" },
276 { 4, "G723", 8000, 6400, 30, "G.723.1" },
277 { 8, "PCMA", 8000, 64000, 20, "G.711 ALaw" },
278 { 18, "G729", 8000, 8000, 20, "G.729" },
279};
280
281
282/*
283 * Init SIP stack
284 */
285static pj_status_t init_sip()
286{
287 unsigned i;
288 pj_status_t status;
289
290 /* init PJLIB-UTIL: */
291 status = pjlib_util_init();
292 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
293
294 /* Must create a pool factory before we can allocate any memory. */
295 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
296
297 /* Create application pool for misc. */
298 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
299
300 /* Create the endpoint: */
301 status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
302 &app.sip_endpt);
303 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
304
305
306 /* Add UDP transport. */
307 {
308 pj_sockaddr_in addr;
309 pjsip_host_port addrname;
310 pjsip_transport *tp;
311
312 pj_bzero(&addr, sizeof(addr));
313 addr.sin_family = pj_AF_INET();
314 addr.sin_addr.s_addr = 0;
315 addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
316
317 if (app.local_addr.slen) {
318
319 addrname.host = app.local_addr;
320 addrname.port = app.sip_port;
321
322 status = pj_sockaddr_in_init(&addr, &app.local_addr,
323 (pj_uint16_t)app.sip_port);
324 if (status != PJ_SUCCESS) {
325 app_perror(THIS_FILE, "Unable to resolve IP interface", status);
326 return status;
327 }
328 }
329
330 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
331 (app.local_addr.slen ? &addrname:NULL),
332 1, &tp);
333 if (status != PJ_SUCCESS) {
334 app_perror(THIS_FILE, "Unable to start UDP transport", status);
335 return status;
336 }
337
338 PJ_LOG(3,(THIS_FILE, "SIP UDP listening on %.*s:%d",
339 (int)tp->local_name.host.slen, tp->local_name.host.ptr,
340 tp->local_name.port));
341 }
342
343 /*
344 * Init transaction layer.
345 * This will create/initialize transaction hash tables etc.
346 */
347 status = pjsip_tsx_layer_init_module(app.sip_endpt);
348 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
349
350 /* Initialize UA layer. */
351 status = pjsip_ua_init_module( app.sip_endpt, NULL );
352 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
353
354 /* Initialize 100rel support */
355 status = pjsip_100rel_init_module(app.sip_endpt);
356 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
357
358 /* Init invite session module. */
359 {
360 pjsip_inv_callback inv_cb;
361
362 /* Init the callback for INVITE session: */
363 pj_bzero(&inv_cb, sizeof(inv_cb));
364 inv_cb.on_state_changed = &call_on_state_changed;
365 inv_cb.on_new_session = &call_on_forked;
366 inv_cb.on_media_update = &call_on_media_update;
367
368 /* Initialize invite session module: */
369 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
370 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
371 }
372
373 /* Register our module to receive incoming requests. */
374 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
375 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
376
377 /* Init calls */
378 for (i=0; i<app.max_calls; ++i)
379 app.call[i].index = i;
380
381 /* Done */
382 return PJ_SUCCESS;
383}
384
385
386/*
387 * Destroy SIP
388 */
389static void destroy_sip()
390{
391 unsigned i;
392
393 app.thread_quit = 1;
394 for (i=0; i<app.thread_count; ++i) {
395 if (app.sip_thread[i]) {
396 pj_thread_join(app.sip_thread[i]);
397 pj_thread_destroy(app.sip_thread[i]);
398 app.sip_thread[i] = NULL;
399 }
400 }
401
402 if (app.sip_endpt) {
403 pjsip_endpt_destroy(app.sip_endpt);
404 app.sip_endpt = NULL;
405 }
406
407}
408
409
410/*
411 * Init media stack.
412 */
413static pj_status_t init_media()
414{
415 unsigned i, count;
416 pj_uint16_t rtp_port;
417 pj_status_t status;
418
419
420 /* Initialize media endpoint so that at least error subsystem is properly
421 * initialized.
422 */
423#if PJ_HAS_THREADS
424 status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
425#else
426 status = pjmedia_endpt_create(&app.cp.factory,
427 pjsip_endpt_get_ioqueue(app.sip_endpt),
428 0, &app.med_endpt);
429#endif
430 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
431
432
433 /* Must register codecs to be supported */
434#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
435 pjmedia_codec_g711_init(app.med_endpt);
436#endif
437
438 /* RTP port counter */
439 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
440
441 /* Init media transport for all calls. */
442 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
443
444 unsigned j;
445
446 /* Create transport for each media in the call */
447 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
448 /* Repeat binding media socket to next port when fails to bind
449 * to current port number.
450 */
451 int retry;
452
453 app.call[i].media[j].call_index = i;
454 app.call[i].media[j].media_index = j;
455
456 status = -1;
457 for (retry=0; retry<100; ++retry,rtp_port+=2) {
458 struct media_stream *m = &app.call[i].media[j];
459
460 status = pjmedia_transport_udp_create2(app.med_endpt,
461 "siprtp",
462 &app.local_addr,
463 rtp_port, 0,
464 &m->transport);
465 if (status == PJ_SUCCESS) {
466 rtp_port += 2;
467 break;
468 }
469 }
470 }
471
472 if (status != PJ_SUCCESS)
473 goto on_error;
474 }
475
476 /* Done */
477 return PJ_SUCCESS;
478
479on_error:
480 destroy_media();
481 return status;
482}
483
484
485/*
486 * Destroy media.
487 */
488static void destroy_media()
489{
490 unsigned i;
491
492 for (i=0; i<app.max_calls; ++i) {
493 unsigned j;
494 for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
495 struct media_stream *m = &app.call[i].media[j];
496
497 if (m->transport) {
498 pjmedia_transport_close(m->transport);
499 m->transport = NULL;
500 }
501 }
502 }
503
504 if (app.med_endpt) {
505 pjmedia_endpt_destroy(app.med_endpt);
506 app.med_endpt = NULL;
507 }
508}
509
510
511/*
512 * Make outgoing call.
513 */
514static pj_status_t make_call(const pj_str_t *dst_uri)
515{
516 unsigned i;
517 struct call *call;
518 pjsip_dialog *dlg;
519 pjmedia_sdp_session *sdp;
520 pjsip_tx_data *tdata;
521 pj_status_t status;
522
523
524 /* Find unused call slot */
525 for (i=0; i<app.max_calls; ++i) {
526 if (app.call[i].inv == NULL)
527 break;
528 }
529
530 if (i == app.max_calls)
531 return PJ_ETOOMANY;
532
533 call = &app.call[i];
534
535 /* Create UAC dialog */
536 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
537 &app.local_uri, /* local URI */
538 &app.local_contact, /* local Contact */
539 dst_uri, /* remote URI */
540 dst_uri, /* remote target */
541 &dlg); /* dialog */
542 if (status != PJ_SUCCESS) {
543 ++app.uac_calls;
544 return status;
545 }
546
547 /* Create SDP */
548 create_sdp( dlg->pool, call, &sdp);
549
550 /* Create the INVITE session. */
551 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
552 if (status != PJ_SUCCESS) {
553 pjsip_dlg_terminate(dlg);
554 ++app.uac_calls;
555 return status;
556 }
557
558
559 /* Attach call data to invite session */
560 call->inv->mod_data[mod_siprtp.id] = call;
561
562 /* Mark start of call */
563 pj_gettimeofday(&call->start_time);
564
565
566 /* Create initial INVITE request.
567 * This INVITE request will contain a perfectly good request and
568 * an SDP body as well.
569 */
570 status = pjsip_inv_invite(call->inv, &tdata);
571 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
572
573
574 /* Send initial INVITE request.
575 * From now on, the invite session's state will be reported to us
576 * via the invite session callbacks.
577 */
578 status = pjsip_inv_send_msg(call->inv, tdata);
579 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
580
581
582 return PJ_SUCCESS;
583}
584
585
586/*
587 * Receive incoming call
588 */
589static void process_incoming_call(pjsip_rx_data *rdata)
590{
591 unsigned i, options;
592 struct call *call;
593 pjsip_dialog *dlg;
594 pjmedia_sdp_session *sdp;
595 pjsip_tx_data *tdata;
596 pj_status_t status;
597
598 /* Find free call slot */
599 for (i=0; i<app.max_calls; ++i) {
600 if (app.call[i].inv == NULL)
601 break;
602 }
603
604 if (i == app.max_calls) {
605 const pj_str_t reason = pj_str("Too many calls");
606 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
607 500, &reason,
608 NULL, NULL);
609 return;
610 }
611
612 call = &app.call[i];
613
614 /* Verify that we can handle the request. */
615 options = 0;
616 status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
617 app.sip_endpt, &tdata);
618 if (status != PJ_SUCCESS) {
619 /*
620 * No we can't handle the incoming INVITE request.
621 */
622 if (tdata) {
623 pjsip_response_addr res_addr;
624
625 pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
626 pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
627 NULL, NULL);
628
629 } else {
630
631 /* Respond with 500 (Internal Server Error) */
632 pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
633 NULL, NULL);
634 }
635
636 return;
637 }
638
639 /* Create UAS dialog */
640 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
641 &app.local_contact, &dlg);
642 if (status != PJ_SUCCESS) {
643 const pj_str_t reason = pj_str("Unable to create dialog");
644 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
645 500, &reason,
646 NULL, NULL);
647 return;
648 }
649
650 /* Create SDP */
651 create_sdp( dlg->pool, call, &sdp);
652
653 /* Create UAS invite session */
654 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
655 if (status != PJ_SUCCESS) {
656 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
657 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
658 return;
659 }
660
661
662 /* Attach call data to invite session */
663 call->inv->mod_data[mod_siprtp.id] = call;
664
665 /* Mark start of call */
666 pj_gettimeofday(&call->start_time);
667
668
669
670 /* Create 200 response .*/
671 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
672 NULL, NULL, &tdata);
673 if (status != PJ_SUCCESS) {
674 status = pjsip_inv_initial_answer(call->inv, rdata,
675 PJSIP_SC_NOT_ACCEPTABLE,
676 NULL, NULL, &tdata);
677 if (status == PJ_SUCCESS)
678 pjsip_inv_send_msg(call->inv, tdata);
679 else
680 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
681 return;
682 }
683
684
685 /* Send the 200 response. */
686 status = pjsip_inv_send_msg(call->inv, tdata);
687 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
688
689
690 /* Done */
691}
692
693
694/* Callback to be called when dialog has forked: */
695static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
696{
697 PJ_UNUSED_ARG(inv);
698 PJ_UNUSED_ARG(e);
699
700 PJ_TODO( HANDLE_FORKING );
701}
702
703
704/* Callback to be called to handle incoming requests outside dialogs: */
705static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
706{
707 /* Ignore strandled ACKs (must not send respone */
708 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
709 return PJ_FALSE;
710
711 /* Respond (statelessly) any non-INVITE requests with 500 */
712 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
713 pj_str_t reason = pj_str("Unsupported Operation");
714 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
715 500, &reason,
716 NULL, NULL);
717 return PJ_TRUE;
718 }
719
720 /* Handle incoming INVITE */
721 process_incoming_call(rdata);
722
723 /* Done */
724 return PJ_TRUE;
725}
726
727
728/* Callback timer to disconnect call (limiting call duration) */
729static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
730 struct pj_timer_entry *entry)
731{
732 struct call *call = entry->user_data;
733
734 PJ_UNUSED_ARG(timer_heap);
735
736 entry->id = 0;
737 hangup_call(call->index);
738}
739
740
741/* Callback to be called when invite session's state has changed: */
742static void call_on_state_changed( pjsip_inv_session *inv,
743 pjsip_event *e)
744{
745 struct call *call = inv->mod_data[mod_siprtp.id];
746
747 PJ_UNUSED_ARG(e);
748
749 if (!call)
750 return;
751
752 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
753
754 pj_time_val null_time = {0, 0};
755
756 if (call->d_timer.id != 0) {
757 pjsip_endpt_cancel_timer(app.sip_endpt, &call->d_timer);
758 call->d_timer.id = 0;
759 }
760
761 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%d (%.*s)",
762 call->index,
763 inv->cause,
764 (int)inv->cause_text.slen,
765 inv->cause_text.ptr));
766
767 if (app.call_report) {
768 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
769 print_call(call->index);
770 }
771
772
773 call->inv = NULL;
774 inv->mod_data[mod_siprtp.id] = NULL;
775
776 destroy_call_media(call->index);
777
778 call->start_time = null_time;
779 call->response_time = null_time;
780 call->connect_time = null_time;
781
782 ++app.uac_calls;
783
784 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
785
786 pj_time_val t;
787
788 pj_gettimeofday(&call->connect_time);
789 if (call->response_time.sec == 0)
790 call->response_time = call->connect_time;
791
792 t = call->connect_time;
793 PJ_TIME_VAL_SUB(t, call->start_time);
794
795 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
796 PJ_TIME_VAL_MSEC(t)));
797
798 if (app.duration != 0) {
799 call->d_timer.id = 1;
800 call->d_timer.user_data = call;
801 call->d_timer.cb = &timer_disconnect_call;
802
803 t.sec = app.duration;
804 t.msec = 0;
805
806 pjsip_endpt_schedule_timer(app.sip_endpt, &call->d_timer, &t);
807 }
808
809 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
810 inv->state == PJSIP_INV_STATE_CONNECTING) {
811
812 if (call->response_time.sec == 0)
813 pj_gettimeofday(&call->response_time);
814
815 }
816}
817
818
819/* Utility */
820static void app_perror(const char *sender, const char *title,
821 pj_status_t status)
822{
823 char errmsg[PJ_ERR_MSG_SIZE];
824
825 pj_strerror(status, errmsg, sizeof(errmsg));
826 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
827}
828
829
830/* Worker thread for SIP */
831static int sip_worker_thread(void *arg)
832{
833 PJ_UNUSED_ARG(arg);
834
835 while (!app.thread_quit) {
836 pj_time_val timeout = {0, 10};
837 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
838 }
839
840 return 0;
841}
842
843
844/* Init application options */
845static pj_status_t init_options(int argc, char *argv[])
846{
847 static char ip_addr[32];
848 static char local_uri[64];
849
850 enum { OPT_START,
851 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
852 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME,
853 OPT_REPORT_FILE };
854
855 struct pj_getopt_option long_options[] = {
856 { "count", 1, 0, 'c' },
857 { "gap", 1, 0, 'g' },
858 { "call-report", 0, 0, 'R' },
859 { "duration", 1, 0, 'd' },
860 { "auto-quit", 0, 0, 'q' },
861 { "local-port", 1, 0, 'p' },
862 { "rtp-port", 1, 0, 'r' },
863 { "ip-addr", 1, 0, 'i' },
864
865 { "log-level", 1, 0, 'l' },
866 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
867 { "log-file", 1, 0, OPT_LOG_FILE },
868
869 { "report-file", 1, 0, OPT_REPORT_FILE },
870
871 /* Don't support this anymore, see comments in USAGE above.
872 { "a-pt", 1, 0, OPT_A_PT },
873 { "a-name", 1, 0, OPT_A_NAME },
874 { "a-clock", 1, 0, OPT_A_CLOCK },
875 { "a-bitrate", 1, 0, OPT_A_BITRATE },
876 { "a-ptime", 1, 0, OPT_A_PTIME },
877 */
878
879 { NULL, 0, 0, 0 },
880 };
881 int c;
882 int option_index;
883
884 /* Get local IP address for the default IP address */
885 {
886 const pj_str_t *hostname;
887 pj_sockaddr_in tmp_addr;
888 char *addr;
889
890 hostname = pj_gethostname();
891 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
892 addr = pj_inet_ntoa(tmp_addr.sin_addr);
893 pj_ansi_strcpy(ip_addr, addr);
894 }
895
896 /* Init defaults */
897 app.max_calls = 1;
898 app.thread_count = 1;
899 app.sip_port = 5060;
900 app.rtp_start_port = RTP_START_PORT;
901 app.local_addr = pj_str(ip_addr);
902 app.log_level = 5;
903 app.app_log_level = 3;
904 app.log_filename = NULL;
905
906 /* Default codecs: */
907 app.audio_codec = audio_codecs[0];
908
909 /* Parse options */
910 pj_optind = 0;
911 while((c=pj_getopt_long(argc,argv, "c:d:p:r:i:l:g:qR",
912 long_options, &option_index))!=-1)
913 {
914 switch (c) {
915 case 'c':
916 app.max_calls = atoi(pj_optarg);
917 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
918 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
919 return 1;
920 }
921 break;
922 case 'g':
923 app.call_gap = atoi(pj_optarg);
924 break;
925 case 'R':
926 app.call_report = PJ_TRUE;
927 break;
928 case 'd':
929 app.duration = atoi(pj_optarg);
930 break;
931 case 'q':
932 app.auto_quit = 1;
933 break;
934
935 case 'p':
936 app.sip_port = atoi(pj_optarg);
937 break;
938 case 'r':
939 app.rtp_start_port = atoi(pj_optarg);
940 break;
941 case 'i':
942 app.local_addr = pj_str(pj_optarg);
943 break;
944
945 case 'l':
946 app.log_level = atoi(pj_optarg);
947 break;
948 case OPT_APP_LOG_LEVEL:
949 app.app_log_level = atoi(pj_optarg);
950 break;
951 case OPT_LOG_FILE:
952 app.log_filename = pj_optarg;
953 break;
954
955 case OPT_A_PT:
956 app.audio_codec.pt = atoi(pj_optarg);
957 break;
958 case OPT_A_NAME:
959 app.audio_codec.name = pj_optarg;
960 break;
961 case OPT_A_CLOCK:
962 app.audio_codec.clock_rate = atoi(pj_optarg);
963 break;
964 case OPT_A_BITRATE:
965 app.audio_codec.bit_rate = atoi(pj_optarg);
966 break;
967 case OPT_A_PTIME:
968 app.audio_codec.ptime = atoi(pj_optarg);
969 break;
970 case OPT_REPORT_FILE:
971 app.report_filename = pj_optarg;
972 break;
973
974 default:
975 puts(USAGE);
976 return 1;
977 }
978 }
979
980 /* Check if URL is specified */
981 if (pj_optind < argc)
982 app.uri_to_call = pj_str(argv[pj_optind]);
983
984 /* Build local URI and contact */
985 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr.ptr, app.sip_port);
986 app.local_uri = pj_str(local_uri);
987 app.local_contact = app.local_uri;
988
989
990 return PJ_SUCCESS;
991}
992
993
994/*****************************************************************************
995 * MEDIA STUFFS
996 */
997
998/*
999 * Create SDP session for a call.
1000 */
1001static pj_status_t create_sdp( pj_pool_t *pool,
1002 struct call *call,
1003 pjmedia_sdp_session **p_sdp)
1004{
1005 pj_time_val tv;
1006 pjmedia_sdp_session *sdp;
1007 pjmedia_sdp_media *m;
1008 pjmedia_sdp_attr *attr;
1009 pjmedia_transport_info tpinfo;
1010 struct media_stream *audio = &call->media[0];
1011
1012 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
1013
1014
1015 /* Get transport info */
1016 pjmedia_transport_info_init(&tpinfo);
1017 pjmedia_transport_get_info(audio->transport, &tpinfo);
1018
1019 /* Create and initialize basic SDP session */
1020 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
1021
1022 pj_gettimeofday(&tv);
1023 sdp->origin.user = pj_str("pjsip-siprtp");
1024 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
1025 sdp->origin.net_type = pj_str("IN");
1026 sdp->origin.addr_type = pj_str("IP4");
1027 sdp->origin.addr = *pj_gethostname();
1028 sdp->name = pj_str("pjsip");
1029
1030 /* Since we only support one media stream at present, put the
1031 * SDP connection line in the session level.
1032 */
1033 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
1034 sdp->conn->net_type = pj_str("IN");
1035 sdp->conn->addr_type = pj_str("IP4");
1036 sdp->conn->addr = app.local_addr;
1037
1038
1039 /* SDP time and attributes. */
1040 sdp->time.start = sdp->time.stop = 0;
1041 sdp->attr_count = 0;
1042
1043 /* Create media stream 0: */
1044
1045 sdp->media_count = 1;
1046 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
1047 sdp->media[0] = m;
1048
1049 /* Standard media info: */
1050 m->desc.media = pj_str("audio");
1051 m->desc.port = pj_ntohs(tpinfo.sock_info.rtp_addr_name.ipv4.sin_port);
1052 m->desc.port_count = 1;
1053 m->desc.transport = pj_str("RTP/AVP");
1054
1055 /* Add format and rtpmap for each codec. */
1056 m->desc.fmt_count = 1;
1057 m->attr_count = 0;
1058
1059 {
1060 pjmedia_sdp_rtpmap rtpmap;
1061 pjmedia_sdp_attr *attr;
1062 char ptstr[10];
1063
1064 sprintf(ptstr, "%d", app.audio_codec.pt);
1065 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
1066 rtpmap.pt = m->desc.fmt[0];
1067 rtpmap.clock_rate = app.audio_codec.clock_rate;
1068 rtpmap.enc_name = pj_str(app.audio_codec.name);
1069 rtpmap.param.slen = 0;
1070
1071 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
1072 m->attr[m->attr_count++] = attr;
1073 }
1074
1075 /* Add sendrecv attribute. */
1076 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1077 attr->name = pj_str("sendrecv");
1078 m->attr[m->attr_count++] = attr;
1079
1080#if 1
1081 /*
1082 * Add support telephony event
1083 */
1084 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
1085 /* Add rtpmap. */
1086 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1087 attr->name = pj_str("rtpmap");
1088 attr->value = pj_str("121 telephone-event/8000");
1089 m->attr[m->attr_count++] = attr;
1090 /* Add fmtp */
1091 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
1092 attr->name = pj_str("fmtp");
1093 attr->value = pj_str("121 0-15");
1094 m->attr[m->attr_count++] = attr;
1095#endif
1096
1097 /* Done */
1098 *p_sdp = sdp;
1099
1100 return PJ_SUCCESS;
1101}
1102
1103
1104#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0)
1105#include <windows.h>
1106static void boost_priority(void)
1107{
1108 SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
1109 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
1110}
1111
1112#elif defined(PJ_LINUX) && PJ_LINUX != 0
1113#include <pthread.h>
1114static void boost_priority(void)
1115{
1116#define POLICY SCHED_FIFO
1117 struct sched_param tp;
1118 int max_prio;
1119 int policy;
1120 int rc;
1121
1122 if (sched_get_priority_min(POLICY) < sched_get_priority_max(POLICY))
1123 max_prio = sched_get_priority_max(POLICY)-1;
1124 else
1125 max_prio = sched_get_priority_max(POLICY)+1;
1126
1127 /*
1128 * Adjust process scheduling algorithm and priority
1129 */
1130 rc = sched_getparam(0, &tp);
1131 if (rc != 0) {
1132 app_perror( THIS_FILE, "sched_getparam error",
1133 PJ_RETURN_OS_ERROR(rc));
1134 return;
1135 }
1136 tp.__sched_priority = max_prio;
1137
1138 rc = sched_setscheduler(0, POLICY, &tp);
1139 if (rc != 0) {
1140 app_perror( THIS_FILE, "sched_setscheduler error",
1141 PJ_RETURN_OS_ERROR(rc));
1142 }
1143
1144 PJ_LOG(4, (THIS_FILE, "New process policy=%d, priority=%d",
1145 policy, tp.__sched_priority));
1146
1147 /*
1148 * Adjust thread scheduling algorithm and priority
1149 */
1150 rc = pthread_getschedparam(pthread_self(), &policy, &tp);
1151 if (rc != 0) {
1152 app_perror( THIS_FILE, "pthread_getschedparam error",
1153 PJ_RETURN_OS_ERROR(rc));
1154 return;
1155 }
1156
1157 PJ_LOG(4, (THIS_FILE, "Old thread policy=%d, priority=%d",
1158 policy, tp.__sched_priority));
1159
1160 policy = POLICY;
1161 tp.__sched_priority = max_prio;
1162
1163 rc = pthread_setschedparam(pthread_self(), policy, &tp);
1164 if (rc != 0) {
1165 app_perror( THIS_FILE, "pthread_setschedparam error",
1166 PJ_RETURN_OS_ERROR(rc));
1167 return;
1168 }
1169
1170 PJ_LOG(4, (THIS_FILE, "New thread policy=%d, priority=%d",
1171 policy, tp.__sched_priority));
1172}
1173
1174#else
1175# define boost_priority()
1176#endif
1177
1178
1179/*
1180 * This callback is called by media transport on receipt of RTP packet.
1181 */
1182static void on_rx_rtp(void *user_data, void *pkt, pj_ssize_t size)
1183{
1184 struct media_stream *strm;
1185 pj_status_t status;
1186 const pjmedia_rtp_hdr *hdr;
1187 const void *payload;
1188 unsigned payload_len;
1189
1190 strm = user_data;
1191
1192 /* Discard packet if media is inactive */
1193 if (!strm->active)
1194 return;
1195
1196 /* Check for errors */
1197 if (size < 0) {
1198 app_perror(THIS_FILE, "RTP recv() error", (pj_status_t)-size);
1199 return;
1200 }
1201
1202 /* Decode RTP packet. */
1203 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1204 pkt, (int)size,
1205 &hdr, &payload, &payload_len);
1206 if (status != PJ_SUCCESS) {
1207 app_perror(THIS_FILE, "RTP decode error", status);
1208 return;
1209 }
1210
1211 //PJ_LOG(4,(THIS_FILE, "Rx seq=%d", pj_ntohs(hdr->seq)));
1212
1213 /* Update the RTCP session. */
1214 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
1215 pj_ntohl(hdr->ts), payload_len);
1216
1217 /* Update RTP session */
1218 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
1219
1220}
1221
1222/*
1223 * This callback is called by media transport on receipt of RTCP packet.
1224 */
1225static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size)
1226{
1227 struct media_stream *strm;
1228
1229 strm = user_data;
1230
1231 /* Discard packet if media is inactive */
1232 if (!strm->active)
1233 return;
1234
1235 /* Check for errors */
1236 if (size < 0) {
1237 app_perror(THIS_FILE, "Error receiving RTCP packet",(pj_status_t)-size);
1238 return;
1239 }
1240
1241 /* Update RTCP session */
1242 pjmedia_rtcp_rx_rtcp(&strm->rtcp, pkt, size);
1243}
1244
1245
1246/*
1247 * Media thread
1248 *
1249 * This is the thread to send and receive both RTP and RTCP packets.
1250 */
1251static int media_thread(void *arg)
1252{
1253 enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
1254 struct media_stream *strm = arg;
1255 char packet[1500];
1256 unsigned msec_interval;
1257 pj_timestamp freq, next_rtp, next_rtcp;
1258
1259
1260 /* Boost thread priority if necessary */
1261 boost_priority();
1262
1263 /* Let things settle */
1264 pj_thread_sleep(100);
1265
1266 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1267 pj_get_timestamp_freq(&freq);
1268
1269 pj_get_timestamp(&next_rtp);
1270 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
1271
1272 next_rtcp = next_rtp;
1273 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
1274
1275
1276 while (!strm->thread_quit_flag) {
1277 pj_timestamp now, lesser;
1278 pj_time_val timeout;
1279 pj_bool_t send_rtp, send_rtcp;
1280
1281 send_rtp = send_rtcp = PJ_FALSE;
1282
1283 /* Determine how long to sleep */
1284 if (next_rtp.u64 < next_rtcp.u64) {
1285 lesser = next_rtp;
1286 send_rtp = PJ_TRUE;
1287 } else {
1288 lesser = next_rtcp;
1289 send_rtcp = PJ_TRUE;
1290 }
1291
1292 pj_get_timestamp(&now);
1293 if (lesser.u64 <= now.u64) {
1294 timeout.sec = timeout.msec = 0;
1295 //printf("immediate "); fflush(stdout);
1296 } else {
1297 pj_uint64_t tick_delay;
1298 tick_delay = lesser.u64 - now.u64;
1299 timeout.sec = 0;
1300 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1301 pj_time_val_normalize(&timeout);
1302
1303 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
1304 }
1305
1306 /* Wait for next interval */
1307 //if (timeout.sec!=0 && timeout.msec!=0) {
1308 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
1309 if (strm->thread_quit_flag)
1310 break;
1311 //}
1312
1313 pj_get_timestamp(&now);
1314
1315 if (send_rtp || next_rtp.u64 <= now.u64) {
1316 /*
1317 * Time to send RTP packet.
1318 */
1319 pj_status_t status;
1320 const void *p_hdr;
1321 const pjmedia_rtp_hdr *hdr;
1322 pj_ssize_t size;
1323 int hdrlen;
1324
1325 /* Format RTP header */
1326 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1327 0, /* marker bit */
1328 strm->bytes_per_frame,
1329 strm->samples_per_frame,
1330 &p_hdr, &hdrlen);
1331 if (status == PJ_SUCCESS) {
1332
1333 //PJ_LOG(4,(THIS_FILE, "\t\tTx seq=%d", pj_ntohs(hdr->seq)));
1334
1335 hdr = (const pjmedia_rtp_hdr*) p_hdr;
1336
1337 /* Copy RTP header to packet */
1338 pj_memcpy(packet, hdr, hdrlen);
1339
1340 /* Zero the payload */
1341 pj_bzero(packet+hdrlen, strm->bytes_per_frame);
1342
1343 /* Send RTP packet */
1344 size = hdrlen + strm->bytes_per_frame;
1345 status = pjmedia_transport_send_rtp(strm->transport,
1346 packet, size);
1347 if (status != PJ_SUCCESS)
1348 app_perror(THIS_FILE, "Error sending RTP packet", status);
1349
1350 } else {
1351 pj_assert(!"RTP encode() error");
1352 }
1353
1354 /* Update RTCP SR */
1355 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1356
1357 /* Schedule next send */
1358 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
1359 }
1360
1361
1362 if (send_rtcp || next_rtcp.u64 <= now.u64) {
1363 /*
1364 * Time to send RTCP packet.
1365 */
1366 void *rtcp_pkt;
1367 int rtcp_len;
1368 pj_ssize_t size;
1369 pj_status_t status;
1370
1371 /* Build RTCP packet */
1372 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1373
1374
1375 /* Send packet */
1376 size = rtcp_len;
1377 status = pjmedia_transport_send_rtcp(strm->transport,
1378 rtcp_pkt, size);
1379 if (status != PJ_SUCCESS) {
1380 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1381 }
1382
1383 /* Schedule next send */
1384 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
1385 1000);
1386 }
1387 }
1388
1389 return 0;
1390}
1391
1392
1393/* Callback to be called when SDP negotiation is done in the call: */
1394static void call_on_media_update( pjsip_inv_session *inv,
1395 pj_status_t status)
1396{
1397 struct call *call;
1398 pj_pool_t *pool;
1399 struct media_stream *audio;
1400 const pjmedia_sdp_session *local_sdp, *remote_sdp;
1401 struct codec *codec_desc = NULL;
1402 unsigned i;
1403
1404 call = inv->mod_data[mod_siprtp.id];
1405 pool = inv->dlg->pool;
1406 audio = &call->media[0];
1407
1408 /* If this is a mid-call media update, then destroy existing media */
1409 if (audio->thread != NULL)
1410 destroy_call_media(call->index);
1411
1412
1413 /* Do nothing if media negotiation has failed */
1414 if (status != PJ_SUCCESS) {
1415 app_perror(THIS_FILE, "SDP negotiation failed", status);
1416 return;
1417 }
1418
1419
1420 /* Capture stream definition from the SDP */
1421 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1422 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1423
1424 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
1425 local_sdp, remote_sdp, 0);
1426 if (status != PJ_SUCCESS) {
1427 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1428 return;
1429 }
1430
1431 /* Get the remainder of codec information from codec descriptor */
1432 if (audio->si.fmt.pt == app.audio_codec.pt)
1433 codec_desc = &app.audio_codec;
1434 else {
1435 /* Find the codec description in codec array */
1436 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1437 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1438 codec_desc = &audio_codecs[i];
1439 break;
1440 }
1441 }
1442
1443 if (codec_desc == NULL) {
1444 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1445 return;
1446 }
1447 }
1448
1449 audio->clock_rate = audio->si.fmt.clock_rate;
1450 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1451 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
1452
1453
1454 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
1455 pj_rand());
1456 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
1457 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
1458 audio->samples_per_frame, 0);
1459
1460
1461 /* Attach media to transport */
1462 status = pjmedia_transport_attach(audio->transport, audio,
1463 &audio->si.rem_addr,
1464 &audio->si.rem_rtcp,
1465 sizeof(pj_sockaddr_in),
1466 &on_rx_rtp,
1467 &on_rx_rtcp);
1468 if (status != PJ_SUCCESS) {
1469 app_perror(THIS_FILE, "Error on pjmedia_transport_attach()", status);
1470 return;
1471 }
1472
1473 /* Start media thread. */
1474 audio->thread_quit_flag = 0;
1475#if PJ_HAS_THREADS
1476 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1477 0, 0, &audio->thread);
1478 if (status != PJ_SUCCESS) {
1479 app_perror(THIS_FILE, "Error creating media thread", status);
1480 return;
1481 }
1482#endif
1483
1484 /* Set the media as active */
1485 audio->active = PJ_TRUE;
1486}
1487
1488
1489
1490/* Destroy call's media */
1491static void destroy_call_media(unsigned call_index)
1492{
1493 struct media_stream *audio = &app.call[call_index].media[0];
1494
1495 if (audio) {
1496 audio->active = PJ_FALSE;
1497
1498 if (audio->thread) {
1499 audio->thread_quit_flag = 1;
1500 pj_thread_join(audio->thread);
1501 pj_thread_destroy(audio->thread);
1502 audio->thread = NULL;
1503 audio->thread_quit_flag = 0;
1504 }
1505
1506 pjmedia_transport_detach(audio->transport, audio);
1507 }
1508}
1509
1510
1511/*****************************************************************************
1512 * USER INTERFACE STUFFS
1513 */
1514
1515static void call_get_duration(int call_index, pj_time_val *dur)
1516{
1517 struct call *call = &app.call[call_index];
1518 pjsip_inv_session *inv;
1519
1520 dur->sec = dur->msec = 0;
1521
1522 if (!call)
1523 return;
1524
1525 inv = call->inv;
1526 if (!inv)
1527 return;
1528
1529 if (inv->state >= PJSIP_INV_STATE_CONFIRMED && call->connect_time.sec) {
1530
1531 pj_gettimeofday(dur);
1532 PJ_TIME_VAL_SUB((*dur), call->connect_time);
1533 }
1534}
1535
1536
1537static const char *good_number(char *buf, pj_int32_t val)
1538{
1539 if (val < 1000) {
1540 pj_ansi_sprintf(buf, "%d", val);
1541 } else if (val < 1000000) {
1542 pj_ansi_sprintf(buf, "%d.%02dK",
1543 val / 1000,
1544 (val % 1000) / 100);
1545 } else {
1546 pj_ansi_sprintf(buf, "%d.%02dM",
1547 val / 1000000,
1548 (val % 1000000) / 10000);
1549 }
1550
1551 return buf;
1552}
1553
1554
1555
1556static void print_avg_stat(void)
1557{
1558#define MIN_(var,val) if ((int)val < (int)var) var = val
1559#define MAX_(var,val) if ((int)val > (int)var) var = val
1560#define AVG_(var,val) var = ( ((var * count) + val) / (count+1) )
1561#define BIGVAL 0x7FFFFFFFL
1562 struct stat_entry
1563 {
1564 int min, avg, max;
1565 };
1566
1567 struct stat_entry call_dur, call_pdd;
1568 pjmedia_rtcp_stat min_stat, avg_stat, max_stat;
1569
1570 char srx_min[16], srx_avg[16], srx_max[16];
1571 char brx_min[16], brx_avg[16], brx_max[16];
1572 char stx_min[16], stx_avg[16], stx_max[16];
1573 char btx_min[16], btx_avg[16], btx_max[16];
1574
1575
1576 unsigned i, count;
1577
1578 pj_bzero(&call_dur, sizeof(call_dur));
1579 call_dur.min = BIGVAL;
1580
1581 pj_bzero(&call_pdd, sizeof(call_pdd));
1582 call_pdd.min = BIGVAL;
1583
1584 pj_bzero(&min_stat, sizeof(min_stat));
1585 min_stat.rx.pkt = min_stat.tx.pkt = BIGVAL;
1586 min_stat.rx.bytes = min_stat.tx.bytes = BIGVAL;
1587 min_stat.rx.loss = min_stat.tx.loss = BIGVAL;
1588 min_stat.rx.dup = min_stat.tx.dup = BIGVAL;
1589 min_stat.rx.reorder = min_stat.tx.reorder = BIGVAL;
1590 min_stat.rx.jitter.min = min_stat.tx.jitter.min = BIGVAL;
1591 min_stat.rtt.min = BIGVAL;
1592
1593 pj_bzero(&avg_stat, sizeof(avg_stat));
1594 pj_bzero(&max_stat, sizeof(max_stat));
1595
1596
1597 for (i=0, count=0; i<app.max_calls; ++i) {
1598
1599 struct call *call = &app.call[i];
1600 struct media_stream *audio = &call->media[0];
1601 pj_time_val dur;
1602 unsigned msec_dur;
1603
1604 if (call->inv == NULL ||
1605 call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
1606 call->connect_time.sec == 0)
1607 {
1608 continue;
1609 }
1610
1611 /* Duration */
1612 call_get_duration(i, &dur);
1613 msec_dur = PJ_TIME_VAL_MSEC(dur);
1614
1615 MIN_(call_dur.min, msec_dur);
1616 MAX_(call_dur.max, msec_dur);
1617 AVG_(call_dur.avg, msec_dur);
1618
1619 /* Connect delay */
1620 if (call->connect_time.sec) {
1621 pj_time_val t = call->connect_time;
1622 PJ_TIME_VAL_SUB(t, call->start_time);
1623 msec_dur = PJ_TIME_VAL_MSEC(t);
1624 } else {
1625 msec_dur = 10;
1626 }
1627
1628 MIN_(call_pdd.min, msec_dur);
1629 MAX_(call_pdd.max, msec_dur);
1630 AVG_(call_pdd.avg, msec_dur);
1631
1632 /* RX Statistisc: */
1633
1634 /* Packets */
1635 MIN_(min_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1636 MAX_(max_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1637 AVG_(avg_stat.rx.pkt, audio->rtcp.stat.rx.pkt);
1638
1639 /* Bytes */
1640 MIN_(min_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1641 MAX_(max_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1642 AVG_(avg_stat.rx.bytes, audio->rtcp.stat.rx.bytes);
1643
1644
1645 /* Packet loss */
1646 MIN_(min_stat.rx.loss, audio->rtcp.stat.rx.loss);
1647 MAX_(max_stat.rx.loss, audio->rtcp.stat.rx.loss);
1648 AVG_(avg_stat.rx.loss, audio->rtcp.stat.rx.loss);
1649
1650 /* Packet dup */
1651 MIN_(min_stat.rx.dup, audio->rtcp.stat.rx.dup);
1652 MAX_(max_stat.rx.dup, audio->rtcp.stat.rx.dup);
1653 AVG_(avg_stat.rx.dup, audio->rtcp.stat.rx.dup);
1654
1655 /* Packet reorder */
1656 MIN_(min_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1657 MAX_(max_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1658 AVG_(avg_stat.rx.reorder, audio->rtcp.stat.rx.reorder);
1659
1660 /* Jitter */
1661 MIN_(min_stat.rx.jitter.min, audio->rtcp.stat.rx.jitter.min);
1662 MAX_(max_stat.rx.jitter.max, audio->rtcp.stat.rx.jitter.max);
1663 AVG_(avg_stat.rx.jitter.mean, audio->rtcp.stat.rx.jitter.mean);
1664
1665
1666 /* TX Statistisc: */
1667
1668 /* Packets */
1669 MIN_(min_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1670 MAX_(max_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1671 AVG_(avg_stat.tx.pkt, audio->rtcp.stat.tx.pkt);
1672
1673 /* Bytes */
1674 MIN_(min_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1675 MAX_(max_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1676 AVG_(avg_stat.tx.bytes, audio->rtcp.stat.tx.bytes);
1677
1678 /* Packet loss */
1679 MIN_(min_stat.tx.loss, audio->rtcp.stat.tx.loss);
1680 MAX_(max_stat.tx.loss, audio->rtcp.stat.tx.loss);
1681 AVG_(avg_stat.tx.loss, audio->rtcp.stat.tx.loss);
1682
1683 /* Packet dup */
1684 MIN_(min_stat.tx.dup, audio->rtcp.stat.tx.dup);
1685 MAX_(max_stat.tx.dup, audio->rtcp.stat.tx.dup);
1686 AVG_(avg_stat.tx.dup, audio->rtcp.stat.tx.dup);
1687
1688 /* Packet reorder */
1689 MIN_(min_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1690 MAX_(max_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1691 AVG_(avg_stat.tx.reorder, audio->rtcp.stat.tx.reorder);
1692
1693 /* Jitter */
1694 MIN_(min_stat.tx.jitter.min, audio->rtcp.stat.tx.jitter.min);
1695 MAX_(max_stat.tx.jitter.max, audio->rtcp.stat.tx.jitter.max);
1696 AVG_(avg_stat.tx.jitter.mean, audio->rtcp.stat.tx.jitter.mean);
1697
1698
1699 /* RTT */
1700 MIN_(min_stat.rtt.min, audio->rtcp.stat.rtt.min);
1701 MAX_(max_stat.rtt.max, audio->rtcp.stat.rtt.max);
1702 AVG_(avg_stat.rtt.mean, audio->rtcp.stat.rtt.mean);
1703
1704 ++count;
1705 }
1706
1707 if (count == 0) {
1708 puts("No active calls");
1709 return;
1710 }
1711
1712 printf("Total %d call(s) active.\n"
1713 " Average Statistics\n"
1714 " min avg max \n"
1715 " -----------------------\n"
1716 " call duration: %7d %7d %7d %s\n"
1717 " connect delay: %7d %7d %7d %s\n"
1718 " RX stat:\n"
1719 " packets: %7s %7s %7s %s\n"
1720 " payload: %7s %7s %7s %s\n"
1721 " loss: %7d %7d %7d %s\n"
1722 " percent loss: %7.3f %7.3f %7.3f %s\n"
1723 " dup: %7d %7d %7d %s\n"
1724 " reorder: %7d %7d %7d %s\n"
1725 " jitter: %7.3f %7.3f %7.3f %s\n"
1726 " TX stat:\n"
1727 " packets: %7s %7s %7s %s\n"
1728 " payload: %7s %7s %7s %s\n"
1729 " loss: %7d %7d %7d %s\n"
1730 " percent loss: %7.3f %7.3f %7.3f %s\n"
1731 " dup: %7d %7d %7d %s\n"
1732 " reorder: %7d %7d %7d %s\n"
1733 " jitter: %7.3f %7.3f %7.3f %s\n"
1734 " RTT : %7.3f %7.3f %7.3f %s\n"
1735 ,
1736 count,
1737 call_dur.min/1000, call_dur.avg/1000, call_dur.max/1000,
1738 "seconds",
1739
1740 call_pdd.min, call_pdd.avg, call_pdd.max,
1741 "ms",
1742
1743 /* rx */
1744
1745 good_number(srx_min, min_stat.rx.pkt),
1746 good_number(srx_avg, avg_stat.rx.pkt),
1747 good_number(srx_max, max_stat.rx.pkt),
1748 "packets",
1749
1750 good_number(brx_min, min_stat.rx.bytes),
1751 good_number(brx_avg, avg_stat.rx.bytes),
1752 good_number(brx_max, max_stat.rx.bytes),
1753 "bytes",
1754
1755 min_stat.rx.loss, avg_stat.rx.loss, max_stat.rx.loss,
1756 "packets",
1757
1758 min_stat.rx.loss*100.0/(min_stat.rx.pkt+min_stat.rx.loss),
1759 avg_stat.rx.loss*100.0/(avg_stat.rx.pkt+avg_stat.rx.loss),
1760 max_stat.rx.loss*100.0/(max_stat.rx.pkt+max_stat.rx.loss),
1761 "%",
1762
1763
1764 min_stat.rx.dup, avg_stat.rx.dup, max_stat.rx.dup,
1765 "packets",
1766
1767 min_stat.rx.reorder, avg_stat.rx.reorder, max_stat.rx.reorder,
1768 "packets",
1769
1770 min_stat.rx.jitter.min/1000.0,
1771 avg_stat.rx.jitter.mean/1000.0,
1772 max_stat.rx.jitter.max/1000.0,
1773 "ms",
1774
1775 /* tx */
1776
1777 good_number(stx_min, min_stat.tx.pkt),
1778 good_number(stx_avg, avg_stat.tx.pkt),
1779 good_number(stx_max, max_stat.tx.pkt),
1780 "packets",
1781
1782 good_number(btx_min, min_stat.tx.bytes),
1783 good_number(btx_avg, avg_stat.tx.bytes),
1784 good_number(btx_max, max_stat.tx.bytes),
1785 "bytes",
1786
1787 min_stat.tx.loss, avg_stat.tx.loss, max_stat.tx.loss,
1788 "packets",
1789
1790 min_stat.tx.loss*100.0/(min_stat.tx.pkt+min_stat.tx.loss),
1791 avg_stat.tx.loss*100.0/(avg_stat.tx.pkt+avg_stat.tx.loss),
1792 max_stat.tx.loss*100.0/(max_stat.tx.pkt+max_stat.tx.loss),
1793 "%",
1794
1795 min_stat.tx.dup, avg_stat.tx.dup, max_stat.tx.dup,
1796 "packets",
1797
1798 min_stat.tx.reorder, avg_stat.tx.reorder, max_stat.tx.reorder,
1799 "packets",
1800
1801 min_stat.tx.jitter.min/1000.0,
1802 avg_stat.tx.jitter.mean/1000.0,
1803 max_stat.tx.jitter.max/1000.0,
1804 "ms",
1805
1806 /* rtt */
1807 min_stat.rtt.min/1000.0,
1808 avg_stat.rtt.mean/1000.0,
1809 max_stat.rtt.max/1000.0,
1810 "ms"
1811 );
1812
1813}
1814
1815
1816#include "siprtp_report.c"
1817
1818
1819static void list_calls()
1820{
1821 unsigned i;
1822 puts("List all calls:");
1823 for (i=0; i<app.max_calls; ++i) {
1824 if (!app.call[i].inv)
1825 continue;
1826 print_call(i);
1827 }
1828}
1829
1830static void hangup_call(unsigned index)
1831{
1832 pjsip_tx_data *tdata;
1833 pj_status_t status;
1834
1835 if (app.call[index].inv == NULL)
1836 return;
1837
1838 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1839 if (status==PJ_SUCCESS && tdata!=NULL)
1840 pjsip_inv_send_msg(app.call[index].inv, tdata);
1841}
1842
1843static void hangup_all_calls()
1844{
1845 unsigned i;
1846 for (i=0; i<app.max_calls; ++i) {
1847 if (!app.call[i].inv)
1848 continue;
1849 hangup_call(i);
1850 pj_thread_sleep(app.call_gap);
1851 }
1852
1853 /* Wait until all calls are terminated */
1854 for (i=0; i<app.max_calls; ++i) {
1855 while (app.call[i].inv)
1856 pj_thread_sleep(10);
1857 }
1858}
1859
1860static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1861{
1862 char *p;
1863
1864 printf("%s (empty to cancel): ", title); fflush(stdout);
1865 if (fgets(buf, (int)len, stdin) == NULL)
1866 return PJ_FALSE;
1867
1868 /* Remove trailing newlines. */
1869 for (p=buf; ; ++p) {
1870 if (*p=='\r' || *p=='\n') *p='\0';
1871 else if (!*p) break;
1872 }
1873
1874 if (!*buf)
1875 return PJ_FALSE;
1876
1877 return PJ_TRUE;
1878}
1879
1880
1881static const char *MENU =
1882"\n"
1883"Enter menu character:\n"
1884" s Summary\n"
1885" l List all calls\n"
1886" h Hangup a call\n"
1887" H Hangup all calls\n"
1888" q Quit\n"
1889"\n";
1890
1891
1892/* Main screen menu */
1893static void console_main()
1894{
1895 char input1[10];
1896 unsigned i;
1897
1898 printf("%s", MENU);
1899
1900 for (;;) {
1901 printf(">>> "); fflush(stdout);
1902 if (fgets(input1, sizeof(input1), stdin) == NULL) {
1903 puts("EOF while reading stdin, will quit now..");
1904 break;
1905 }
1906
1907 switch (input1[0]) {
1908
1909 case 's':
1910 print_avg_stat();
1911 break;
1912
1913 case 'l':
1914 list_calls();
1915 break;
1916
1917 case 'h':
1918 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1919 break;
1920
1921 i = atoi(input1);
1922 hangup_call(i);
1923 break;
1924
1925 case 'H':
1926 hangup_all_calls();
1927 break;
1928
1929 case 'q':
1930 goto on_exit;
1931
1932 default:
1933 puts("Invalid command");
1934 printf("%s", MENU);
1935 break;
1936 }
1937
1938 fflush(stdout);
1939 }
1940
1941on_exit:
1942 hangup_all_calls();
1943}
1944
1945
1946/*****************************************************************************
1947 * Below is a simple module to log all incoming and outgoing SIP messages
1948 */
1949
1950
1951/* Notification on incoming messages */
1952static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
1953{
1954 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1955 "%s\n"
1956 "--end msg--",
1957 rdata->msg_info.len,
1958 pjsip_rx_data_get_info(rdata),
1959 rdata->pkt_info.src_name,
1960 rdata->pkt_info.src_port,
1961 rdata->msg_info.msg_buf));
1962
1963 /* Always return false, otherwise messages will not get processed! */
1964 return PJ_FALSE;
1965}
1966
1967/* Notification on outgoing messages */
1968static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
1969{
1970
1971 /* Important note:
1972 * tp_info field is only valid after outgoing messages has passed
1973 * transport layer. So don't try to access tp_info when the module
1974 * has lower priority than transport layer.
1975 */
1976
1977 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1978 "%s\n"
1979 "--end msg--",
1980 (tdata->buf.cur - tdata->buf.start),
1981 pjsip_tx_data_get_info(tdata),
1982 tdata->tp_info.dst_name,
1983 tdata->tp_info.dst_port,
1984 tdata->buf.start));
1985
1986 /* Always return success, otherwise message will not get sent! */
1987 return PJ_SUCCESS;
1988}
1989
1990/* The module instance. */
1991static pjsip_module msg_logger =
1992{
1993 NULL, NULL, /* prev, next. */
1994 { "mod-siprtp-log", 14 }, /* Name. */
1995 -1, /* Id */
1996 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1997 NULL, /* load() */
1998 NULL, /* start() */
1999 NULL, /* stop() */
2000 NULL, /* unload() */
2001 &logger_on_rx_msg, /* on_rx_request() */
2002 &logger_on_rx_msg, /* on_rx_response() */
2003 &logger_on_tx_msg, /* on_tx_request. */
2004 &logger_on_tx_msg, /* on_tx_response() */
2005 NULL, /* on_tsx_state() */
2006
2007};
2008
2009
2010
2011/*****************************************************************************
2012 * Console application custom logging:
2013 */
2014
2015
2016static FILE *log_file;
2017
2018
2019static void app_log_writer(int level, const char *buffer, int len)
2020{
2021 /* Write to both stdout and file. */
2022
2023 if (level <= app.app_log_level)
2024 pj_log_write(level, buffer, len);
2025
2026 if (log_file) {
2027 pj_size_t count = fwrite(buffer, len, 1, log_file);
2028 PJ_UNUSED_ARG(count);
2029 fflush(log_file);
2030 }
2031}
2032
2033
2034pj_status_t app_logging_init(void)
2035{
2036 /* Redirect log function to ours */
2037
2038 pj_log_set_log_func( &app_log_writer );
2039
2040 /* If output log file is desired, create the file: */
2041
2042 if (app.log_filename) {
2043 log_file = fopen(app.log_filename, "wt");
2044 if (log_file == NULL) {
2045 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
2046 app.log_filename));
2047 return -1;
2048 }
2049 }
2050
2051 return PJ_SUCCESS;
2052}
2053
2054
2055void app_logging_shutdown(void)
2056{
2057 /* Close logging file, if any: */
2058
2059 if (log_file) {
2060 fclose(log_file);
2061 log_file = NULL;
2062 }
2063}
2064
2065
2066/*
2067 * main()
2068 */
2069int main(int argc, char *argv[])
2070{
2071 unsigned i;
2072 pj_status_t status;
2073
2074 /* Must init PJLIB first */
2075 status = pj_init();
2076 if (status != PJ_SUCCESS)
2077 return 1;
2078
2079 /* Get command line options */
2080 status = init_options(argc, argv);
2081 if (status != PJ_SUCCESS)
2082 return 1;
2083
2084 /* Verify options: */
2085
2086 /* Auto-quit can not be specified for UAS */
2087 if (app.auto_quit && app.uri_to_call.slen == 0) {
2088 printf("Error: --auto-quit option only valid for outgoing "
2089 "mode (UAC) only\n");
2090 return 1;
2091 }
2092
2093 /* Init logging */
2094 status = app_logging_init();
2095 if (status != PJ_SUCCESS)
2096 return 1;
2097
2098 /* Init SIP etc */
2099 status = init_sip();
2100 if (status != PJ_SUCCESS) {
2101 app_perror(THIS_FILE, "Initialization has failed", status);
2102 destroy_sip();
2103 return 1;
2104 }
2105
2106 /* Register module to log incoming/outgoing messages */
2107 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
2108
2109 /* Init media */
2110 status = init_media();
2111 if (status != PJ_SUCCESS) {
2112 app_perror(THIS_FILE, "Media initialization failed", status);
2113 destroy_sip();
2114 return 1;
2115 }
2116
2117 /* Start worker threads */
2118#if PJ_HAS_THREADS
2119 for (i=0; i<app.thread_count; ++i) {
2120 pj_thread_create( app.pool, "app", &sip_worker_thread, NULL,
2121 0, 0, &app.sip_thread[i]);
2122 }
2123#endif
2124
2125 /* If URL is specified, then make call immediately */
2126 if (app.uri_to_call.slen) {
2127 unsigned i;
2128
2129 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
2130 app.uri_to_call.ptr));
2131
2132 for (i=0; i<app.max_calls; ++i) {
2133 status = make_call(&app.uri_to_call);
2134 if (status != PJ_SUCCESS) {
2135 app_perror(THIS_FILE, "Error making call", status);
2136 break;
2137 }
2138 pj_thread_sleep(app.call_gap);
2139 }
2140
2141 if (app.auto_quit) {
2142 /* Wait for calls to complete */
2143 while (app.uac_calls < app.max_calls)
2144 pj_thread_sleep(100);
2145 pj_thread_sleep(200);
2146 } else {
2147#if PJ_HAS_THREADS
2148 /* Start user interface loop */
2149 console_main();
2150#endif
2151 }
2152
2153 } else {
2154
2155 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
2156 app.max_calls));
2157
2158#if PJ_HAS_THREADS
2159 /* Start user interface loop */
2160 console_main();
2161#endif
2162 }
2163
2164#if !PJ_HAS_THREADS
2165 PJ_LOG(3,(THIS_FILE, "Press Ctrl-C to quit"));
2166 for (;;) {
2167 pj_time_val t = {0, 10};
2168 pjsip_endpt_handle_events(app.sip_endpt, &t);
2169 }
2170#endif
2171
2172 /* Shutting down... */
2173 destroy_sip();
2174 destroy_media();
2175
2176 if (app.pool) {
2177 pj_pool_release(app.pool);
2178 app.pool = NULL;
2179 pj_caching_pool_destroy(&app.cp);
2180 }
2181
2182 app_logging_shutdown();
2183
2184 /* Shutdown PJLIB */
2185 pj_shutdown();
2186
2187 return 0;
2188}
2189