BLOG | DOCUMENTATION | TRAC

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:

siprtp.jpg
1 /* $Id: siprtp.c 5747 2018-02-26 10:14:27Z nanang $ */
2 /*
3  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19  */
20 
21 
22 
23 
24 
25 /* Usage */
26 static const char *USAGE =
27 " PURPOSE: \n"
28 " This program establishes SIP INVITE session and media, and calculate \n"
29 " the media quality (packet lost, jitter, rtt, etc.). Unlike normal \n"
30 " pjmedia applications, this program bypasses all pjmedia stream \n"
31 " framework and transmit encoded RTP packets manually using own thread. \n"
32 "\n"
33 " USAGE:\n"
34 " siprtp [options] => to start in server mode\n"
35 " siprtp [options] URL => to start in client mode\n"
36 "\n"
37 " Program options:\n"
38 " --count=N, -c Set number of calls to create (default:1) \n"
39 " --gap=N -g Set call gapping to N msec (default:0)\n"
40 " --duration=SEC, -d Set maximum call duration (default:unlimited) \n"
41 " --auto-quit, -q Quit when calls have been completed (default:no)\n"
42 " --call-report -R Display report on call termination (default:yes)\n"
43 "\n"
44 " Address and ports options:\n"
45 " --local-port=PORT,-p Set local SIP port (default: 5060)\n"
46 " --rtp-port=PORT, -r Set start of RTP port (default: 4000)\n"
47 " --ip-addr=IP, -i Set local IP address to use (otherwise it will\n"
48 " try to determine local IP address from hostname)\n"
49 "\n"
50 " Logging Options:\n"
51 " --log-level=N, -l Set log verbosity level (default=5)\n"
52 " --app-log-level=N Set app screen log verbosity (default=3)\n"
53 " --log-file=FILE Write log to file FILE\n"
54 " --report-file=FILE Write report to file FILE\n"
55 "\n"
56 /* Don't support this anymore, because codec is properly examined in
57  pjmedia_session_info_from_sdp() function.
58 
59 " Codec Options:\n"
60 " --a-pt=PT Set audio payload type to PT (default=0)\n"
61 " --a-name=NAME Set audio codec name to NAME (default=pcmu)\n"
62 " --a-clock=RATE Set audio codec rate to RATE Hz (default=8000Hz)\n"
63 " --a-bitrate=BPS Set audio codec bitrate to BPS (default=64000bps)\n"
64 " --a-ptime=MS Set audio frame time to MS msec (default=20ms)\n"
65 */
66 ;
67 
68 
69 /* Include all headers. */
70 #include <pjsip.h>
71 #include <pjmedia.h>
72 #include <pjmedia-codec.h>
73 #include <pjsip_ua.h>
74 #include <pjsip_simple.h>
75 #include <pjlib-util.h>
76 #include <pjlib.h>
77 
78 #include <stdlib.h>
79 
80 /* Uncomment these to disable threads.
81  * NOTE:
82  * when threading is disabled, siprtp won't transmit any
83  * RTP packets.
84  */
85 /*
86 #undef PJ_HAS_THREADS
87 #define PJ_HAS_THREADS 0
88 */
89 
90 
91 #if PJ_HAS_HIGH_RES_TIMER==0
92 # error "High resolution timer is needed for this sample"
93 #endif
94 
95 #define THIS_FILE "siprtp.c"
96 #define MAX_CALLS 1024
97 #define RTP_START_PORT 4000
98 
99 
100 /* Codec descriptor: */
101 struct codec
102 {
103  unsigned pt;
104  char* name;
105  unsigned clock_rate;
106  unsigned bit_rate;
107  unsigned ptime;
108  char* description;
109 };
110 
111 
112 /* A bidirectional media stream created when the call is active. */
113 struct media_stream
114 {
115  /* Static: */
116  unsigned call_index; /* Call owner. */
117  unsigned media_index; /* Media index in call. */
118  pjmedia_transport *transport; /* To send/recv RTP/RTCP */
119 
120  /* Active? */
121  pj_bool_t active; /* Non-zero if is in call. */
122 
123  /* Current stream info: */
124  pjmedia_stream_info si; /* Current stream info. */
125 
126  /* More info: */
127  unsigned clock_rate; /* clock rate */
128  unsigned samples_per_frame; /* samples per frame */
129  unsigned bytes_per_frame; /* frame size. */
130 
131  /* RTP session: */
132  pjmedia_rtp_session out_sess; /* outgoing RTP session */
133  pjmedia_rtp_session in_sess; /* incoming RTP session */
134 
135  /* RTCP stats: */
136  pjmedia_rtcp_session rtcp; /* incoming RTCP session. */
137 
138  /* Thread: */
139  pj_bool_t thread_quit_flag; /* Stop media thread. */
140  pj_thread_t *thread; /* Media thread. */
141 };
142 
143 
144 /* This is a call structure that is created when the application starts
145  * and only destroyed when the application quits.
146  */
147 struct call
148 {
149  unsigned index;
150  pjsip_inv_session *inv;
151  unsigned media_count;
152  struct media_stream media[1];
153  pj_time_val start_time;
154  pj_time_val response_time;
155  pj_time_val connect_time;
156 
158 };
159 
160 
161 /* Application's global variables */
162 static struct app
163 {
164  unsigned max_calls;
165  unsigned call_gap;
166  pj_bool_t call_report;
167  unsigned uac_calls;
168  unsigned duration;
169  pj_bool_t auto_quit;
170  unsigned thread_count;
171  int sip_port;
172  int rtp_start_port;
173  pj_str_t local_addr;
174  pj_str_t local_uri;
175  pj_str_t local_contact;
176 
177  int app_log_level;
178  int log_level;
179  char *log_filename;
180  char *report_filename;
181 
182  struct codec audio_codec;
183 
184  pj_str_t uri_to_call;
185 
186  pj_caching_pool cp;
187  pj_pool_t *pool;
188 
189  pjsip_endpoint *sip_endpt;
190  pj_bool_t thread_quit;
191  pj_thread_t *sip_thread[1];
192 
193  pjmedia_endpt *med_endpt;
194  struct call call[MAX_CALLS];
195 } app;
196 
197 
198 
199 /*
200  * Prototypes:
201  */
202 
203 /* Callback to be called when SDP negotiation is done in the call: */
204 static void call_on_media_update( pjsip_inv_session *inv,
205  pj_status_t status);
206 
207 /* Callback to be called when invite session's state has changed: */
208 static void call_on_state_changed( pjsip_inv_session *inv,
209  pjsip_event *e);
210 
211 /* Callback to be called when dialog has forked: */
212 static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e);
213 
214 /* Callback to be called to handle incoming requests outside dialogs: */
215 static pj_bool_t on_rx_request( pjsip_rx_data *rdata );
216 
217 /* Worker thread prototype */
218 static int sip_worker_thread(void *arg);
219 
220 /* Create SDP for call */
221 static pj_status_t create_sdp( pj_pool_t *pool,
222  struct call *call,
223  pjmedia_sdp_session **p_sdp);
224 
225 /* Hangup call */
226 static void hangup_call(unsigned index);
227 
228 /* Destroy the call's media */
229 static void destroy_call_media(unsigned call_index);
230 
231 /* Destroy media. */
232 static void destroy_media();
233 
234 /* This callback is called by media transport on receipt of RTP packet. */
235 static void on_rx_rtp(void *user_data, void *pkt, pj_ssize_t size);
236 
237 /* This callback is called by media transport on receipt of RTCP packet. */
238 static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size);
239 
240 /* Display error */
241 static void app_perror(const char *sender, const char *title,
242  pj_status_t status);
243 
244 /* Print call */
245 static void print_call(int call_index);
246 
247 
248 /* This is a PJSIP module to be registered by application to handle
249  * incoming requests outside any dialogs/transactions. The main purpose
250  * here is to handle incoming INVITE request message, where we will
251  * create a dialog and INVITE session for it.
252  */
253 static pjsip_module mod_siprtp =
254 {
255  NULL, NULL, /* prev, next. */
256  { "mod-siprtpapp", 13 }, /* Name. */
257  -1, /* Id */
258  PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
259  NULL, /* load() */
260  NULL, /* start() */
261  NULL, /* stop() */
262  NULL, /* unload() */
263  &on_rx_request, /* on_rx_request() */
264  NULL, /* on_rx_response() */
265  NULL, /* on_tx_request. */
266  NULL, /* on_tx_response() */
267  NULL, /* on_tsx_state() */
268 };
269 
270 
271 /* Codec constants */
272 struct codec audio_codecs[] =
273 {
274  { 0, "PCMU", 8000, 64000, 20, "G.711 ULaw" },
275  { 3, "GSM", 8000, 13200, 20, "GSM" },
276  { 4, "G723", 8000, 6400, 30, "G.723.1" },
277  { 8, "PCMA", 8000, 64000, 20, "G.711 ALaw" },
278  { 18, "G729", 8000, 8000, 20, "G.729" },
279 };
280 
281 
282 /*
283  * Init SIP stack
284  */
285 static pj_status_t init_sip()
286 {
287  unsigned i;
288  pj_status_t status;
289 
290  /* init PJLIB-UTIL: */
291  status = pjlib_util_init();
292  PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
293 
294  /* Must create a pool factory before we can allocate any memory. */
296 
297  /* Create application pool for misc. */
298  app.pool = pj_pool_create(&app.cp.factory, "app", 1000, 1000, NULL);
299 
300  /* Create the endpoint: */
301  status = pjsip_endpt_create(&app.cp.factory, pj_gethostname()->ptr,
302  &app.sip_endpt);
303  PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
304 
305 
306  /* Add UDP transport. */
307  {
308  pj_sockaddr_in addr;
309  pjsip_host_port addrname;
310  pjsip_transport *tp;
311 
312  pj_bzero(&addr, sizeof(addr));
313  addr.sin_family = pj_AF_INET();
314  addr.sin_addr.s_addr = 0;
315  addr.sin_port = pj_htons((pj_uint16_t)app.sip_port);
316 
317  if (app.local_addr.slen) {
318 
319  addrname.host = app.local_addr;
320  addrname.port = app.sip_port;
321 
322  status = pj_sockaddr_in_init(&addr, &app.local_addr,
323  (pj_uint16_t)app.sip_port);
324  if (status != PJ_SUCCESS) {
325  app_perror(THIS_FILE, "Unable to resolve IP interface", status);
326  return status;
327  }
328  }
329 
330  status = pjsip_udp_transport_start( app.sip_endpt, &addr,
331  (app.local_addr.slen ? &addrname:NULL),
332  1, &tp);
333  if (status != PJ_SUCCESS) {
334  app_perror(THIS_FILE, "Unable to start UDP transport", status);
335  return status;
336  }
337 
338  PJ_LOG(3,(THIS_FILE, "SIP UDP listening on %.*s:%d",
339  (int)tp->local_name.host.slen, tp->local_name.host.ptr,
340  tp->local_name.port));
341  }
342 
343  /*
344  * Init transaction layer.
345  * This will create/initialize transaction hash tables etc.
346  */
347  status = pjsip_tsx_layer_init_module(app.sip_endpt);
348  PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
349 
350  /* Initialize UA layer. */
351  status = pjsip_ua_init_module( app.sip_endpt, NULL );
352  PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
353 
354  /* Initialize 100rel support */
355  status = pjsip_100rel_init_module(app.sip_endpt);
356  PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
357 
358  /* Init invite session module. */
359  {
360  pjsip_inv_callback inv_cb;
361 
362  /* Init the callback for INVITE session: */
363  pj_bzero(&inv_cb, sizeof(inv_cb));
364  inv_cb.on_state_changed = &call_on_state_changed;
365  inv_cb.on_new_session = &call_on_forked;
366  inv_cb.on_media_update = &call_on_media_update;
367 
368  /* Initialize invite session module: */
369  status = pjsip_inv_usage_init(app.sip_endpt, &inv_cb);
370  PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
371  }
372 
373  /* Register our module to receive incoming requests. */
374  status = pjsip_endpt_register_module( app.sip_endpt, &mod_siprtp);
375  PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
376 
377  /* Init calls */
378  for (i=0; i<app.max_calls; ++i)
379  app.call[i].index = i;
380 
381  /* Done */
382  return PJ_SUCCESS;
383 }
384 
385 
386 /*
387  * Destroy SIP
388  */
389 static void destroy_sip()
390 {
391  unsigned i;
392 
393  app.thread_quit = 1;
394  for (i=0; i<app.thread_count; ++i) {
395  if (app.sip_thread[i]) {
396  pj_thread_join(app.sip_thread[i]);
397  pj_thread_destroy(app.sip_thread[i]);
398  app.sip_thread[i] = NULL;
399  }
400  }
401 
402  if (app.sip_endpt) {
403  pjsip_endpt_destroy(app.sip_endpt);
404  app.sip_endpt = NULL;
405  }
406 
407 }
408 
409 
410 /*
411  * Init media stack.
412  */
413 static pj_status_t init_media()
414 {
415  unsigned i, count;
416  pj_uint16_t rtp_port;
417  pj_status_t status;
418 
419 
420  /* Initialize media endpoint so that at least error subsystem is properly
421  * initialized.
422  */
423 #if PJ_HAS_THREADS
424  status = pjmedia_endpt_create(&app.cp.factory, NULL, 1, &app.med_endpt);
425 #else
426  status = pjmedia_endpt_create(&app.cp.factory,
427  pjsip_endpt_get_ioqueue(app.sip_endpt),
428  0, &app.med_endpt);
429 #endif
430  PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
431 
432 
433  /* Must register codecs to be supported */
434 #if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
435  pjmedia_codec_g711_init(app.med_endpt);
436 #endif
437 
438  /* RTP port counter */
439  rtp_port = (pj_uint16_t)(app.rtp_start_port & 0xFFFE);
440 
441  /* Init media transport for all calls. */
442  for (i=0, count=0; i<app.max_calls; ++i, ++count) {
443 
444  unsigned j;
445 
446  /* Create transport for each media in the call */
447  for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
448  /* Repeat binding media socket to next port when fails to bind
449  * to current port number.
450  */
451  int retry;
452 
453  app.call[i].media[j].call_index = i;
454  app.call[i].media[j].media_index = j;
455 
456  status = -1;
457  for (retry=0; retry<100; ++retry,rtp_port+=2) {
458  struct media_stream *m = &app.call[i].media[j];
459 
460  status = pjmedia_transport_udp_create2(app.med_endpt,
461  "siprtp",
462  &app.local_addr,
463  rtp_port, 0,
464  &m->transport);
465  if (status == PJ_SUCCESS) {
466  rtp_port += 2;
467  break;
468  }
469  }
470  }
471 
472  if (status != PJ_SUCCESS)
473  goto on_error;
474  }
475 
476  /* Done */
477  return PJ_SUCCESS;
478 
479 on_error:
480  destroy_media();
481  return status;
482 }
483 
484 
485 /*
486  * Destroy media.
487  */
488 static void destroy_media()
489 {
490  unsigned i;
491 
492  for (i=0; i<app.max_calls; ++i) {
493  unsigned j;
494  for (j=0; j<PJ_ARRAY_SIZE(app.call[0].media); ++j) {
495  struct media_stream *m = &app.call[i].media[j];
496 
497  if (m->transport) {
498  pjmedia_transport_close(m->transport);
499  m->transport = NULL;
500  }
501  }
502  }
503 
504  if (app.med_endpt) {
505  pjmedia_endpt_destroy(app.med_endpt);
506  app.med_endpt = NULL;
507  }
508 }
509 
510 
511 /*
512  * Make outgoing call.
513  */
514 static pj_status_t make_call(const pj_str_t *dst_uri)
515 {
516  unsigned i;
517  struct call *call;
518  pjsip_dialog *dlg;
519  pjmedia_sdp_session *sdp;
520  pjsip_tx_data *tdata;
521  pj_status_t status;
522 
523 
524  /* Find unused call slot */
525  for (i=0; i<app.max_calls; ++i) {
526  if (app.call[i].inv == NULL)
527  break;
528  }
529 
530  if (i == app.max_calls)
531  return PJ_ETOOMANY;
532 
533  call = &app.call[i];
534 
535  /* Create UAC dialog */
536  status = pjsip_dlg_create_uac( pjsip_ua_instance(),
537  &app.local_uri, /* local URI */
538  &app.local_contact, /* local Contact */
539  dst_uri, /* remote URI */
540  dst_uri, /* remote target */
541  &dlg); /* dialog */
542  if (status != PJ_SUCCESS) {
543  ++app.uac_calls;
544  return status;
545  }
546 
547  /* Create SDP */
548  create_sdp( dlg->pool, call, &sdp);
549 
550  /* Create the INVITE session. */
551  status = pjsip_inv_create_uac( dlg, sdp, 0, &call->inv);
552  if (status != PJ_SUCCESS) {
553  pjsip_dlg_terminate(dlg);
554  ++app.uac_calls;
555  return status;
556  }
557 
558 
559  /* Attach call data to invite session */
560  call->inv->mod_data[mod_siprtp.id] = call;
561 
562  /* Mark start of call */
563  pj_gettimeofday(&call->start_time);
564 
565 
566  /* Create initial INVITE request.
567  * This INVITE request will contain a perfectly good request and
568  * an SDP body as well.
569  */
570  status = pjsip_inv_invite(call->inv, &tdata);
571  PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
572 
573 
574  /* Send initial INVITE request.
575  * From now on, the invite session's state will be reported to us
576  * via the invite session callbacks.
577  */
578  status = pjsip_inv_send_msg(call->inv, tdata);
579  PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
580 
581 
582  return PJ_SUCCESS;
583 }
584 
585 
586 /*
587  * Receive incoming call
588  */
589 static void process_incoming_call(pjsip_rx_data *rdata)
590 {
591  unsigned i, options;
592  struct call *call;
593  pjsip_dialog *dlg;
594  pjmedia_sdp_session *sdp;
595  pjsip_tx_data *tdata;
596  pj_status_t status;
597 
598  /* Find free call slot */
599  for (i=0; i<app.max_calls; ++i) {
600  if (app.call[i].inv == NULL)
601  break;
602  }
603 
604  if (i == app.max_calls) {
605  const pj_str_t reason = pj_str("Too many calls");
606  pjsip_endpt_respond_stateless( app.sip_endpt, rdata,
607  500, &reason,
608  NULL, NULL);
609  return;
610  }
611 
612  call = &app.call[i];
613 
614  /* Verify that we can handle the request. */
615  options = 0;
616  status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
617  app.sip_endpt, &tdata);
618  if (status != PJ_SUCCESS) {
619  /*
620  * No we can't handle the incoming INVITE request.
621  */
622  if (tdata) {
623  pjsip_response_addr res_addr;
624 
625  pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
626  pjsip_endpt_send_response(app.sip_endpt, &res_addr, tdata,
627  NULL, NULL);
628 
629  } else {
630 
631  /* Respond with 500 (Internal Server Error) */
632  pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 500, NULL,
633  NULL, NULL);
634  }
635 
636  return;
637  }
638 
639  /* Create UAS dialog */
640  status = pjsip_dlg_create_uas_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: */
698 static 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: */
708 static 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) */
732 static void timer_disconnect_call( pj_timer_heap_t *timer_heap,
733  struct pj_timer_entry *entry)
734 {
735  struct call *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: */
745 static void call_on_state_changed( pjsip_inv_session *inv,
746  pjsip_event *e)
747 {
748  struct call *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;
803  call->d_timer.user_data = call;
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 */
823 static 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 */
834 static 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 */
848 static 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  */
1004 static 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;
1009  pjmedia_sdp_session *sdp;
1010  pjmedia_sdp_media *m;
1011  pjmedia_sdp_attr *attr;
1012  pjmedia_transport_info tpinfo;
1013  struct media_stream *audio = &call->media[0];
1014 
1015  PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL);
1016 
1017 
1018  /* Get transport info */
1019  pjmedia_transport_info_init(&tpinfo);
1020  pjmedia_transport_get_info(audio->transport, &tpinfo);
1021 
1022  /* Create and initialize basic SDP session */
1023  sdp = pj_pool_zalloc (pool, sizeof(pjmedia_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  */
1036  sdp->conn = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_conn));
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;
1049  m = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_media));
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(pool, sizeof(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(pool, sizeof(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(pool, sizeof(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>
1108 static 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>
1116 static 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  */
1184 static 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 = 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  */
1227 static void on_rx_rtcp(void *user_data, void *pkt, pj_ssize_t size)
1228 {
1229  struct media_stream *strm;
1230 
1231  strm = 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  */
1253 static int media_thread(void *arg)
1254 {
1255  enum { RTCP_INTERVAL = 5000, RTCP_RAND = 2000 };
1256  struct media_stream *strm = 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: */
1396 static 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 = 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 */
1494 static 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 
1518 static 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 
1540 static 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 
1559 static 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 
1822 static 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 
1833 static 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 
1846 static 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 
1863 static 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 
1884 static 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 */
1896 static 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 
1944 on_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 */
1955 static 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 */
1971 static 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. */
1994 static 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 
2019 static FILE *log_file;
2020 
2021 
2022 static 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 
2037 pj_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 
2058 void 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  */
2072 int 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 
unsigned clock_rate
Definition: codec.h:240
pj_status_t pjmedia_transport_close(pjmedia_transport *tp)
Definition: transport.h:993
pj_status_t pjmedia_sdp_neg_get_active_local(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **local)
void pj_log_write(int level, const char *buffer, int len)
pj_uint16_t sin_family
struct pjmedia_sdp_media::@12 desc
PJMEDIA main header file.
pj_status_t pjmedia_sdp_neg_get_active_remote(pjmedia_sdp_neg *neg, const pjmedia_sdp_session **remote)
struct pj_timer_heap_t pj_timer_heap_t
pj_ssize_t slen
pj_in_addr sin_addr
void pj_log_set_log_func(pj_log_func *func)
pj_uint16_t pj_ntohs(pj_uint16_t netshort)
#define PJ_ARRAY_SIZE(a)
unsigned tx_pt
Definition: stream.h:117
void pjmedia_rtcp_rx_rtcp(pjmedia_rtcp_session *session, const void *rtcp_pkt, pj_size_t size)
void pjmedia_rtcp_build_rtcp(pjmedia_rtcp_session *session, void **rtcp_pkt, int *len)
#define PJ_ASSERT_RETURN(expr, retval)
pj_uint32_t pkt
Definition: rtcp.h:175
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_BEGIN_DECL typedef int pj_int32_t
void pj_pool_release(pj_pool_t *pool)
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:766
#define PJ_TIME_VAL_SUB(t1, t2)
int pj_bool_t
struct pj_thread_t pj_thread_t
pj_str_t value
Definition: sdp.h:88
void * user_data
void pjmedia_rtp_session_update(pjmedia_rtp_session *ses, const pjmedia_rtp_hdr *hdr, pjmedia_rtp_status *seq_st)
void * pj_memcpy(void *dst, const void *src, pj_size_t size)
pj_uint16_t sin_port
unsigned loss
Definition: rtcp.h:178
Definition: transport.h:547
pjmedia_codec_info fmt
Definition: stream.h:115
#define PJ_TODO(id)
pj_str_t param
Definition: sdp.h:217
pj_str_t user
Definition: sdp.h:658
pjmedia_rtcp_stat stat
Definition: rtcp.h:257
pj_status_t pjmedia_transport_send_rtcp(pjmedia_transport *tp, const void *pkt, pj_size_t size)
Definition: transport.h:848
struct pjmedia_endpt pjmedia_endpt
Definition: types.h:187
pj_status_t pj_get_timestamp(pj_timestamp *ts)
unsigned pt
Definition: codec.h:238
pj_str_t addr
Definition: sdp.h:663
#define PJ_TIME_VAL_MSEC(t)
pj_str_t media
Definition: sdp.h:465
Definition: transport.h:501
pj_status_t pjmedia_sdp_rtpmap_to_attr(pj_pool_t *pool, const pjmedia_sdp_rtpmap *rtpmap, pjmedia_sdp_attr **p_attr)
pj_str_t transport
Definition: sdp.h:468
#define PJ_LOG(level, arg)
pj_status_t pjmedia_transport_get_info(pjmedia_transport *tp, pjmedia_transport_info *info)
Definition: transport.h:686
pjmedia_sdp_conn * conn
Definition: sdp.h:667
int pj_status_t
pj_uint16_t pj_htons(pj_uint16_t hostshort)
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)
unsigned attr_count
Definition: sdp.h:679
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)
Definition: sdp.h:212
pjmedia_sock_info sock_info
Definition: transport.h:552
pj_pool_factory_policy pj_pool_factory_default_policy
pjmedia_sdp_media * media[16]
Definition: sdp.h:683
Definition: rtcp.h:232
pj_str_t * pj_strdup2(pj_pool_t *pool, pj_str_t *dst, const char *src)
pj_str_t addr_type
Definition: sdp.h:662
pj_status_t pj_sockaddr_in_init(pj_sockaddr_in *addr, const pj_str_t *cp, pj_uint16_t port)
Definition: sdp.h:381
pj_uint32_t pj_ntohl(pj_uint32_t netlong)
unsigned int pj_uint32_t
pj_str_t addr_type
Definition: sdp.h:384
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_timer_heap_callback * cb
void pjmedia_rtcp_rx_rtp(pjmedia_rtcp_session *session, unsigned seq, unsigned ts, unsigned payload)
Include all codecs API in PJMEDIA-CODEC.
void pj_time_val_normalize(pj_time_val *t)
pj_str_t pt
Definition: sdp.h:214
Definition: siprtp.c:113
#define PJ_ETOOMANY
PJ_TRUE
Definition: stream.h:93
const pj_str_t * pj_gethostname(void)
#define pj_assert(expr)
#define PJ_ASSERT_ON_FAIL(expr, exec_on_fail)
pj_str_t name
Definition: sdp.h:666
pj_uint32_t bytes
Definition: rtcp.h:176
pj_uint32_t ts
Definition: rtp.h:104
pj_status_t pjmedia_endpt_create(pj_pool_factory *pf, pj_ioqueue_t *ioqueue, unsigned worker_cnt, pjmedia_endpt **p_endpt)
Definition: endpoint.h:105
#define PJ_RETURN_OS_ERROR(os_code)
unsigned attr_count
Definition: sdp.h:476
void pjmedia_transport_info_init(pjmedia_transport_info *info)
Definition: transport.h:668
struct pjmedia_sdp_session::@14 time
pj_timer_entry d_timer
Definition: siprtp.c:157
pj_sockaddr rem_rtcp
Definition: stream.h:99
void * user_data
pj_str_t pj_str(char *str)
pj_str_t net_type
Definition: sdp.h:661
void pjmedia_rtcp_tx_rtp(pjmedia_rtcp_session *session, unsigned ptsize)
unsigned fmt_count
Definition: sdp.h:469
void pj_caching_pool_init(pj_caching_pool *ch_pool, const pj_pool_factory_policy *policy, pj_size_t max_capacity)
Definition: rtcp.h:197
pjmedia_sdp_attr * attr[(32 *2+4)]
Definition: sdp.h:477
unsigned reorder
Definition: rtcp.h:179
long pj_ssize_t
pj_str_t addr
Definition: sdp.h:385
PJ_BEGIN_DECL pj_status_t pjlib_util_init(void)
void pj_bzero(void *dst, pj_size_t size)
Definition: sdp.h:85
pj_str_t fmt[32]
Definition: sdp.h:470
pj_str_t pj_strerror(pj_status_t statcode, char *buf, pj_size_t bufsize)
pj_status_t pj_gettimeofday(pj_time_val *tv)
Definition: sdp.h:653
Definition: rtp.h:186
pj_uint16_t seq
Definition: rtp.h:103
pj_str_t name
Definition: sdp.h:87
pj_math_stat jitter
Definition: rtcp.h:189
#define PJ_INET_ADDRSTRLEN
pjmedia_rtcp_stream_stat rx
Definition: rtcp.h:202
unsigned clock_rate
Definition: sdp.h:216
pj_uint32_t s_addr
pj_sockaddr rem_addr
Definition: stream.h:98
Definition: pjsip-perf.c:172
#define PJ_ERR_MSG_SIZE
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)
Definition: rtp.h:86
int id
pj_status_t pj_thread_join(pj_thread_t *thread)
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:957
pj_uint16_t port
Definition: sdp.h:466
pj_status_t pj_thread_destroy(pj_thread_t *thread)
pj_str_t net_type
Definition: sdp.h:383
pj_sockaddr_in ipv4
unsigned media_count
Definition: sdp.h:682
void pj_shutdown(void)
pj_sockaddr rtp_addr_name
Definition: transport.h:286
pj_status_t pj_get_timestamp_freq(pj_timestamp *freq)
Definition: siprtp.c:101
pj_uint32_t id
Definition: sdp.h:659
pj_status_t pj_thread_sleep(unsigned msec)
struct pjmedia_sdp_session::@13 origin
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)
pj_status_t pj_inet_ntop(int af, const void *src, char *dst, int size)
pj_uint32_t start
Definition: sdp.h:675
Definition: sdp.h:460
void * pj_pool_zalloc(pj_pool_t *pool, pj_size_t size)
PJ_BEGIN_DECL pj_status_t pjmedia_codec_g711_init(pjmedia_endpt *endpt)
PJ_SUCCESS
unsigned short pj_uint16_t
pj_uint32_t version
Definition: sdp.h:660
pj_math_stat rtt
Definition: rtcp.h:204
pj_status_t pjmedia_rtp_session_init(pjmedia_rtp_session *ses, int default_pt, pj_uint32_t sender_ssrc)
pj_status_t pjmedia_transport_send_rtp(pjmedia_transport *tp, const void *pkt, pj_size_t size)
Definition: transport.h:828
pj_status_t pj_init(void)
unsigned port_count
Definition: sdp.h:467
pj_uint32_t stop
Definition: sdp.h:676
void pjmedia_transport_detach(pjmedia_transport *tp, void *user_data)
Definition: transport.h:809
unsigned dup
Definition: rtcp.h:180
pj_status_t pjmedia_endpt_destroy(pjmedia_endpt *endpt)
Definition: endpoint.h:146
void pjmedia_rtcp_init(pjmedia_rtcp_session *session, char *name, unsigned clock_rate, unsigned samples_per_frame, pj_uint32_t ssrc)
size_t pj_size_t
PJ_FALSE
int pj_rand(void)
pj_str_t enc_name
Definition: sdp.h:215
#define PJ_UNUSED_ARG(arg)
pjmedia_rtcp_stream_stat tx
Definition: rtcp.h:201
void pj_caching_pool_destroy(pj_caching_pool *ch_pool)
#define pj_AF_INET()

 


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