WARNING: The online documentation has moved to https://docs.pjsip.org.

Visit the new documentation at https://docs.pjsip.org:

BLOG | DOCUMENTATION | GITHUB

Home --> Documentations --> PJMEDIA Reference

Samples: Using SIP and Custom RTP/RTCP to Monitor Quality

This source is an example to demonstrate using SIP and RTP/RTCP framework to measure the network quality/impairment from the SIP call. This program can be used to make calls or to receive calls from other SIP endpoint (or other siprtp program), and to display the media quality statistics at the end of the call.

Note that the remote peer must support RTCP.

The layout of the program has been designed so that custom reporting can be generated instead of plain human readable text.

The source code of the file is pjsip-apps/src/samples/siprtp.c

Screenshots on WinXP:

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

 


PJMEDIA small footprint Open Source media stack
Copyright (C) 2006-2008 Teluu Inc.