blob: 6d1b1dcc54d67a2b9eb07cde682de0393266212c [file] [log] [blame]
Benny Prijono60b980e2006-04-03 22:41:26 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20
Benny Prijonobf13fee2006-04-20 11:13:32 +000021
22
23/* Usage */
24static const char *USAGE =
25" PURPOSE: \n"
26" This program establishes SIP INVITE session and media, and calculate \n"
27" the media quality (packet lost, jitter, rtt, etc.). Unlike normal \n"
28" pjmedia applications, this program bypasses all pjmedia stream \n"
29" framework and transmit encoded RTP packets manually using own thread. \n"
30"\n"
31" USAGE:\n"
32" siprtp [options] => to start in server mode\n"
33" siprtp [options] URL => to start in client mode\n"
34"\n"
35" Program options:\n"
36" --count=N, -c Set number of calls to create (default:1) \n"
37"\n"
38" Address and ports options:\n"
39" --local-port=PORT,-p Set local SIP port (default: 5060)\n"
40" --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
41" --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
42" try to determine local IP address from hostname)\n"
43"\n"
44" Logging Options:\n"
45" --log-level=N, -l Set log verbosity level (default=5)\n"
46" --app-log-level=N Set app screen log verbosity (default=3)\n"
47" --log-file=FILE Write log to file FILE\n"
48"\n"
49" Codec Options:\n"
50" --a-pt=PT Set audio payload type to PT (default=0)\n"
51" --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
52" --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
53" --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
54" --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
55;
56
57
Benny Prijono60b980e2006-04-03 22:41:26 +000058/* Include all headers. */
59#include <pjsip.h>
60#include <pjmedia.h>
61#include <pjmedia-codec.h>
62#include <pjsip_ua.h>
63#include <pjsip_simple.h>
64#include <pjlib-util.h>
65#include <pjlib.h>
66
67#include <stdlib.h>
68
Benny Prijono9a0eab52006-04-04 19:43:24 +000069
70#if PJ_HAS_HIGH_RES_TIMER==0
71# error "High resolution timer is needed for this sample"
72#endif
73
Benny Prijono60b980e2006-04-03 22:41:26 +000074#define THIS_FILE "siprtp.c"
75#define MAX_CALLS 1024
76#define RTP_START_PORT 44100
77
78
Benny Prijono4adcb912006-04-04 13:12:38 +000079/* Codec descriptor: */
80struct codec
81{
82 unsigned pt;
83 char* name;
84 unsigned clock_rate;
85 unsigned bit_rate;
86 unsigned ptime;
87 char* description;
88};
89
90
Benny Prijono60b980e2006-04-03 22:41:26 +000091/* A bidirectional media stream */
92struct media_stream
93{
94 /* Static: */
95 pj_uint16_t port; /* RTP port (RTCP is +1) */
96
97 /* Current stream info: */
98 pjmedia_stream_info si; /* Current stream info. */
99
100 /* More info: */
101 unsigned clock_rate; /* clock rate */
102 unsigned samples_per_frame; /* samples per frame */
103 unsigned bytes_per_frame; /* frame size. */
104
105 /* Sockets: */
106 pj_sock_t rtp_sock; /* RTP socket. */
107 pj_sock_t rtcp_sock; /* RTCP socket. */
108
109 /* RTP session: */
110 pjmedia_rtp_session out_sess; /* outgoing RTP session */
111 pjmedia_rtp_session in_sess; /* incoming RTP session */
112
113 /* RTCP stats: */
114 pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
Benny Prijono4adcb912006-04-04 13:12:38 +0000115
Benny Prijono60b980e2006-04-03 22:41:26 +0000116 /* Thread: */
117 pj_bool_t thread_quit_flag; /* worker thread quit flag */
118 pj_thread_t *thread; /* RTP/RTCP worker thread */
119};
120
121
122struct call
123{
124 unsigned index;
125 pjsip_inv_session *inv;
126 unsigned media_count;
127 struct media_stream media[2];
Benny Prijono4adcb912006-04-04 13:12:38 +0000128 pj_time_val start_time;
129 pj_time_val response_time;
130 pj_time_val connect_time;
Benny Prijono60b980e2006-04-03 22:41:26 +0000131};
132
133
134static struct app
135{
136 unsigned max_calls;
137 unsigned thread_count;
138 int sip_port;
139 int rtp_start_port;
140 char *local_addr;
141 pj_str_t local_uri;
142 pj_str_t local_contact;
Benny Prijono4adcb912006-04-04 13:12:38 +0000143
144 int app_log_level;
145 int log_level;
146 char *log_filename;
147
148 struct codec audio_codec;
Benny Prijono60b980e2006-04-03 22:41:26 +0000149
150 pj_str_t uri_to_call;
151
152 pj_caching_pool cp;
153 pj_pool_t *pool;
154
155 pjsip_endpoint *sip_endpt;
156 pj_bool_t thread_quit;
157 pj_thread_t *thread[1];
158
159 pjmedia_endpt *med_endpt;
160 struct call call[MAX_CALLS];
161} app;
162
163
164
165/*
166 * Prototypes:
167 */
168
169/* Callback to be called when SDP negotiation is done in the call: */
170static void call_on_media_update( pjsip_inv_session *inv,
171 pj_status_t status);
172
173/* Callback to be called when invite session's state has changed: */
174static void call_on_state_changed( pjsip_inv_session *inv,
175 pjsip_event *e);
176
177/* Callback to be called when dialog has forked: */
178static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
179
180/* Callback to be called to handle incoming requests outside dialogs: */
181static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
182
183/* Worker thread prototype */
184static int worker_thread(void *arg);
185
186/* Create SDP for call */
187static pj_status_t create_sdp( pj_pool_t *pool,
188 struct call *call,
189 pjmedia_sdp_session **p_sdp);
190
191/* Destroy the call's media */
192static void destroy_call_media(unsigned call_index);
193
194/* Display error */
195static void app_perror(const char *sender, const char *title,
196 pj_status_t status);
197
Benny Prijonod7a13f12006-04-05 19:08:16 +0000198/* Print call */
199static void print_call(int call_index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000200
201
Benny Prijono60b980e2006-04-03 22:41:26 +0000202/* This is a PJSIP module to be registered by application to handle
203 * incoming requests outside any dialogs/transactions. The main purpose
204 * here is to handle incoming INVITE request message, where we will
205 * create a dialog and INVITE session for it.
206 */
207static pjsip_module mod_siprtp =
208{
209 NULL, NULL, /* prev, next. */
210 { "mod-siprtpapp", 13 }, /* Name. */
211 -1, /* Id */
212 PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
213 NULL, /* load() */
214 NULL, /* start() */
215 NULL, /* stop() */
216 NULL, /* unload() */
217 &on_rx_request, /* on_rx_request() */
218 NULL, /* on_rx_response() */
219 NULL, /* on_tx_request. */
220 NULL, /* on_tx_response() */
221 NULL, /* on_tsx_state() */
222};
223
224
Benny Prijono4adcb912006-04-04 13:12:38 +0000225/* Codec constants */
226struct codec audio_codecs[] =
227{
228 { 0, "pcmu", 8000, 64000, 20, "G.711 ULaw" },
229 { 3, "gsm", 8000, 13200, 20, "GSM" },
230 { 4, "g723", 8000, 6400, 30, "G.723.1" },
231 { 8, "pcma", 8000, 64000, 20, "G.711 ALaw" },
232 { 18, "g729", 8000, 8000, 20, "G.729" },
233};
234
235
Benny Prijono60b980e2006-04-03 22:41:26 +0000236/*
237 * Init SIP stack
238 */
239static pj_status_t init_sip()
240{
Benny Prijono60b980e2006-04-03 22:41:26 +0000241 pj_status_t status;
242
243 /* init PJLIB-UTIL: */
244 status = pjlib_util_init();
245 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
246
247 /* Must create a pool factory before we can allocate any memory. */
248 pj_caching_pool_init(&app.cp, &pj_pool_factory_default_policy, 0);
249
250 /* Create application pool for misc. */
251 app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
252
253 /* Create global endpoint: */
254 {
255 const pj_str_t *hostname;
256 const char *endpt_name;
257
258 /* Endpoint MUST be assigned a globally unique name.
259 * The name will be used as the hostname in Warning header.
260 */
261
262 /* For this implementation, we'll use hostname for simplicity */
263 hostname = pj_gethostname();
264 endpt_name = hostname->ptr;
265
266 /* Create the endpoint: */
267
268 status = pjsip_endpt_create(&app.cp.factory, endpt_name,
269 &app.sip_endpt);
270 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
271 }
272
273
274 /* Add UDP transport. */
275 {
276 pj_sockaddr_in addr;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000277 pjsip_host_port addrname;
Benny Prijono60b980e2006-04-03 22:41:26 +0000278
Benny Prijono49ce9a72006-04-05 16:56:19 +0000279 pj_memset(&addr, 0, sizeof(addr));
Benny Prijono60b980e2006-04-03 22:41:26 +0000280 addr.sin_family = PJ_AF_INET;
281 addr.sin_addr.s_addr = 0;
282 addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
283
Benny Prijono49ce9a72006-04-05 16:56:19 +0000284 if (app.local_addr) {
285 addrname.host = pj_str(app.local_addr);
286 addrname.port = app.sip_port;
287 }
288
289 status = pjsip_udp_transport_start( app.sip_endpt, &addr,
290 (app.local_addr ? &addrname:NULL),
Benny Prijono60b980e2006-04-03 22:41:26 +0000291 1, NULL);
Benny Prijono49ce9a72006-04-05 16:56:19 +0000292 if (status != PJ_SUCCESS) {
293 app_perror(THIS_FILE, "Unable to start UDP transport", status);
Benny Prijono60b980e2006-04-03 22:41:26 +0000294 return status;
Benny Prijono49ce9a72006-04-05 16:56:19 +0000295 }
Benny Prijono60b980e2006-04-03 22:41:26 +0000296 }
297
298 /*
299 * Init transaction layer.
300 * This will create/initialize transaction hash tables etc.
301 */
302 status = pjsip_tsx_layer_init_module(app.sip_endpt);
303 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
304
305 /* Initialize UA layer. */
306 status = pjsip_ua_init_module( app.sip_endpt, NULL );
307 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
308
309 /* Init invite session module. */
310 {
311 pjsip_inv_callback inv_cb;
312
313 /* Init the callback for INVITE session: */
314 pj_memset(&inv_cb, 0, sizeof(inv_cb));
315 inv_cb.on_state_changed = &call_on_state_changed;
316 inv_cb.on_new_session = &call_on_forked;
317 inv_cb.on_media_update = &call_on_media_update;
318
319 /* Initialize invite session module: */
320 status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
321 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
322 }
323
324 /* Register our module to receive incoming requests. */
325 status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
326 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
327
328
Benny Prijono60b980e2006-04-03 22:41:26 +0000329 /* Done */
330 return PJ_SUCCESS;
331}
332
333
334/*
335 * Destroy SIP
336 */
337static void destroy_sip()
338{
339 unsigned i;
340
341 app.thread_quit = 1;
342 for (i=0; i<app.thread_count; ++i) {
343 if (app.thread[i]) {
344 pj_thread_join(app.thread[i]);
345 pj_thread_destroy(app.thread[i]);
346 app.thread[i] = NULL;
347 }
348 }
349
350 if (app.sip_endpt) {
351 pjsip_endpt_destroy(app.sip_endpt);
352 app.sip_endpt = NULL;
353 }
354
355 if (app.pool) {
356 pj_pool_release(app.pool);
357 app.pool = NULL;
358 pj_caching_pool_destroy(&app.cp);
359 }
360}
361
362
363/*
364 * Init media stack.
365 */
366static pj_status_t init_media()
367{
368 pj_ioqueue_t *ioqueue;
369 unsigned i, count;
370 pj_uint16_t rtp_port;
371 pj_str_t temp;
372 pj_sockaddr_in addr;
373 pj_status_t status;
374
375
376 /* Get the ioqueue from the SIP endpoint */
377 ioqueue = pjsip_endpt_get_ioqueue(app.sip_endpt);
378
379
380 /* Initialize media endpoint so that at least error subsystem is properly
381 * initialized.
382 */
383 status = pjmedia_endpt_create(&app.cp.factory, ioqueue, 1,
384 &app.med_endpt);
385 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
386
387
388 /* Determine address to bind socket */
389 pj_memset(&addr, 0, sizeof(addr));
390 addr.sin_family = PJ_AF_INET;
391 i = pj_inet_aton(pj_cstr(&temp, app.local_addr), &addr.sin_addr);
392 if (i == 0) {
393 PJ_LOG(3,(THIS_FILE,
394 "Error: invalid local address %s (expecting IP)",
395 app.local_addr));
396 return -1;
397 }
398
Benny Prijono60b980e2006-04-03 22:41:26 +0000399 /* RTP port counter */
400 rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
401
402
403 /* Init media sockets. */
404 for (i=0, count=0; i<app.max_calls; ++i, ++count) {
405
406 int retry;
407
408 app.call[i].index = i;
409
410 /* Repeat binding media socket to next port when fails to bind
411 * to current port number.
412 */
413 retry = 0;
414 do {
415 struct media_stream *m = &app.call[i].media[0];
416
417 ++retry;
418 rtp_port += 2;
419 m->port = rtp_port;
420
421 /* Create and bind RTP socket */
422 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0,
423 &m->rtp_sock);
424 if (status != PJ_SUCCESS)
425 goto on_error;
426
427 addr.sin_port = pj_htons(rtp_port);
428 status = pj_sock_bind(m->rtp_sock, &addr, sizeof(addr));
429 if (status != PJ_SUCCESS) {
430 pj_sock_close(m->rtp_sock), m->rtp_sock=0;
431 continue;
432 }
433
434
435 /* Create and bind RTCP socket */
436 status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0,
437 &m->rtcp_sock);
438 if (status != PJ_SUCCESS)
439 goto on_error;
440
441 addr.sin_port = pj_htons((pj_uint16_t)(rtp_port+1));
442 status = pj_sock_bind(m->rtcp_sock, &addr, sizeof(addr));
443 if (status != PJ_SUCCESS) {
444 pj_sock_close(m->rtp_sock), m->rtp_sock=0;
445 pj_sock_close(m->rtcp_sock), m->rtcp_sock=0;
446 continue;
447 }
448
449 } while (status != PJ_SUCCESS && retry < 100);
450
451 if (status != PJ_SUCCESS)
452 goto on_error;
453
454 }
455
456 /* Done */
457 return PJ_SUCCESS;
458
459on_error:
460 for (i=0; i<count; ++i) {
461 struct media_stream *m = &app.call[i].media[0];
462
463 pj_sock_close(m->rtp_sock), m->rtp_sock=0;
464 pj_sock_close(m->rtcp_sock), m->rtcp_sock=0;
465 }
466
467 return status;
468}
469
470
471/*
472 * Destroy media.
473 */
474static void destroy_media()
475{
476 unsigned i;
477
478 for (i=0; i<app.max_calls; ++i) {
479 struct media_stream *m = &app.call[i].media[0];
480
481 if (m->rtp_sock)
482 pj_sock_close(m->rtp_sock), m->rtp_sock = 0;
483
484 if (m->rtcp_sock)
485 pj_sock_close(m->rtcp_sock), m->rtcp_sock = 0;
486 }
487
488 if (app.med_endpt) {
489 pjmedia_endpt_destroy(app.med_endpt);
490 app.med_endpt = NULL;
491 }
492}
493
494
495/*
496 * Make outgoing call.
497 */
498static pj_status_t make_call(const pj_str_t *dst_uri)
499{
500 unsigned i;
501 struct call *call;
502 pjsip_dialog *dlg;
503 pjmedia_sdp_session *sdp;
504 pjsip_tx_data *tdata;
505 pj_status_t status;
506
507
508 /* Find unused call slot */
509 for (i=0; i<app.max_calls; ++i) {
510 if (app.call[i].inv == NULL)
511 break;
512 }
513
514 if (i == app.max_calls)
515 return PJ_ETOOMANY;
516
517 call = &app.call[i];
518
519 /* Create UAC dialog */
520 status = pjsip_dlg_create_uac( pjsip_ua_instance(),
521 &app.local_uri, /* local URI */
522 &app.local_contact, /* local Contact */
523 dst_uri, /* remote URI */
524 dst_uri, /* remote target */
525 &dlg); /* dialog */
526 if (status != PJ_SUCCESS)
527 return status;
528
529 /* Create SDP */
530 create_sdp( dlg->pool, call, &sdp);
531
532 /* Create the INVITE session. */
533 status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
534 if (status != PJ_SUCCESS) {
535 pjsip_dlg_terminate(dlg);
536 return status;
537 }
538
539
540 /* Attach call data to invite session */
541 call->inv->mod_data[mod_siprtp.id] = call;
542
Benny Prijono4adcb912006-04-04 13:12:38 +0000543 /* Mark start of call */
544 pj_gettimeofday(&call->start_time);
545
Benny Prijono60b980e2006-04-03 22:41:26 +0000546
547 /* Create initial INVITE request.
548 * This INVITE request will contain a perfectly good request and
549 * an SDP body as well.
550 */
551 status = pjsip_inv_invite(call->inv, &tdata);
552 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
553
554
555 /* Send initial INVITE request.
556 * From now on, the invite session's state will be reported to us
557 * via the invite session callbacks.
558 */
559 status = pjsip_inv_send_msg(call->inv, tdata);
560 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
561
562
563 return PJ_SUCCESS;
564}
565
566
567/*
568 * Receive incoming call
569 */
570static void process_incoming_call(pjsip_rx_data *rdata)
571{
572 unsigned i;
573 struct call *call;
574 pjsip_dialog *dlg;
575 pjmedia_sdp_session *sdp;
576 pjsip_tx_data *tdata;
577 pj_status_t status;
578
579 /* Find free call slot */
580 for (i=0; i<app.max_calls; ++i) {
581 if (app.call[i].inv == NULL)
582 break;
583 }
584
585 if (i == app.max_calls) {
586 const pj_str_t reason = pj_str("Too many calls");
587 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
588 500, &reason,
589 NULL, NULL);
590 return;
591 }
592
593 call = &app.call[i];
594
595 /* Create UAS dialog */
596 status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
597 &app.local_contact, &dlg);
598 if (status != PJ_SUCCESS) {
599 const pj_str_t reason = pj_str("Unable to create dialog");
600 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
601 500, &reason,
602 NULL, NULL);
603 return;
604 }
605
606 /* Create SDP */
607 create_sdp( dlg->pool, call, &sdp);
608
609 /* Create UAS invite session */
610 status = pjsip_inv_create_uas( dlg, rdata, sdp, 0, &call->inv);
611 if (status != PJ_SUCCESS) {
Benny Prijono4adcb912006-04-04 13:12:38 +0000612 pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata);
613 pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
Benny Prijono60b980e2006-04-03 22:41:26 +0000614 return;
615 }
616
Benny Prijono4adcb912006-04-04 13:12:38 +0000617
Benny Prijono60b980e2006-04-03 22:41:26 +0000618 /* Attach call data to invite session */
619 call->inv->mod_data[mod_siprtp.id] = call;
620
Benny Prijono4adcb912006-04-04 13:12:38 +0000621 /* Mark start of call */
622 pj_gettimeofday(&call->start_time);
623
624
625
Benny Prijono60b980e2006-04-03 22:41:26 +0000626 /* Create 200 response .*/
627 status = pjsip_inv_initial_answer(call->inv, rdata, 200,
628 NULL, NULL, &tdata);
Benny Prijono4adcb912006-04-04 13:12:38 +0000629 if (status != PJ_SUCCESS) {
630 status = pjsip_inv_initial_answer(call->inv, rdata,
631 PJSIP_SC_NOT_ACCEPTABLE,
632 NULL, NULL, &tdata);
633 if (status == PJ_SUCCESS)
634 pjsip_inv_send_msg(call->inv, tdata);
635 else
636 pjsip_inv_terminate(call->inv, 500, PJ_FALSE);
637 return;
638 }
639
Benny Prijono60b980e2006-04-03 22:41:26 +0000640
641 /* Send the 200 response. */
642 status = pjsip_inv_send_msg(call->inv, tdata);
643 PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
644
645
646 /* Done */
647}
648
649
650/* Callback to be called when dialog has forked: */
651static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e)
652{
653 PJ_UNUSED_ARG(inv);
654 PJ_UNUSED_ARG(e);
655
656 PJ_TODO( HANDLE_FORKING );
657}
658
659
660/* Callback to be called to handle incoming requests outside dialogs: */
661static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
662{
Benny Prijono4adcb912006-04-04 13:12:38 +0000663 /* Ignore strandled ACKs (must not send respone */
664 if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
665 return PJ_FALSE;
666
Benny Prijono60b980e2006-04-03 22:41:26 +0000667 /* Respond (statelessly) any non-INVITE requests with 500 */
668 if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) {
669 pj_str_t reason = pj_str("Unsupported Operation");
670 pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
671 500, &reason,
672 NULL, NULL);
673 return PJ_TRUE;
674 }
675
676 /* Handle incoming INVITE */
677 process_incoming_call(rdata);
678
679 /* Done */
680 return PJ_TRUE;
681}
682
683
684/* Callback to be called when invite session's state has changed: */
685static void call_on_state_changed( pjsip_inv_session *inv,
686 pjsip_event *e)
687{
Benny Prijono4adcb912006-04-04 13:12:38 +0000688 struct call *call = inv->mod_data[mod_siprtp.id];
689
Benny Prijono60b980e2006-04-03 22:41:26 +0000690 PJ_UNUSED_ARG(e);
691
Benny Prijono4adcb912006-04-04 13:12:38 +0000692 if (!call)
693 return;
Benny Prijono60b980e2006-04-03 22:41:26 +0000694
Benny Prijono4adcb912006-04-04 13:12:38 +0000695 if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
696
697 pj_time_val null_time = {0, 0};
Benny Prijono60b980e2006-04-03 22:41:26 +0000698
Benny Prijonod7a13f12006-04-05 19:08:16 +0000699 PJ_LOG(3,(THIS_FILE, "Call #%d disconnected. Reason=%s",
700 call->index,
701 pjsip_get_status_text(inv->cause)->ptr));
702 PJ_LOG(3,(THIS_FILE, "Call #%d statistics:", call->index));
703 print_call(call->index);
704
705
Benny Prijono60b980e2006-04-03 22:41:26 +0000706 call->inv = NULL;
707 inv->mod_data[mod_siprtp.id] = NULL;
708
709 destroy_call_media(call->index);
Benny Prijono4adcb912006-04-04 13:12:38 +0000710
711 call->start_time = null_time;
712 call->response_time = null_time;
713 call->connect_time = null_time;
714
Benny Prijono4adcb912006-04-04 13:12:38 +0000715
716 } else if (inv->state == PJSIP_INV_STATE_CONFIRMED) {
717
718 pj_time_val t;
719
720 pj_gettimeofday(&call->connect_time);
721 if (call->response_time.sec == 0)
722 call->response_time = call->connect_time;
723
724 t = call->connect_time;
725 PJ_TIME_VAL_SUB(t, call->start_time);
726
727 PJ_LOG(3,(THIS_FILE, "Call #%d connected in %d ms", call->index,
728 PJ_TIME_VAL_MSEC(t)));
729
730 } else if ( inv->state == PJSIP_INV_STATE_EARLY ||
731 inv->state == PJSIP_INV_STATE_CONNECTING) {
732
733 if (call->response_time.sec == 0)
734 pj_gettimeofday(&call->response_time);
735
Benny Prijono60b980e2006-04-03 22:41:26 +0000736 }
737}
738
739
740/* Utility */
741static void app_perror(const char *sender, const char *title,
742 pj_status_t status)
743{
744 char errmsg[PJ_ERR_MSG_SIZE];
745
746 pj_strerror(status, errmsg, sizeof(errmsg));
747 PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
748}
749
750
751/* Worker thread */
752static int worker_thread(void *arg)
753{
754 PJ_UNUSED_ARG(arg);
755
756 while (!app.thread_quit) {
757 pj_time_val timeout = {0, 10};
758 pjsip_endpt_handle_events(app.sip_endpt, &timeout);
759 }
760
761 return 0;
762}
763
764
Benny Prijono60b980e2006-04-03 22:41:26 +0000765/* Init application options */
766static pj_status_t init_options(int argc, char *argv[])
767{
768 static char ip_addr[32];
769 static char local_uri[64];
770
Benny Prijono4adcb912006-04-04 13:12:38 +0000771 enum { OPT_START,
772 OPT_APP_LOG_LEVEL, OPT_LOG_FILE,
773 OPT_A_PT, OPT_A_NAME, OPT_A_CLOCK, OPT_A_BITRATE, OPT_A_PTIME };
774
Benny Prijono60b980e2006-04-03 22:41:26 +0000775 struct pj_getopt_option long_options[] = {
Benny Prijono4adcb912006-04-04 13:12:38 +0000776 { "count", 1, 0, 'c' },
777 { "local-port", 1, 0, 'p' },
778 { "rtp-port", 1, 0, 'r' },
779 { "ip-addr", 1, 0, 'i' },
780
781 { "log-level", 1, 0, 'l' },
782 { "app-log-level", 1, 0, OPT_APP_LOG_LEVEL },
783 { "log-file", 1, 0, OPT_LOG_FILE },
784 { "a-pt", 1, 0, OPT_A_PT },
785 { "a-name", 1, 0, OPT_A_NAME },
786 { "a-clock", 1, 0, OPT_A_CLOCK },
787 { "a-bitrate", 1, 0, OPT_A_BITRATE },
788 { "a-ptime", 1, 0, OPT_A_PTIME },
789
Benny Prijono60b980e2006-04-03 22:41:26 +0000790 { NULL, 0, 0, 0 },
791 };
792 int c;
793 int option_index;
794
795 /* Get local IP address for the default IP address */
796 {
797 const pj_str_t *hostname;
798 pj_sockaddr_in tmp_addr;
799 char *addr;
800
801 hostname = pj_gethostname();
802 pj_sockaddr_in_init(&tmp_addr, hostname, 0);
803 addr = pj_inet_ntoa(tmp_addr.sin_addr);
804 pj_ansi_strcpy(ip_addr, addr);
805 }
806
Benny Prijono4adcb912006-04-04 13:12:38 +0000807 /* Init defaults */
Benny Prijono60b980e2006-04-03 22:41:26 +0000808 app.max_calls = 1;
809 app.thread_count = 1;
810 app.sip_port = 5060;
811 app.rtp_start_port = 4000;
812 app.local_addr = ip_addr;
Benny Prijono4adcb912006-04-04 13:12:38 +0000813 app.log_level = 5;
814 app.app_log_level = 3;
815 app.log_filename = NULL;
816
817 /* Default codecs: */
818 app.audio_codec = audio_codecs[0];
Benny Prijono60b980e2006-04-03 22:41:26 +0000819
820 /* Parse options */
821 pj_optind = 0;
Benny Prijono4adcb912006-04-04 13:12:38 +0000822 while((c=pj_getopt_long(argc,argv, "c:p:r:i:l:",
Benny Prijono60b980e2006-04-03 22:41:26 +0000823 long_options, &option_index))!=-1)
824 {
825 switch (c) {
826 case 'c':
827 app.max_calls = atoi(pj_optarg);
828 if (app.max_calls < 0 || app.max_calls > MAX_CALLS) {
829 PJ_LOG(3,(THIS_FILE, "Invalid max calls value %s", pj_optarg));
830 return 1;
831 }
832 break;
833 case 'p':
834 app.sip_port = atoi(pj_optarg);
835 break;
836 case 'r':
837 app.rtp_start_port = atoi(pj_optarg);
838 break;
839 case 'i':
840 app.local_addr = pj_optarg;
841 break;
Benny Prijono4adcb912006-04-04 13:12:38 +0000842
843 case 'l':
844 app.log_level = atoi(pj_optarg);
845 break;
846 case OPT_APP_LOG_LEVEL:
847 app.app_log_level = atoi(pj_optarg);
848 break;
849 case OPT_LOG_FILE:
850 app.log_filename = pj_optarg;
851 break;
852
853 case OPT_A_PT:
854 app.audio_codec.pt = atoi(pj_optarg);
855 break;
856 case OPT_A_NAME:
857 app.audio_codec.name = pj_optarg;
858 break;
859 case OPT_A_CLOCK:
860 app.audio_codec.clock_rate = atoi(pj_optarg);
861 break;
862 case OPT_A_BITRATE:
863 app.audio_codec.bit_rate = atoi(pj_optarg);
864 break;
865 case OPT_A_PTIME:
866 app.audio_codec.ptime = atoi(pj_optarg);
867 break;
868
Benny Prijono60b980e2006-04-03 22:41:26 +0000869 default:
870 puts(USAGE);
871 return 1;
872 }
873 }
874
875 /* Check if URL is specified */
876 if (pj_optind < argc)
877 app.uri_to_call = pj_str(argv[pj_optind]);
878
879 /* Build local URI and contact */
880 pj_ansi_sprintf( local_uri, "sip:%s:%d", app.local_addr, app.sip_port);
881 app.local_uri = pj_str(local_uri);
882 app.local_contact = app.local_uri;
883
884
885 return PJ_SUCCESS;
886}
887
888
Benny Prijono4adcb912006-04-04 13:12:38 +0000889/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +0000890 * MEDIA STUFFS
891 */
892
893/*
894 * Create SDP session for a call.
895 */
896static pj_status_t create_sdp( pj_pool_t *pool,
897 struct call *call,
898 pjmedia_sdp_session **p_sdp)
899{
900 pj_time_val tv;
901 pjmedia_sdp_session *sdp;
902 pjmedia_sdp_media *m;
903 pjmedia_sdp_attr *attr;
904 struct media_stream *audio = &call->media[0];
905
906 PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
907
908
909 /* Create and initialize basic SDP session */
910 sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session));
911
912 pj_gettimeofday(&tv);
913 sdp->origin.user = pj_str("pjsip-siprtp");
914 sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
915 sdp->origin.net_type = pj_str("IN");
916 sdp->origin.addr_type = pj_str("IP4");
917 sdp->origin.addr = *pj_gethostname();
918 sdp->name = pj_str("pjsip");
919
920 /* Since we only support one media stream at present, put the
921 * SDP connection line in the session level.
922 */
923 sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
924 sdp->conn->net_type = pj_str("IN");
925 sdp->conn->addr_type = pj_str("IP4");
926 sdp->conn->addr = pj_str(app.local_addr);
927
928
929 /* SDP time and attributes. */
930 sdp->time.start = sdp->time.stop = 0;
931 sdp->attr_count = 0;
932
933 /* Create media stream 0: */
934
935 sdp->media_count = 1;
936 m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
937 sdp->media[0] = m;
938
939 /* Standard media info: */
940 m->desc.media = pj_str("audio");
941 m->desc.port = audio->port;
942 m->desc.port_count = 1;
943 m->desc.transport = pj_str("RTP/AVP");
944
945 /* Add format and rtpmap for each codec. */
946 m->desc.fmt_count = 1;
947 m->attr_count = 0;
948
949 {
950 pjmedia_sdp_rtpmap rtpmap;
951 pjmedia_sdp_attr *attr;
Benny Prijono4adcb912006-04-04 13:12:38 +0000952 char ptstr[10];
Benny Prijono60b980e2006-04-03 22:41:26 +0000953
Benny Prijono4adcb912006-04-04 13:12:38 +0000954 sprintf(ptstr, "%d", app.audio_codec.pt);
955 pj_strdup2(pool, &m->desc.fmt[0], ptstr);
956 rtpmap.pt = m->desc.fmt[0];
957 rtpmap.clock_rate = app.audio_codec.clock_rate;
958 rtpmap.enc_name = pj_str(app.audio_codec.name);
Benny Prijono60b980e2006-04-03 22:41:26 +0000959 rtpmap.param.slen = 0;
960
961 pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
962 m->attr[m->attr_count++] = attr;
963 }
964
965 /* Add sendrecv attribute. */
966 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
967 attr->name = pj_str("sendrecv");
968 m->attr[m->attr_count++] = attr;
969
970#if 1
971 /*
972 * Add support telephony event
973 */
Benny Prijono4adcb912006-04-04 13:12:38 +0000974 m->desc.fmt[m->desc.fmt_count++] = pj_str("121");
Benny Prijono60b980e2006-04-03 22:41:26 +0000975 /* Add rtpmap. */
976 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
977 attr->name = pj_str("rtpmap");
Benny Prijono4adcb912006-04-04 13:12:38 +0000978 attr->value = pj_str(":121 telephone-event/8000");
Benny Prijono60b980e2006-04-03 22:41:26 +0000979 m->attr[m->attr_count++] = attr;
980 /* Add fmtp */
981 attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr));
982 attr->name = pj_str("fmtp");
Benny Prijono4adcb912006-04-04 13:12:38 +0000983 attr->value = pj_str(":121 0-15");
Benny Prijono60b980e2006-04-03 22:41:26 +0000984 m->attr[m->attr_count++] = attr;
985#endif
986
987 /* Done */
988 *p_sdp = sdp;
989
990 return PJ_SUCCESS;
991}
992
993
Benny Prijono4adcb912006-04-04 13:12:38 +0000994/*
995 * Media thread
996 *
997 * This is the thread to send and receive both RTP and RTCP packets.
998 */
Benny Prijono60b980e2006-04-03 22:41:26 +0000999static int media_thread(void *arg)
1000{
Benny Prijono14b7b662006-04-07 15:01:51 +00001001 enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
Benny Prijono60b980e2006-04-03 22:41:26 +00001002 struct media_stream *strm = arg;
1003 char packet[1500];
Benny Prijono9a0eab52006-04-04 19:43:24 +00001004 unsigned msec_interval;
1005 pj_timestamp freq, next_rtp, next_rtcp;
Benny Prijono60b980e2006-04-03 22:41:26 +00001006
Benny Prijono9a0eab52006-04-04 19:43:24 +00001007 msec_interval = strm->samples_per_frame * 1000 / strm->clock_rate;
1008 pj_get_timestamp_freq(&freq);
1009
1010 pj_get_timestamp(&next_rtp);
1011 next_rtp.u64 += (freq.u64 * msec_interval / 1000);
Benny Prijono60b980e2006-04-03 22:41:26 +00001012
1013 next_rtcp = next_rtp;
Benny Prijono14b7b662006-04-07 15:01:51 +00001014 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) / 1000);
Benny Prijono60b980e2006-04-03 22:41:26 +00001015
1016
1017 while (!strm->thread_quit_flag) {
1018 pj_fd_set_t set;
Benny Prijono9a0eab52006-04-04 19:43:24 +00001019 pj_timestamp now, lesser;
1020 pj_time_val timeout;
Benny Prijono60b980e2006-04-03 22:41:26 +00001021 int rc;
1022
1023 /* Determine how long to sleep */
Benny Prijono9a0eab52006-04-04 19:43:24 +00001024 if (next_rtp.u64 < next_rtcp.u64)
Benny Prijono60b980e2006-04-03 22:41:26 +00001025 lesser = next_rtp;
1026 else
1027 lesser = next_rtcp;
1028
Benny Prijono9a0eab52006-04-04 19:43:24 +00001029 pj_get_timestamp(&now);
1030 if (lesser.u64 <= now.u64) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001031 timeout.sec = timeout.msec = 0;
Benny Prijono9a0eab52006-04-04 19:43:24 +00001032 //printf("immediate "); fflush(stdout);
1033 } else {
1034 pj_uint64_t tick_delay;
1035 tick_delay = lesser.u64 - now.u64;
1036 timeout.sec = 0;
1037 timeout.msec = (pj_uint32_t)(tick_delay * 1000 / freq.u64);
1038 pj_time_val_normalize(&timeout);
1039
1040 //printf("%d:%03d ", timeout.sec, timeout.msec); fflush(stdout);
Benny Prijono60b980e2006-04-03 22:41:26 +00001041 }
1042
1043 PJ_FD_ZERO(&set);
1044 PJ_FD_SET(strm->rtp_sock, &set);
1045 PJ_FD_SET(strm->rtcp_sock, &set);
1046
1047 rc = pj_sock_select(FD_SETSIZE, &set, NULL, NULL, &timeout);
1048
Benny Prijono9a0eab52006-04-04 19:43:24 +00001049 if (rc > 0 && PJ_FD_ISSET(strm->rtp_sock, &set)) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001050
1051 /*
1052 * Process incoming RTP packet.
1053 */
1054 pj_status_t status;
1055 pj_ssize_t size;
1056 const pjmedia_rtp_hdr *hdr;
1057 const void *payload;
1058 unsigned payload_len;
1059
1060 size = sizeof(packet);
1061 status = pj_sock_recv(strm->rtp_sock, packet, &size, 0);
1062 if (status != PJ_SUCCESS) {
1063 app_perror(THIS_FILE, "RTP recv() error", status);
1064 continue;
1065 }
1066
Benny Prijono4adcb912006-04-04 13:12:38 +00001067
Benny Prijono60b980e2006-04-03 22:41:26 +00001068 /* Decode RTP packet. */
1069 status = pjmedia_rtp_decode_rtp(&strm->in_sess,
1070 packet, size,
1071 &hdr,
1072 &payload, &payload_len);
1073 if (status != PJ_SUCCESS) {
1074 app_perror(THIS_FILE, "RTP decode error", status);
Benny Prijono60b980e2006-04-03 22:41:26 +00001075 continue;
1076 }
1077
1078 /* Update the RTCP session. */
1079 pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq),
Benny Prijono69968232006-04-06 19:29:03 +00001080 pj_ntohl(hdr->ts), payload_len);
Benny Prijono60b980e2006-04-03 22:41:26 +00001081
Benny Prijono69968232006-04-06 19:29:03 +00001082 /* Update RTP session */
1083 pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL);
Benny Prijono9a0eab52006-04-04 19:43:24 +00001084 }
1085
1086 if (rc > 0 && PJ_FD_ISSET(strm->rtcp_sock, &set)) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001087
1088 /*
1089 * Process incoming RTCP
1090 */
1091 pj_status_t status;
1092 pj_ssize_t size;
1093
1094 size = sizeof(packet);
1095 status = pj_sock_recv( strm->rtcp_sock, packet, &size, 0);
1096 if (status != PJ_SUCCESS)
1097 app_perror(THIS_FILE, "Error receiving RTCP packet", status);
Benny Prijono69968232006-04-06 19:29:03 +00001098 else
1099 pjmedia_rtcp_rx_rtcp(&strm->rtcp, packet, size);
Benny Prijono60b980e2006-04-03 22:41:26 +00001100 }
1101
1102
Benny Prijono9a0eab52006-04-04 19:43:24 +00001103 pj_get_timestamp(&now);
Benny Prijono60b980e2006-04-03 22:41:26 +00001104
Benny Prijono9a0eab52006-04-04 19:43:24 +00001105 if (next_rtp.u64 <= now.u64) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001106 /*
1107 * Time to send RTP packet.
1108 */
1109 pj_status_t status;
1110 const pjmedia_rtp_hdr *hdr;
1111 pj_ssize_t size;
1112 int hdrlen;
1113
1114 /* Format RTP header */
1115 status = pjmedia_rtp_encode_rtp( &strm->out_sess, strm->si.tx_pt,
1116 0, /* marker bit */
1117 strm->bytes_per_frame,
1118 strm->samples_per_frame,
1119 &hdr, &hdrlen);
1120 if (status == PJ_SUCCESS) {
1121
1122 /* Copy RTP header to packet */
1123 pj_memcpy(packet, hdr, hdrlen);
1124
1125 /* Zero the payload */
1126 pj_memset(packet+hdrlen, 0, strm->bytes_per_frame);
1127
1128 /* Send RTP packet */
1129 size = hdrlen + strm->bytes_per_frame;
1130 status = pj_sock_sendto( strm->rtp_sock, packet, &size, 0,
1131 &strm->si.rem_addr,
1132 sizeof(strm->si.rem_addr));
1133
1134 if (status != PJ_SUCCESS)
1135 app_perror(THIS_FILE, "Error sending RTP packet", status);
1136
1137 }
1138
1139 /* Update RTCP SR */
1140 pjmedia_rtcp_tx_rtp( &strm->rtcp, (pj_uint16_t)strm->bytes_per_frame);
1141
1142 /* Schedule next send */
Benny Prijono9a0eab52006-04-04 19:43:24 +00001143 next_rtp.u64 += (msec_interval * freq.u64 / 1000);
Benny Prijono60b980e2006-04-03 22:41:26 +00001144 }
1145
1146
Benny Prijono9a0eab52006-04-04 19:43:24 +00001147 if (next_rtcp.u64 <= now.u64) {
Benny Prijono60b980e2006-04-03 22:41:26 +00001148 /*
1149 * Time to send RTCP packet.
1150 */
1151 pjmedia_rtcp_pkt *rtcp_pkt;
1152 int rtcp_len;
1153 pj_sockaddr_in rem_addr;
1154 pj_ssize_t size;
1155 int port;
1156 pj_status_t status;
1157
1158 /* Build RTCP packet */
1159 pjmedia_rtcp_build_rtcp(&strm->rtcp, &rtcp_pkt, &rtcp_len);
1160
1161
1162 /* Calculate address based on RTP address */
1163 rem_addr = strm->si.rem_addr;
1164 port = pj_ntohs(strm->si.rem_addr.sin_port) + 1;
1165 rem_addr.sin_port = pj_htons((pj_uint16_t)port);
1166
1167 /* Send packet */
1168 size = rtcp_len;
1169 status = pj_sock_sendto(strm->rtcp_sock, rtcp_pkt, &size, 0,
1170 &rem_addr, sizeof(rem_addr));
1171 if (status != PJ_SUCCESS) {
1172 app_perror(THIS_FILE, "Error sending RTCP packet", status);
1173 }
1174
Benny Prijono69968232006-04-06 19:29:03 +00001175 /* Schedule next send */
Benny Prijono14b7b662006-04-07 15:01:51 +00001176 next_rtcp.u64 += (freq.u64 * (RTCP_INTERVAL+(pj_rand()%RTCP_RAND)) /
1177 1000);
Benny Prijono60b980e2006-04-03 22:41:26 +00001178 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001179 }
1180
1181 return 0;
1182}
1183
1184
1185/* Callback to be called when SDP negotiation is done in the call: */
1186static void call_on_media_update( pjsip_inv_session *inv,
1187 pj_status_t status)
1188{
1189 struct call *call;
1190 pj_pool_t *pool;
1191 struct media_stream *audio;
Benny Prijono49ce9a72006-04-05 16:56:19 +00001192 const pjmedia_sdp_session *local_sdp, *remote_sdp;
Benny Prijono4adcb912006-04-04 13:12:38 +00001193 struct codec *codec_desc = NULL;
1194 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001195
1196 call = inv->mod_data[mod_siprtp.id];
1197 pool = inv->dlg->pool;
1198 audio = &call->media[0];
1199
1200 /* If this is a mid-call media update, then destroy existing media */
1201 if (audio->thread != NULL)
1202 destroy_call_media(call->index);
1203
1204
1205 /* Do nothing if media negotiation has failed */
1206 if (status != PJ_SUCCESS) {
1207 app_perror(THIS_FILE, "SDP negotiation failed", status);
1208 return;
1209 }
1210
1211
1212 /* Capture stream definition from the SDP */
1213 pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
1214 pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
1215
1216 status = pjmedia_stream_info_from_sdp(&audio->si, inv->pool, app.med_endpt,
1217 local_sdp, remote_sdp, 0);
1218 if (status != PJ_SUCCESS) {
1219 app_perror(THIS_FILE, "Error creating stream info from SDP", status);
1220 return;
1221 }
1222
Benny Prijono4adcb912006-04-04 13:12:38 +00001223 /* Get the remainder of codec information from codec descriptor */
1224 if (audio->si.fmt.pt == app.audio_codec.pt)
1225 codec_desc = &app.audio_codec;
1226 else {
1227 /* Find the codec description in codec array */
1228 for (i=0; i<PJ_ARRAY_SIZE(audio_codecs); ++i) {
1229 if (audio_codecs[i].pt == audio->si.fmt.pt) {
1230 codec_desc = &audio_codecs[i];
1231 break;
1232 }
1233 }
1234
1235 if (codec_desc == NULL) {
1236 PJ_LOG(3, (THIS_FILE, "Error: Invalid codec payload type"));
1237 return;
1238 }
1239 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001240
Benny Prijono15953012006-04-27 22:37:08 +00001241 audio->clock_rate = audio->si.fmt.clock_rate;
Benny Prijono4adcb912006-04-04 13:12:38 +00001242 audio->samples_per_frame = audio->clock_rate * codec_desc->ptime / 1000;
1243 audio->bytes_per_frame = codec_desc->bit_rate * codec_desc->ptime / 1000 / 8;
Benny Prijono60b980e2006-04-03 22:41:26 +00001244
1245
1246 pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt,
Benny Prijono9d8a8732006-04-04 13:39:58 +00001247 pj_rand());
Benny Prijono60b980e2006-04-03 22:41:26 +00001248 pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0);
Benny Prijono6d7a45f2006-04-24 23:13:00 +00001249 pjmedia_rtcp_init(&audio->rtcp, "rtcp", audio->clock_rate,
Benny Prijono69968232006-04-06 19:29:03 +00001250 audio->samples_per_frame, 0);
Benny Prijono60b980e2006-04-03 22:41:26 +00001251
Benny Prijono4adcb912006-04-04 13:12:38 +00001252
Benny Prijono4adcb912006-04-04 13:12:38 +00001253
Benny Prijono60b980e2006-04-03 22:41:26 +00001254 /* Start media thread. */
1255 audio->thread_quit_flag = 0;
1256 status = pj_thread_create( inv->pool, "media", &media_thread, audio,
1257 0, 0, &audio->thread);
1258 if (status != PJ_SUCCESS) {
1259 app_perror(THIS_FILE, "Error creating media thread", status);
1260 }
1261}
1262
1263
1264
1265/* Destroy call's media */
1266static void destroy_call_media(unsigned call_index)
1267{
1268 struct media_stream *audio = &app.call[call_index].media[0];
1269
1270 if (audio->thread) {
1271 audio->thread_quit_flag = 1;
1272 pj_thread_join(audio->thread);
1273 pj_thread_destroy(audio->thread);
1274 audio->thread = NULL;
1275 audio->thread_quit_flag = 0;
Benny Prijono4adcb912006-04-04 13:12:38 +00001276
1277 /* Flush RTP/RTCP packets */
1278 {
1279 pj_fd_set_t set;
1280 pj_time_val timeout = {0, 0};
1281 char packet[1500];
1282 pj_ssize_t size;
1283 pj_status_t status;
1284 int rc;
1285
1286 do {
1287 PJ_FD_ZERO(&set);
1288 PJ_FD_SET(audio->rtp_sock, &set);
1289 PJ_FD_SET(audio->rtcp_sock, &set);
1290
1291 rc = pj_sock_select(FD_SETSIZE, &set, NULL, NULL, &timeout);
1292 if (rc > 0 && PJ_FD_ISSET(audio->rtp_sock, &set)) {
1293 size = sizeof(packet);
1294 status = pj_sock_recv(audio->rtp_sock, packet, &size, 0);
1295
1296 }
1297 if (rc > 0 && PJ_FD_ISSET(audio->rtcp_sock, &set)) {
1298 size = sizeof(packet);
1299 status = pj_sock_recv(audio->rtcp_sock, packet, &size, 0);
1300 }
1301
1302 } while (rc > 0);
1303 }
Benny Prijono60b980e2006-04-03 22:41:26 +00001304 }
1305}
1306
1307
Benny Prijono4adcb912006-04-04 13:12:38 +00001308/*****************************************************************************
Benny Prijono60b980e2006-04-03 22:41:26 +00001309 * USER INTERFACE STUFFS
1310 */
1311
1312static const char *good_number(char *buf, pj_int32_t val)
1313{
1314 if (val < 1000) {
1315 pj_ansi_sprintf(buf, "%d", val);
1316 } else if (val < 1000000) {
1317 pj_ansi_sprintf(buf, "%d.%dK",
1318 val / 1000,
1319 (val % 1000) / 100);
1320 } else {
1321 pj_ansi_sprintf(buf, "%d.%02dM",
1322 val / 1000000,
1323 (val % 1000000) / 10000);
1324 }
1325
1326 return buf;
1327}
1328
1329
1330static void print_call(int call_index)
1331{
Benny Prijono4adcb912006-04-04 13:12:38 +00001332 struct call *call = &app.call[call_index];
Benny Prijono60b980e2006-04-03 22:41:26 +00001333 int len;
Benny Prijono4adcb912006-04-04 13:12:38 +00001334 pjsip_inv_session *inv = call->inv;
Benny Prijono60b980e2006-04-03 22:41:26 +00001335 pjsip_dialog *dlg = inv->dlg;
Benny Prijono4adcb912006-04-04 13:12:38 +00001336 struct media_stream *audio = &call->media[0];
Benny Prijono60b980e2006-04-03 22:41:26 +00001337 char userinfo[128];
Benny Prijono69968232006-04-06 19:29:03 +00001338 char duration[80], last_update[80];
Benny Prijono4adcb912006-04-04 13:12:38 +00001339 char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16];
Benny Prijono69968232006-04-06 19:29:03 +00001340 pj_time_val now;
Benny Prijono60b980e2006-04-03 22:41:26 +00001341
Benny Prijono69968232006-04-06 19:29:03 +00001342 pj_gettimeofday(&now);
Benny Prijono60b980e2006-04-03 22:41:26 +00001343
Benny Prijono4adcb912006-04-04 13:12:38 +00001344 /* Print duration */
Benny Prijonoc3238072006-04-05 22:05:04 +00001345 if (inv->state >= PJSIP_INV_STATE_CONFIRMED) {
Benny Prijono4adcb912006-04-04 13:12:38 +00001346
Benny Prijono4adcb912006-04-04 13:12:38 +00001347 PJ_TIME_VAL_SUB(now, call->connect_time);
1348
Benny Prijono49ce9a72006-04-05 16:56:19 +00001349 sprintf(duration, " [duration: %02ld:%02ld:%02ld.%03ld]",
Benny Prijono4adcb912006-04-04 13:12:38 +00001350 now.sec / 3600,
1351 (now.sec % 3600) / 60,
1352 (now.sec % 60),
1353 now.msec);
1354
1355 } else {
1356 duration[0] = '\0';
1357 }
1358
1359
1360
1361 /* Call number and state */
1362 printf("Call #%d: %s%s\n", call_index, pjsip_inv_state_name(inv->state),
1363 duration);
1364
1365
1366
1367 /* Call identification */
Benny Prijono60b980e2006-04-03 22:41:26 +00001368 len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
1369 if (len < 1)
1370 pj_ansi_strcpy(userinfo, "<--uri too long-->");
1371 else
1372 userinfo[len] = '\0';
Benny Prijono4adcb912006-04-04 13:12:38 +00001373
Benny Prijono60b980e2006-04-03 22:41:26 +00001374 printf(" %s\n", userinfo);
1375
Benny Prijono4adcb912006-04-04 13:12:38 +00001376
Benny Prijono5b3b4602006-04-06 20:36:27 +00001377 if (call->inv == NULL || call->inv->state < PJSIP_INV_STATE_CONFIRMED ||
1378 call->connect_time.sec == 0)
1379 {
1380 return;
1381 }
1382
1383
Benny Prijono4adcb912006-04-04 13:12:38 +00001384 /* Signaling quality */
1385 {
1386 char pdd[64], connectdelay[64];
1387 pj_time_val t;
1388
1389 if (call->response_time.sec) {
1390 t = call->response_time;
1391 PJ_TIME_VAL_SUB(t, call->start_time);
Benny Prijono49ce9a72006-04-05 16:56:19 +00001392 sprintf(pdd, "got 1st response in %ld ms", PJ_TIME_VAL_MSEC(t));
Benny Prijono4adcb912006-04-04 13:12:38 +00001393 } else {
1394 pdd[0] = '\0';
1395 }
1396
1397 if (call->connect_time.sec) {
1398 t = call->connect_time;
1399 PJ_TIME_VAL_SUB(t, call->start_time);
Benny Prijono49ce9a72006-04-05 16:56:19 +00001400 sprintf(connectdelay, ", connected after: %ld ms",
1401 PJ_TIME_VAL_MSEC(t));
Benny Prijono4adcb912006-04-04 13:12:38 +00001402 } else {
1403 connectdelay[0] = '\0';
1404 }
1405
1406 printf(" Signaling quality: %s%s\n", pdd, connectdelay);
1407 }
1408
1409
Benny Prijono49ce9a72006-04-05 16:56:19 +00001410 printf(" Stream #0: audio %.*s@%dHz, %dms/frame, %sB/s (%sB/s +IP hdr)\n",
1411 (int)audio->si.fmt.encoding_name.slen,
1412 audio->si.fmt.encoding_name.ptr,
1413 audio->clock_rate,
1414 audio->samples_per_frame * 1000 / audio->clock_rate,
1415 good_number(bps, audio->bytes_per_frame * audio->clock_rate / audio->samples_per_frame),
1416 good_number(ipbps, (audio->bytes_per_frame+32) * audio->clock_rate / audio->samples_per_frame));
Benny Prijono4adcb912006-04-04 13:12:38 +00001417
Benny Prijono69968232006-04-06 19:29:03 +00001418 if (audio->rtcp.stat.rx.update_cnt == 0)
1419 strcpy(last_update, "never");
1420 else {
1421 pj_gettimeofday(&now);
1422 PJ_TIME_VAL_SUB(now, audio->rtcp.stat.rx.update);
Benny Prijonoe7483e52006-04-06 21:08:35 +00001423 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
Benny Prijono69968232006-04-06 19:29:03 +00001424 now.sec / 3600,
1425 (now.sec % 3600) / 60,
1426 now.sec % 60,
1427 now.msec);
1428 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001429
Benny Prijono69968232006-04-06 19:29:03 +00001430 printf(" RX stat last update: %s\n"
1431 " total %s packets %sB received (%sB +IP hdr)%s\n"
1432 " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
Benny Prijono28903eb2006-04-06 21:01:25 +00001433 " (msec) min avg max last\n"
1434 " loss period: %7.3f %7.3f %7.3f %7.3f%s\n"
1435 " jitter : %7.3f %7.3f %7.3f %7.3f%s\n",
Benny Prijono69968232006-04-06 19:29:03 +00001436 last_update,
1437 good_number(packets, audio->rtcp.stat.rx.pkt),
1438 good_number(bytes, audio->rtcp.stat.rx.bytes),
1439 good_number(ipbytes, audio->rtcp.stat.rx.bytes + audio->rtcp.stat.rx.pkt * 32),
Benny Prijono4adcb912006-04-04 13:12:38 +00001440 "",
Benny Prijono69968232006-04-06 19:29:03 +00001441 audio->rtcp.stat.rx.loss,
1442 audio->rtcp.stat.rx.loss * 100.0 / audio->rtcp.stat.rx.pkt,
1443 audio->rtcp.stat.rx.dup,
1444 audio->rtcp.stat.rx.dup * 100.0 / audio->rtcp.stat.rx.pkt,
1445 audio->rtcp.stat.rx.reorder,
1446 audio->rtcp.stat.rx.reorder * 100.0 / audio->rtcp.stat.rx.pkt,
Benny Prijono4adcb912006-04-04 13:12:38 +00001447 "",
Benny Prijono69968232006-04-06 19:29:03 +00001448 audio->rtcp.stat.rx.loss_period.min / 1000.0,
1449 audio->rtcp.stat.rx.loss_period.avg / 1000.0,
1450 audio->rtcp.stat.rx.loss_period.max / 1000.0,
1451 audio->rtcp.stat.rx.loss_period.last / 1000.0,
Benny Prijono4adcb912006-04-04 13:12:38 +00001452 "",
Benny Prijono69968232006-04-06 19:29:03 +00001453 audio->rtcp.stat.rx.jitter.min / 1000.0,
1454 audio->rtcp.stat.rx.jitter.avg / 1000.0,
1455 audio->rtcp.stat.rx.jitter.max / 1000.0,
1456 audio->rtcp.stat.rx.jitter.last / 1000.0,
Benny Prijono4adcb912006-04-04 13:12:38 +00001457 ""
1458 );
1459
1460
Benny Prijono69968232006-04-06 19:29:03 +00001461 if (audio->rtcp.stat.tx.update_cnt == 0)
1462 strcpy(last_update, "never");
1463 else {
1464 pj_gettimeofday(&now);
1465 PJ_TIME_VAL_SUB(now, audio->rtcp.stat.tx.update);
Benny Prijonoe7483e52006-04-06 21:08:35 +00001466 sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
Benny Prijono69968232006-04-06 19:29:03 +00001467 now.sec / 3600,
1468 (now.sec % 3600) / 60,
1469 now.sec % 60,
1470 now.msec);
1471 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001472
Benny Prijono69968232006-04-06 19:29:03 +00001473 printf(" TX stat last update: %s\n"
Benny Prijono393e5f32006-04-08 09:34:58 +00001474 " total %s packets %sB sent (%sB +IP hdr)%s\n"
Benny Prijono69968232006-04-06 19:29:03 +00001475 " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n"
Benny Prijono28903eb2006-04-06 21:01:25 +00001476 " (msec) min avg max last\n"
1477 " loss period: %7.3f %7.3f %7.3f %7.3f%s\n"
1478 " jitter : %7.3f %7.3f %7.3f %7.3f%s\n",
Benny Prijono69968232006-04-06 19:29:03 +00001479 last_update,
1480 good_number(packets, audio->rtcp.stat.tx.pkt),
1481 good_number(bytes, audio->rtcp.stat.tx.bytes),
1482 good_number(ipbytes, audio->rtcp.stat.tx.bytes + audio->rtcp.stat.tx.pkt * 32),
Benny Prijono4adcb912006-04-04 13:12:38 +00001483 "",
Benny Prijono69968232006-04-06 19:29:03 +00001484 audio->rtcp.stat.tx.loss,
1485 audio->rtcp.stat.tx.loss * 100.0 / audio->rtcp.stat.tx.pkt,
1486 audio->rtcp.stat.tx.dup,
1487 audio->rtcp.stat.tx.dup * 100.0 / audio->rtcp.stat.tx.pkt,
1488 audio->rtcp.stat.tx.reorder,
1489 audio->rtcp.stat.tx.reorder * 100.0 / audio->rtcp.stat.tx.pkt,
Benny Prijono4adcb912006-04-04 13:12:38 +00001490 "",
Benny Prijono69968232006-04-06 19:29:03 +00001491 audio->rtcp.stat.tx.loss_period.min / 1000.0,
1492 audio->rtcp.stat.tx.loss_period.avg / 1000.0,
1493 audio->rtcp.stat.tx.loss_period.max / 1000.0,
1494 audio->rtcp.stat.tx.loss_period.last / 1000.0,
Benny Prijono4adcb912006-04-04 13:12:38 +00001495 "",
Benny Prijono69968232006-04-06 19:29:03 +00001496 audio->rtcp.stat.tx.jitter.min / 1000.0,
1497 audio->rtcp.stat.tx.jitter.avg / 1000.0,
1498 audio->rtcp.stat.tx.jitter.max / 1000.0,
1499 audio->rtcp.stat.tx.jitter.last / 1000.0,
Benny Prijono4adcb912006-04-04 13:12:38 +00001500 ""
1501 );
1502
Benny Prijono69968232006-04-06 19:29:03 +00001503
Benny Prijono393e5f32006-04-08 09:34:58 +00001504 printf(" RTT delay : %7.3f %7.3f %7.3f %7.3f%s\n",
Benny Prijono69968232006-04-06 19:29:03 +00001505 audio->rtcp.stat.rtt.min / 1000.0,
1506 audio->rtcp.stat.rtt.avg / 1000.0,
1507 audio->rtcp.stat.rtt.max / 1000.0,
1508 audio->rtcp.stat.rtt.last / 1000.0,
1509 ""
1510 );
Benny Prijonoa1d03b42006-04-05 12:53:42 +00001511
Benny Prijono60b980e2006-04-03 22:41:26 +00001512}
1513
1514
1515static void list_calls()
1516{
1517 unsigned i;
1518 puts("List all calls:");
1519 for (i=0; i<app.max_calls; ++i) {
1520 if (!app.call[i].inv)
1521 continue;
1522 print_call(i);
1523 }
1524}
1525
1526static void hangup_call(unsigned index)
1527{
1528 pjsip_tx_data *tdata;
1529 pj_status_t status;
1530
1531 if (app.call[index].inv == NULL)
1532 return;
1533
1534 status = pjsip_inv_end_session(app.call[index].inv, 603, NULL, &tdata);
1535 if (status==PJ_SUCCESS && tdata!=NULL)
1536 pjsip_inv_send_msg(app.call[index].inv, tdata);
1537}
1538
1539static void hangup_all_calls()
1540{
1541 unsigned i;
1542 for (i=0; i<app.max_calls; ++i) {
1543 if (!app.call[i].inv)
1544 continue;
1545 hangup_call(i);
1546 }
1547}
1548
1549static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1550{
1551 char *p;
1552
1553 printf("%s (empty to cancel): ", title); fflush(stdout);
1554 fgets(buf, len, stdin);
1555
1556 /* Remove trailing newlines. */
1557 for (p=buf; ; ++p) {
1558 if (*p=='\r' || *p=='\n') *p='\0';
1559 else if (!*p) break;
1560 }
1561
1562 if (!*buf)
1563 return PJ_FALSE;
1564
1565 return PJ_TRUE;
1566}
1567
1568
1569static const char *MENU =
1570"\n"
1571"Enter menu character:\n"
1572" l List all calls\n"
1573" h Hangup a call\n"
1574" H Hangup all calls\n"
1575" q Quit\n"
1576"\n";
1577
1578
1579/* Main screen menu */
1580static void console_main()
1581{
1582 char input1[10];
1583 unsigned i;
1584
Benny Prijono4adcb912006-04-04 13:12:38 +00001585 printf("%s", MENU);
1586
Benny Prijono60b980e2006-04-03 22:41:26 +00001587 for (;;) {
1588 printf(">>> "); fflush(stdout);
1589 fgets(input1, sizeof(input1), stdin);
1590
1591 switch (input1[0]) {
1592 case 'l':
1593 list_calls();
1594 break;
1595
1596 case 'h':
1597 if (!simple_input("Call number to hangup", input1, sizeof(input1)))
1598 break;
1599
1600 i = atoi(input1);
1601 hangup_call(i);
1602 break;
1603
1604 case 'H':
1605 hangup_all_calls();
1606 break;
1607
1608 case 'q':
1609 goto on_exit;
1610
1611 default:
Benny Prijono4adcb912006-04-04 13:12:38 +00001612 puts("Invalid command");
Benny Prijono60b980e2006-04-03 22:41:26 +00001613 printf("%s", MENU);
1614 break;
1615 }
1616
1617 fflush(stdout);
1618 }
1619
1620on_exit:
Benny Prijono4adcb912006-04-04 13:12:38 +00001621 hangup_all_calls();
Benny Prijono60b980e2006-04-03 22:41:26 +00001622}
1623
1624
Benny Prijono4adcb912006-04-04 13:12:38 +00001625/*****************************************************************************
1626 * Below is a simple module to log all incoming and outgoing SIP messages
1627 */
1628
1629
Benny Prijono60b980e2006-04-03 22:41:26 +00001630/* Notification on incoming messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001631static pj_bool_t logger_on_rx_msg(pjsip_rx_data *rdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001632{
1633 PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
1634 "%s\n"
1635 "--end msg--",
1636 rdata->msg_info.len,
1637 pjsip_rx_data_get_info(rdata),
1638 rdata->pkt_info.src_name,
1639 rdata->pkt_info.src_port,
1640 rdata->msg_info.msg_buf));
1641
1642 /* Always return false, otherwise messages will not get processed! */
1643 return PJ_FALSE;
1644}
1645
1646/* Notification on outgoing messages */
Benny Prijono4adcb912006-04-04 13:12:38 +00001647static pj_status_t logger_on_tx_msg(pjsip_tx_data *tdata)
Benny Prijono60b980e2006-04-03 22:41:26 +00001648{
1649
1650 /* Important note:
1651 * tp_info field is only valid after outgoing messages has passed
1652 * transport layer. So don't try to access tp_info when the module
1653 * has lower priority than transport layer.
1654 */
1655
1656 PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
1657 "%s\n"
1658 "--end msg--",
1659 (tdata->buf.cur - tdata->buf.start),
1660 pjsip_tx_data_get_info(tdata),
1661 tdata->tp_info.dst_name,
1662 tdata->tp_info.dst_port,
1663 tdata->buf.start));
1664
1665 /* Always return success, otherwise message will not get sent! */
1666 return PJ_SUCCESS;
1667}
1668
1669/* The module instance. */
1670static pjsip_module msg_logger =
1671{
1672 NULL, NULL, /* prev, next. */
1673 { "mod-siprtp-log", 14 }, /* Name. */
1674 -1, /* Id */
1675 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
1676 NULL, /* load() */
1677 NULL, /* start() */
1678 NULL, /* stop() */
1679 NULL, /* unload() */
Benny Prijono4adcb912006-04-04 13:12:38 +00001680 &logger_on_rx_msg, /* on_rx_request() */
1681 &logger_on_rx_msg, /* on_rx_response() */
1682 &logger_on_tx_msg, /* on_tx_request. */
1683 &logger_on_tx_msg, /* on_tx_response() */
Benny Prijono60b980e2006-04-03 22:41:26 +00001684 NULL, /* on_tsx_state() */
1685
1686};
1687
1688
1689
Benny Prijono4adcb912006-04-04 13:12:38 +00001690/*****************************************************************************
1691 * Console application custom logging:
1692 */
1693
1694
1695static FILE *log_file;
1696
1697
1698static void app_log_writer(int level, const char *buffer, int len)
1699{
1700 /* Write to both stdout and file. */
1701
1702 if (level <= app.app_log_level)
1703 pj_log_write(level, buffer, len);
1704
1705 if (log_file) {
1706 fwrite(buffer, len, 1, log_file);
1707 fflush(log_file);
1708 }
1709}
1710
1711
1712pj_status_t app_logging_init(void)
1713{
1714 /* Redirect log function to ours */
1715
1716 pj_log_set_log_func( &app_log_writer );
1717
1718 /* If output log file is desired, create the file: */
1719
1720 if (app.log_filename) {
1721 log_file = fopen(app.log_filename, "wt");
1722 if (log_file == NULL) {
1723 PJ_LOG(1,(THIS_FILE, "Unable to open log file %s",
1724 app.log_filename));
1725 return -1;
1726 }
1727 }
1728
1729 return PJ_SUCCESS;
1730}
1731
1732
1733void app_logging_shutdown(void)
1734{
1735 /* Close logging file, if any: */
1736
1737 if (log_file) {
1738 fclose(log_file);
1739 log_file = NULL;
1740 }
1741}
1742
Benny Prijono60b980e2006-04-03 22:41:26 +00001743
1744/*
1745 * main()
1746 */
1747int main(int argc, char *argv[])
1748{
Benny Prijono4adcb912006-04-04 13:12:38 +00001749 unsigned i;
Benny Prijono60b980e2006-04-03 22:41:26 +00001750 pj_status_t status;
1751
Benny Prijono4adcb912006-04-04 13:12:38 +00001752 /* Must init PJLIB first */
Benny Prijono60b980e2006-04-03 22:41:26 +00001753 status = pj_init();
1754 if (status != PJ_SUCCESS)
1755 return 1;
1756
Benny Prijono4adcb912006-04-04 13:12:38 +00001757 /* Get command line options */
Benny Prijono60b980e2006-04-03 22:41:26 +00001758 status = init_options(argc, argv);
1759 if (status != PJ_SUCCESS)
1760 return 1;
1761
Benny Prijono4adcb912006-04-04 13:12:38 +00001762 /* Init logging */
1763 status = app_logging_init();
1764 if (status != PJ_SUCCESS)
1765 return 1;
1766
1767 /* Init SIP etc */
Benny Prijono60b980e2006-04-03 22:41:26 +00001768 status = init_sip();
1769 if (status != PJ_SUCCESS) {
1770 app_perror(THIS_FILE, "Initialization has failed", status);
1771 destroy_sip();
1772 return 1;
1773 }
1774
Benny Prijono4adcb912006-04-04 13:12:38 +00001775 /* Register module to log incoming/outgoing messages */
Benny Prijono60b980e2006-04-03 22:41:26 +00001776 pjsip_endpt_register_module(app.sip_endpt, &msg_logger);
1777
Benny Prijono4adcb912006-04-04 13:12:38 +00001778 /* Init media */
Benny Prijono60b980e2006-04-03 22:41:26 +00001779 status = init_media();
1780 if (status != PJ_SUCCESS) {
1781 app_perror(THIS_FILE, "Media initialization failed", status);
1782 destroy_sip();
1783 return 1;
1784 }
1785
Benny Prijono9a0eab52006-04-04 19:43:24 +00001786 /* Start worker threads */
1787 for (i=0; i<app.thread_count; ++i) {
1788 pj_thread_create( app.pool, "app", &worker_thread, NULL,
1789 0, 0, &app.thread[i]);
1790 }
1791
Benny Prijono4adcb912006-04-04 13:12:38 +00001792 /* If URL is specified, then make call immediately */
Benny Prijono60b980e2006-04-03 22:41:26 +00001793 if (app.uri_to_call.slen) {
1794 unsigned i;
1795
Benny Prijono4adcb912006-04-04 13:12:38 +00001796 PJ_LOG(3,(THIS_FILE, "Making %d calls to %s..", app.max_calls,
1797 app.uri_to_call.ptr));
1798
Benny Prijono60b980e2006-04-03 22:41:26 +00001799 for (i=0; i<app.max_calls; ++i) {
1800 status = make_call(&app.uri_to_call);
1801 if (status != PJ_SUCCESS) {
1802 app_perror(THIS_FILE, "Error making call", status);
1803 break;
1804 }
1805 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001806
1807 } else {
1808
1809 PJ_LOG(3,(THIS_FILE, "Ready for incoming calls (max=%d)",
1810 app.max_calls));
Benny Prijono60b980e2006-04-03 22:41:26 +00001811 }
Benny Prijono4adcb912006-04-04 13:12:38 +00001812
Benny Prijono4adcb912006-04-04 13:12:38 +00001813 /* Start user interface loop */
Benny Prijono60b980e2006-04-03 22:41:26 +00001814 console_main();
1815
Benny Prijono4adcb912006-04-04 13:12:38 +00001816
1817 /* Shutting down... */
Benny Prijono60b980e2006-04-03 22:41:26 +00001818 destroy_media();
1819 destroy_sip();
Benny Prijono4adcb912006-04-04 13:12:38 +00001820 app_logging_shutdown();
1821
Benny Prijono60b980e2006-04-03 22:41:26 +00001822
1823 return 0;
1824}
1825